Merge branch 'chonk-fixes' into 'master'

Optimised sub-terrain chonk storage, fixed hash chunk bug, altered terrain base

See merge request veloren/veloren!211
This commit is contained in:
Joshua Barretto 2019-06-06 11:38:58 +00:00
commit 5c812a73bc
31 changed files with 639 additions and 306 deletions

9
Cargo.lock generated
View File

@ -782,6 +782,11 @@ name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "frustum_query"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -2569,6 +2574,7 @@ dependencies = [
name = "veloren-client"
version = "0.2.0"
dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2581,6 +2587,7 @@ version = "0.2.0"
dependencies = [
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"dot_vox 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2633,6 +2640,7 @@ dependencies = [
"euc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"frustum_query 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_device_gl 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_window_glutin 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2950,6 +2958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum frustum_query 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e1771c26abed26b2527d888742fffd27dab86d205bf4846748abf29c06ef5a05"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"

View File

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
log = "0.4"
specs = "0.14"
vek = "0.9"
threadpool = "1.7"

View File

@ -12,13 +12,14 @@ use common::{
msg::{ClientMsg, ClientState, ServerInfo, ServerMsg},
net::PostBox,
state::State,
terrain::chonk::ChonkMetrics,
};
use log::{info, log_enabled};
use std::{
collections::HashMap,
net::SocketAddr,
time::{Duration, Instant},
};
use threadpool::ThreadPool;
use vek::*;
@ -43,6 +44,7 @@ pub struct Client {
state: State,
entity: EcsEntity,
view_distance: Option<u32>,
loaded_distance: Option<u32>,
pending_chunks: HashMap<Vec2<i32>, Instant>,
}
@ -73,11 +75,15 @@ impl Client {
postbox.send_message(ClientMsg::Ping);
let mut thread_pool = threadpool::Builder::new()
.thread_name("veloren-worker".into())
.build();
// We reduce the thread count by 1 to keep rendering smooth
thread_pool.set_num_threads((thread_pool.max_count() - 1).max(1));
Ok(Self {
client_state,
thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into())
.build(),
thread_pool,
server_info,
postbox,
@ -89,11 +95,18 @@ impl Client {
state,
entity,
view_distance,
loaded_distance: None,
pending_chunks: HashMap::new(),
})
}
#[allow(dead_code)]
pub fn with_thread_pool(mut self, thread_pool: ThreadPool) -> Self {
self.thread_pool = thread_pool;
self
}
/// Request a state transition to `ClientState::Registered`.
pub fn register(&mut self, player: comp::Player) {
self.postbox.send_message(ClientMsg::Register { player });
@ -131,6 +144,10 @@ impl Client {
self.view_distance
}
pub fn loaded_distance(&self) -> Option<u32> {
self.loaded_distance
}
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) {
@ -240,7 +257,7 @@ impl Client {
if (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| e.abs() as u32)
.reduce_max()
> view_distance
> view_distance + 1
{
chunks_to_remove.push(key);
}
@ -251,29 +268,36 @@ impl Client {
// Request chunks from the server.
// TODO: This is really inefficient.
let mut all_loaded = true;
'outer: for dist in 0..=view_distance as i32 {
for i in chunk_pos.x - dist..=chunk_pos.x + dist {
for j in chunk_pos.y - dist..=chunk_pos.y + dist {
for i in chunk_pos.x - dist..=chunk_pos.x + 1 + dist {
for j in chunk_pos.y - dist..=chunk_pos.y + 1 + dist {
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;
if self.state.terrain().get_key(key).is_none() {
if !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;
}
}
all_loaded = false;
}
}
}
if all_loaded {
self.loaded_distance = Some((dist - 1).max(0) as u32);
}
}
// If chunks are taking too long, assume they're no longer pending.
let now = Instant::now();
self.pending_chunks
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(10));
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3));
}
// Send a ping to the server once every second
@ -308,6 +332,16 @@ impl Client {
}
}
// Output debug metrics
if log_enabled!(log::Level::Info) && self.tick % 600 == 0 {
let metrics = self
.state
.terrain()
.iter()
.fold(ChonkMetrics::default(), |a, (_, c)| a + c.get_metrics());
info!("{:?}", metrics);
}
// 7) Finish the tick, pass control back to the frontend.
self.tick += 1;
@ -421,7 +455,7 @@ impl Client {
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &threadpool::ThreadPool {
pub fn thread_pool(&self) -> &ThreadPool {
&self.thread_pool
}

View File

@ -23,3 +23,4 @@ rand = "0.6.5"
rayon = "1.0"
lazy_static = "1.3"
lz4-compress = "0.1"
fxhash = "0.2"

View File

@ -26,11 +26,11 @@ impl Block {
}
}
pub fn get_opacity(&self) -> f32 {
pub fn get_opacity(&self) -> Option<f32> {
match self.kind {
0 => 0.0,
1 => 0.3,
2 => 1.0,
0 => None,
1 => Some(0.85),
2 => Some(1.0),
_ => unimplemented!(),
}
}

View File

@ -3,8 +3,9 @@ use crate::{
vol::{BaseVol, ReadVol, VolSize, WriteVol},
volumes::chunk::{Chunk, ChunkErr},
};
use fxhash::FxHashMap;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::{collections::HashMap, ops::Add};
use vek::*;
#[derive(Debug)]
@ -35,14 +36,44 @@ impl Chonk {
}
}
pub fn get_z_min(&self) -> i32 {
pub fn get_min_z(&self) -> i32 {
self.z_offset
}
pub fn get_z_max(&self) -> i32 {
pub fn get_max_z(&self) -> i32 {
self.z_offset + (self.sub_chunks.len() as u32 * SUB_CHUNK_HEIGHT) as i32
}
pub fn get_metrics(&self) -> ChonkMetrics {
ChonkMetrics {
chonks: 1,
homogeneous: self
.sub_chunks
.iter()
.filter(|s| match s {
SubChunk::Homogeneous(_) => true,
_ => false,
})
.count(),
hash: self
.sub_chunks
.iter()
.filter(|s| match s {
SubChunk::Hash(_, _) => true,
_ => false,
})
.count(),
heterogeneous: self
.sub_chunks
.iter()
.filter(|s| match s {
SubChunk::Heterogeneous(_) => true,
_ => false,
})
.count(),
}
}
fn sub_chunk_idx(&self, z: i32) -> usize {
((z - self.z_offset) as u32 / SUB_CHUNK_HEIGHT as u32) as usize
}
@ -75,7 +106,7 @@ impl ReadVol for Chonk {
- Vec3::unit_z()
* (self.z_offset + sub_chunk_idx as i32 * SUB_CHUNK_HEIGHT as i32);
Ok(map.get(&rpos).unwrap_or(cblock))
Ok(map.get(&rpos.map(|e| e as u8)).unwrap_or(cblock))
}
SubChunk::Heterogeneous(chunk) => {
let rpos = pos
@ -87,64 +118,102 @@ impl ReadVol for Chonk {
}
}
}
#[inline(always)]
unsafe fn get_unchecked(&self, pos: Vec3<i32>) -> &Block {
if pos.z < self.z_offset {
// Below the terrain
&self.below
} else if pos.z >= self.z_offset + SUB_CHUNK_HEIGHT as i32 * self.sub_chunks.len() as i32 {
// Above the terrain
&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) => block,
SubChunk::Hash(cblock, map) => {
let rpos = pos
- Vec3::unit_z()
* (self.z_offset + sub_chunk_idx as i32 * SUB_CHUNK_HEIGHT as i32);
map.get(&rpos.map(|e| e as u8)).unwrap_or(cblock)
}
SubChunk::Heterogeneous(chunk) => {
let rpos = pos
- Vec3::unit_z()
* (self.z_offset + sub_chunk_idx as i32 * SUB_CHUNK_HEIGHT as i32);
chunk.get_unchecked(rpos)
}
}
}
}
}
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 pos.z < self.z_offset {
self.sub_chunks.insert(0, SubChunk::Homogeneous(self.below));
self.z_offset -= SUB_CHUNK_HEIGHT as i32;
}
while self.sub_chunks.get(sub_chunk_idx).is_none() {
self.sub_chunks.push(SubChunk::Homogeneous(self.above));
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 * SUB_CHUNK_HEIGHT as i32);
match &mut self.sub_chunks[sub_chunk_idx] {
// Can't fail
SubChunk::Homogeneous(cblock) if block == *cblock => Ok(()),
SubChunk::Homogeneous(cblock) => {
let mut map = FxHashMap::default();
map.insert(rpos.map(|e| e as u8), block);
self.sub_chunks[sub_chunk_idx] = SubChunk::Hash(*cblock, map);
Ok(())
}
SubChunk::Hash(cblock, map) if block == *cblock => Ok(()),
SubChunk::Hash(cblock, map) if map.len() < 4096 => {
map.insert(rpos.map(|e| e as u8), block);
Ok(())
}
SubChunk::Hash(cblock, map) => {
let mut new_chunk = Chunk::filled(*cblock, ());
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope)
for (map_pos, map_block) in map {
new_chunk
.set(map_pos.map(|e| e as i32), *map_block)
.unwrap(); // Can't fail (I hope!)
}
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(())
}
let rpos = pos
- Vec3::unit_z() * (self.z_offset + sub_chunk_idx as i32 * SUB_CHUNK_HEIGHT as i32);
/*
SubChunk::Homogeneous(cblock) => {
let mut new_chunk = Chunk::filled(*cblock, ());
match &mut self.sub_chunks[sub_chunk_idx] {
// Can't fail
SubChunk::Homogeneous(cblock) if *cblock == block => Ok(()),
SubChunk::Homogeneous(cblock) => {
let mut map = HashMap::new();
map.insert(rpos, block);
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope!)
self.sub_chunks[sub_chunk_idx] = SubChunk::Hash(*cblock, map);
Ok(())
}
SubChunk::Hash(cblock, map) if map.len() < 1024 => {
map.insert(rpos, block);
Ok(())
}
SubChunk::Hash(cblock, map) => {
let mut new_chunk = Chunk::filled(*cblock, ());
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope)
for (map_pos, map_block) in map {
new_chunk.set(*map_pos, *map_block).unwrap(); // Can't fail (I hope!)
}
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(())
}
/*
SubChunk::Homogeneous(cblock) => {
let mut new_chunk = Chunk::filled(*cblock, ());
new_chunk.set(rpos, block).unwrap(); // Can't fail (I hope!)
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(())
}
*/
SubChunk::Heterogeneous(chunk) => chunk
.set(rpos, block)
.map_err(|err| ChonkError::ChunkError(err)),
//_ => unimplemented!(),
self.sub_chunks[sub_chunk_idx] = SubChunk::Heterogeneous(new_chunk);
Ok(())
}
*/
SubChunk::Heterogeneous(chunk) => chunk
.set(rpos, block)
.map_err(|err| ChonkError::ChunkError(err)),
//_ => unimplemented!(),
}
}
}
@ -152,7 +221,7 @@ impl WriteVol for Chonk {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SubChunk {
Homogeneous(Block),
Hash(Block, HashMap<Vec3<i32>, Block>),
Hash(Block, FxHashMap<Vec3<u8>, Block>),
Heterogeneous(Chunk<Block, TerrainChunkSize, ()>),
}
@ -161,3 +230,35 @@ impl SubChunk {
SubChunk::Homogeneous(block)
}
}
#[derive(Debug)]
pub struct ChonkMetrics {
chonks: usize,
homogeneous: usize,
hash: usize,
heterogeneous: usize,
}
impl Default for ChonkMetrics {
fn default() -> Self {
ChonkMetrics {
chonks: 0,
homogeneous: 0,
hash: 0,
heterogeneous: 0,
}
}
}
impl Add for ChonkMetrics {
type Output = Self;
fn add(self, other: Self::Output) -> Self {
Self::Output {
chonks: self.chonks + other.chonks,
homogeneous: self.homogeneous + other.homogeneous,
hash: self.hash + other.hash,
heterogeneous: self.heterogeneous + other.heterogeneous,
}
}
}

