Merge branch 'master' into 'master'

Reimplemented Voxygen

See merge request veloren/fresh!3

Former-commit-id: 0c99632b70987bd6ab7444cf89c712056dd57e0b
This commit is contained in:
Joshua Barretto 2019-01-31 16:17:17 +00:00
commit 71648ce4a8
90 changed files with 4079 additions and 57 deletions

View File

@ -3,6 +3,9 @@ members = [
"common",
"client",
"server",
"server-cli",
"voxygen",
"world",
]
[profile.dev]

View File

@ -5,6 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { path = "../common" }
common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" }
specs = "0.14"
vek = "0.9"
threadpool = "1.7"

View File

@ -1,10 +1,20 @@
// Standard
use std::time::Duration;
// Internal
use common::state::State;
// Library
use specs::Entity as EcsEntity;
use vek::*;
use threadpool;
pub enum ClientErr {
// Project
use common::{
state::State,
terrain::TerrainChunk,
};
use world::World;
#[derive(Debug)]
pub enum Error {
ServerShutdown,
Other(String),
}
@ -14,20 +24,65 @@ pub struct Input {
}
pub struct Client {
state: State,
thread_pool: threadpool::ThreadPool,
// TODO: Add "meta" state here
tick: u64,
state: State,
player: Option<EcsEntity>,
// Testing
world: World,
pub chunk: Option<TerrainChunk>,
}
impl Client {
/// Create a new `Client`.
pub fn new() -> Self {
Self {
thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into())
.build(),
tick: 0,
state: State::new(),
player: None,
// Testing
world: World::new(),
chunk: None,
}
}
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e: threads that
/// block on I/O operations are exempt).
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
// TODO: Get rid of this
pub fn with_test_state(mut self) -> Self {
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
self
}
// TODO: Get rid of this
pub fn load_chunk(&mut self, pos: Vec3<i32>) {
self.state.terrain_mut().insert(pos, self.world.generate_chunk(pos));
self.state.changes_mut().new_chunks.push(pos);
}
/// Get a reference to the client's game state.
pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get the current tick number.
pub fn get_tick(&self) -> u64 {
self.tick
}
/// Execute a single client tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> {
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the
@ -44,6 +99,13 @@ impl Client {
self.state.tick(dt);
// Finish the tick, pass control back to the frontend (step 6)
self.tick += 1;
Ok(())
}
/// Clean up the client after a tick
pub fn cleanup(&mut self) {
// Cleanup the local state
self.state.cleanup();
}
}

View File

@ -1,9 +1,12 @@
[package]
name = "common"
name = "veloren-common"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
specs = "0.14"
shred = "0.7"
vek = "0.9"
dot_vox = "1.0"
threadpool = "1.7"

50
common/src/clock.rs Normal file
View File

@ -0,0 +1,50 @@
// Standard
use std::{
thread,
time::{Duration, SystemTime},
};
const CLOCK_SMOOTHING: f64 = 0.9;
pub struct Clock {
last_sys_time: SystemTime,
last_delta: Option<Duration>,
running_tps_average: f64,
}
impl Clock {
pub fn new() -> Self {
Self {
last_sys_time: SystemTime::now(),
last_delta: None,
running_tps_average: 0.0,
}
}
pub fn get_tps(&self) -> f64 { self.running_tps_average }
pub fn get_last_delta(&self) -> Duration { self.last_delta.unwrap_or(Duration::new(0, 0)) }
pub fn get_avg_delta(&self) -> Duration { Duration::from_float_secs(self.running_tps_average) }
pub fn tick(&mut self, tgt: Duration) {
let delta = SystemTime::now()
.duration_since(self.last_sys_time)
.expect("Time went backwards!");
// Attempt to sleep to fill the gap
if let Some(sleep_dur) = tgt.checked_sub(delta) {
thread::sleep(sleep_dur);
}
let delta = SystemTime::now()
.duration_since(self.last_sys_time)
.expect("Time went backwards!");
self.last_sys_time = SystemTime::now();
self.last_delta = Some(delta);
self.running_tps_average =
CLOCK_SMOOTHING * self.running_tps_average +
(1.0 - CLOCK_SMOOTHING) * delta.as_float_secs();
}
}

View File

@ -1,7 +1,7 @@
pub mod phys;
// External
use specs::{World as EcsWorld, Builder};
use specs::World as EcsWorld;
pub fn register_local_components(ecs_world: &mut EcsWorld) {
ecs_world.register::<phys::Pos>();

38
common/src/figure/cell.rs Normal file
View File

@ -0,0 +1,38 @@
// Library
use vek::*;
// Crate
use crate::vol::Vox;
/// A type representing a single voxel in a figure
#[derive(Copy, Clone, Debug)]
pub enum Cell {
Filled([u8; 3]),
Empty,
}
impl Cell {
pub fn new(rgb: Rgb<u8>) -> Self {
Cell::Filled(rgb.into_array())
}
pub fn get_color(&self) -> Option<Rgb<u8>> {
match self {
Cell::Filled(col) => Some(Rgb::from(*col)),
Cell::Empty => None,
}
}
}
impl Vox for Cell {
fn empty() -> Self {
Cell::Empty
}
fn is_empty(&self) -> bool {
match self {
Cell::Filled(_) => false,
Cell::Empty => true,
}
}
}

55
common/src/figure/mod.rs Normal file
View File

@ -0,0 +1,55 @@
pub mod cell;
// Library
use vek::*;
use dot_vox::DotVoxData;
// Crate
use crate::{
vol::{Vox, WriteVol},
volumes::dyna::Dyna,
};
// Local
use self::cell::Cell;
/// A type representing a volume that may be part of an animated figure.
///
/// Figures are used to represent things like characters, NPCs, mobs, etc.
pub type Segment = Dyna<Cell, ()>;
impl From<DotVoxData> for Segment {
fn from(dot_vox_data: DotVoxData) -> Self {
if let Some(model) = dot_vox_data.models.get(0) {
let palette = dot_vox_data
.palette
.iter()
.map(|col| Rgba::from(col.to_ne_bytes()).into())
.collect::<Vec<_>>();
let mut segment = Segment::filled(
Vec3::new(
model.size.x,
model.size.y,
model.size.z,
),
Cell::empty(),
(),
);
for voxel in &model.voxels {
if let Some(&color) = palette.get(voxel.i as usize) {
// TODO: Maybe don't ignore this error?
let _ = segment.set(
Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| e as i32),
Cell::new(color),
);
}
}
segment
} else {
Segment::filled(Vec3::zero(), Cell::empty(), ())
}
}
}

View File

@ -1,7 +1,10 @@
#![feature(euclidean_division)]
#![feature(euclidean_division, duration_float)]
pub mod clock;
pub mod comp;
pub mod figure;
pub mod state;
pub mod terrain;
pub mod util;
pub mod volumes;
pub mod vol;

View File

@ -1,39 +1,119 @@
// Standard
use std::time::Duration;
// External
// Library
use specs::World as EcsWorld;
use shred::{Fetch, FetchMut};
use vek::*;
// Crate
use crate::{
comp,
terrain::TerrainMap,
vol::VolSize,
};
// A type used to represent game state stored on both the client and the server. This includes
// things like entity components, terrain data, and global state like weather, time of day, etc.
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this
const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0;
/// A resource to store the time of day
struct TimeOfDay(f64);
/// A resource to store the tick (i.e: physics) time
struct Time(f64);
pub struct Changes {
pub new_chunks: Vec<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>,
pub removed_chunks: Vec<Vec3<i32>>,
}
impl Changes {
pub fn default() -> Self {
Self {
new_chunks: vec![],
changed_chunks: vec![],
removed_chunks: vec![],
}
}
pub fn cleanup(&mut self) {
self.new_chunks.clear();
self.changed_chunks.clear();
self.removed_chunks.clear();
}
}
/// A type used to represent game state stored on both the client and the server. This includes
/// things like entity components, terrain data, and global state like weather, time of day, etc.
pub struct State {
ecs_world: EcsWorld,
terrain_map: TerrainMap,
time: f64,
changes: Changes,
}
impl State {
/// Create a new `State`.
pub fn new() -> Self {
let mut ecs_world = EcsWorld::new();
// Register resources used by the ECS
ecs_world.add_resource(TimeOfDay(0.0));
ecs_world.add_resource(Time(0.0));
ecs_world.add_resource(TerrainMap::new());
// Register common components with the state
comp::register_local_components(&mut ecs_world);
Self {
ecs_world,
terrain_map: TerrainMap::new(),
time: 0.0,
changes: Changes::default(),
}
}
// Execute a single tick, simulating the game state by the given duration
/// Get a reference to the internal ECS world
pub fn ecs_world(&self) -> &EcsWorld { &self.ecs_world }
/// Get a reference to the `Changes` structure of the state. This contains
/// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes { &self.changes }
// TODO: Get rid of this since it shouldn't be needed
pub fn changes_mut(&mut self) -> &mut Changes { &mut self.changes }
/// Get the current in-game time of day.
///
/// Note that this should not be used for physics, animations or other such localised timings.
pub fn get_time_of_day(&self) -> f64 {
self.ecs_world.read_resource::<TimeOfDay>().0
}
/// Get the current in-game time.
///
/// Note that this does not correspond to the time of day.
pub fn get_time(&self) -> f64 {
self.ecs_world.read_resource::<Time>().0
}
/// Get a reference to this state's terrain.
pub fn terrain<'a>(&'a self) -> Fetch<'a, TerrainMap> {
self.ecs_world.read_resource::<TerrainMap>()
}
// TODO: Get rid of this since it shouldn't be needed
pub fn terrain_mut<'a>(&'a mut self) -> FetchMut<'a, TerrainMap> {
self.ecs_world.write_resource::<TerrainMap>()
}
/// Execute a single tick, simulating the game state by the given duration.
pub fn tick(&mut self, dt: Duration) {
println!("Ticked!");
// Change the time accordingly
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR;
self.ecs_world.write_resource::<Time>().0 += dt.as_float_secs();
}
/// Clean up the state after a tick
pub fn cleanup(&mut self) {
// Clean up data structures from the last tick
self.changes.cleanup();
}
}

View File

