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",
|
"common",
|
||||||
"client",
|
"client",
|
||||||
"server",
|
"server",
|
||||||
|
"server-cli",
|
||||||
|
"voxygen",
|
||||||
|
"world",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
@ -5,6 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { package = "veloren-common", path = "../common" }
|
||||||
|
world = { package = "veloren-world", path = "../world" }
|
||||||
|
|
||||||
specs = "0.14"
|
specs = "0.14"
|
||||||
|
vek = "0.9"
|
||||||
|
threadpool = "1.7"
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
// Standard
|
// Standard
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// Internal
|
// Library
|
||||||
use common::state::State;
|
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,
|
ServerShutdown,
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
@ -14,20 +24,65 @@ pub struct Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
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 {
|
impl Client {
|
||||||
|
/// Create a new `Client`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
thread_pool: threadpool::Builder::new()
|
||||||
|
.thread_name("veloren-worker".into())
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
tick: 0,
|
||||||
state: State::new(),
|
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
|
/// 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
|
// 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
|
// 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
|
// the core developers before making significant changes to this code. Here is the
|
||||||
@ -44,6 +99,13 @@ impl Client {
|
|||||||
self.state.tick(dt);
|
self.state.tick(dt);
|
||||||
|
|
||||||
// Finish the tick, pass control back to the frontend (step 6)
|
// Finish the tick, pass control back to the frontend (step 6)
|
||||||
|
self.tick += 1;
|
||||||
Ok(())
|
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]
|
[package]
|
||||||
name = "common"
|
name = "veloren-common"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
specs = "0.14"
|
specs = "0.14"
|
||||||
|
shred = "0.7"
|
||||||
vek = "0.9"
|
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;
|
pub mod phys;
|
||||||
|
|
||||||
// External
|
// External
|
||||||
use specs::{World as EcsWorld, Builder};
|
use specs::World as EcsWorld;
|
||||||
|
|
||||||
pub fn register_local_components(ecs_world: &mut EcsWorld) {
|
pub fn register_local_components(ecs_world: &mut EcsWorld) {
|
||||||
ecs_world.register::<phys::Pos>();
|
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 comp;
|
||||||
|
pub mod figure;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
|
pub mod util;
|
||||||
pub mod volumes;
|
pub mod volumes;
|
||||||
pub mod vol;
|
pub mod vol;
|
||||||
|
@ -1,39 +1,119 @@
|
|||||||
// Standard
|
// Standard
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// External
|
// Library
|
||||||
use specs::World as EcsWorld;
|
use specs::World as EcsWorld;
|
||||||
|
use shred::{Fetch, FetchMut};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
comp,
|
comp,
|
||||||
terrain::TerrainMap,
|
terrain::TerrainMap,
|
||||||
vol::VolSize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A type used to represent game state stored on both the client and the server. This includes
|
/// How much faster should an in-game day be compared to a real day?
|
||||||
// things like entity components, terrain data, and global state like weather, time of day, etc.
|
// 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 {
|
pub struct State {
|
||||||
ecs_world: EcsWorld,
|
ecs_world: EcsWorld,
|
||||||
terrain_map: TerrainMap,
|
changes: Changes,
|
||||||
time: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
/// Create a new `State`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut ecs_world = EcsWorld::new();
|
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);
|
comp::register_local_components(&mut ecs_world);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ecs_world,
|
ecs_world,
|
||||||
terrain_map: TerrainMap::new(),
|
changes: Changes::default(),
|
||||||
time: 0.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
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 {
|
pub enum BiomeKind {
|
||||||
|
Void,
|
||||||
Grassland,
|
Grassland,
|
||||||
Ocean,
|
Ocean,
|
||||||
Mountain,
|
Mountain,
|
||||||
|
@ -1,4 +1,41 @@
|
|||||||
|
// Library
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
// Crate
|
||||||
|
use crate::vol::Vox;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
kind: u8,
|
kind: u8,
|
||||||
color: [u8; 3],
|
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 block;
|
||||||
pub mod biome;
|
pub mod biome;
|
||||||
|
|
||||||
|
// Reexports
|
||||||
|
pub use self::{
|
||||||
|
block::Block,
|
||||||
|
biome::BiomeKind,
|
||||||
|
};
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
vol::VolSize,
|
vol::VolSize,
|
||||||
volumes::vol_map::VolMap,
|
volumes::{
|
||||||
|
vol_map::VolMap,
|
||||||
|
chunk::Chunk,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local
|
// TerrainChunkSize
|
||||||
use self::{
|
|
||||||
block::Block,
|
|
||||||
biome::BiomeKind,
|
|
||||||
};
|
|
||||||
|
|
||||||
// ChunkSize
|
pub struct TerrainChunkSize;
|
||||||
|
|
||||||
pub struct ChunkSize;
|
impl VolSize for TerrainChunkSize {
|
||||||
|
|
||||||
impl VolSize for ChunkSize {
|
|
||||||
const SIZE: Vec3<u32> = Vec3 { x: 32, y: 32, z: 32 };
|
const SIZE: Vec3<u32> = Vec3 { x: 32, y: 32, z: 32 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkMeta
|
// TerrainChunkMeta
|
||||||
|
|
||||||
pub struct ChunkMeta {
|
pub struct TerrainChunkMeta {
|
||||||
biome: BiomeKind,
|
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
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
/// A voxel
|
||||||
|
pub trait Vox {
|
||||||
|
fn empty() -> Self;
|
||||||
|
fn is_empty(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A volume that contains voxel data.
|
||||||
pub trait BaseVol {
|
pub trait BaseVol {
|
||||||
type Vox;
|
type Vox: Vox;
|
||||||
type Err;
|
type Err;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SizedVol: BaseVol {
|
// Utility types
|
||||||
const SIZE: Vec3<u32>;
|
|
||||||
|
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 {
|
pub trait ReadVol: BaseVol {
|
||||||
|
/// Get a reference to the voxel at the provided position in the volume.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Err>;
|
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 {
|
pub trait WriteVol: BaseVol {
|
||||||
|
/// Set the voxel at the provided position in the volume to the provided value.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Err>;
|
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility traits
|
// Utility traits
|
||||||
|
|
||||||
|
/// Used to specify a volume's compile-time size. This exists as a substitute until const generics
|
||||||
|
/// are implemented.
|
||||||
pub trait VolSize {
|
pub trait VolSize {
|
||||||
const SIZE: Vec3<u32>;
|
const SIZE: Vec3<u32>;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use vek::*;
|
|||||||
|
|
||||||
// Local
|
// Local
|
||||||
use crate::vol::{
|
use crate::vol::{
|
||||||
|
Vox,
|
||||||
BaseVol,
|
BaseVol,
|
||||||
SizedVol,
|
SizedVol,
|
||||||
ReadVol,
|
ReadVol,
|
||||||
@ -13,20 +14,24 @@ use crate::vol::{
|
|||||||
VolSize,
|
VolSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ChunkErr {
|
pub enum ChunkErr {
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A volume with dimensions known at compile-time.
|
||||||
// V = Voxel
|
// V = Voxel
|
||||||
// S = Size (replace when const generics are a thing)
|
// S = Size (replace when const generics are a thing)
|
||||||
// M = Metadata
|
// M = Metadata
|
||||||
pub struct Chunk<V, S: VolSize, M> {
|
pub struct Chunk<V: Vox, S: VolSize, M> {
|
||||||
vox: Vec<V>,
|
vox: Vec<V>,
|
||||||
meta: M,
|
meta: M,
|
||||||
phantom: PhantomData<S>,
|
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)]
|
#[inline(always)]
|
||||||
fn idx_for(pos: Vec3<i32>) -> Option<usize> {
|
fn idx_for(pos: Vec3<i32>) -> Option<usize> {
|
||||||
if
|
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 Vox = V;
|
||||||
type Err = ChunkErr;
|
type Err = ChunkErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, S: VolSize, M> SizedVol for Chunk<V, S, M> {
|
impl<V: Vox, S: VolSize, M> SizedVol for Chunk<V, S, M> {
|
||||||
const SIZE: Vec3<u32> = Vec3 { x: 32, y: 32, z: 32 };
|
#[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)]
|
#[inline(always)]
|
||||||
fn get(&self, pos: Vec3<i32>) -> Result<&V, ChunkErr> {
|
fn get(&self, pos: Vec3<i32>) -> Result<&V, ChunkErr> {
|
||||||
Self::idx_for(pos)
|
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)]
|
#[inline(always)]
|
||||||
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), ChunkErr> {
|
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), ChunkErr> {
|
||||||
Self::idx_for(pos)
|
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 {
|
pub fn filled(vox: V, meta: M) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vox: vec![vox; S::SIZE.product() as usize],
|
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 {
|
pub fn metadata(&self) -> &M {
|
||||||
&self.meta
|
&self.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the internal metadata.
|
||||||
pub fn metadata_mut(&mut self) -> &mut M {
|
pub fn metadata_mut(&mut self) -> &mut M {
|
||||||
&mut self.meta
|
&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 chunk;
|
||||||
pub mod vol_map;
|
pub mod vol_map;
|
||||||
|
@ -7,27 +7,35 @@ use vek::*;
|
|||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
vol::{
|
vol::{
|
||||||
|
Vox,
|
||||||
BaseVol,
|
BaseVol,
|
||||||
|
SizedVol,
|
||||||
ReadVol,
|
ReadVol,
|
||||||
|
SampleVol,
|
||||||
WriteVol,
|
WriteVol,
|
||||||
VolSize,
|
VolSize,
|
||||||
},
|
},
|
||||||
volumes::chunk::{Chunk, ChunkErr},
|
volumes::{
|
||||||
|
chunk::{Chunk, ChunkErr},
|
||||||
|
dyna::{Dyna, DynaErr},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum VolMapErr {
|
pub enum VolMapErr {
|
||||||
NoSuchChunk,
|
NoSuchChunk,
|
||||||
ChunkErr(ChunkErr),
|
ChunkErr(ChunkErr),
|
||||||
|
DynaErr(DynaErr),
|
||||||
}
|
}
|
||||||
|
|
||||||
// V = Voxel
|
// V = Voxel
|
||||||
// S = Size (replace with a const when const generics is a thing)
|
// S = Size (replace with a const when const generics is a thing)
|
||||||
// M = Chunk metadata
|
// 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>>,
|
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)]
|
#[inline(always)]
|
||||||
fn chunk_key(pos: Vec3<i32>) -> Vec3<i32> {
|
fn chunk_key(pos: Vec3<i32>) -> Vec3<i32> {
|
||||||
pos.map2(S::SIZE, |e, sz| e.div_euclid(sz as 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 Vox = V;
|
||||||
type Err = VolMapErr;
|
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)]
|
#[inline(always)]
|
||||||
fn get(&self, pos: Vec3<i32>) -> Result<&V, VolMapErr> {
|
fn get(&self, pos: Vec3<i32>) -> Result<&V, VolMapErr> {
|
||||||
let ck = Self::chunk_key(pos);
|
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)]
|
#[inline(always)]
|
||||||
fn set(&mut self, pos: Vec3<i32>, vox: V) -> Result<(), VolMapErr> {
|
fn set(&mut self, pos: Vec3<i32>, vox: V) -> Result<(), VolMapErr> {
|
||||||
let ck = Self::chunk_key(pos);
|
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 {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
chunks: HashMap::new(),
|
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]
|
[package]
|
||||||
name = "server"
|
name = "veloren-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { package = "veloren-common", path = "../common" }
|
||||||
|
world = { package = "veloren-world", path = "../world" }
|
||||||
|
|
||||||
specs = "0.14"
|
specs = "0.14"
|
||||||
|
@ -3,9 +3,10 @@ use std::time::Duration;
|
|||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
use common::state::State;
|
use common::state::State;
|
||||||
|
use world::World;
|
||||||
|
|
||||||
pub enum ClientErr {
|
#[derive(Debug)]
|
||||||
ServerShutdown,
|
pub enum Error {
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,19 +16,32 @@ pub struct Input {
|
|||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
state: State,
|
state: State,
|
||||||
|
world: World,
|
||||||
|
|
||||||
// TODO: Add "meta" state here
|
// TODO: Add "meta" state here
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
/// Create a new `Server`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::new(),
|
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
|
/// 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
|
// 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
|
// 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
|
// 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)
|
// Finish the tick, pass control back to the frontend (step 6)
|
||||||
Ok(())
|
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