View File

@ -1,4 +1,5 @@
use crate::ray::{Ray, RayUntil};
use std::fmt::Debug;
use vek::*;
/// A voxel.
@ -18,7 +19,7 @@ pub trait Vox: Sized {
/// A volume that contains voxel data.
pub trait BaseVol {
type Vox: Vox;
type Err;
type Err: Debug;
}
// Utility types
@ -73,6 +74,11 @@ pub trait ReadVol: BaseVol {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Err>;
#[inline(always)]
unsafe fn get_unchecked(&self, pos: Vec3<i32>) -> &Self::Vox {
self.get(pos).unwrap()
}
fn ray(&self, from: Vec3<f32>, to: Vec3<f32>) -> Ray<Self, fn(&Self::Vox) -> bool>
where
Self: Sized,

View File

@ -32,14 +32,18 @@ impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
if pos.map(|e| e >= 0).reduce_and()
&& pos.map2(S::SIZE, |e, lim| e < lim as i32).reduce_and()
{
Some(
(pos.x * S::SIZE.y as i32 * S::SIZE.z as i32 + pos.y * S::SIZE.z as i32 + pos.z)
as usize,
)
Some(Self::idx_for_unchecked(pos))
} else {
None
}
}
/// Used to transform a voxel position in the volume into its corresponding index
/// in the voxel array.
#[inline(always)]
fn idx_for_unchecked(pos: Vec3<i32>) -> usize {
(pos.x * S::SIZE.y as i32 * S::SIZE.z as i32 + pos.y * S::SIZE.z as i32 + pos.z) as usize
}
}
impl<V: Vox, S: VolSize, M> BaseVol for Chunk<V, S, M> {
@ -61,6 +65,11 @@ impl<V: Vox, S: VolSize, M> ReadVol for Chunk<V, S, M> {
.and_then(|idx| self.vox.get(idx))
.ok_or(ChunkErr::OutOfBounds)
}
#[inline(always)]
unsafe fn get_unchecked(&self, pos: Vec3<i32>) -> &V {
self.vox.get_unchecked(Self::idx_for_unchecked(pos))
}
}
impl<V: Vox, S: VolSize, M> WriteVol for Chunk<V, S, M> {

View File

@ -27,11 +27,18 @@ impl<V: Vox, M> Dyna<V, M> {
#[inline(always)]
fn idx_for(sz: Vec3<u32>, pos: Vec3<i32>) -> Option<usize> {
if pos.map(|e| e >= 0).reduce_and() && pos.map2(sz, |e, lim| e < lim as i32).reduce_and() {
Some((pos.x * sz.y as i32 * sz.z as i32 + pos.y * sz.z as i32 + pos.z) as usize)
Some(Self::idx_for_unchecked(sz, pos))
} else {
None
}
}
/// Used to transform a voxel position in the volume into its corresponding index
/// in the voxel array.
#[inline(always)]
fn idx_for_unchecked(sz: Vec3<u32>, pos: Vec3<i32>) -> usize {
(pos.x * sz.y as i32 * sz.z as i32 + pos.y * sz.z as i32 + pos.z) as usize
}
}
impl<V: Vox, M> BaseVol for Dyna<V, M> {
@ -53,6 +60,12 @@ impl<V: Vox, M> ReadVol for Dyna<V, M> {
.and_then(|idx| self.vox.get(idx))
.ok_or(DynaErr::OutOfBounds)
}
#[inline(always)]
unsafe fn get_unchecked(&self, pos: Vec3<i32>) -> &V {
self.vox
.get_unchecked(Self::idx_for_unchecked(self.sz, pos))
}
}
impl<V: Vox, M> WriteVol for Dyna<V, M> {

View File

@ -6,8 +6,10 @@ use crate::{
dyna::{Dyna, DynaErr},
},
};
use fxhash::FxHashMap;
use std::{
collections::{hash_map, HashMap},
fmt::Debug,
marker::PhantomData,
sync::Arc,
};
@ -26,7 +28,7 @@ pub enum VolMap2dErr<V: BaseVol> {
// M = Chunk metadata
#[derive(Clone)]
pub struct VolMap2d<V: BaseVol, S: VolSize> {
chunks: HashMap<Vec2<i32>, Arc<V>>,
chunks: FxHashMap<Vec2<i32>, Arc<V>>,
phantom: PhantomData<S>,
}
@ -50,12 +52,12 @@ impl<V: BaseVol, S: VolSize> VolMap2d<V, S> {
}
}
impl<V: BaseVol, S: VolSize> BaseVol for VolMap2d<V, S> {
impl<V: BaseVol + Debug, 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> {
impl<V: BaseVol + ReadVol + Debug, 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);
@ -67,11 +69,18 @@ impl<V: BaseVol + ReadVol, S: VolSize> ReadVol for VolMap2d<V, S> {
chunk.get(co).map_err(|err| VolMap2dErr::ChunkErr(err))
})
}
#[inline(always)]
unsafe fn get_unchecked(&self, pos: Vec3<i32>) -> &V::Vox {
let ck = Self::chunk_key(pos);
let co = Self::chunk_offs(pos);
self.chunks.get(&ck).unwrap().get_unchecked(co)
}
}
// 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> {
impl<I: Into<Aabr<i32>>, V: BaseVol + ReadVol + Debug, 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.
@ -99,7 +108,7 @@ impl<I: Into<Aabr<i32>>, V: BaseVol + ReadVol, S: VolSize> SampleVol<I> for VolM
}
}
impl<V: BaseVol + WriteVol + Clone, S: VolSize + Clone> WriteVol for VolMap2d<V, S> {
impl<V: BaseVol + WriteVol + Clone + Debug, 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);
@ -122,7 +131,7 @@ impl<V: BaseVol, S: VolSize> VolMap2d<V, S> {
.reduce_and()
{
Ok(Self {
chunks: HashMap::new(),
chunks: FxHashMap::default(),
phantom: PhantomData,
})
} else {

View File

@ -6,7 +6,7 @@ use crate::{
dyna::{Dyna, DynaErr},
},
};
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use std::{collections::HashMap, fmt::Debug, marker::PhantomData, sync::Arc};
use vek::*;
#[derive(Debug)]
@ -45,12 +45,12 @@ impl<V: BaseVol, S: VolSize> VolMap3d<V, S> {
}
}
impl<V: BaseVol, S: VolSize> BaseVol for VolMap3d<V, S> {
impl<V: BaseVol + Debug, 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> {
impl<V: BaseVol + ReadVol + Debug, 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);
@ -66,7 +66,7 @@ impl<V: BaseVol + ReadVol, S: VolSize> ReadVol for VolMap3d<V, S> {
// 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> {
impl<I: Into<Aabb<i32>>, V: BaseVol + ReadVol + Debug, 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.
@ -95,7 +95,7 @@ impl<I: Into<Aabb<i32>>, V: BaseVol + ReadVol, S: VolSize> SampleVol<I> for VolM
}
}
impl<V: BaseVol + WriteVol + Clone, S: VolSize + Clone> WriteVol for VolMap3d<V, S> {
impl<V: BaseVol + WriteVol + Clone + Debug, 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);