@ -1,4 +1,5 @@
pub enum BiomeKind {
Void,
Grassland,
Ocean,
Mountain,

View File

@ -1,4 +1,41 @@
// Library
use vek::*;
// Crate
use crate::vol::Vox;
#[derive(Copy, Clone, Debug)]
pub struct Block {
kind: u8,
color: [u8; 3],
}
impl Block {
pub fn new(kind: u8, color: Rgb<u8>) -> Self {
Self {
kind,
color: color.into_array(),
}
}
pub fn get_color(&self) -> Option<Rgb<u8>> {
if self.is_empty() {
None
} else {
Some(self.color.into())
}
}
}
impl Vox for Block {
fn empty() -> Self {
Self {
kind: 0,
color: [0; 3],
}
}
fn is_empty(&self) -> bool {
self.kind == 0
}
}

View File

@ -1,35 +1,47 @@
pub mod block;
pub mod biome;
// Reexports
pub use self::{
block::Block,
biome::BiomeKind,
};
// Library
use vek::*;
// Crate
use crate::{
vol::VolSize,
volumes::vol_map::VolMap,
volumes::{
vol_map::VolMap,
chunk::Chunk,
},
};
// Local
use self::{
block::Block,
biome::BiomeKind,
};
// TerrainChunkSize
// ChunkSize
pub struct TerrainChunkSize;
pub struct ChunkSize;
impl VolSize for ChunkSize {
impl VolSize for TerrainChunkSize {
const SIZE: Vec3<u32> = Vec3 { x: 32, y: 32, z: 32 };
}
// ChunkMeta
// TerrainChunkMeta
pub struct ChunkMeta {
pub struct TerrainChunkMeta {
biome: BiomeKind,
}
// TerrainMap
impl TerrainChunkMeta {
pub fn void() -> Self {
Self {
biome: BiomeKind::Void,
}
}
}
pub type TerrainMap = VolMap<Block, ChunkSize, ChunkMeta>;
// Terrain type aliases
pub type TerrainChunk = Chunk<Block, TerrainChunkSize, TerrainChunkMeta>;
pub type TerrainMap = VolMap<Block, TerrainChunkSize, TerrainChunkMeta>;

1
common/src/util/mod.rs Normal file
View File

@ -0,0 +1 @@

View File

@ -1,27 +1,95 @@
// Library
use vek::*;
/// A voxel
pub trait Vox {
fn empty() -> Self;
fn is_empty(&self) -> bool;
}
/// A volume that contains voxel data.
pub trait BaseVol {
type Vox;
type Vox: Vox;
type Err;
}
pub trait SizedVol: BaseVol {
const SIZE: Vec3<u32>;
// Utility types
pub struct VoxPosIter {
pos: Vec3<u32>,
sz: Vec3<u32>,
}
impl Iterator for VoxPosIter {
type Item = Vec3<i32>;
fn next(&mut self) -> Option<Self::Item> {
let mut old_pos = self.pos;
if old_pos.z == self.sz.z {
old_pos.z = 0;
old_pos.y += 1;
if old_pos.y == self.sz.y {
old_pos.y = 0;
old_pos.x += 1;
if old_pos.x == self.sz.x {
return None;
}
}
}
self.pos = old_pos + Vec3::unit_z();
Some(old_pos.map(|e| e as i32))
}
}
/// A volume that has a finite size.
pub trait SizedVol: BaseVol {
/// Get the size of the volume.
#[inline(always)]
fn get_size(&self) -> Vec3<u32>;
/// Iterate through all potential voxel positions in this volume
fn iter_positions(&self) -> VoxPosIter {
VoxPosIter {
pos: Vec3::zero(),
sz: self.get_size(),
}
}
}
/// A volume that provides read access to its voxel data.
pub trait ReadVol: BaseVol {
/// Get a reference to the voxel at the provided position in the volume.
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Err>;
}
/// 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 {
type Sample: BaseVol + ReadVol;
/// Take a sample of the volume by cloning voxels within the provided range.
///
/// Note that value and accessibility of voxels outside the bounds of the sample is
/// implementation-defined and should not be used.
///
/// 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>;
}
/// A volume that provides write access to its voxel data.
pub trait WriteVol: BaseVol {
/// Set the voxel at the provided position in the volume to the provided value.
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Err>;
}
// Utility traits
/// Used to specify a volume's compile-time size. This exists as a substitute until const generics
/// are implemented.
pub trait VolSize {
const SIZE: Vec3<u32>;
}

View File

@ -6,6 +6,7 @@ use vek::*;
// Local
use crate::vol::{
Vox,
BaseVol,
SizedVol,
ReadVol,
@ -13,20 +14,24 @@ use crate::vol::{
VolSize,
};
#[derive(Debug)]
pub enum ChunkErr {
OutOfBounds,
}
/// A volume with dimensions known at compile-time.
// V = Voxel
// S = Size (replace when const generics are a thing)
// M = Metadata
pub struct Chunk<V, S: VolSize, M> {
pub struct Chunk<V: Vox, S: VolSize, M> {
vox: Vec<V>,
meta: M,
phantom: PhantomData<S>,
}
impl<V, S: VolSize, M> Chunk<V, S, M> {
impl<V: Vox, S: VolSize, M> Chunk<V, S, M> {
/// Used to transform a voxel position in the volume into its corresponding index in the voxel
// array.
#[inline(always)]
fn idx_for(pos: Vec3<i32>) -> Option<usize> {
if
@ -44,16 +49,17 @@ impl<V, S: VolSize, M> Chunk<V, S, M> {
}
}
impl<V, S: VolSize, M> BaseVol for Chunk<V, S, M> {
impl<V: Vox, S: VolSize, M> BaseVol for Chunk<V, S, M> {
type Vox = V;
type Err = ChunkErr;
}
impl<V, S: VolSize, M> SizedVol for Chunk<V, S, M> {
const SIZE: Vec3<u32> = Vec3 { x: 32, y: 32, z: 32 };
impl<V: Vox, S: VolSize, M> SizedVol for Chunk<V, S, M> {
#[inline(always)]
fn get_size(&self) -> Vec3<u32> { S::SIZE }
}
impl<V, S: VolSize, M> ReadVol for Chunk<V, S, M> {
impl<V: Vox, S: VolSize, M> ReadVol for Chunk<V, S, M> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, ChunkErr> {
Self::idx_for(pos)
@ -62,7 +68,7 @@ impl<V, S: VolSize, M> ReadVol for Chunk<V, S, M> {
}
}
impl<V, S: VolSize, M> WriteVol for Chunk<V, S, M> {
impl<V: Vox, S: VolSize, M> WriteVol for Chunk<V, S, M> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), ChunkErr> {
Self::idx_for(pos)
@ -72,7 +78,9 @@ impl<V, S: VolSize, M> WriteVol for Chunk<V, S, M> {
}
}
impl<V: Clone, S: VolSize, M> Chunk<V, S, M> {
impl<V: Vox + Clone, S: VolSize, M> Chunk<V, S, M> {
/// Create a new `Chunk` with the provided dimensions and all voxels filled with duplicates of
/// the provided voxel.
pub fn filled(vox: V, meta: M) -> Self {
Self {
vox: vec![vox; S::SIZE.product() as usize],
@ -81,10 +89,12 @@ impl<V: Clone, S: VolSize, M> Chunk<V, S, M> {
}
}
/// Get a reference to the internal metadata.
pub fn metadata(&self) -> &M {
&self.meta
}
/// Get a mutable reference to the internal metadata.
pub fn metadata_mut(&mut self) -> &mut M {
&mut self.meta
}

View File

@ -0,0 +1,97 @@
// Library
use vek::*;
// Local
use crate::vol::{
Vox,
BaseVol,
SizedVol,
ReadVol,
WriteVol,
};
#[derive(Debug)]
pub enum DynaErr {
OutOfBounds,
}
/// A volume with dimensions known only at the creation of the object.
// V = Voxel
// S = Size (replace when const generics are a thing)
// M = Metadata
pub struct Dyna<V: Vox, M> {
vox: Vec<V>,
meta: M,
sz: Vec3<u32>,
}
impl<V: Vox, M> Dyna<V, M> {
/// Used to transform a voxel position in the volume into its corresponding index in the voxel
// array.
#[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)
} else {
None
}
}
}
impl<V: Vox, M> BaseVol for Dyna<V, M> {
type Vox = V;
type Err = DynaErr;
}
impl<V: Vox, M> SizedVol for Dyna<V, M> {
#[inline(always)]
fn get_size(&self) -> Vec3<u32> { self.sz }
}
impl<V: Vox, M> ReadVol for Dyna<V, M> {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&V, DynaErr> {
Self::idx_for(self.sz, pos)
.and_then(|idx| self.vox.get(idx))
.ok_or(DynaErr::OutOfBounds)
}
}
impl<V: Vox, M> WriteVol for Dyna<V, M> {
#[inline(always)]
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), DynaErr> {
Self::idx_for(self.sz, pos)
.and_then(|idx| self.vox.get_mut(idx))
.map(|old_vox| *old_vox = vox)
.ok_or(DynaErr::OutOfBounds)
}
}
impl<V: Vox + Clone, M> Dyna<V, M> {
/// Create a new `Dyna` with the provided dimensions and all voxels filled with duplicates of
/// the provided voxel.
pub fn filled(sz: Vec3<u32>, vox: V, meta: M) -> Self {
Self {
vox: vec![vox; sz.product() as usize],
meta,
sz,
}
}
/// Get a reference to the internal metadata.
pub fn metadata(&self) -> &M {
&self.meta
}
/// Get a mutable reference to the internal metadata.
pub fn metadata_mut(&mut self) -> &mut M {
&mut self.meta
}
}

View File

@ -1,2 +1,3 @@
pub mod dyna;
pub mod chunk;
pub mod vol_map;

View File

@ -7,27 +7,35 @@ use vek::*;
// Crate
use crate::{
vol::{
Vox,
BaseVol,
SizedVol,
ReadVol,
SampleVol,
WriteVol,
VolSize,
},
volumes::chunk::{Chunk, ChunkErr},
volumes::{
chunk::{Chunk, ChunkErr},
dyna::{Dyna, DynaErr},
},
};
#[derive(Debug)]
pub enum VolMapErr {
NoSuchChunk,
ChunkErr(ChunkErr),
DynaErr(DynaErr),
}
// V = Voxel
// S = Size (replace with a const when const generics is a thing)
// M = Chunk metadata
pub struct VolMap<V, S: VolSize, M> {
pub struct VolMap<V: Vox, S: VolSize, M> {
chunks: HashMap<Vec3<i32>, Chunk<V, S, M>>,
}
impl<V, S: VolSize, M> VolMap<V, S, M> {
impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
#[inline(always)]
fn chunk_key(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| e.div_euclid(sz as i32))
@ -39,12 +47,12 @@ impl<V, S: VolSize, M> VolMap<V, S, M> {
}
}
impl<V, S: VolSize, M> BaseVol for VolMap<V, S, M> {
impl<V: Vox, S: VolSize, M> BaseVol for VolMap<V, S, M> {
type Vox = V;
type Err = VolMapErr;
}
impl<V, S: VolSize, M> ReadVol for VolMap<V, S, M> {
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);
@ -57,7 +65,42 @@ impl<V, S: VolSize, M> ReadVol for VolMap<V, S, M> {
}
}
impl<V, S: VolSize, M> WriteVol for VolMap<V, S, M> {
impl<V: Vox + Clone, S: VolSize, M> SampleVol for VolMap<V, S, M> {
type Sample = Dyna<V, ()>;
/// 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(),
(),
);
for pos in sample.iter_positions() {
sample.set(pos, self.get(range.min + pos)?.clone())
.map_err(|err| VolMapErr::DynaErr(err))?;
}
Ok(sample)
}
}
impl<V: Vox, S: VolSize, M> 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);
@ -70,10 +113,20 @@ impl<V, S: VolSize, M> WriteVol for VolMap<V, S, M> {
}
}
impl<V, S: VolSize, M> VolMap<V, S, M> {
impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
pub fn new() -> Self {
Self {
chunks: HashMap::new(),
}
}
pub fn chunk_size() -> Vec3<u32> { S::SIZE }
pub fn insert(&mut self, key: Vec3<i32>, chunk: Chunk<V, S, M>) -> Option<Chunk<V, S, M>> {
self.chunks.insert(key, chunk)
}
pub fn remove(&mut self, key: &Vec3<i32>) -> Option<Chunk<V, S, M>> {
self.chunks.remove(key)
}
}

3
server-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

12
server-cli/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "veloren-server-cli"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
server = { package = "veloren-server", path = "../server" }
common = { package = "veloren-common", path = "../common" }
log = "0.4"
pretty_env_logger = "0.3"

35
server-cli/src/main.rs Normal file
View File

@ -0,0 +1,35 @@
// Standard
use std::time::Duration;
// Library
use log::info;
// Project
use server::{self, Server};
use common::clock::Clock;
const FPS: u64 = 60;
fn main() {
// Init logging
pretty_env_logger::init();
info!("Starting server-cli...");
// Set up an fps clock
let mut clock = Clock::new();
// Create server
let mut server = Server::new();
loop {
server.tick(server::Input {}, clock.get_last_delta())
.expect("Failed to tick server");
// Clean up the server after a tick
server.cleanup();
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
}
}

View File

@ -1,10 +1,11 @@
[package]
name = "server"
name = "veloren-server"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { path = "../common" }
common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" }
specs = "0.14"

View File

@ -3,9 +3,10 @@ use std::time::Duration;
// Internal
use common::state::State;
use world::World;
pub enum ClientErr {
ServerShutdown,
#[derive(Debug)]
pub enum Error {
Other(String),
}
@ -15,19 +16,32 @@ pub struct Input {
pub struct Server {
state: State,
world: World,
// TODO: Add "meta" state here
}
impl Server {
/// Create a new `Server`.
pub fn new() -> Self {
Self {
state: State::new(),
world: World::new(),
}
}
/// Get a reference to the server's game state.
pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the server's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get a reference to the server's world.
pub fn world(&self) -> &World { &self.world }
/// Get a mutable reference to the server's world.
pub fn world_mut(&mut self) -> &mut World { &mut self.world }
/// Execute a single server tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> {
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
// This tick function is the centre of the Veloren universe. Most server-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the
@ -48,4 +62,10 @@ impl Server {
// Finish the tick, pass control back to the frontend (step 6)
Ok(())
}
/// Clean up the server after a tick
pub fn cleanup(&mut self) {
// Cleanup the local state
self.state.cleanup();
}
}

3
voxygen/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

32
voxygen/Cargo.toml Normal file
View File

@ -0,0 +1,32 @@
[package]
name = "voxygen"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[features]
gl = ["gfx_device_gl"]
default = ["gl"]
[dependencies]
common = { package = "veloren-common", path = "../common" }
client = { package = "veloren-client", path = "../client" }
# Graphics
gfx = "0.17"
gfx_device_gl = { version = "0.15", optional = true }
gfx_window_glutin = "0.28"
glutin = "0.19"
# Mathematics
vek = "0.9"
# Utility
glsl-include = "0.2"
failure = "0.1"
lazy_static = "1.1"
log = "0.4"
pretty_env_logger = "0.3"
dot_vox = "1.0"
image = "0.21"

View File

@ -0,0 +1,49 @@
#version 330 core
in vec3 f_pos;
in vec3 f_norm;
in vec3 f_col;
flat in uint f_bone_idx;
layout (std140)
uniform u_locals {
mat4 model_mat;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
struct BoneData {
mat4 bone_mat;
};
layout (std140)
uniform u_bones {
BoneData bones[16];
};
out vec4 tgt_color;
void main() {
vec3 world_norm = (
model_mat *
bones[f_bone_idx].bone_mat *
vec4(f_norm, 0.0)
).xyz;
float ambient = 0.5;
vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1));
float sun_diffuse = dot(sun_dir, world_norm) * 0.5;
tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0);
}

View File

@ -0,0 +1,50 @@
#version 330 core
in vec3 v_pos;
in vec3 v_norm;
in vec3 v_col;
in uint v_bone_idx;
layout (std140)
uniform u_locals {
mat4 model_mat;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
struct BoneData {
mat4 bone_mat;
};
layout (std140)
uniform u_bones {
BoneData bones[16];
};
out vec3 f_pos;
out vec3 f_norm;
out vec3 f_col;
flat out uint f_bone_idx;
void main() {
f_pos = v_pos;
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);
}

View File

@ -0,0 +1,46 @@
#version 330 core
in vec3 f_pos;
layout (std140)
uniform u_locals {
vec4 nul;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
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.0, 0.3, 1.0);
const vec3 SKY_BOTTOM = vec3(0.0, 0.05, 0.2);
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.8, 0.5);
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.8, 0.5);
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 = min(pow(max(dot(dir, sun_dir), 0.0) + 0.01, 16.0), 1.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 * 0.5;
}
void main() {
tgt_color = vec4(get_sky_color(normalize(f_pos), time_of_day.x), 1.0);
}

View File

@ -0,0 +1,31 @@
#version 330 core
in vec3 v_pos;
layout (std140)
uniform u_locals {
vec4 nul;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
out vec3 f_pos;
void main() {
f_pos = v_pos;
gl_Position =
proj_mat *
view_mat *
vec4(v_pos + cam_pos.xyz, 1);
gl_Position.z = 0.0;
}

View File

@ -0,0 +1,33 @@
#version 330 core
in vec3 f_pos;
in vec3 f_norm;
in vec3 f_col;
layout (std140)
uniform u_locals {
vec3 model_offs;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
out vec4 tgt_color;
void main() {
float ambient = 0.5;
vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1));
float sun_diffuse = dot(sun_dir, f_norm) * 0.5;
tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0);
}

View File

@ -0,0 +1,36 @@
#version 330 core
in vec3 v_pos;
in vec3 v_norm;
in vec3 v_col;
layout (std140)
uniform u_locals {
vec3 model_offs;
};
layout (std140)
uniform u_globals {
mat4 view_mat;
mat4 proj_mat;
vec4 cam_pos;
vec4 focus_pos;
vec4 view_distance;
vec4 time_of_day;
vec4 tick;
};
out vec3 f_pos;
out vec3 f_norm;
out vec3 f_col;
void main() {
f_pos = v_pos;
f_norm = v_norm;
f_col = v_col;
gl_Position =
proj_mat *
view_mat *
vec4(v_pos + model_offs, 1);
}

View File

@ -0,0 +1,11 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(early_fragment_tests) in;
layout(location = 0) in vec4 frag_color;
layout(location = 0) out vec4 color;
void main() {
color = frag_color;
}

View File

@ -0,0 +1,11 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 pos;
layout(location = 1) in vec4 color;
layout(location = 0) out vec4 frag_color;
void main() {
frag_color = color;
gl_Position = vec4(pos, 1.0);
}

17
voxygen/shaders/ui.frag Normal file
View File

@ -0,0 +1,17 @@
#version 330 core
in vec3 f_pos;
in vec2 f_uv;
layout (std140)
uniform u_locals {
vec4 bounds;
};
uniform sampler2D u_tex;
out vec4 tgt_color;
void main() {
tgt_color = texture(u_tex, f_uv);
}

22
voxygen/shaders/ui.vert Normal file
View File

@ -0,0 +1,22 @@
#version 330 core
in vec3 v_pos;
in vec2 v_uv;
layout (std140)
uniform u_locals {
vec4 bounds;
};
uniform sampler2D u_tex;
out vec3 f_pos;
out vec2 f_uv;
void main() {
f_uv = v_uv;
f_pos = vec3(vec2(bounds.x, bounds.y) + v_pos.xy * vec2(bounds.z, bounds.w), 0);
f_pos.xy = vec2(f_pos.x * 2.0 - 1.0, f_pos.y * -2.0 + 1.0);
gl_Position = vec4(f_pos, 1);
}

View File

@ -0,0 +1,66 @@
pub mod run;
// Reexports
pub use self::run::RunAnimation;
// Crate
use crate::render::FigureBoneData;
// Local
use super::{
Skeleton,
Bone,
};
pub struct CharacterSkeleton {
head: Bone,
chest: Bone,
belt: Bone,
shorts: Bone,
l_hand: Bone,
r_hand: Bone,
l_foot: Bone,
r_foot: Bone,
back: Bone,
}
impl CharacterSkeleton {
pub fn new() -> Self {
Self {
head: Bone::default(),
chest: Bone::default(),
belt: Bone::default(),
shorts: Bone::default(),
l_hand: Bone::default(),
r_hand: Bone::default(),
l_foot: Bone::default(),
r_foot: Bone::default(),
back: Bone::default(),
}
}
}
impl Skeleton for CharacterSkeleton {
fn compute_matrices(&self) -> [FigureBoneData; 16] {
let chest_mat = self.chest.compute_base_matrix();
[
FigureBoneData::new(self.head.compute_base_matrix()),
FigureBoneData::new(chest_mat),
FigureBoneData::new(self.belt.compute_base_matrix()),
FigureBoneData::new(self.shorts.compute_base_matrix()),
FigureBoneData::new(self.l_hand.compute_base_matrix()),
FigureBoneData::new(self.r_hand.compute_base_matrix()),
FigureBoneData::new(self.l_foot.compute_base_matrix()),
FigureBoneData::new(self.r_foot.compute_base_matrix()),
FigureBoneData::new(chest_mat * self.back.compute_base_matrix()),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
]
}
}

View File

@ -0,0 +1,50 @@
// Standard
use std::f32::consts::PI;
// Library
use vek::*;
// Local
use super::{
CharacterSkeleton,
super::Animation,
};
pub struct RunAnimation;
impl Animation for RunAnimation {
type Skeleton = CharacterSkeleton;
type Dependency = f64;
fn update_skeleton(
skeleton: &mut Self::Skeleton,
time: f64,
) {
let wave = (time as f32 * 12.0).sin();
let wave_slow = (time as f32 * 6.0 + PI).sin();
let wave_dip = (wave_slow.abs() - 0.5).abs();
skeleton.head.offset = Vec3::unit_z() * 13.0;
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
skeleton.chest.offset = Vec3::unit_z() * 9.0;
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
skeleton.belt.offset = Vec3::unit_z() * 7.0;
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2);
skeleton.shorts.offset = Vec3::unit_z() * 4.0;
skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1);
skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0);
skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0);
skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0);
skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0);
skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0);
skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0);
skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0);
skeleton.back.ori = Quaternion::rotation_y(2.5);
}
}

