mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'master' into 'master'
Reimplemented Voxygen See merge request veloren/fresh!3 Former-commit-id: 0c99632b70987bd6ab7444cf89c712056dd57e0b
This commit is contained in:
commit
71648ce4a8
@ -3,6 +3,9 @@ members = [
|
||||
"common",
|
||||
"client",
|
||||
"server",
|
||||
"server-cli",
|
||||
"voxygen",
|
||||
"world",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
50
common/src/clock.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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
38
common/src/figure/cell.rs
Normal 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
55
common/src/figure/mod.rs
Normal 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(), ())
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub enum BiomeKind {
|
||||
Void,
|
||||
Grassland,
|
||||
Ocean,
|
||||
Mountain,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
1
common/src/util/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
97
common/src/volumes/dyna.rs
Normal file
97
common/src/volumes/dyna.rs
Normal 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
|
||||
}
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod dyna;
|
||||
pub mod chunk;
|
||||
pub mod vol_map;
|
||||
|
@ -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
3
server-cli/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
12
server-cli/Cargo.toml
Normal file
12
server-cli/Cargo.toml
Normal 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
35
server-cli/src/main.rs
Normal 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));
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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
3
voxygen/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
32
voxygen/Cargo.toml
Normal file
32
voxygen/Cargo.toml
Normal 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"
|
49
voxygen/shaders/figure.frag
Normal file
49
voxygen/shaders/figure.frag
Normal 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);
|
||||
}
|
50
voxygen/shaders/figure.vert
Normal file
50
voxygen/shaders/figure.vert
Normal 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);
|
||||
}
|
46
voxygen/shaders/skybox.frag
Normal file
46
voxygen/shaders/skybox.frag
Normal 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);
|
||||
}
|
31
voxygen/shaders/skybox.vert
Normal file
31
voxygen/shaders/skybox.vert
Normal 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;
|
||||
}
|
33
voxygen/shaders/terrain.frag
Normal file
33
voxygen/shaders/terrain.frag
Normal 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);
|
||||
}
|
36
voxygen/shaders/terrain.vert
Normal file
36
voxygen/shaders/terrain.vert
Normal 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);
|
||||
}
|
11
voxygen/shaders/test/shader.frag
Normal file
11
voxygen/shaders/test/shader.frag
Normal 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;
|
||||
}
|
11
voxygen/shaders/test/shader.vert
Normal file
11
voxygen/shaders/test/shader.vert
Normal 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
17
voxygen/shaders/ui.frag
Normal 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
22
voxygen/shaders/ui.vert
Normal 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);
|
||||
}
|
66
voxygen/src/anim/character/mod.rs
Normal file
66
voxygen/src/anim/character/mod.rs
Normal 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(),
|
||||
]
|
||||
}
|
||||
}
|
50
voxygen/src/anim/character/run.rs
Normal file
50
voxygen/src/anim/character/run.rs
Normal 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
40
voxygen/src/anim/mod.rs
Normal 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
33
voxygen/src/error.rs
Normal 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
123
voxygen/src/main.rs
Normal 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
1
voxygen/src/menu/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod title;
|
74
voxygen/src/menu/title.rs
Normal file
74
voxygen/src/menu/title.rs
Normal 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
15
voxygen/src/mesh/mod.rs
Normal 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
149
voxygen/src/mesh/segment.rs
Normal 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
147
voxygen/src/mesh/terrain.rs
Normal 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
|
||||
}
|
||||
}
|
37
voxygen/src/render/consts.rs
Normal file
37
voxygen/src/render/consts.rs
Normal 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))
|
||||
}
|
||||
}
|
99
voxygen/src/render/mesh.rs
Normal file
99
voxygen/src/render/mesh.rs
Normal 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
69
voxygen/src/render/mod.rs
Normal 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>;
|
||||
}
|
36
voxygen/src/render/model.rs
Normal file
36
voxygen/src/render/model.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
95
voxygen/src/render/pipelines/figure.rs
Normal file
95
voxygen/src/render/pipelines/figure.rs
Normal 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;
|
||||
}
|
66
voxygen/src/render/pipelines/mod.rs
Normal file
66
voxygen/src/render/pipelines/mod.rs
Normal 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],
|
||||
}
|
||||
}
|
||||
}
|
110
voxygen/src/render/pipelines/skybox.rs
Normal file
110
voxygen/src/render/pipelines/skybox.rs
Normal 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
|
||||
}
|
68
voxygen/src/render/pipelines/terrain.rs
Normal file
68
voxygen/src/render/pipelines/terrain.rs
Normal 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;
|
||||
}
|
76
voxygen/src/render/pipelines/ui.rs
Normal file
76
voxygen/src/render/pipelines/ui.rs
Normal 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
|
||||
}
|
297
voxygen/src/render/renderer.rs
Normal file
297
voxygen/src/render/renderer.rs
Normal 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),
|
||||
}))?,
|
||||
})
|
||||
}
|
58
voxygen/src/render/texture.rs
Normal file
58
voxygen/src/render/texture.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
10
voxygen/src/render/util.rs
Normal file
10
voxygen/src/render/util.rs
Normal 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]],
|
||||
]
|
||||
}
|
78
voxygen/src/scene/camera.rs
Normal file
78
voxygen/src/scene/camera.rs
Normal 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; }
|
||||
}
|
79
voxygen/src/scene/figure.rs
Normal file
79
voxygen/src/scene/figure.rs
Normal 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
183
voxygen/src/scene/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
187
voxygen/src/scene/terrain.rs
Normal file
187
voxygen/src/scene/terrain.rs
Normal 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
134
voxygen/src/session.rs
Normal 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" }
|
||||
}
|
80
voxygen/src/ui/element/image.rs
Normal file
80
voxygen/src/ui/element/image.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
181
voxygen/src/ui/element/mod.rs
Normal file
181
voxygen/src/ui/element/mod.rs
Normal 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
85
voxygen/src/ui/mod.rs
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
30
voxygen/src/ui/size_request.rs
Normal file
30
voxygen/src/ui/size_request.rs
Normal 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
47
voxygen/src/ui/span.rs
Normal 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
149
voxygen/src/window.rs
Normal 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
1
voxygen/test_assets/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
BIN
voxygen/test_assets/belt.vox
Normal file
BIN
voxygen/test_assets/belt.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/chest.vox
Normal file
BIN
voxygen/test_assets/chest.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/belt.vox
Normal file
BIN
voxygen/test_assets/elf/belt.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/chest.vox
Normal file
BIN
voxygen/test_assets/elf/chest.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/foot.vox
Normal file
BIN
voxygen/test_assets/elf/foot.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/hand.vox
Normal file
BIN
voxygen/test_assets/elf/hand.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/head.vox
Normal file
BIN
voxygen/test_assets/elf/head.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/pants.vox
Normal file
BIN
voxygen/test_assets/elf/pants.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/elf/sword.vox
Normal file
BIN
voxygen/test_assets/elf/sword.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/foot.vox
Normal file
BIN
voxygen/test_assets/foot.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/hand.vox
Normal file
BIN
voxygen/test_assets/hand.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/head.vox
Normal file
BIN
voxygen/test_assets/head.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/knight.vox
Normal file
BIN
voxygen/test_assets/knight.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/pants.vox
Normal file
BIN
voxygen/test_assets/pants.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/sword.vox
Normal file
BIN
voxygen/test_assets/sword.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/test.png
(Stored with Git LFS)
Normal file
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
BIN
voxygen/test_assets/wall.png
(Stored with Git LFS)
Normal file
Binary file not shown.
3
world/.gitignore
vendored
Normal file
3
world/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
10
world/Cargo.toml
Normal file
10
world/Cargo.toml
Normal 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
61
world/src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user