View File

@ -122,6 +122,12 @@ impl Server {
Ok(this)
}
#[allow(dead_code)]
pub fn with_thread_pool(mut self, thread_pool: ThreadPool) -> Self {
self.thread_pool = thread_pool;
self
}
/// Get a reference to the server's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State {
@ -306,7 +312,7 @@ impl Server {
.map(|e: i32| e.abs())
.reduce_max() as u32;
if dist <= view_distance {
if dist <= view_distance + 1 {
self.clients.notify(
entity,
ServerMsg::TerrainChunkUpdate {
@ -338,7 +344,11 @@ impl Server {
.map(|e: i32| e.abs() as u32)
.reduce_max();
if player.view_distance.map(|vd| dist <= vd).unwrap_or(false) {
if player
.view_distance
.map(|vd| dist <= vd + 1)
.unwrap_or(false)
{
should_drop = false;
break;
}

View File

@ -55,3 +55,4 @@ portpicker = "0.1"
num = "0.2"
backtrace = "0.3"
rand = "0.5"
frustum_query = "0.1.2"

View File

@ -1,6 +1,7 @@
#version 330 core
#include <globals.glsl>
#include <sky.glsl>
in vec3 f_pos;
in vec3 f_norm;
@ -37,5 +38,11 @@ void main() {
float sun_diffuse = dot(sun_dir, world_norm) * 0.5;
tgt_color = model_col * vec4(f_col * (ambient + sun_diffuse), 1.0);
vec3 surf_color = model_col.rgb * f_col * (ambient + sun_diffuse);
float fog_level = fog(f_pos.xy, focus_pos.xy);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x);
vec3 color = mix(surf_color, fog_color, fog_level);
tgt_color = vec4(color, 1.0);
}

View File

@ -28,15 +28,12 @@ out vec3 f_col;
flat out uint f_bone_idx;
void main() {
f_pos = v_pos;
f_pos = (model_mat *
bones[v_bone_idx].bone_mat *
vec4(v_pos, 1)).xyz;
f_norm = v_norm;
f_col = v_col;
f_bone_idx = v_bone_idx;
gl_Position =
proj_mat *
view_mat *
model_mat *
bones[v_bone_idx].bone_mat *
vec4(v_pos, 1);
gl_Position = proj_mat * view_mat * vec4(f_pos, 1);
}

View File

@ -1 +1,28 @@
const float PI = 3.141592;
vec3 get_sky_color(vec3 dir, float time_of_day) {
const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0);
const vec3 SKY_TOP = vec3(0.1, 0.5, 1.0);
const vec3 SKY_BOTTOM = vec3(0.025, 0.08, 0.2);
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.7, 0.5) * 0.5;
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.9, 0.35) * 200.0;
float sun_angle_rad = time_of_day * TIME_FACTOR;
vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad));
vec3 sun_halo = pow(max(dot(dir, sun_dir), 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = pow(max(dot(dir, sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_light = sun_halo + sun_surf;
return mix(SKY_BOTTOM, SKY_TOP, (dir.z + 1.0) / 2.0) + sun_light;
}
float fog(vec2 f_pos, vec2 focus_pos) {
float dist = distance(f_pos, focus_pos) / view_distance.x;
float min_fog = 0.75;
float max_fog = 1.0;
return clamp((dist - min_fog) / (max_fog - min_fog), 0.0, 1.0);
}

View File

@ -171,5 +171,5 @@ void main() {
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
tgt_color = final_color;
tgt_color = vec4(final_color.rgb, 1);
}

View File

@ -1,6 +1,7 @@
#version 330 core
#include <globals.glsl>
#include <sky.glsl>
in vec3 f_pos;
@ -11,27 +12,6 @@ uniform u_locals {
out vec4 tgt_color;
const float PI = 3.141592;
vec3 get_sky_color(vec3 dir, float time_of_day) {
const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0);
const vec3 SKY_TOP = vec3(0.1, 0.5, 1.0);
const vec3 SKY_BOTTOM = vec3(0.025, 0.08, 0.2);
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.7, 0.5);
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.9, 0.35) * 200.0;
float sun_angle_rad = time_of_day * TIME_FACTOR;
vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad));
vec3 sun_halo = pow(max(dot(dir, sun_dir), 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = pow(max(dot(dir, sun_dir) - 0.0045, 0.0), 1000.0) * SUN_SURF_COLOR;
vec3 sun_light = sun_halo + sun_surf;
return mix(SKY_BOTTOM, SKY_TOP, (dir.z + 1.0) / 2.0) + sun_light;
}
void main() {
tgt_color = vec4(get_sky_color(normalize(f_pos), time_of_day.x), 1.0);
}

View File

@ -1,9 +1,10 @@
#version 330 core
#include <globals.glsl>
#include <sky.glsl>
in vec3 f_pos;
in vec3 f_norm;
flat in uint f_pos_norm;
in vec3 f_col;
in float f_light;
@ -15,6 +16,18 @@ uniform u_locals {
out vec4 tgt_color;
void main() {
// Calculate normal from packed data
vec3 f_norm;
uint norm_axis = (f_pos_norm >> 30) & 0x3u;
float norm_dir = float((f_pos_norm >> 29) & 0x1u) * 2.0 - 1.0;
if (norm_axis == 0u) {
f_norm = vec3(1.0, 0.0, 0.0) * norm_dir;
} else if (norm_axis == 1u) {
f_norm = vec3(0.0, 1.0, 0.0) * norm_dir;
} else {
f_norm = vec3(0.0, 0.0, 1.0) * norm_dir;
}
float glob_ambience = 0.001;
float sun_ambience = 0.9;
@ -28,5 +41,11 @@ void main() {
vec3 light = vec3(static_light);
tgt_color = vec4(f_col * light, 1.0);
vec3 surf_color = f_col * light;
float fog_level = fog(f_pos.xy, focus_pos.xy);
vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x);
vec3 color = mix(surf_color, fog_color, fog_level);
tgt_color = vec4(color, 1.0);
}

View File

@ -11,7 +11,7 @@ uniform u_locals {
};
out vec3 f_pos;
out vec3 f_norm;
flat out uint f_pos_norm;
out vec3 f_col;
out float f_light;
@ -22,22 +22,14 @@ void main() {
float((v_pos_norm >> 16) & 0x1FFFu)
) + model_offs;
f_pos_norm = v_pos_norm;
f_col = vec3(
float((v_col_light >> 8) & 0xFFu),
float((v_col_light >> 16) & 0xFFu),
float((v_col_light >> 24) & 0xFFu)
) / 255.0;
uint norm_axis = (v_pos_norm >> 30) & 0x3u;
float norm_dir = float((v_pos_norm >> 29) & 0x1u) * 2.0 - 1.0;
if (norm_axis == 0u) {
f_norm = vec3(1.0, 0.0, 0.0) * norm_dir;
} else if (norm_axis == 1u) {
f_norm = vec3(0.0, 1.0, 0.0) * norm_dir;
} else {
f_norm = vec3(0.0, 0.0, 1.0) * norm_dir;
}
f_light = float(v_col_light & 0xFFu) / 255.0;
gl_Position =

View File

@ -37,8 +37,8 @@ impl Animation for RunAnimation {
);
next.head.offset = Vec3::new(0.0, 3.0, 12.0 + wave_cos * 1.3);
next.head.ori =
Quaternion::rotation_z(head_look.x) * Quaternion::rotation_x(head_look.y + 0.35);
next.head.ori = Quaternion::rotation_z(head_look.x + wave * 0.1)
* Quaternion::rotation_x(head_look.y + 0.35);
next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + wave_cos * 1.1);
@ -53,20 +53,20 @@ impl Animation for RunAnimation {
next.shorts.ori = Quaternion::rotation_z(wave * 0.6);
next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(-8.0, 3.0 + wave_cos * 5.0, 9.0 - wave * 2.0) / 11.0;
next.l_hand.offset = Vec3::new(-9.0, 3.0 + wave_cos * 8.0, 12.0 - wave * 1.0) / 11.0;
next.l_hand.ori = Quaternion::rotation_x(wave_cos * 1.1);
next.l_hand.scale = Vec3::one() / 11.0;
next.r_hand.offset = Vec3::new(8.0, 3.0 - wave_cos * 5.0, 9.0 + wave * 2.0) / 11.0;
next.r_hand.offset = Vec3::new(9.0, 3.0 - wave_cos * 8.0, 12.0 + wave * 1.0) / 11.0;
next.r_hand.ori = Quaternion::rotation_x(wave_cos * -1.1);
next.r_hand.scale = Vec3::one() / 11.0;
next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave * 1.0, 6.0);
next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave * 1.5);
next.l_foot.offset = Vec3::new(-3.4, 0.0 + wave_cos * 1.0, 6.0);
next.l_foot.ori = Quaternion::rotation_x(-0.0 - wave_cos * 1.5);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, 0.0 - wave * 1.0, 6.0);
next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave * 1.5);
next.r_foot.offset = Vec3::new(3.4, 0.0 - wave_cos * 1.0, 6.0);
next.r_foot.ori = Quaternion::rotation_x(-0.0 + wave_cos * 1.5);
next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-7.0, -5.0, 15.0);