40
voxygen/src/anim/mod.rs Normal file
View File

@ -0,0 +1,40 @@
pub mod character;
// Library
use vek::*;
// Crate
use crate::render::FigureBoneData;
#[derive(Copy, Clone)]
pub struct Bone {
pub offset: Vec3<f32>,
pub ori: Quaternion<f32>,
}
impl Bone {
pub fn default() -> Self {
Self {
offset: Vec3::zero(),
ori: Quaternion::identity(),
}
}
pub fn compute_base_matrix(&self) -> Mat4<f32> {
Mat4::<f32>::translation_3d(self.offset) * Mat4::from(self.ori)
}
}
pub trait Skeleton {
fn compute_matrices(&self) -> [FigureBoneData; 16];
}
pub trait Animation {
type Skeleton;
type Dependency;
fn update_skeleton(
skeleton: &mut Self::Skeleton,
dependency: Self::Dependency,
);
}

33
voxygen/src/error.rs Normal file
View File

@ -0,0 +1,33 @@
// Standard
use std::any;
// Project
use client;
// Crate
use crate::render::RenderError;
/// Represents any error that may be triggered by Voxygen
#[derive(Debug)]
pub enum Error {
/// An error relating to the internal client
ClientError(client::Error),
/// A miscellaneous error relating to a backend dependency
BackendError(Box<any::Any>),
/// An error relating the rendering subsystem
RenderError(RenderError),
// A miscellaneous error with an unknown or unspecified source
Other(failure::Error),
}
impl From<RenderError> for Error {
fn from(err: RenderError) -> Self {
Error::RenderError(err)
}
}
impl From<client::Error> for Error {
fn from(err: client::Error) -> Self {
Error::ClientError(err)
}
}

123
voxygen/src/main.rs Normal file
View File

@ -0,0 +1,123 @@
#![feature(drain_filter)]
pub mod anim;
pub mod error;
pub mod menu;
pub mod mesh;
pub mod render;
pub mod scene;
pub mod session;
pub mod ui;
pub mod window;
// Reexports
pub use crate::error::Error;
// Standard
use std::mem;
// Library
use log;
use pretty_env_logger;
// Crate
use crate::{
menu::title::TitleState,
window::Window,
};
/// A type used to store state that is shared between all play states
pub struct GlobalState {
window: Window,
}
impl GlobalState {
/// Called after a change in play state has occured (usually used to reverse any temporary
/// effects a state may have made).
pub fn on_play_state_changed(&mut self) {
self.window.grab_cursor(false);
}
}
// States can either close (and revert to a previous state), push a new state on top of themselves,
// or switch to a totally different state
pub enum PlayStateResult {
/// Pop all play states in reverse order and shut down the program
Shutdown,
/// Close the current play state and pop it from the play state stack
Pop,
/// Push a new play state onto the play state stack
Push(Box<dyn PlayState>),
/// Switch the current play state with a new play state
Switch(Box<dyn PlayState>),
}
/// A trait representing a playable game state. This may be a menu, a game session, the title
/// screen, etc.
pub trait PlayState {
/// Play the state until some change of state is required (i.e: a menu is opened or the game
/// is closed).
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult;
/// Get a descriptive name for this state type
fn name(&self) -> &'static str;
}
fn main() {
// Init logging
pretty_env_logger::init();
// Set up the global state
let mut global_state = GlobalState {
window: Window::new()
.expect("Failed to create window"),
};
// Set up the initial play state
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(TitleState::new(
&mut global_state.window.renderer_mut(),
))];
states.last().map(|current_state| {
log::info!("Started game with state '{}'", current_state.name())
});
// What's going on here?
// ---------------------
// The state system used by Voxygen allows for the easy development of stack-based menus.
// For example, you may want a "title" state that can push a "main menu" state on top of it,
// which can in turn push a "settings" state or a "game session" state on top of it.
// The code below manages the state transfer logic automatically so that we don't have to
// re-engineer it for each menu we decide to add to the game.
while let Some(state_result) = states.last_mut().map(|last| last.play(&mut global_state)){
// Implement state transfer logic
match state_result {
PlayStateResult::Shutdown => {
log::info!("Shutting down all states...");
while states.last().is_some() {
states.pop().map(|old_state| {
log::info!("Popped state '{}'", old_state.name());
global_state.on_play_state_changed();
});
}
},
PlayStateResult::Pop => {
states.pop().map(|old_state| {
log::info!("Popped state '{}'", old_state.name());
global_state.on_play_state_changed();
});
},
PlayStateResult::Push(new_state) => {
log::info!("Pushed state '{}'", new_state.name());
states.push(new_state);
global_state.on_play_state_changed();
},
PlayStateResult::Switch(mut new_state) => {
states.last_mut().map(|old_state| {
log::info!("Switching to state '{}' from state '{}'", new_state.name(), old_state.name());
mem::swap(old_state, &mut new_state);
global_state.on_play_state_changed();
});
},
}
}
}

1
voxygen/src/menu/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod title;

74
voxygen/src/menu/title.rs Normal file
View File

@ -0,0 +1,74 @@
// Library
use vek::*;
use image;
// Crate
use crate::{
PlayState,
PlayStateResult,
GlobalState,
window::Event,
session::SessionState,
render::Renderer,
ui::{
Ui,
element::{
Widget,
image::Image,
},
},
};
pub struct TitleState {
ui: Ui,
}
impl TitleState {
/// Create a new `TitleState`
pub fn new(renderer: &mut Renderer) -> Self {
let img = Image::new(renderer, &image::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/test.png")).unwrap()).unwrap();
let widget = Widget::new(renderer, img).unwrap();
Self {
ui: Ui::new(renderer, widget).unwrap(),
}
}
}
// The background colour
const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
impl PlayState for TitleState {
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
loop {
// Handle window events
for event in global_state.window.fetch_events() {
match event {
Event::Close => return PlayStateResult::Shutdown,
// When space is pressed, start a session
Event::Char(' ') => return PlayStateResult::Push(
Box::new(SessionState::new(global_state.window.renderer_mut())),
),
// Ignore all other events
_ => {},
}
}
// Clear the screen
global_state.window.renderer_mut().clear(BG_COLOR);
// Maintain the UI
self.ui.maintain(global_state.window.renderer_mut());
// Draw the UI to the screen
self.ui.render(global_state.window.renderer_mut());
// Finish the frame
global_state.window.renderer_mut().flush();
global_state.window
.swap_buffers()
.expect("Failed to swap window buffers");
}
}
fn name(&self) -> &'static str { "Title" }
}

15
voxygen/src/mesh/mod.rs Normal file
View File

@ -0,0 +1,15 @@
pub mod segment;
pub mod terrain;
// Crate
use crate::render::{
self,
Mesh,
};
pub trait Meshable {
type Pipeline: render::Pipeline;
type Supplement;
fn generate_mesh(&self, supp: Self::Supplement) -> Mesh<Self::Pipeline>;
}

149
voxygen/src/mesh/segment.rs Normal file
View File

@ -0,0 +1,149 @@
// Library
use vek::*;
// Project
use common::{
vol::{
Vox,
SizedVol,
ReadVol,
},
figure::Segment,
};
// Crate
use crate::{
mesh::Meshable,
render::{
self,
Mesh,
Quad,
FigurePipeline,
},
};
type FigureVertex = <FigurePipeline as render::Pipeline>::Vertex;
// Utility function
// TODO: Evaluate how useful this is
fn create_quad(
origin: Vec3<f32>,
unit_x: Vec3<f32>,
unit_y: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
bone: u8,
) -> Quad<FigurePipeline> {
Quad::new(
FigureVertex::new(origin, norm, col, bone),
FigureVertex::new(origin + unit_x, norm, col, bone),
FigureVertex::new(origin + unit_x + unit_y, norm, col, bone),
FigureVertex::new(origin + unit_y, norm, col, bone),
)
}
impl Meshable for Segment {
type Pipeline = FigurePipeline;
type Supplement = Vec3<f32>;
fn generate_mesh(&self, offs: Self::Supplement) -> Mesh<Self::Pipeline> {
let mut mesh = Mesh::new();
for pos in self.iter_positions() {
if let Some(col) = self
.get(pos)
.ok()
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
// -x
if self.get(pos - Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
-Vec3::unit_y(),
Vec3::unit_z(),
-Vec3::unit_x(),
col,
0,
));
}
// +x
if self.get(pos + Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32) + Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
col,
0,
));
}
// -y
if self.get(pos - Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32),
Vec3::unit_x(),
Vec3::unit_z(),
-Vec3::unit_y(),
col,
0,
));
}
// +y
if self.get(pos + Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32) + Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
col,
0,
));
}
// -z
if self.get(pos - Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32),
Vec3::unit_y(),
Vec3::unit_x(),
-Vec3::unit_z(),
col,
0,
));
}
// +z
if self.get(pos + Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
offs + pos.map(|e| e as f32) + Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
col,
0,
));
}
}
}
mesh
}
}

147
voxygen/src/mesh/terrain.rs Normal file
View File

@ -0,0 +1,147 @@
// Library
use vek::*;
// Project
use common::{
vol::{
Vox,
SizedVol,
ReadVol,
},
volumes::dyna::Dyna,
terrain::Block,
};
// Crate
use crate::{
mesh::Meshable,
render::{
self,
Mesh,
Quad,
TerrainPipeline,
},
};
type TerrainVertex = <TerrainPipeline as render::Pipeline>::Vertex;
// Utility function
// TODO: Evaluate how useful this is
fn create_quad(
origin: Vec3<f32>,
unit_x: Vec3<f32>,
unit_y: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
) -> Quad<TerrainPipeline> {
Quad::new(
TerrainVertex::new(origin, norm, col),
TerrainVertex::new(origin + unit_x, norm, col),
TerrainVertex::new(origin + unit_x + unit_y, norm, col),
TerrainVertex::new(origin + unit_y, norm, col),
)
}
impl<M> Meshable for Dyna<Block, M> {
type Pipeline = TerrainPipeline;
type Supplement = ();
fn generate_mesh(&self, _: Self::Supplement) -> Mesh<Self::Pipeline> {
let mut mesh = Mesh::new();
for pos in self
.iter_positions()
.filter(|pos| pos.map(|e| e >= 1).reduce_and())
.filter(|pos| pos.map2(self.get_size(), |e, sz| e < sz as i32 - 1).reduce_and())
{
if let Some(col) = self
.get(pos)
.ok()
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
// -x
if self.get(pos - Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(),
-Vec3::unit_y(),
Vec3::unit_z(),
-Vec3::unit_x(),
col,
));
}
// +x
if self.get(pos + Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
col,
));
}
// -y
if self.get(pos - Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32),
Vec3::unit_x(),
Vec3::unit_z(),
-Vec3::unit_y(),
col,
));
}
// +y
if self.get(pos + Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
col,
));
}
// -z
if self.get(pos - Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32),
Vec3::unit_y(),
Vec3::unit_x(),
-Vec3::unit_z(),
col,
));
}
// +z
if self.get(pos + Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
col,
));
}
}
}
mesh
}
}

View File

@ -0,0 +1,37 @@
// Library
use gfx::{
self,
traits::FactoryExt,
};
// Local
use super::{
RenderError,
gfx_backend,
};
/// A handle to a series of constants sitting on the GPU. This is used to hold information used in
/// the rendering process that does not change throughout a single render pass.
#[derive(Clone)]
pub struct Consts<T: Copy + gfx::traits::Pod> {
pub buf: gfx::handle::Buffer<gfx_backend::Resources, T>,
}
impl<T: Copy + gfx::traits::Pod> Consts<T> {
/// Create a new `Const<T>`
pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Self {
Self {
buf: factory.create_constant_buffer(len),
}
}
/// Update the GPU-side value represented by this constant handle.
pub fn update(
&mut self,
encoder: &mut gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
vals: &[T],
) -> Result<(), RenderError> {
encoder.update_buffer(&self.buf, vals, 0)
.map_err(|err| RenderError::UpdateError(err))
}
}

View File

@ -0,0 +1,99 @@
// Local
use super::Pipeline;
/// A `Vec`-based mesh structure used to store mesh data on the CPU.
#[derive(Clone)]
pub struct Mesh<P: Pipeline> {
verts: Vec<P::Vertex>,
}
impl<P: Pipeline> Mesh<P> {
/// Create a new `Mesh`
pub fn new() -> Self {
Self { verts: vec![] }
}
/// Get a slice referencing the vertices of this mesh.
pub fn vertices(&self) -> &[P::Vertex] {
&self.verts
}
/// Push a new vertex onto the end of this mesh.
pub fn push(&mut self, vert: P::Vertex) {
self.verts.push(vert);
}
/// Push a new polygon onto the end of this mesh.
pub fn push_tri(&mut self, tri: Tri<P>) {
self.verts.push(tri.a);
self.verts.push(tri.b);
self.verts.push(tri.c);
}
/// Push a new quad onto the end of this mesh.
pub fn push_quad(&mut self, quad: Quad<P>) {
// A quad is composed of two triangles. The code below converts the former to the latter.
// Tri 1
self.verts.push(quad.a.clone());
self.verts.push(quad.b);
self.verts.push(quad.c.clone());
// Tri 2
self.verts.push(quad.c);
self.verts.push(quad.d);
self.verts.push(quad.a);
}
/// Push the vertices of another mesh onto the end of this mesh
pub fn push_mesh(&mut self, other: &Mesh<P>) {
self.verts.extend_from_slice(other.vertices());
}
/// Push the vertices of another mesh onto the end of this mesh
pub fn push_mesh_map<F: FnMut(P::Vertex) -> P::Vertex>(&mut self, other: &Mesh<P>, mut f: F) {
// Reserve enough space in our Vec. This isn't necessary, but it tends to reduce the number
// of required (re)allocations.
self.verts.reserve(other.vertices().len());
for vert in other.vertices() {
self.verts.push(f(vert.clone()));
}
}
}
/// Represents a triangle stored on the CPU.
pub struct Tri<P: Pipeline> {
a: P::Vertex,
b: P::Vertex,
c: P::Vertex,
}
impl<P: Pipeline> Tri<P> {
pub fn new(
a: P::Vertex,
b: P::Vertex,
c: P::Vertex,
) -> Self {
Self { a, b, c }
}
}
/// Represents a quad stored on the CPU.
pub struct Quad<P: Pipeline> {
a: P::Vertex,
b: P::Vertex,
c: P::Vertex,
d: P::Vertex,
}
impl<P: Pipeline> Quad<P> {
pub fn new(
a: P::Vertex,
b: P::Vertex,
c: P::Vertex,
d: P::Vertex,
) -> Self {
Self { a, b, c, d }
}
}

69
voxygen/src/render/mod.rs Normal file
View File

@ -0,0 +1,69 @@
pub mod consts;
pub mod mesh;
pub mod model;
pub mod pipelines;
pub mod renderer;
pub mod texture;
mod util;
// Reexports
pub use self::{
consts::Consts,
mesh::{Mesh, Tri, Quad},
model::Model,
texture::Texture,
renderer::{Renderer, TgtColorFmt, TgtDepthFmt},
pipelines::{
Globals,
figure::{
FigurePipeline,
Locals as FigureLocals,
BoneData as FigureBoneData,
},
skybox::{
create_mesh as create_skybox_mesh,
SkyboxPipeline,
Locals as SkyboxLocals,
},
terrain::{
TerrainPipeline,
Locals as TerrainLocals,
},
ui::{
create_quad_mesh as create_ui_quad_mesh,
UiPipeline,
Locals as UiLocals,
},
},
};
#[cfg(feature = "gl")]
use gfx_device_gl as gfx_backend;
// Library
use gfx;
/// Used to represent one of many possible errors that may be omitted by the rendering subsystem
#[derive(Debug)]
pub enum RenderError {
PipelineError(gfx::PipelineStateError<String>),
UpdateError(gfx::UpdateError<usize>),
CombinedError(gfx::CombinedError),
}
/// Used to represent a specific rendering configuration.
///
/// Note that pipelines are tied to the
/// rendering backend, and as such it is necessary to modify the rendering subsystem when adding
/// new pipelines - custom pipelines are not currently an objective of the rendering subsystem.
///
/// # Examples
///
/// - `SkyboxPipeline`
/// - `FigurePipeline`
pub trait Pipeline {
type Vertex:
Clone +
gfx::traits::Pod +
gfx::pso::buffer::Structure<gfx::format::Format>;
}