View File

@ -3,6 +3,7 @@ use crate::{
menu::char_selection::CharSelectionState, singleplayer::Singleplayer, Direction, GlobalState,
PlayState, PlayStateResult,
};
use client::Client;
use common::comp;
use log::warn;
use std::net::SocketAddr;
@ -15,7 +16,7 @@ pub struct StartSingleplayerState {
impl StartSingleplayerState {
/// Create a new `MainMenuState`.
pub fn new() -> Self {
let (singleplayer, sock) = Singleplayer::new();
let (singleplayer, sock) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
Self { singleplayer, sock }
}

View File

@ -1,18 +1,14 @@
// Library
use vek::*;
// Project
use crate::{
mesh::{vol, Meshable},
render::{self, Mesh, Quad, TerrainPipeline},
};
use common::{
terrain::Block,
vol::{BaseVol, ReadVol, SizedVol, VolSize, Vox},
volumes::{dyna::Dyna, vol_map_2d::VolMap2d, vol_map_3d::VolMap3d},
};
// Crate
use crate::{
mesh::{vol, Meshable},
render::{self, Mesh, Quad, TerrainPipeline},
};
use std::fmt::Debug;
use vek::*;
type TerrainVertex = <TerrainPipeline as render::Pipeline>::Vertex;
@ -46,7 +42,7 @@ impl<M> Meshable for Dyna<Block, M> {
}
*/
impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2d<V, S> {
impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone> Meshable for VolMap2d<V, S> {
type Pipeline = TerrainPipeline;
type Supplement = Aabb<i32>;
@ -55,7 +51,7 @@ impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2
for x in range.min.x + 1..range.max.x - 1 {
for y in range.min.y + 1..range.max.y - 1 {
let mut neighbour_light = [[1.0f32; 3]; 3];
let mut neighbour_light = [[(1.0f32, 0.0); 3]; 3];
for z in (range.min.z..range.max.z).rev() {
let pos = Vec3::new(x, y, z);
@ -66,7 +62,7 @@ impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2
.iter()
.map(|col| col.iter())
.flatten()
.fold(0.0, |a, x| a + x)
.fold(0.0, |a, (x, _)| a + x)
/ 9.0;
let light = avg_light;
@ -89,13 +85,19 @@ impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2
// Accumulate shade under opaque blocks
for i in 0..3 {
for j in 0..3 {
neighbour_light[i][j] = if let Ok(opacity) = self
let max_opacity = neighbour_light[i][j].1;
neighbour_light[i][j] = if let Some(opacity) = self
.get(pos + Vec3::new(i as i32 - 1, j as i32 - 1, 0))
.map(|vox| vox.get_opacity())
.ok()
.and_then(|vox| vox.get_opacity())
{
neighbour_light[i][j] * (1.0 - opacity * 0.5)
(
(neighbour_light[i][j].0 * (1.0 - max_opacity * 0.3))
.max(1.0 - max_opacity * 0.999),
max_opacity.max(opacity),
)
} else {
(neighbour_light[i][j] * 1.05).min(1.0)
((neighbour_light[i][j].0 * 1.02).min(1.0), max_opacity)
};
}
}
@ -107,7 +109,7 @@ impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap2
}
/*
impl<V: BaseVol<Vox = Block> + ReadVol, S: VolSize + Clone> Meshable for VolMap3d<V, S> {
impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone> Meshable for VolMap3d<V, S> {
type Pipeline = TerrainPipeline;
type Supplement = Aabb<i32>;

View File

@ -85,7 +85,7 @@ impl Renderer {
let mut include_ctx = IncludeContext::new();
include_ctx.include("globals.glsl", globals);
include_ctx.include("sky.glsl", globals);
include_ctx.include("sky.glsl", sky);
// Construct a pipeline for rendering skyboxes
let skybox_pipeline = create_pipeline(
@ -230,9 +230,7 @@ impl Renderer {
/// Queue the clearing of the color and depth targets ready for a new frame to be rendered.
/// TODO: Make a version of this that doesn't clear the colour target for speed.
pub fn clear(&mut self, col: Rgba<f32>) {
self.encoder.clear(&self.tgt_color_view, col.into_array());
self.encoder.clear_depth(&self.tgt_depth_view, 1.0);
self.encoder.clear(&self.win_color_view, col.into_array());
self.encoder.clear_depth(&self.win_depth_view, 1.0);
}

View File

@ -11,7 +11,7 @@ use vek::*;
const NEAR_PLANE: f32 = 0.1;
const FAR_PLANE: f32 = 10000.0;
const INTERP_TIME: f32 = 0.1;
const INTERP_TIME: f32 = 0.05;
pub struct Camera {
tgt_focus: Vec3<f32>,

View File

@ -742,7 +742,8 @@ impl<S: Skeleton> FigureState<S> {
) {
let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(pos)
* Mat4::rotation_z(-ori.x.atan2(ori.y)); // + f32::consts::PI / 2.0);
* Mat4::rotation_z(-ori.x.atan2(ori.y))
* Mat4::scaling_3d(Vec3::from(0.8));
let locals = FigureLocals::new(mat, col);
renderer.update_consts(&mut self.locals, &[locals]).unwrap();

View File

@ -40,6 +40,7 @@ pub struct Scene {
skybox: Skybox,
postprocess: PostProcess,
terrain: Terrain,
loaded_distance: f32,
figure_mgr: FigureMgr,
}
@ -64,6 +65,7 @@ impl Scene {
.unwrap(),
},
terrain: Terrain::new(),
loaded_distance: 0.0,
figure_mgr: FigureMgr::new(),
}
}
@ -127,6 +129,10 @@ impl Scene {
// Compute camera matrices.
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client);
// Update chunk loaded distance smoothly for nice shader fog
let loaded_distance = client.loaded_distance().unwrap_or(0) as f32 * 32.0;
self.loaded_distance = (0.98 * self.loaded_distance + 0.02 * loaded_distance).max(0.01);
// Update global constants.
renderer
.update_consts(
@ -136,7 +142,7 @@ impl Scene {
proj_mat,
cam_pos,
self.camera.get_focus_pos(),
10.0,
self.loaded_distance,
client.state().get_time_of_day(),
client.state().get_time(),
renderer.get_resolution(),
@ -145,7 +151,14 @@ impl Scene {
.expect("Failed to update global constants");
// Maintain the terrain.
self.terrain.maintain(renderer, client);
self.terrain.maintain(
renderer,
client,
self.camera.get_focus_pos(),
self.loaded_distance,
view_mat,
proj_mat,
);
// Maintain the figures.
self.figure_mgr.maintain(renderer, client);

View File

@ -3,14 +3,21 @@ use crate::{
render::{Consts, Globals, Mesh, Model, Renderer, TerrainLocals, TerrainPipeline},
};
use client::Client;
use common::{terrain::TerrainMap, vol::SampleVol, volumes::vol_map_2d::VolMap2dErr};
use std::{collections::HashMap, i32, sync::mpsc, time::Duration};
use common::{
terrain::{TerrainChunkSize, TerrainMap},
vol::{SampleVol, VolSize},
volumes::vol_map_2d::VolMap2dErr,
};
use frustum_query::frustum::Frustum;
use std::{collections::HashMap, i32, ops::Mul, sync::mpsc, time::Duration};
use vek::*;
struct TerrainChunk {
// GPU data
model: Model<TerrainPipeline>,
locals: Consts<TerrainLocals>,
visible: bool,
z_bounds: (f32, f32),
}
struct ChunkMeshState {
@ -22,6 +29,7 @@ struct ChunkMeshState {
/// A type produced by mesh worker threads corresponding to the position and mesh of a chunk.
struct MeshWorkerResponse {
pos: Vec2<i32>,
z_bounds: (f32, f32),
mesh: Mesh<TerrainPipeline>,
started_tick: u64,
}
@ -29,12 +37,14 @@ struct MeshWorkerResponse {
/// Function executed by worker threads dedicated to chunk meshing.
fn mesh_worker(
pos: Vec2<i32>,
z_bounds: (f32, f32),
started_tick: u64,
volume: <TerrainMap as SampleVol<Aabr<i32>>>::Sample,
range: Aabb<i32>,
) -> MeshWorkerResponse {
MeshWorkerResponse {
pos,
z_bounds,
mesh: volume.generate_mesh(range),
started_tick,
}
@ -66,7 +76,15 @@ impl Terrain {
}
/// Maintain terrain data. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
pub fn maintain(
&mut self,
renderer: &mut Renderer,
client: &Client,
focus_pos: Vec3<f32>,
loaded_distance: f32,
view_mat: Mat4<f32>,
proj_mat: Mat4<f32>,
) {
let current_tick = client.get_tick();
// Add any recently created or changed chunks to the list of chunks to be meshed.
@ -143,16 +161,16 @@ impl Terrain {
};
// The region to actually mesh
let z_min = volume
let min_z = volume
.iter()
.fold(i32::MAX, |min, (_, chunk)| chunk.get_z_min().min(min));
let z_max = volume
.fold(i32::MAX, |min, (_, chunk)| chunk.get_min_z().min(min));
let max_z = volume
.iter()
.fold(i32::MIN, |max, (_, chunk)| chunk.get_z_max().max(max));
.fold(i32::MIN, |max, (_, chunk)| chunk.get_max_z().max(max));
let aabb = Aabb {
min: Vec3::from(aabr.min) + Vec3::unit_z() * z_min,
max: Vec3::from(aabr.max) + Vec3::unit_z() * z_max,
min: Vec3::from(aabr.min) + Vec3::unit_z() * (min_z - 1),
max: Vec3::from(aabr.max) + Vec3::unit_z() * (max_z + 1),
};
// Clone various things so that they can be moved into the thread.
@ -161,7 +179,13 @@ impl Terrain {
// Queue the worker thread.
client.thread_pool().execute(move || {
let _ = send.send(mesh_worker(pos, current_tick, volume, aabb));
let _ = send.send(mesh_worker(
pos,
(min_z as f32, max_z as f32),
current_tick,
volume,
aabb,
));
});
todo.active_worker = true;
}
@ -190,6 +214,8 @@ impl Terrain {
.into_array(),
}])
.expect("Failed to upload chunk locals to the GPU!"),
visible: false,
z_bounds: response.z_bounds,
},
);
}
@ -198,11 +224,50 @@ impl Terrain {
_ => {}
}
}
// Construct view frustum
let frustum = Frustum::from_modelview_and_projection(
&view_mat.into_col_array(),
&proj_mat.into_col_array(),
);
// Update chunk visibility
let chunk_sz = TerrainChunkSize::SIZE.x as f32;
for (pos, chunk) in &mut self.chunks {
let chunk_pos = pos.map(|e| e as f32 * chunk_sz);
// Limit focus_pos to chunk bounds and ensure the chunk is within the fog boundary
let nearest_in_chunk = Vec2::from(focus_pos).clamped(chunk_pos, chunk_pos + chunk_sz);
let in_range = Vec2::<f32>::from(focus_pos).distance_squared(nearest_in_chunk)
< loaded_distance.powf(2.0);
// Ensure the chunk is within the view frustrum
let chunk_mid = Vec3::new(
chunk_pos.x + chunk_sz / 2.0,
chunk_pos.y + chunk_sz / 2.0,
(chunk.z_bounds.0 + chunk.z_bounds.1) * 0.5,
);
let chunk_radius = (chunk.z_bounds.1 - chunk.z_bounds.0)
.max(chunk_sz / 2.0)
.powf(2.0)
.mul(2.0)
.sqrt();
let in_frustum = frustum.sphere_intersecting(
&chunk_mid.x,
&chunk_mid.y,
&chunk_mid.z,
&chunk_radius,
);
chunk.visible = in_range && in_frustum;
}
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
for (_, chunk) in &self.chunks {
renderer.render_terrain_chunk(&chunk.model, globals, &chunk.locals);
for (pos, chunk) in &self.chunks {
if chunk.visible {
renderer.render_terrain_chunk(&chunk.model, globals, &chunk.locals);
}
}
}
}

View File

@ -1,3 +1,4 @@
use client::Client;
use common::clock::Clock;
use log::info;
use portpicker::pick_unused_port;
@ -23,7 +24,7 @@ pub struct Singleplayer {
}
impl Singleplayer {
pub fn new() -> (Self, SocketAddr) {
pub fn new(client: Option<&Client>) -> (Self, SocketAddr) {
let (sender, receiver) = channel();
let sock = SocketAddr::from((
@ -34,6 +35,11 @@ impl Singleplayer {
// Create server
let server = Server::bind(sock.clone()).expect("Failed to create server instance!");
let server = match client {
Some(client) => server.with_thread_pool(client.thread_pool().clone()),
None => server,
};
let thread = thread::spawn(move || {
run_server(server, receiver);
});

View File

@ -49,12 +49,16 @@ impl World {
let warp_nz = BasicMulti::new().set_octaves(3).set_seed(self.sim.seed + 0);
let base_z = match self.sim.get_base_z(chunk_pos.map(|e| e as u32)) {
let chunk_size2d = Vec2::from(TerrainChunkSize::SIZE);
let base_z = match self.sim.get_interpolated(
chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2),
|chunk| chunk.get_base_z(),
) {
Some(base_z) => base_z as i32,
None => return TerrainChunk::new(0, water, air, TerrainChunkMeta::void()),
};
let mut chunk = TerrainChunk::new(base_z, stone, air, TerrainChunkMeta::void());
let mut chunk = TerrainChunk::new(base_z - 8, stone, air, TerrainChunkMeta::void());
let mut world_sampler = self.sim.sampler();
@ -64,12 +68,17 @@ impl World {
+ Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32);
let wposf2d = wpos2d.map(|e| e as f64);
let min_z = self
.sim
.get_interpolated(wpos2d, |chunk| chunk.get_min_z())
.unwrap_or(0.0) as i32;
let max_z = self
.sim
.get_interpolated(wpos2d, |chunk| chunk.get_max_z())
.unwrap_or(0.0) as i32;
for z in base_z..max_z.max(sim::SEA_LEVEL as i32) {
for z in min_z..max_z {
let lpos = Vec3::new(x, y, z);
let wpos =
lpos + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32);

View File

@ -49,7 +49,7 @@ impl WorldSim {
chaos_nz: RidgedMulti::new().set_octaves(7).set_seed(seed + 2),
hill_nz: SuperSimplex::new().set_seed(seed + 3),
alt_nz: HybridMulti::new()
.set_octaves(7)
.set_octaves(8)
.set_persistence(0.1)
.set_seed(seed + 4),
temp_nz: SuperSimplex::new().set_seed(seed + 5),
@ -57,7 +57,7 @@ impl WorldSim {
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 7),
warp_nz: BasicMulti::new().set_octaves(3).set_seed(seed + 8),
tree_nz: BasicMulti::new()
.set_octaves(8)
.set_octaves(12)
.set_persistence(0.75)
.set_seed(seed + 9),
cave_0_nz: SuperSimplex::new().set_seed(seed + 10),
@ -75,7 +75,7 @@ impl WorldSim {
seed,
chunks,
gen_ctx,
tree_gen: StructureGen2d::new(seed, 32, 28),
tree_gen: StructureGen2d::new(seed, 24, 16),
}
}
@ -165,15 +165,14 @@ impl<'a> Sampler<'a> {
let rock = (sim.gen_ctx.small_nz.get((wposf.div(100.0)).into_array()) as f32)
.mul(rockiness)
.sub(0.2)
.sub(0.35)
.max(0.0)
.mul(2.0);
.mul(6.0);
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?
+ sim.gen_ctx.small_nz.get((wposf.div(256.0)).into_array()) as f32
* chaos.max(0.2)
* 64.0
+ rock * 15.0;
* 64.0;
let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
@ -182,7 +181,7 @@ impl<'a> Sampler<'a> {
.mul(0.5);
// Colours
let cold_grass = Rgb::new(0.1, 0.6, 0.3);
let cold_grass = Rgb::new(0.0, 0.55, 0.15);
let warm_grass = Rgb::new(0.25, 0.8, 0.05);
let cold_stone = Rgb::new(0.55, 0.7, 0.75);
let warm_stone = Rgb::new(0.65, 0.65, 0.35);
@ -191,7 +190,7 @@ impl<'a> Sampler<'a> {
let snow = Rgb::broadcast(1.0);
let grass = Rgb::lerp(cold_grass, warm_grass, marble);
let grassland = Rgb::lerp(grass, warm_stone, rock.mul(5.0).min(0.8));
let grassland = grass; //Rgb::lerp(grass, warm_stone, rock.mul(5.0).min(0.8));
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
let ground = Rgb::lerp(
@ -204,7 +203,7 @@ impl<'a> Sampler<'a> {
let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0)
.div(1000.0)
.div(800.0)
.into_array(),
) as f32)
.powf(2.0)
@ -240,9 +239,14 @@ impl<'a> Sampler<'a> {
Rgb::lerp(
cliff,
snow,
(alt - SEA_LEVEL - 350.0 - alt_base - temp * 48.0) / 12.0,
(alt - SEA_LEVEL
- 0.3 * MOUNTAIN_HEIGHT
- alt_base
- temp * 96.0
- marble * 24.0)
/ 12.0,
),
(alt - SEA_LEVEL - 150.0) / 180.0,
(alt - SEA_LEVEL - 0.15 * MOUNTAIN_HEIGHT) / 180.0,
),
// Beach
(alt - SEA_LEVEL - 2.0) / 5.0,
@ -251,6 +255,7 @@ impl<'a> Sampler<'a> {
close_trees: sim.tree_gen.sample(wpos),
cave_xy,
cave_alt,
rock,
})
}
@ -275,6 +280,7 @@ impl<'a> Sampler<'a> {
close_trees,
cave_xy,
cave_alt,
rock,
} = *self.sample_2d(wpos2d)?;
// Apply warping
@ -289,7 +295,6 @@ impl<'a> Sampler<'a> {
.mul(110.0);
let height = alt + warp;
let temp = 0.0;
// Sample blocks
@ -298,8 +303,9 @@ impl<'a> Sampler<'a> {
let dirt = Block::new(1, Rgb::new(128, 90, 0));
let sand = Block::new(1, Rgb::new(180, 150, 50));
let water = Block::new(1, Rgb::new(100, 150, 255));
let warm_stone = Block::new(1, Rgb::new(165, 165, 90));
let ground_block = if (wposf.z as f32) < height - 4.0 {
let block = if (wposf.z as f32) < height - 4.0 {
// Underground
Some(stone)
} else if (wposf.z as f32) < height {
@ -312,7 +318,8 @@ impl<'a> Sampler<'a> {
None
};
let ground_block = if let Some(block) = ground_block {
// Caves
let block = block.and_then(|block| {
// Underground
let cave = cave_xy.powf(2.0)
* (wposf.z as f32 - cave_alt)
@ -327,11 +334,18 @@ impl<'a> Sampler<'a> {
} else {
Some(block)
}
} else {
None
};
});
let block = match ground_block {
// Rocks
let block = block.or_else(|| {
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock {
Some(warm_stone)
} else {
None
}
});
let block = match block {
Some(block) => block,
None => (&close_trees)
.iter()
@ -343,8 +357,9 @@ impl<'a> Sampler<'a> {
{
let tree_pos3d =
Vec3::new(tree_pos.x, tree_pos.y, tree_sample.alt as i32);
let rpos = wpos - tree_pos3d;
block.or(TREES[*tree_seed as usize % TREES.len()]
.get(wpos - tree_pos3d)
.get((rpos * 160) / 128) // Scaling
.map(|b| b.clone())
.unwrap_or(Block::empty()))
}
@ -357,6 +372,113 @@ impl<'a> Sampler<'a> {
}
}
#[derive(Copy, Clone)]
pub struct Sample2d {
pub alt: f32,
pub chaos: f32,
pub surface_color: Rgb<f32>,
pub tree_density: f32,
pub close_trees: [(Vec2<i32>, u32); 9],
pub cave_xy: f32,
pub cave_alt: f32,
pub rock: f32,
}
#[derive(Copy, Clone)]
pub struct Sample3d {
pub block: Block,
}
pub const SEA_LEVEL: f32 = 128.0;
pub const MOUNTAIN_HEIGHT: f32 = 900.0;
const Z_TOLERANCE: (f32, f32) = (64.0, 64.0);
pub struct SimChunk {
pub chaos: f32,
pub alt_base: f32,
pub alt: f32,
pub temp: f32,
pub rockiness: f32,
pub tree_density: f32,
}
impl SimChunk {
fn generate(pos: Vec2<u32>, gen_ctx: &mut GenCtx) -> Self {
let wposf = (pos * Vec2::from(TerrainChunkSize::SIZE)).map(|e| e as f64);
let hill = (0.0
+ gen_ctx
.hill_nz
.get((wposf.div(3_500.0)).into_array())
.mul(1.0) as f32
+ gen_ctx
.hill_nz
.get((wposf.div(1_000.0)).into_array())
.mul(0.3) as f32)
.add(0.3)
.max(0.0);
let chaos = (gen_ctx.chaos_nz.get((wposf.div(2_000.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(1.5)
.add(0.1 * hill);
let chaos = chaos + chaos.mul(16.0).sin().mul(0.02);
let alt_base = gen_ctx.alt_nz.get((wposf.div(6_000.0)).into_array()) as f32;
let alt_base = alt_base
.mul(0.4)
.add(alt_base.mul(128.0).sin().mul(0.005))
.mul(800.0);
let alt_main = gen_ctx.alt_nz.get((wposf.div(3_000.0)).into_array()) as f32;
let alt = SEA_LEVEL
+ alt_base
+ (0.0
+ alt_main
+ gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32
* alt_main.max(0.05)
* chaos
* 1.6)
.add(1.0)
.mul(0.5)
.mul(chaos)
.mul(MOUNTAIN_HEIGHT);
Self {
chaos,
alt_base,
alt,
temp: (gen_ctx.temp_nz.get((wposf.div(8192.0)).into_array()) as f32),
rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
.sub(0.1)
.mul(1.2)
.max(0.0),
tree_density: (gen_ctx.tree_nz.get((wposf.div(1024.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.mul(1.0 - chaos * 0.85)
.add(0.1)
.mul(if alt > SEA_LEVEL + 2.0 { 1.0 } else { 0.0 }),
}
}
pub fn get_base_z(&self) -> f32 {
self.alt - Z_TOLERANCE.0 * self.chaos
}
pub fn get_min_z(&self) -> f32 {
self.alt - Z_TOLERANCE.0 * (self.chaos + 0.5)
}
pub fn get_max_z(&self) -> f32 {
(self.alt + Z_TOLERANCE.1).max(SEA_LEVEL + 1.0)
}
}
lazy_static! {
static ref TREES: [Arc<Structure>; 61] = [
// green oaks
@ -653,103 +775,3 @@ lazy_static! {
];
}
#[derive(Copy, Clone)]
pub struct Sample2d {
pub alt: f32,
pub chaos: f32,
pub surface_color: Rgb<f32>,
pub tree_density: f32,
pub close_trees: [(Vec2<i32>, u32); 9],
pub cave_xy: f32,
pub cave_alt: f32,
}
#[derive(Copy, Clone)]
pub struct Sample3d {
pub block: Block,
}
const Z_TOLERANCE: (f32, f32) = (126.0, 94.0);
pub const SEA_LEVEL: f32 = 128.0;
pub struct SimChunk {
pub chaos: f32,
pub alt_base: f32,
pub alt: f32,
pub temp: f32,
pub rockiness: f32,
pub tree_density: f32,
}
impl SimChunk {
fn generate(pos: Vec2<u32>, gen_ctx: &mut GenCtx) -> Self {
let wposf = (pos * Vec2::from(TerrainChunkSize::SIZE)).map(|e| e as f64);
let hill = (0.0
+ gen_ctx
.hill_nz
.get((wposf.div(3_500.0)).into_array())
.mul(1.0) as f32
+ gen_ctx
.hill_nz
.get((wposf.div(1_000.0)).into_array())
.mul(0.3) as f32)
.add(0.3)
.max(0.0);
let chaos = (gen_ctx.chaos_nz.get((wposf.div(4_000.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(1.5)
.add(0.1 * hill);
let chaos = chaos + chaos.mul(16.0).sin().mul(0.02);
let alt_base = gen_ctx.alt_nz.get((wposf.div(6_000.0)).into_array()) as f32;
let alt_base = alt_base
.mul(0.4)
.add(alt_base.mul(128.0).sin().mul(0.004))
.mul(600.0);
let alt_main = gen_ctx.alt_nz.get((wposf.div(1_500.0)).into_array()) as f32;
let alt = SEA_LEVEL
+ alt_base
+ (0.0
+ alt_main
+ gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32
* alt_main.max(0.05)
* chaos
* 1.3)
.add(1.0)
.mul(0.5)
.mul(chaos)
.mul(1200.0);
Self {
chaos,
alt_base,
alt,
temp: (gen_ctx.temp_nz.get((wposf.div(8192.0)).into_array()) as f32),
rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
.sub(0.1)
.mul(1.2)
.max(0.0),
tree_density: (gen_ctx.tree_nz.get((wposf.div(1024.0)).into_array()) as f32)
.add(1.0)
.mul(0.5)
.mul(1.0 - chaos * 0.85)
.add(0.1)
.mul(if alt > SEA_LEVEL + 3.0 { 1.0 } else { 0.0 }),
}
}
pub fn get_base_z(&self) -> f32 {
self.alt - Z_TOLERANCE.0 * (self.chaos + 0.3)
}
pub fn get_max_z(&self) -> f32 {
self.alt + Z_TOLERANCE.1
}
}