View File

@ -0,0 +1,36 @@
// Library
use gfx::{
self,
traits::FactoryExt,
};
// Local
use super::{
mesh::Mesh,
Pipeline,
gfx_backend,
};
/// Represents a mesh that has been sent to the GPU.
pub struct Model<P: Pipeline> {
pub vbuf: gfx::handle::Buffer<gfx_backend::Resources, P::Vertex>,
pub slice: gfx::Slice<gfx_backend::Resources>,
}
impl<P: Pipeline> Model<P> {
pub fn new(
factory: &mut gfx_backend::Factory,
mesh: &Mesh<P>,
) -> Self {
Self {
vbuf: factory.create_vertex_buffer(mesh.vertices()),
slice: gfx::Slice {
start: 0,
end: mesh.vertices().len() as u32,
base_vertex: 0,
instances: None,
buffer: gfx::IndexBuffer::Auto,
},
}
}
}

View File

@ -0,0 +1,95 @@
// Library
use gfx::{
self,
// Macros
gfx_defines,
gfx_vertex_struct_meta,
gfx_constant_struct_meta,
gfx_impl_struct_meta,
gfx_pipeline,
gfx_pipeline_inner,
};
use vek::*;
// Local
use super::{
Globals,
super::{
Pipeline,
TgtColorFmt,
TgtDepthFmt,
util::arr_to_mat,
},
};
gfx_defines! {
vertex Vertex {
pos: [f32; 3] = "v_pos",
norm: [f32; 3] = "v_norm",
col: [f32; 3] = "v_col",
bone_idx: u8 = "v_bone_idx",
}
constant Locals {
model_mat: [[f32; 4]; 4] = "model_mat",
}
constant BoneData {
bone_mat: [[f32; 4]; 4] = "bone_mat",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
bones: gfx::ConstantBuffer<BoneData> = "u_bones",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,
}
}
impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>, bone_idx: u8) -> Self {
Self {
pos: pos.into_array(),
col: col.into_array(),
norm: norm.into_array(),
bone_idx,
}
}
pub fn with_bone_idx(mut self, bone_idx: u8) -> Self {
self.bone_idx = bone_idx;
self
}
}
impl Locals {
pub fn default() -> Self {
Self {
model_mat: arr_to_mat(Mat4::identity().into_col_array()),
}
}
}
impl BoneData {
pub fn new(bone_mat: Mat4<f32>) -> Self {
Self {
bone_mat: arr_to_mat(bone_mat.into_col_array()),
}
}
pub fn default() -> Self {
Self {
bone_mat: arr_to_mat(Mat4::identity().into_col_array()),
}
}
}
pub struct FigurePipeline;
impl Pipeline for FigurePipeline {
type Vertex = Vertex;
}

View File

@ -0,0 +1,66 @@
pub mod figure;
pub mod skybox;
pub mod terrain;
pub mod ui;
// Library
use gfx::{
self,
// Macros
gfx_defines,
gfx_constant_struct_meta,
gfx_impl_struct_meta,
};
use vek::*;
// Local
use super::util::arr_to_mat;
gfx_defines! {
constant Globals {
view_mat: [[f32; 4]; 4] = "view_mat",
proj_mat: [[f32; 4]; 4] = "proj_mat",
cam_pos: [f32; 4] = "cam_pos",
focus_pos: [f32; 4] = "focus_pos",
// TODO: Fix whatever alignment issue requires these uniforms to be aligned
view_distance: [f32; 4] = "view_distance",
time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64
tick: [f32; 4] = "tick",
}
}
impl Globals {
/// Create global consts with default values.
pub fn default() -> Self {
Self {
view_mat: arr_to_mat(Mat4::identity().into_col_array()),
proj_mat: arr_to_mat(Mat4::identity().into_col_array()),
cam_pos: [0.0; 4],
focus_pos: [0.0; 4],
view_distance: [0.0; 4],
time_of_day: [0.0; 4],
tick: [0.0; 4],
}
}
/// Create global consts from the provided parameters.
pub fn new(
view_mat: Mat4<f32>,
proj_mat: Mat4<f32>,
cam_pos: Vec3<f32>,
focus_pos: Vec3<f32>,
view_distance: f32,
time_of_day: f64,
tick: f64,
) -> Self {
Self {
view_mat: arr_to_mat(view_mat.into_col_array()),
proj_mat: arr_to_mat(proj_mat.into_col_array()),
cam_pos: Vec4::from(cam_pos).into_array(),
focus_pos: Vec4::from(focus_pos).into_array(),
view_distance: [view_distance; 4],
time_of_day: [time_of_day as f32; 4],
tick: [tick as f32; 4],
}
}
}

View File

@ -0,0 +1,110 @@
// Library
use gfx::{
self,
// Macros
gfx_defines,
gfx_vertex_struct_meta,
gfx_constant_struct_meta,
gfx_impl_struct_meta,
gfx_pipeline,
gfx_pipeline_inner,
};
// Local
use super::{
Globals,
super::{
Pipeline,
TgtColorFmt,
TgtDepthFmt,
Mesh,
Quad,
},
};
gfx_defines! {
vertex Vertex {
pos: [f32; 3] = "v_pos",
}
constant Locals {
nul: [f32; 4] = "nul",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::PASS_TEST,
}
}
impl Locals {
pub fn default() -> Self {
Self { nul: [0.0; 4] }
}
}
pub struct SkyboxPipeline;
impl Pipeline for SkyboxPipeline {
type Vertex = Vertex;
}
pub fn create_mesh() -> Mesh<SkyboxPipeline> {
let mut mesh = Mesh::new();
// -x
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [-1.0, -1.0, -1.0] },
Vertex { pos: [-1.0, 1.0, -1.0] },
Vertex { pos: [-1.0, 1.0, 1.0] },
Vertex { pos: [-1.0, -1.0, 1.0] },
));
// +x
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [ 1.0, -1.0, 1.0] },
Vertex { pos: [ 1.0, 1.0, 1.0] },
Vertex { pos: [ 1.0, 1.0, -1.0] },
Vertex { pos: [ 1.0, -1.0, -1.0] },
));
// -y
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [ 1.0, -1.0, -1.0] },
Vertex { pos: [-1.0, -1.0, -1.0] },
Vertex { pos: [-1.0, -1.0, 1.0] },
Vertex { pos: [ 1.0, -1.0, 1.0] },
));
// +y
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [ 1.0, 1.0, 1.0] },
Vertex { pos: [-1.0, 1.0, 1.0] },
Vertex { pos: [-1.0, 1.0, -1.0] },
Vertex { pos: [ 1.0, 1.0, -1.0] },
));
// -z
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [-1.0, -1.0, -1.0] },
Vertex { pos: [ 1.0, -1.0, -1.0] },
Vertex { pos: [ 1.0, 1.0, -1.0] },
Vertex { pos: [-1.0, 1.0, -1.0] },
));
// +z
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [-1.0, 1.0, 1.0] },
Vertex { pos: [ 1.0, 1.0, 1.0] },
Vertex { pos: [ 1.0, -1.0, 1.0] },
Vertex { pos: [-1.0, -1.0, 1.0] },
));
mesh
}

View File

@ -0,0 +1,68 @@
// Library
use gfx::{
self,
// Macros
gfx_defines,
gfx_vertex_struct_meta,
gfx_constant_struct_meta,
gfx_impl_struct_meta,
gfx_pipeline,
gfx_pipeline_inner,
};
use vek::*;
// Local
use super::{
Globals,
super::{
Pipeline,
TgtColorFmt,
TgtDepthFmt,
},
};
gfx_defines! {
vertex Vertex {
pos: [f32; 3] = "v_pos",
norm: [f32; 3] = "v_norm",
col: [f32; 3] = "v_col",
}
constant Locals {
model_offs: [f32; 3] = "model_offs",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,
}
}
impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>) -> Self {
Self {
pos: pos.into_array(),
col: col.into_array(),
norm: norm.into_array(),
}
}
}
impl Locals {
pub fn default() -> Self {
Self {
model_offs: [0.0; 3],
}
}
}
pub struct TerrainPipeline;
impl Pipeline for TerrainPipeline {
type Vertex = Vertex;
}

View File

@ -0,0 +1,76 @@
// Library
use gfx::{
self,
// Macros
gfx_defines,
gfx_vertex_struct_meta,
gfx_constant_struct_meta,
gfx_impl_struct_meta,
gfx_pipeline,
gfx_pipeline_inner,
};
// Local
use super::{
Globals,
super::{
Pipeline,
TgtColorFmt,
TgtDepthFmt,
Mesh,
Quad,
},
};
gfx_defines! {
vertex Vertex {
pos: [f32; 3] = "v_pos",
uv: [f32; 2] = "v_uv",
}
constant Locals {
bounds: [f32; 4] = "bounds",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
locals: gfx::ConstantBuffer<Locals> = "u_locals",
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::PASS_TEST,
}
}
impl Locals {
pub fn default() -> Self {
Self { bounds: [0.0, 0.0, 1.0, 1.0] }
}
pub fn new(bounds: [f32; 4]) -> Self {
Self {
bounds,
}
}
}
pub struct UiPipeline;
impl Pipeline for UiPipeline {
type Vertex = Vertex;
}
pub fn create_quad_mesh() -> Mesh<UiPipeline> {
let mut mesh = Mesh::new();
#[rustfmt::skip]
mesh.push_quad(Quad::new(
Vertex { pos: [0.0, 0.0, 0.0], uv: [0.0, 0.0] },
Vertex { pos: [0.0, 1.0, 0.0], uv: [0.0, 1.0] },
Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 1.0] },
Vertex { pos: [1.0, 0.0, 0.0], uv: [1.0, 0.0] },
));
mesh
}

View File

@ -0,0 +1,297 @@
// Library
use vek::*;
use gfx::{
self,
traits::{Device, FactoryExt},
};
use image;
// Local
use super::{
consts::Consts,
mesh::Mesh,
model::Model,
texture::Texture,
Pipeline,
RenderError,
gfx_backend,
pipelines::{
Globals,
figure,
skybox,
terrain,
ui,
},
};
/// Represents the format of the window's color target.
pub type TgtColorFmt = gfx::format::Rgba8;
/// Represents the format of the window's depth target.
pub type TgtDepthFmt = gfx::format::DepthStencil;
/// A handle to a window color target.
pub type TgtColorView = gfx::handle::RenderTargetView<gfx_backend::Resources, TgtColorFmt>;
/// A handle to a window depth target.
pub type TgtDepthView = gfx::handle::DepthStencilView<gfx_backend::Resources, TgtDepthFmt>;
/// A type that encapsulates rendering state. `Renderer` is central to Voxygen's rendering
/// subsystem and contains any state necessary to interact with the GPU, along with pipeline state
/// objects (PSOs) needed to renderer different kinds of models to the screen.
pub struct Renderer {
device: gfx_backend::Device,
encoder: gfx::Encoder<gfx_backend::Resources, gfx_backend::CommandBuffer>,
factory: gfx_backend::Factory,
tgt_color_view: TgtColorView,
tgt_depth_view: TgtDepthView,
skybox_pipeline: GfxPipeline<skybox::pipe::Init<'static>>,
figure_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
terrain_pipeline: GfxPipeline<terrain::pipe::Init<'static>>,
ui_pipeline: GfxPipeline<ui::pipe::Init<'static>>,
}
impl Renderer {
/// Create a new `Renderer` from a variety of backend-specific components and the window
/// targets.
pub fn new(
device: gfx_backend::Device,
mut factory: gfx_backend::Factory,
tgt_color_view: TgtColorView,
tgt_depth_view: TgtDepthView,
) -> Result<Self, RenderError> {
// Construct a pipeline for rendering skyboxes
let skybox_pipeline = create_pipeline(
&mut factory,
skybox::pipe::new(),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.vert")),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.frag")),
)?;
// Construct a pipeline for rendering figures
let figure_pipeline = create_pipeline(
&mut factory,
figure::pipe::new(),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/figure.vert")),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/figure.frag")),
)?;
// Construct a pipeline for rendering terrain
let terrain_pipeline = create_pipeline(
&mut factory,
terrain::pipe::new(),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/terrain.vert")),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/terrain.frag")),
)?;
// Construct a pipeline for rendering UI elements
let ui_pipeline = create_pipeline(
&mut factory,
ui::pipe::new(),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/ui.vert")),
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/ui.frag")),
)?;
Ok(Self {
device,
encoder: factory.create_command_buffer().into(),
factory,
tgt_color_view,
tgt_depth_view,
skybox_pipeline,
figure_pipeline,
terrain_pipeline,
ui_pipeline,
})
}
/// Get references to the internal render target views that get displayed directly by the window.
pub fn target_views(&self) -> (&TgtColorView, &TgtDepthView) {
(&self.tgt_color_view, &self.tgt_depth_view)
}
/// Get mutable references to the internal render target views that get displayed directly by the window.
pub fn target_views_mut(&mut self) -> (&mut TgtColorView, &mut TgtDepthView) {
(&mut self.tgt_color_view, &mut self.tgt_depth_view)
}
/// Get the resolution of the render target.
pub fn get_resolution(&self) -> Vec2<u16> {
Vec2::new(
self.tgt_color_view.get_dimensions().0,
self.tgt_color_view.get_dimensions().1,
)
}
/// 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);
}
/// Perform all queued draw calls for this frame and clean up discarded items.
pub fn flush(&mut self) {
self.encoder.flush(&mut self.device);
self.device.cleanup();
}
/// Create a new set of constants with the provided values.
pub fn create_consts<T: Copy + gfx::traits::Pod>(
&mut self,
vals: &[T],
) -> Result<Consts<T>, RenderError> {
let mut consts = Consts::new(&mut self.factory, vals.len());
consts.update(&mut self.encoder, vals)?;
Ok(consts)
}
/// Update a set of constants with the provided values.
pub fn update_consts<T: Copy + gfx::traits::Pod>(
&mut self,
consts: &mut Consts<T>,
vals: &[T]
) -> Result<(), RenderError> {
consts.update(&mut self.encoder, vals)
}
/// Create a new model from the provided mesh.
pub fn create_model<P: Pipeline>(&mut self, mesh: &Mesh<P>) -> Result<Model<P>, RenderError> {
Ok(Model::new(
&mut self.factory,
mesh,
))
}
/// Create a new texture from the provided image.
pub fn create_texture<P: Pipeline>(&mut self, image: &image::DynamicImage) -> Result<Texture<P>, RenderError> {
Texture::new(
&mut self.factory,
image,
)
}
/// Queue the rendering of the provided skybox model in the upcoming frame.
pub fn render_skybox(
&mut self,
model: &Model<skybox::SkyboxPipeline>,
globals: &Consts<Globals>,
locals: &Consts<skybox::Locals>,
) {
self.encoder.draw(
&model.slice,
&self.skybox_pipeline.pso,
&skybox::pipe::Data {
vbuf: model.vbuf.clone(),
locals: locals.buf.clone(),
globals: globals.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
);
}
/// Queue the rendering of the provided figure model in the upcoming frame.
pub fn render_figure(
&mut self,
model: &Model<figure::FigurePipeline>,
globals: &Consts<Globals>,
locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>,
) {
self.encoder.draw(
&model.slice,
&self.figure_pipeline.pso,
&figure::pipe::Data {
vbuf: model.vbuf.clone(),
locals: locals.buf.clone(),
globals: globals.buf.clone(),
bones: bones.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
);
}
/// Queue the rendering of the provided terrain chunk model in the upcoming frame.
pub fn render_terrain_chunk(
&mut self,
model: &Model<terrain::TerrainPipeline>,
globals: &Consts<Globals>,
locals: &Consts<terrain::Locals>,
) {
self.encoder.draw(
&model.slice,
&self.terrain_pipeline.pso,
&terrain::pipe::Data {
vbuf: model.vbuf.clone(),
locals: locals.buf.clone(),
globals: globals.buf.clone(),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
);
}
/// Queue the rendering of the provided UI element in the upcoming frame.
pub fn render_ui_element(
&mut self,
model: &Model<ui::UiPipeline>,
locals: &Consts<ui::Locals>,
tex: &Texture<ui::UiPipeline>,
) {
self.encoder.draw(
&model.slice,
&self.ui_pipeline.pso,
&ui::pipe::Data {
vbuf: model.vbuf.clone(),
locals: locals.buf.clone(),
tex: (tex.srv.clone(), tex.sampler.clone()),
tgt_color: self.tgt_color_view.clone(),
tgt_depth: self.tgt_depth_view.clone(),
},
);
}
}
struct GfxPipeline<P: gfx::pso::PipelineInit> {
pso: gfx::pso::PipelineState<gfx_backend::Resources, P::Meta>,
}
/// Create a new pipeline from the provided vertex shader and fragment shader.
fn create_pipeline<'a, P: gfx::pso::PipelineInit>(
factory: &mut gfx_backend::Factory,
pipe: P,
vs: &[u8],
fs: &[u8],
) -> Result<GfxPipeline<P>, RenderError> {
let program = factory
.link_program(vs, fs)
.map_err(|err| RenderError::PipelineError(gfx::PipelineStateError::Program(err)))?;
Ok(GfxPipeline {
pso: factory.create_pipeline_from_program(
&program,
gfx::Primitive::TriangleList,
gfx::state::Rasterizer {
front_face: gfx::state::FrontFace::CounterClockwise,
cull_face: gfx::state::CullFace::Back,
method: gfx::state::RasterMethod::Fill,
offset: None,
samples: Some(gfx::state::MultiSample),
},
pipe,
)
// Do some funky things to work around an oddity in gfx's error ownership rules
.map_err(|err| RenderError::PipelineError(match err {
gfx::PipelineStateError::Program(err) =>
gfx::PipelineStateError::Program(err),
gfx::PipelineStateError::DescriptorInit(err) =>
gfx::PipelineStateError::DescriptorInit(err.into()),
gfx::PipelineStateError::DeviceCreate(err) =>
gfx::PipelineStateError::DeviceCreate(err),
}))?,
})
}

View File

@ -0,0 +1,58 @@
// Standard
use std::marker::PhantomData;
// Library
use gfx::{
self,
traits::{Factory, FactoryExt},
};
use image::{
DynamicImage,
GenericImageView,
};
// Local
use super::{
RenderError,
mesh::Mesh,
Pipeline,
gfx_backend,
};
type ShaderFormat = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb);
/// Represents an image that has been uploaded to the GPU.
pub struct Texture<P: Pipeline> {
pub tex: gfx::handle::Texture<gfx_backend::Resources, <ShaderFormat as gfx::format::Formatted>::Surface>,
pub srv: gfx::handle::ShaderResourceView<gfx_backend::Resources, <ShaderFormat as gfx::format::Formatted>::View>,
pub sampler: gfx::handle::Sampler<gfx_backend::Resources>,
_phantom: PhantomData<P>,
}
impl<P: Pipeline> Texture<P> {
pub fn new(
factory: &mut gfx_backend::Factory,
image: &DynamicImage,
) -> Result<Self, RenderError> {
let (tex, srv) = factory.create_texture_immutable_u8::<ShaderFormat>(
gfx::texture::Kind::D2(
image.width() as u16,
image.height() as u16,
gfx::texture::AaMode::Single,
),
gfx::texture::Mipmap::Provided,
&[&image.to_rgba().into_raw()],
)
.map_err(|err| RenderError::CombinedError(err))?;
Ok(Self {
tex,
srv,
sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
gfx::texture::FilterMethod::Scale,
gfx::texture::WrapMode::Clamp,
)),
_phantom: PhantomData,
})
}
}

View File

@ -0,0 +1,10 @@
// TODO: Get rid of this ugliness
#[rustfmt::skip]
pub fn arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] {
[
[arr[ 0], arr[ 1], arr[ 2], arr[ 3]],
[arr[ 4], arr[ 5], arr[ 6], arr[ 7]],
[arr[ 8], arr[ 9], arr[10], arr[11]],
[arr[12], arr[13], arr[14], arr[15]],
]
}

View File

@ -0,0 +1,78 @@
// Standard
use std::f32::consts::PI;
// Library
use vek::*;
const NEAR_PLANE: f32 = 0.1;
const FAR_PLANE: f32 = 10000.0;
pub struct Camera {
focus: Vec3<f32>,
ori: Vec3<f32>,
dist: f32,
fov: f32,
aspect: f32,
}
impl Camera {
/// Create a new `Camera` with default parameters.
pub fn new() -> Self {
Self {
focus: Vec3::unit_z() * 10.0,
ori: Vec3::zero(),
dist: 150.0,
fov: 1.3,
aspect: 1.618,
}
}
/// Compute the transformation matrices (view matrix and projection matrix) and position of the
/// camera.
pub fn compute_dependents(&self) -> (Mat4<f32>, Mat4<f32>, Vec3<f32>) {
let view_mat = Mat4::<f32>::identity()
* Mat4::translation_3d(-Vec3::unit_z() * self.dist)
* Mat4::rotation_z(self.ori.z)
* Mat4::rotation_x(self.ori.y)
* Mat4::rotation_y(self.ori.x)
* Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
* Mat4::translation_3d(-self.focus);
let proj_mat = Mat4::perspective_rh_no(
self.fov,
self.aspect,
NEAR_PLANE,
FAR_PLANE,
);
// TODO: Make this more efficient
let cam_pos = Vec3::from(view_mat.inverted() * Vec4::unit_w());
(view_mat, proj_mat, cam_pos)
}
/// Rotate the camera about its focus by the given delta, limiting the input accordingly.
pub fn rotate_by(&mut self, delta: Vec3<f32>) {
self.ori += delta;
// Clamp camera pitch to the vertical limits
self.ori.y = self.ori.y
.min(PI / 2.0)
.max(-PI / 2.0);
}
/// Zoom the camera by the given delta, limiting the input accordingly.
pub fn zoom_by(&mut self, delta: f32) {
// Clamp camera dist to the 0 <= x <= infinity range
self.dist = (self.dist + delta).max(0.0);
}
/// Get the focus position of the camera.
pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
/// Set the focus position of the camera.
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.focus = focus; }
/// Get the aspect ratio of the camera.
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
/// Set the aspect ratio of the camera.
pub fn set_aspect_ratio(&mut self, aspect: f32) { self.aspect = aspect; }
}

View File

@ -0,0 +1,79 @@
// Crate
use crate::{
Error,
render::{
Consts,
Globals,
Mesh,
Model,
Renderer,
FigurePipeline,
FigureBoneData,
FigureLocals,
},
anim::Skeleton,
};
pub struct Figure<S: Skeleton> {
// GPU data
model: Model<FigurePipeline>,
bone_consts: Consts<FigureBoneData>,
locals: Consts<FigureLocals>,
// CPU data
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
pub skeleton: S,
}
impl<S: Skeleton> Figure<S> {
pub fn new(
renderer: &mut Renderer,
bone_meshes: [Option<Mesh<FigurePipeline>>; 16],
skeleton: S,
) -> Result<Self, Error> {
let mut this = Self {
model: renderer.create_model(&Mesh::new())?,
bone_consts: renderer.create_consts(&skeleton.compute_matrices())?,
locals: renderer.create_consts(&[FigureLocals::default()])?,
bone_meshes,
skeleton,
};
this.update_model(renderer)?;
Ok(this)
}
pub fn update_model(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
let mut mesh = Mesh::new();
self.bone_meshes
.iter()
.enumerate()
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
.for_each(|(i, bone_mesh)| {
mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8))
});
self.model = renderer.create_model(&mesh)?;
Ok(())
}
pub fn update_skeleton(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())?;
Ok(())
}
pub fn update_locals(&mut self, renderer: &mut Renderer, locals: FigureLocals) -> Result<(), Error> {
renderer.update_consts(&mut self.locals, &[locals])?;
Ok(())
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
renderer.render_figure(
&self.model,
globals,
&self.locals,
&self.bone_consts,
);
}
}

183
voxygen/src/scene/mod.rs Normal file
View File

@ -0,0 +1,183 @@
pub mod camera;
pub mod figure;
pub mod terrain;
// Library
use vek::*;
use dot_vox;
// Project
use common::figure::Segment;
use client::Client;
// Crate
use crate::{
render::{
Consts,
Globals,
Model,
Renderer,
SkyboxPipeline,
SkyboxLocals,
FigureLocals,
create_skybox_mesh,
},
window::Event,
mesh::Meshable,
anim::{
Animation,
character::{CharacterSkeleton, RunAnimation},
},
};
// Local
use self::{
camera::Camera,
figure::Figure,
terrain::Terrain,
};
// TODO: Don't hard-code this
const CURSOR_PAN_SCALE: f32 = 0.005;
struct Skybox {
model: Model<SkyboxPipeline>,
locals: Consts<SkyboxLocals>,
}
pub struct Scene {
globals: Consts<Globals>,
camera: Camera,
skybox: Skybox,
terrain: Terrain,
test_figure: Figure<CharacterSkeleton>,
}
// TODO: Make a proper asset loading system
fn load_segment(filename: &'static str) -> Segment {
Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/").to_string() + filename)).unwrap())
}
impl Scene {
/// Create a new `Scene` with default parameters.
pub fn new(renderer: &mut Renderer, client: &Client) -> Self {
Self {
globals: renderer
.create_consts(&[Globals::default()])
.unwrap(),
camera: Camera::new(),
skybox: Skybox {
model: renderer
.create_model(&create_skybox_mesh())
.unwrap(),
locals: renderer
.create_consts(&[SkyboxLocals::default()])
.unwrap(),
},
terrain: Terrain::new(),
test_figure: Figure::new(
renderer,
[
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))),
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))),
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))),
None,
None,
None,
None,
None,
None,
None,
],
CharacterSkeleton::new(),
)
.unwrap(),
}
}
/// Get a reference to the scene's camera.
pub fn camera(&self) -> &Camera { &self.camera }
/// Get a mutable reference to the scene's camera.
pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
/// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.).
///
/// If the event is handled, return true
pub fn handle_input_event(&mut self, event: Event) -> bool {
match event {
// When the window is resized, change the camera's aspect ratio
Event::Resize(dims) => {
self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
true
},
// Panning the cursor makes the camera rotate
Event::CursorPan(delta) => {
self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE);
true
},
// Zoom the camera when a zoom event occurs
Event::Zoom(delta) => {
self.camera.zoom_by(delta);
true
},
// All other events are unhandled
_ => false,
}
}
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
// Compute camera matrices
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
// Update global constants
renderer.update_consts(&mut self.globals, &[Globals::new(
view_mat,
proj_mat,
cam_pos,
self.camera.get_focus_pos(),
10.0,
client.state().get_time_of_day(),
client.state().get_time(),
)])
.expect("Failed to update global constants");
// Maintain the terrain
self.terrain.maintain(renderer, client);
// TODO: Don't do this here
RunAnimation::update_skeleton(
&mut self.test_figure.skeleton,
client.state().get_time(),
);
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
self.test_figure.update_skeleton(renderer).unwrap();
}
/// Render the scene using the provided `Renderer`
pub fn render_to(&self, renderer: &mut Renderer) {
// Render the skybox first (it appears over everything else so must be rendered first)
renderer.render_skybox(
&self.skybox.model,
&self.globals,
&self.skybox.locals,
);
// Render terrain
self.terrain.render(renderer, &self.globals);
// Render the test figure
self.test_figure.render(renderer, &self.globals);
}
}

View File

@ -0,0 +1,187 @@
// Standard
use std::{
collections::{HashMap, LinkedList},
sync::mpsc,
time::Duration,
};
// Library
use vek::*;
// Project
use client::Client;
use common::{
terrain::TerrainMap,
volumes::vol_map::VolMapErr,
vol::SampleVol,
};
// Crate
use crate::{
render::{
Consts,
Globals,
Mesh,
Model,
Renderer,
TerrainPipeline,
TerrainLocals,
},
mesh::Meshable,
};
struct TerrainChunk {
// GPU data
model: Model<TerrainPipeline>,
locals: Consts<TerrainLocals>,
}
struct ChunkMeshState {
pos: Vec3<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>,
mesh: Mesh<TerrainPipeline>,
started_tick: u64,
}
/// Function executed by worker threads dedicated to chunk meshing
fn mesh_worker(
pos: Vec3<i32>,
started_tick: u64,
volume: <TerrainMap as SampleVol>::Sample,
) -> MeshWorkerResponse {
MeshWorkerResponse {
pos,
mesh: volume.generate_mesh(()),
started_tick,
}
}
pub struct Terrain {
chunks: HashMap<Vec3<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: LinkedList<ChunkMeshState>,
}
impl Terrain {
pub fn new() -> Self {
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating with
// worker threads that are meshing chunks.
let (send, recv) = mpsc::channel();
Self {
chunks: HashMap::new(),
mesh_send_tmp: send,
mesh_recv: recv,
mesh_todo: LinkedList::new(),
}
}
/// Maintain terrain data. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let current_tick = client.get_tick();
// Add any recently created or changed chunks to the list of chunks to be meshed
for pos in client.state().changes().new_chunks.iter()
.chain(client.state().changes().changed_chunks.iter())
{
// TODO: ANOTHER PROBLEM HERE!
// What happens if the block on the edge of a chunk gets modified? We need to spawn
// a mesh worker to remesh its neighbour(s) too since their ambient occlusion and face
// elision information changes too!
match self.mesh_todo.iter_mut().find(|todo| todo.pos == *pos) {
Some(todo) => todo.started_tick = current_tick,
// The chunk it's queued yet, add it to the queue
None => self.mesh_todo.push_back(ChunkMeshState {
pos: *pos,
started_tick: current_tick,
active_worker: false,
}),
}
}
// Remove any models for chunks that have been recently removed
for pos in &client.state().changes().removed_chunks {
self.chunks.remove(pos);
self.mesh_todo.drain_filter(|todo| todo.pos == *pos);
}
// Clone the sender to the thread can send us the chunk data back
// TODO: It's a bit hacky cloning it here and then cloning it again below. Fix this.
let send = self.mesh_send_tmp.clone();
self.mesh_todo
.iter_mut()
// Only spawn workers for meshing jobs without an active worker already
.filter(|todo| !todo.active_worker)
.for_each(|todo| {
// 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 {
min: todo.pos.map2(TerrainMap::chunk_size(), |e, sz| e * sz as i32 - 1),
max: todo.pos.map2(TerrainMap::chunk_size(), |e, sz| (e + 1) * sz as i32 + 1),
};
// 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) {
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,
_ => panic!("Unhandled edge case"),
};
// Clone various things to that they can be moved into the thread
let send = send.clone();
let pos = todo.pos;
// Queue the worker thread
client.thread_pool().execute(move || {
send.send(mesh_worker(pos, current_tick, volume))
.expect("Failed to send chunk mesh to main thread");
});
todo.active_worker = true;
});
// Receive chunk meshes from worker threads, upload them to the GPU and then store them
while let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) {
match self.mesh_todo.iter().find(|todo| todo.pos == response.pos) {
// It's the mesh we want, insert the newly finished model into the terrain model
// data structure (convert the mesh to a model first of course)
Some(todo) if response.started_tick == todo.started_tick => {
self.chunks.insert(response.pos, TerrainChunk {
model: renderer.create_model(&response.mesh).expect("Failed to upload chunk mesh to the GPU"),
locals: renderer.create_consts(&[TerrainLocals {
model_offs: 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"),
});
},
// Chunk must have been removed, or it was spawned on an old tick. Drop the mesh
// since it's either out of date or no longer needed
_ => continue,
}
}
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
for (_, chunk) in &self.chunks {
renderer.render_terrain_chunk(
&chunk.model,
globals,
&chunk.locals,
);
}
}
}

134
voxygen/src/session.rs Normal file
View File

@ -0,0 +1,134 @@
// Standard
use std::time::Duration;
// Library
use vek::*;
// Project
use common::clock::Clock;
use client::{
self,
Client,
};
// Crate
use crate::{
Error,
PlayState,
PlayStateResult,
GlobalState,
window::{Event, Key},
render::Renderer,
scene::Scene,
};
const FPS: u64 = 60;
pub struct SessionState {
scene: Scene,
client: Client,
}
/// Represents an active game session (i.e: one that is being played)
impl SessionState {
/// Create a new `SessionState`
pub fn new(renderer: &mut Renderer) -> Self {
let client = Client::new().with_test_state(); // <--- TODO: Remove this
Self {
// Create a scene for this session. The scene handles visible elements of the game world
scene: Scene::new(renderer, &client),
client,
}
}
}
// The background colour
const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
impl SessionState {
/// Tick the session (and the client attached to it)
pub fn tick(&mut self, dt: Duration) -> Result<(), Error> {
self.client.tick(client::Input {}, dt)?;
Ok(())
}
/// Clean up the session (and the client attached to it) after a tick
pub fn cleanup(&mut self) {
self.client.cleanup();
}
/// Render the session to the screen.
///
/// This method should be called once per frame.
pub fn render(&mut self, renderer: &mut Renderer) {
// Clear the screen
renderer.clear(BG_COLOR);
// Render the screen using the global renderer
self.scene.render_to(renderer);
// Finish the frame
renderer.flush();
}
}
impl PlayState for SessionState {
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
// Trap the cursor
global_state.window.grab_cursor(true);
// Set up an fps clock
let mut clock = Clock::new();
// Load a few chunks TODO: Remove this
for x in -6..7 {
for y in -6..7 {
for z in -1..2 {
self.client.load_chunk(Vec3::new(x, y, z));
}
}
}
// Game loop
loop {
// Handle window events
for event in global_state.window.fetch_events() {
let _handled = match event {
Event::Close => return PlayStateResult::Shutdown,
// When 'q' is pressed, exit the session
Event::Char('q') => return PlayStateResult::Pop,
// Toggle cursor grabbing
Event::KeyDown(Key::ToggleCursor) => {
global_state.window.grab_cursor(!global_state.window.is_cursor_grabbed());
},
// Pass all other events to the scene
event => { self.scene.handle_input_event(event); },
};
// TODO: Do something if the event wasn't handled?
}
// Perform an in-game tick
self.tick(clock.get_last_delta())
.expect("Failed to tick the scene");
// Maintain the scene
self.scene.maintain(global_state.window.renderer_mut(), &self.client);
// Render the session
self.render(global_state.window.renderer_mut());
// Display the frame on the window
global_state.window
.swap_buffers()
.expect("Failed to swap window buffers");
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
// Clean things up after the tick
self.cleanup();
}
}
fn name(&self) -> &'static str { "Session" }
}

View File

@ -0,0 +1,80 @@
// Standard
use std::{
rc::Rc,
cell::RefCell,
};
// Library
use image::DynamicImage;
use vek::*;
// Crate
use crate::render::{
Consts,
UiLocals,
Renderer,
Texture,
UiPipeline,
};
// Local
use super::{
super::{
UiError,
Cache,
},
Element,
Bounds,
SizeRequest,
};
#[derive(Clone)]
pub struct Image {
texture: Rc<Texture<UiPipeline>>,
locals: Consts<UiLocals>,
}
impl Image {
pub fn new(renderer: &mut Renderer, image: &DynamicImage) -> Result<Self, UiError> {
Ok(Self {
texture: Rc::new(
renderer.create_texture(image)
.map_err(|err| UiError::RenderError(err))?
),
locals: renderer.create_consts(&[UiLocals::default()])
.map_err(|err| UiError::RenderError(err))?,
})
}
}
impl Element for Image {
fn get_hsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
fn get_vsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
fn maintain(
&mut self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
) {
renderer.update_consts(&mut self.locals, &[UiLocals::new(
[bounds.x, bounds.y, bounds.w, bounds.h],
)])
.expect("Could not update UI image consts");
}
fn render(
&self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
) {
renderer.render_ui_element(
cache.model(),
&self.locals,
&self.texture,
);
}
}

View File

@ -0,0 +1,181 @@
pub mod image;
// Standard
use std::rc::Rc;
// Library
use vek::*;
// Crate
use crate::render::{
Renderer,
Texture,
Consts,
UiLocals,
UiPipeline,
};
// Local
use super::{
UiError,
Cache,
Span,
SizeRequest,
};
// Bounds
pub type Bounds<T> = Rect<T, T>;
pub trait BoundsExt {
fn relative_to(self, other: Self) -> Self;
}
impl BoundsExt for Bounds<f32> {
fn relative_to(self, other: Self) -> Self {
Self::new(
other.x + self.x * other.w,
other.y + self.y * other.h,
self.w * other.w,
self.h * other.h,
)
}
}
pub trait BoundsSpan {
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32>;
}
impl BoundsSpan for Bounds<Span> {
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32> {
Bounds::new(
self.x.to_rel(resolution.x).rel,
self.y.to_rel(resolution.y).rel,
self.w.to_rel(resolution.x).rel,
self.h.to_rel(resolution.y).rel,
)
}
}
// Element
pub trait Element: 'static {
//fn deep_clone(&self) -> Rc<dyn Element>;
fn get_hsize_request(&self) -> SizeRequest;
fn get_vsize_request(&self) -> SizeRequest;
fn maintain(
&mut self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
);
fn render(
&self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
);
}
// Surface
#[derive(Clone)]
pub enum Surface {
Transparent,
Color(Rgba<f32>),
Texture(Rc<Texture<UiPipeline>>),
Bevel,
}
// Widget
#[derive(Clone)]
pub struct Widget<E: Element> {
inner: Box<E>,
background: Surface,
margin_top: Span,
margin_bottom: Span,
margin_left: Span,
margin_right: Span,
locals: Consts<UiLocals>,
}
impl<E: Element> Widget<E> {
pub fn new(renderer: &mut Renderer, inner: E) -> Result<Box<Self>, UiError> {
Ok(Box::new(Self {
inner: Box::new(inner),
background: Surface::Transparent,
margin_top: Span::rel(0.2),
margin_bottom: Span::rel(0.2),
margin_left: Span::rel(0.2),
margin_right: Span::rel(0.2),
locals: renderer.create_consts(&[UiLocals::default()])
.map_err(|err| UiError::RenderError(err))?,
}))
}
fn get_inner_bounds(&self) -> Bounds<Span> {
Bounds::new(
self.margin_left,
self.margin_top,
Span::full() - self.margin_left - self.margin_right,
Span::full() - self.margin_top - self.margin_bottom,
)
}
}
impl<E: Element> Element for Widget<E> {
fn get_hsize_request(&self) -> SizeRequest {
self.inner.get_hsize_request() + self.margin_left + self.margin_right
}
fn get_vsize_request(&self) -> SizeRequest {
self.inner.get_vsize_request() + self.margin_top + self.margin_bottom
}
fn maintain(
&mut self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
) {
renderer.update_consts(&mut self.locals, &[UiLocals::new(
[bounds.x, bounds.y, bounds.w, bounds.h],
)])
.expect("Could not update UI image consts");
let inner_bounds = self
.get_inner_bounds()
.in_resolution(resolution)
.relative_to(bounds);
self.inner.maintain(renderer, cache, inner_bounds, resolution);
}
fn render(
&self,
renderer: &mut Renderer,
cache: &Cache,
bounds: Bounds<f32>,
resolution: Vec2<f32>,
) {
renderer.render_ui_element(
cache.model(),
&self.locals,
&cache.blank_texture(),
);
let inner_bounds = self
.get_inner_bounds()
.in_resolution(resolution)
.relative_to(bounds);
self.inner.render(renderer, cache, inner_bounds, resolution);
}
}

85
voxygen/src/ui/mod.rs Normal file
View File

@ -0,0 +1,85 @@
pub mod element;
pub mod size_request;
pub mod span;
// Reexports
pub use self::{
span::Span,
size_request::SizeRequest,
};
// Library
use image::DynamicImage;
// Crate
use crate::{
Error,
render::{
RenderError,
Renderer,
Model,
Texture,
UiPipeline,
create_ui_quad_mesh,
},
};
// Local
use self::element::{
Element,
Bounds,
};
#[derive(Debug)]
pub enum UiError {
RenderError(RenderError),
}
pub struct Cache {
model: Model<UiPipeline>,
blank_texture: Texture<UiPipeline>,
}
impl Cache {
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
Ok(Self {
model: renderer.create_model(&create_ui_quad_mesh())?,
blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?,
})
}
pub fn model(&self) -> &Model<UiPipeline> { &self.model }
pub fn blank_texture(&self) -> &Texture<UiPipeline> { &self.blank_texture }
}
pub struct Ui {
base: Box<dyn Element>,
cache: Cache,
}
impl Ui {
pub fn new<E: Element>(renderer: &mut Renderer, base: Box<E>) -> Result<Self, Error> {
Ok(Self {
base,
cache: Cache::new(renderer)?,
})
}
pub fn maintain(&mut self, renderer: &mut Renderer) {
self.base.maintain(
renderer,
&self.cache,
Bounds::new(0.0, 0.0, 1.0, 1.0),
renderer.get_resolution().map(|e| e as f32),
)
}
pub fn render(&self, renderer: &mut Renderer) {
self.base.render(
renderer,
&self.cache,
Bounds::new(0.0, 0.0, 1.0, 1.0),
renderer.get_resolution().map(|e| e as f32),
);
}
}

View File

@ -0,0 +1,30 @@
// Standard
use std::ops::Add;
// Local
use super::Span;
pub struct SizeRequest {
min: Span,
max: Span,
}
impl SizeRequest {
pub fn indifferent() -> Self {
Self {
min: Span::rel(0.0),
max: Span::rel(std::f32::INFINITY),
}
}
}
impl Add<Span> for SizeRequest {
type Output = Self;
fn add(self, span: Span) -> Self {
Self {
min: self.min + span,
max: self.max + span,
}
}
}

47
voxygen/src/ui/span.rs Normal file
View File

@ -0,0 +1,47 @@
// Standard
use std::ops::{Add, Sub};
#[derive(Copy, Clone)]
pub struct Span {
pub rel: f32,
pub abs: f32,
}
impl Span {
pub fn rel(rel: f32) -> Self { Self { rel, abs: 0.0 } }
pub fn abs(abs: f32) -> Self { Self { rel: 0.0, abs } }
pub fn full() -> Self { Self { rel: 1.0, abs: 0.0 } }
pub fn half() -> Self { Self { rel: 0.5, abs: 0.0 } }
pub fn none() -> Self { Self { rel: 0.0, abs: 0.0 } }
pub fn to_abs(self, res: f32) -> Self {
Self { rel: 0.0, abs: self.rel * res + self.abs }
}
pub fn to_rel(self, res: f32) -> Self {
Self { rel: self.rel + self.abs / res, abs: 0.0 }
}
}
impl Add for Span {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
rel: self.rel + other.rel,
abs: self.abs + other.abs,
}
}
}
impl Sub for Span {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
rel: self.rel - other.rel,
abs: self.abs - other.abs,
}
}
}

149
voxygen/src/window.rs Normal file
View File

@ -0,0 +1,149 @@
// Library
use glutin;
use gfx_window_glutin;
use vek::*;
// Crate
use crate::{
Error,
render::{
Renderer,
TgtColorFmt,
TgtDepthFmt,
},
};
pub struct Window {
events_loop: glutin::EventsLoop,
renderer: Renderer,
window: glutin::GlWindow,
cursor_grabbed: bool,
}
impl Window {
pub fn new() -> Result<Window, Error> {
let events_loop = glutin::EventsLoop::new();
let win_builder = glutin::WindowBuilder::new()
.with_title("Veloren (Voxygen)")
.with_dimensions(glutin::dpi::LogicalSize::new(800.0, 500.0))
.with_maximized(false);
let ctx_builder = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2)))
.with_vsync(true);
let (
window,
device,
factory,
tgt_color_view,
tgt_depth_view,
) = gfx_window_glutin::init::<TgtColorFmt, TgtDepthFmt>(
win_builder,
ctx_builder,
&events_loop,
).map_err(|err| Error::BackendError(Box::new(err)))?;
let tmp = Ok(Self {
events_loop,
renderer: Renderer::new(
device,
factory,
tgt_color_view,
tgt_depth_view,
)?,
window,
cursor_grabbed: false,
});
tmp
}
pub fn renderer(&self) -> &Renderer { &self.renderer }
pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer }
pub fn fetch_events(&mut self) -> Vec<Event> {
// Copy data that is needed by the events closure to avoid lifetime errors
// TODO: Remove this if/when the compiler permits it
let cursor_grabbed = self.cursor_grabbed;
let renderer = &mut self.renderer;
let window = &mut self.window;
let mut events = vec![];
self.events_loop.poll_events(|event| match event {
glutin::Event::WindowEvent { event, .. } => match event {
glutin::WindowEvent::CloseRequested => events.push(Event::Close),
glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => {
let (mut color_view, mut depth_view) = renderer.target_views_mut();
gfx_window_glutin::update_views(
&window,
&mut color_view,
&mut depth_view,
);
events.push(Event::Resize(Vec2::new(width as u32, height as u32)));
},
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode {
Some(glutin::VirtualKeyCode::Escape) => events.push(if input.state == glutin::ElementState::Pressed {
Event::KeyDown(Key::ToggleCursor)
} else {
Event::KeyUp(Key::ToggleCursor)
}),
_ => {},
},
_ => {},
},
glutin::Event::DeviceEvent { event, .. } => match event {
glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed =>
events.push(Event::CursorPan(Vec2::new(dx as f32, dy as f32))),
glutin::DeviceEvent::MouseWheel {
delta: glutin::MouseScrollDelta::LineDelta(_x, y),
..
} if cursor_grabbed => events.push(Event::Zoom(y as f32)),
_ => {},
},
_ => {},
});
events
}
pub fn swap_buffers(&self) -> Result<(), Error> {
self.window.swap_buffers()
.map_err(|err| Error::BackendError(Box::new(err)))
}
pub fn is_cursor_grabbed(&self) -> bool {
self.cursor_grabbed
}
pub fn grab_cursor(&mut self, grab: bool) {
self.cursor_grabbed = grab;
self.window.hide_cursor(grab);
self.window.grab_cursor(grab)
.expect("Failed to grab/ungrab cursor");
}
}
/// Represents a key that the game recognises after keyboard mapping
pub enum Key {
ToggleCursor,
}
/// Represents an incoming event from the window
pub enum Event {
/// The window has been requested to close.
Close,
/// The window has been resized
Resize(Vec2<u32>),
/// A key has been typed that corresponds to a specific character.
Char(char),
/// The cursor has been panned across the screen while grabbed.
CursorPan(Vec2<f32>),
/// The camera has been requested to zoom.
Zoom(f32),
/// A key that the game recognises has been pressed down
KeyDown(Key),
/// A key that the game recognises has been released down
KeyUp(Key),
}

1
voxygen/test_assets/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.png filter=lfs diff=lfs merge=lfs -text

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
voxygen/test_assets/test.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
voxygen/test_assets/wall.png (Stored with Git LFS) Normal file

Binary file not shown.

3
world/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

10
world/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "veloren-world"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
vek = "0.9"
noise = "0.5"

61
world/src/lib.rs Normal file
View File

@ -0,0 +1,61 @@
// Library
use vek::*;
use noise::{NoiseFn, Perlin};
// Project
use common::{
vol::{Vox, SizedVol, WriteVol},
terrain::{
Block,
TerrainChunk,
TerrainChunkMeta,
},
};
#[derive(Debug)]
pub enum Error {
Other(String),
}
pub struct World;
impl World {
pub fn new() -> Self {
Self
}
pub fn generate_chunk(&self, chunk_pos: Vec3<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(50, 255, 0));
let sand = Block::new(3, Rgb::new(180, 150, 50));
let perlin_nz = Perlin::new();
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);
let freq = 1.0 / 32.0;
let ampl = 16.0;
let offs = 16.0;
let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl + offs;
chunk.set(lpos, if wposf.z < height {
if wposf.z < height - 1.0 {
stone
} else {
sand
}
} else {
air
}).unwrap();
}
chunk
}
}