Merge branch 'master' into 'master'

Switch to actor system, terrain performance improvements

See merge request veloren/veloren!131

Former-commit-id: 363bd800e2bd068e8ef7b1ef167c3a3cee01cf4d
This commit is contained in:
Joshua Barretto
2019-05-13 10:47:47 +00:00
14 changed files with 438 additions and 274 deletions

View File

@ -13,7 +13,7 @@ pub enum Race {
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Gender { pub enum BodyType {
Female, Female,
Male, Male,
Unspecified, Unspecified,
@ -21,32 +21,32 @@ pub enum Gender {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Head { pub enum Head {
DefaultHead, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Chest { pub enum Chest {
DefaultChest, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Belt { pub enum Belt {
DefaultBelt, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pants { pub enum Pants {
DefaultPants, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Hand { pub enum Hand {
DefaultHand, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Foot { pub enum Foot {
DefaultFoot, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -62,42 +62,45 @@ pub enum Weapon {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Shoulder { pub enum Shoulder {
DefaultShoulder, Default,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Draw { pub enum Draw {
DefaultDraw, Default,
} }
use Belt::*; const ALL_RACES: [Race; 6] = [
use Chest::*; Race::Danari,
use Draw::*; Race::Dwarf,
use Foot::*; Race::Elf,
use Gender::*; Race::Human,
use Hand::*; Race::Orc,
use Head::*; Race::Undead,
use Pants::*; ];
use Race::*; const ALL_BODY_TYPES: [BodyType; 3] = [BodyType::Female, BodyType::Male, BodyType::Unspecified];
use Shoulder::*; const ALL_HEADS: [Head; 1] = [Head::Default];
use Weapon::*; const ALL_CHESTS: [Chest; 1] = [Chest::Default];
const ALL_BELTS: [Belt; 1] = [Belt::Default];
const ALL_RACES: [Race; 6] = [Danari, Dwarf, Elf, Human, Orc, Undead]; const ALL_PANTS: [Pants; 1] = [Pants::Default];
const ALL_GENDERS: [Gender; 3] = [Female, Male, Unspecified]; const ALL_HANDS: [Hand; 1] = [Hand::Default];
const ALL_HEADS: [Head; 1] = [DefaultHead]; const ALL_FEET: [Foot; 1] = [Foot::Default];
const ALL_CHESTS: [Chest; 1] = [DefaultChest]; const ALL_WEAPONS: [Weapon; 7] = [
const ALL_BELTS: [Belt; 1] = [DefaultBelt]; Weapon::Daggers,
const ALL_PANTS: [Pants; 1] = [DefaultPants]; Weapon::SwordShield,
const ALL_HANDS: [Hand; 1] = [DefaultHand]; Weapon::Sword,
const ALL_FEET: [Foot; 1] = [DefaultFoot]; Weapon::Axe,
const ALL_WEAPONS: [Weapon; 7] = [Daggers, SwordShield, Sword, Axe, Hammer, Bow, Staff]; Weapon::Hammer,
const ALL_SHOULDERS: [Shoulder; 1] = [DefaultShoulder]; Weapon::Bow,
const ALL_DRAW: [Draw; 1] = [DefaultDraw]; Weapon::Staff,
];
const ALL_SHOULDERS: [Shoulder; 1] = [Shoulder::Default];
const ALL_DRAW: [Draw; 1] = [Draw::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Character { pub struct HumanoidBody {
pub race: Race, pub race: Race,
pub gender: Gender, pub body_type: BodyType,
pub head: Head, pub head: Head,
pub chest: Chest, pub chest: Chest,
pub belt: Belt, pub belt: Belt,
@ -109,11 +112,11 @@ pub struct Character {
pub draw: Draw, pub draw: Draw,
} }
impl Character { impl HumanoidBody {
pub fn random() -> Self { pub fn random() -> Self {
Self { Self {
race: *thread_rng().choose(&ALL_RACES).unwrap(), race: *thread_rng().choose(&ALL_RACES).unwrap(),
gender: *thread_rng().choose(&ALL_GENDERS).unwrap(), body_type: *thread_rng().choose(&ALL_BODY_TYPES).unwrap(),
head: *thread_rng().choose(&ALL_HEADS).unwrap(), head: *thread_rng().choose(&ALL_HEADS).unwrap(),
chest: *thread_rng().choose(&ALL_CHESTS).unwrap(), chest: *thread_rng().choose(&ALL_CHESTS).unwrap(),
belt: *thread_rng().choose(&ALL_BELTS).unwrap(), belt: *thread_rng().choose(&ALL_BELTS).unwrap(),
@ -127,6 +130,20 @@ impl Character {
} }
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Body {
Humanoid(HumanoidBody),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Actor {
Character { name: String, body: Body },
}
impl Component for Actor {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct AnimationHistory { pub struct AnimationHistory {
pub last: Option<Animation>, pub last: Option<Animation>,
@ -151,10 +168,6 @@ pub enum Animation {
Jump, Jump,
} }
impl Component for Character {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Component for AnimationHistory { impl Component for AnimationHistory {
type Storage = FlaggedStorage<Self, VecStorage<Self>>; type Storage = FlaggedStorage<Self, VecStorage<Self>>;
} }

View File

@ -1,11 +1,13 @@
pub mod actor;
pub mod agent; pub mod agent;
pub mod character;
pub mod phys; pub mod phys;
pub mod player; pub mod player;
// Reexports // Reexports
pub use actor::Actor;
pub use actor::Animation;
pub use actor::AnimationHistory;
pub use actor::Body;
pub use actor::HumanoidBody;
pub use agent::{Agent, Control}; pub use agent::{Agent, Control};
pub use character::Animation;
pub use character::AnimationHistory;
pub use character::Character;
pub use player::Player; pub use player::Player;

View File

@ -7,12 +7,15 @@ pub enum ClientMsg {
Register { Register {
player: comp::Player, player: comp::Player,
}, },
Character(comp::Character), Character {
name: String,
body: comp::HumanoidBody,
},
RequestState(ClientState), RequestState(ClientState),
Ping, Ping,
Pong, Pong,
Chat(String), Chat(String),
PlayerAnimation(comp::character::AnimationHistory), PlayerAnimation(comp::AnimationHistory),
PlayerPhysics { PlayerPhysics {
pos: comp::phys::Pos, pos: comp::phys::Pos,
vel: comp::phys::Vel, vel: comp::phys::Vel,

View File

@ -9,7 +9,7 @@ sphynx::sum_type! {
Pos(comp::phys::Pos), Pos(comp::phys::Pos),
Vel(comp::phys::Vel), Vel(comp::phys::Vel),
Dir(comp::phys::Dir), Dir(comp::phys::Dir),
Character(comp::Character), Actor(comp::Actor),
Player(comp::Player), Player(comp::Player),
} }
} }
@ -20,7 +20,7 @@ sphynx::sum_type! {
Pos(PhantomData<comp::phys::Pos>), Pos(PhantomData<comp::phys::Pos>),
Vel(PhantomData<comp::phys::Vel>), Vel(PhantomData<comp::phys::Vel>),
Dir(PhantomData<comp::phys::Dir>), Dir(PhantomData<comp::phys::Dir>),
Character(PhantomData<comp::Character>), Actor(PhantomData<comp::Actor>),
Player(PhantomData<comp::Player>), Player(PhantomData<comp::Player>),
} }
} }

View File

@ -95,7 +95,7 @@ impl State {
// Create a new Sphynx ECS world // Create a new Sphynx ECS world
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsPacket>) { fn setup_sphynx_world(ecs: &mut sphynx::World<EcsPacket>) {
// Register synced components // Register synced components
ecs.register_synced::<comp::Character>(); ecs.register_synced::<comp::Actor>();
ecs.register_synced::<comp::Player>(); ecs.register_synced::<comp::Player>();
// Register unsynched (or synced by other means) components // Register unsynched (or synced by other means) components
@ -110,7 +110,7 @@ impl State {
ecs.add_resource(TimeOfDay(0.0)); ecs.add_resource(TimeOfDay(0.0));
ecs.add_resource(Time(0.0)); ecs.add_resource(Time(0.0));
ecs.add_resource(DeltaTime(0.0)); ecs.add_resource(DeltaTime(0.0));
ecs.add_resource(TerrainMap::new()); ecs.add_resource(TerrainMap::new().unwrap());
} }
/// Register a component with the state's ECS /// Register a component with the state's ECS
@ -177,7 +177,7 @@ impl State {
if self if self
.ecs .ecs
.write_resource::<TerrainMap>() .write_resource::<TerrainMap>()
.insert(key, chunk) .insert(key, Arc::new(chunk))
.is_some() .is_some()
{ {
self.changes.changed_chunks.insert(key); self.changes.changed_chunks.insert(key);

View File

@ -1,11 +1,12 @@
// Standard // Standard
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
// Library // Library
use vek::*; use vek::*;
// Crate // Crate
use crate::{ use crate::{
terrain::TerrainChunkMeta,
vol::{BaseVol, ReadVol, SampleVol, SizedVol, VolSize, Vox, WriteVol}, vol::{BaseVol, ReadVol, SampleVol, SizedVol, VolSize, Vox, WriteVol},
volumes::{ volumes::{
chunk::{Chunk, ChunkErr}, chunk::{Chunk, ChunkErr},
@ -18,24 +19,33 @@ pub enum VolMapErr {
NoSuchChunk, NoSuchChunk,
ChunkErr(ChunkErr), ChunkErr(ChunkErr),
DynaErr(DynaErr), DynaErr(DynaErr),
InvalidChunkSize,
} }
// 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
#[derive(Clone)]
pub struct VolMap<V: Vox, S: VolSize, M> { pub struct VolMap<V: Vox, S: VolSize, M> {
chunks: HashMap<Vec3<i32>, Chunk<V, S, M>>, chunks: HashMap<Vec3<i32>, Arc<Chunk<V, S, M>>>,
} }
impl<V: Vox, 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> { pub 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| {
// Horrid, but it's faster than a cheetah with a red bull blood transfusion
let log2 = (sz - 1).count_ones();
((((e as i64 + (1 << 32)) as u64) >> log2) - (1 << (32 - log2))) as i32
})
} }
#[inline(always)] #[inline(always)]
fn chunk_offs(pos: Vec3<i32>) -> Vec3<i32> { pub fn chunk_offs(pos: Vec3<i32>) -> Vec3<i32> {
pos.map2(S::SIZE, |e, sz| e.rem_euclid(sz as i32)) pos.map2(S::SIZE, |e, sz| {
// Horrid, but it's even faster than the aforementioned cheetah
(((e as i64 + (1 << 32)) as u64) & (sz - 1) as u64) as i32
})
} }
} }
@ -58,8 +68,8 @@ impl<V: Vox, S: VolSize, M> ReadVol for VolMap<V, S, M> {
} }
} }
impl<V: Vox + Clone, S: VolSize, M> SampleVol for VolMap<V, S, M> { impl<V: Vox + Clone, S: VolSize, M: Clone> SampleVol for VolMap<V, S, M> {
type Sample = Dyna<V, ()>; type Sample = VolMap<V, S, M>;
/// Take a sample of the terrain by cloning the voxels within the provided range. /// Take a sample of the terrain by cloning the voxels within the provided range.
/// ///
@ -80,24 +90,59 @@ impl<V: Vox + Clone, S: VolSize, M> SampleVol for VolMap<V, S, M> {
} }
*/ */
let mut sample = Dyna::filled(range.size().map(|e| e as u32).into(), V::empty(), ()); // let mut sample = Dyna::filled(range.size().map(|e| e as u32).into(), V::empty(), ());
for pos in sample.iter_positions() { // let mut last_chunk_pos = self.pos_key(range.min);
sample // let mut last_chunk = self.get_key(last_chunk_pos);
.set(
pos, // for pos in sample.iter_positions() {
self.get(range.min + pos) // let new_chunk_pos = self.pos_key(range.min + pos);
.map(|v| v.clone()) // if last_chunk_pos != new_chunk_pos {
.unwrap_or(V::empty()), // last_chunk = self.get_key(new_chunk_pos);
) // last_chunk_pos = new_chunk_pos;
.map_err(|err| VolMapErr::DynaErr(err))?; // }
// sample
// .set(
// pos,
// if let Some(chunk) = last_chunk {
// chunk
// .get(Self::chunk_offs(range.min + pos))
// .map(|v| v.clone())
// .unwrap_or(V::empty())
// // Fallback in case the chunk doesn't exist
// } else {
// self.get(range.min + pos)
// .map(|v| v.clone())
// .unwrap_or(V::empty())
// },
// )
// .map_err(|err| VolMapErr::DynaErr(err))?;
// }
// Ok(sample)
let mut sample = VolMap::new()?;
let chunk_min = Self::chunk_key(range.min);
let chunk_max = Self::chunk_key(range.max);
for x in chunk_min.x..=chunk_max.x {
for y in chunk_min.y..=chunk_max.y {
for z in chunk_min.z..=chunk_max.z {
let chunk_key = Vec3::new(x, y, z);
let chunk = self.get_key_arc(chunk_key).map(|v| v.clone());
if let Some(chunk) = chunk {
sample.insert(chunk_key, chunk);
}
}
}
} }
Ok(sample) Ok(sample)
} }
} }
impl<V: Vox, S: VolSize, M> WriteVol for VolMap<V, S, M> { impl<V: Vox + Clone, S: VolSize + Clone, M: Clone> 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);
@ -106,15 +151,24 @@ impl<V: Vox, S: VolSize, M> WriteVol for VolMap<V, S, M> {
.ok_or(VolMapErr::NoSuchChunk) .ok_or(VolMapErr::NoSuchChunk)
.and_then(|chunk| { .and_then(|chunk| {
let co = Self::chunk_offs(pos); let co = Self::chunk_offs(pos);
chunk.set(co, vox).map_err(|err| VolMapErr::ChunkErr(err)) Arc::make_mut(chunk)
.set(co, vox)
.map_err(|err| VolMapErr::ChunkErr(err))
}) })
} }
} }
impl<V: Vox, S: VolSize, M> VolMap<V, S, M> { impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
pub fn new() -> Self { pub fn new() -> Result<Self, VolMapErr> {
Self { if Self::chunk_size()
chunks: HashMap::new(), .map(|e| e.is_power_of_two() && e > 0)
.reduce_and()
{
Ok(Self {
chunks: HashMap::new(),
})
} else {
Err(VolMapErr::InvalidChunkSize)
} }
} }
@ -122,15 +176,26 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
S::SIZE S::SIZE
} }
pub fn insert(&mut self, key: Vec3<i32>, chunk: Chunk<V, S, M>) -> Option<Chunk<V, S, M>> { pub fn insert(
&mut self,
key: Vec3<i32>,
chunk: Arc<Chunk<V, S, M>>,
) -> Option<Arc<Chunk<V, S, M>>> {
self.chunks.insert(key, chunk) self.chunks.insert(key, chunk)
} }
pub fn get_key(&self, key: Vec3<i32>) -> Option<&Chunk<V, S, M>> { pub fn get_key(&self, key: Vec3<i32>) -> Option<&Chunk<V, S, M>> {
match self.chunks.get(&key) {
Some(arc_chunk) => Some(arc_chunk.as_ref()),
None => None,
}
}
pub fn get_key_arc(&self, key: Vec3<i32>) -> Option<&Arc<Chunk<V, S, M>>> {
self.chunks.get(&key) self.chunks.get(&key)
} }
pub fn remove(&mut self, key: Vec3<i32>) -> Option<Chunk<V, S, M>> { pub fn remove(&mut self, key: Vec3<i32>) -> Option<Arc<Chunk<V, S, M>>> {
self.chunks.remove(&key) self.chunks.remove(&key)
} }
@ -150,11 +215,11 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
} }
pub struct ChunkIter<'a, V: Vox, S: VolSize, M> { pub struct ChunkIter<'a, V: Vox, S: VolSize, M> {
iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Chunk<V, S, M>>, iter: std::collections::hash_map::Iter<'a, Vec3<i32>, Arc<Chunk<V, S, M>>>,
} }
impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkIter<'a, V, S, M> { impl<'a, V: Vox, S: VolSize, M> Iterator for ChunkIter<'a, V, S, M> {
type Item = (Vec3<i32>, &'a Chunk<V, S, M>); type Item = (Vec3<i32>, &'a Arc<Chunk<V, S, M>>);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(k, c)| (*k, c)) self.iter.next().map(|(k, c)| (*k, c))

View File

@ -191,22 +191,21 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
.read_component_cloned::<comp::phys::Pos>(entity) .read_component_cloned::<comp::phys::Pos>(entity)
{ {
Some(pos) => { Some(pos) => {
let mut current = entity; server
.create_npc(
for _ in 0..1 { "Bungo".to_owned(),
current = server comp::Body::Humanoid(comp::HumanoidBody::random()),
.create_npc(comp::Character::random()) )
.with(comp::Control::default()) .with(comp::Control::default())
.with(comp::Agent::Pet { .with(comp::Agent::Pet {
target: current, target: entity,
offset: Vec2::zero(), offset: Vec2::zero(),
}) })
.with(pos) .with(pos)
.build(); .build();
}
server server
.clients .clients
.notify(entity, ServerMsg::Chat("Pet spawned!".to_owned())); .notify(entity, ServerMsg::Chat("Spawned pet!".to_owned()));
} }
None => server None => server
.clients .clients

View File

@ -14,7 +14,6 @@ use crate::{
}; };
use common::{ use common::{
comp, comp,
comp::character::Animation,
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg}, msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
net::PostOffice, net::PostOffice,
state::{State, Uid}, state::{State, Uid},
@ -81,10 +80,13 @@ impl Server {
}; };
for i in 0..4 { for i in 0..4 {
this.create_npc(comp::Character::random()) this.create_npc(
.with(comp::Control::default()) "Tobermory".to_owned(),
.with(comp::Agent::Wanderer(Vec2::zero())) comp::Body::Humanoid(comp::HumanoidBody::random()),
.build(); )
.with(comp::Control::default())
.with(comp::Agent::Wanderer(Vec2::zero()))
.build();
} }
Ok(this) Ok(this)
@ -114,24 +116,31 @@ impl Server {
/// Build a non-player character /// Build a non-player character
#[allow(dead_code)] #[allow(dead_code)]
pub fn create_npc(&mut self, character: comp::Character) -> EcsEntityBuilder { pub fn create_npc(&mut self, name: String, body: comp::Body) -> EcsEntityBuilder {
self.state self.state
.ecs_mut() .ecs_mut()
.create_entity_synced() .create_entity_synced()
.with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))) .with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)))
.with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y())) .with(comp::phys::Dir(Vec3::unit_y()))
.with(comp::AnimationHistory::new(Animation::Idle)) .with(comp::AnimationHistory::new(comp::Animation::Idle))
.with(character) .with(comp::Actor::Character { name, body })
} }
pub fn create_player_character( pub fn create_player_character(
state: &mut State, state: &mut State,
entity: EcsEntity, entity: EcsEntity,
client: &mut Client, client: &mut Client,
character: comp::Character, name: String,
body: comp::HumanoidBody,
) { ) {
state.write_component(entity, character); state.write_component(
entity,
comp::Actor::Character {
name,
body: comp::Body::Humanoid(body),
},
);
state.write_component(entity, comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))); state.write_component(entity, comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)));
state.write_component(entity, comp::phys::Vel(Vec3::zero())); state.write_component(entity, comp::phys::Vel(Vec3::zero()));
state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
@ -139,7 +148,7 @@ impl Server {
state.write_component(entity, comp::phys::ForceUpdate); state.write_component(entity, comp::phys::ForceUpdate);
// Set initial animation // Set initial animation
state.write_component(entity, comp::AnimationHistory::new(Animation::Idle)); state.write_component(entity, comp::AnimationHistory::new(comp::Animation::Idle));
// Tell the client his request was successful // Tell the client his request was successful
client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character))); client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character)));
@ -337,13 +346,13 @@ impl Server {
// Use RequestState instead (No need to send `player` again) // Use RequestState instead (No need to send `player` again)
_ => client.error_state(RequestStateError::Impossible), _ => client.error_state(RequestStateError::Impossible),
}, },
ClientMsg::Character(character) => match client.client_state { ClientMsg::Character { name, body } => match client.client_state {
// Become Registered first // Become Registered first
ClientState::Connected => { ClientState::Connected => {
client.error_state(RequestStateError::Impossible) client.error_state(RequestStateError::Impossible)
} }
ClientState::Registered | ClientState::Spectator => { ClientState::Registered | ClientState::Spectator => {
Self::create_player_character(state, entity, client, character) Self::create_player_character(state, entity, client, name, body)
} }
ClientState::Character => { ClientState::Character => {
client.error_state(RequestStateError::Already) client.error_state(RequestStateError::Already)

View File

@ -76,7 +76,10 @@ impl PlayState for CharSelectionState {
self.client self.client
.borrow_mut() .borrow_mut()
.postbox .postbox
.send_message(ClientMsg::Character(self.char_selection_ui.character)); .send_message(ClientMsg::Character {
name: self.char_selection_ui.character_name.clone(),
body: self.char_selection_ui.character_body,
});
return PlayStateResult::Switch(Box::new(SessionState::new( return PlayStateResult::Switch(Box::new(SessionState::new(
&mut global_state.window, &mut global_state.window,
self.client.clone(), self.client.clone(),

View File

@ -13,7 +13,7 @@ use crate::{
}, },
}; };
use client::Client; use client::Client;
use common::{comp::Character, figure::Segment}; use common::{comp::HumanoidBody, figure::Segment};
use vek::*; use vek::*;
struct Skybox { struct Skybox {
@ -108,7 +108,7 @@ impl Scene {
let model = self.figure_model_cache.get_or_create_model( let model = self.figure_model_cache.get_or_create_model(
renderer, renderer,
Character::random(), HumanoidBody::random(),
client.get_tick(), client.get_tick(),
); );
renderer.render_figure( renderer.render_figure(

View File

@ -7,8 +7,9 @@ use crate::{
}, },
window::Window, window::Window,
}; };
use common::comp::character::{ use common::comp::{
Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon, actor::{Belt, BodyType, Chest, Foot, Hand, Head, Pants, Race, Weapon},
HumanoidBody,
}; };
use conrod_core::{ use conrod_core::{
color, color,
@ -37,7 +38,7 @@ widget_ids! {
weapon_heading, weapon_heading,
weapon_description, weapon_description,
races_bg, races_bg,
gender_bg, body_type_bg,
desc_bg, desc_bg,
skin_eyes_window, skin_eyes_window,
hair_window, hair_window,
@ -71,8 +72,8 @@ widget_ids! {
race_4, race_4,
race_5, race_5,
race_6, race_6,
gender_1, body_type_1,
gender_2, body_type_2,
weapon_1, weapon_1,
weapon_2, weapon_2,
weapon_3, weapon_3,
@ -267,8 +268,8 @@ pub struct CharSelectionUi {
fonts: Fonts, fonts: Fonts,
character_creation: bool, character_creation: bool,
selected_char_no: Option<i32>, selected_char_no: Option<i32>,
character_name: String, pub character_name: String,
pub character: Character, pub character_body: HumanoidBody,
creation_state: CreationState, creation_state: CreationState,
} }
@ -293,7 +294,7 @@ impl CharSelectionUi {
character_creation: false, character_creation: false,
selected_char_no: None, selected_char_no: None,
character_name: "Character Name".to_string(), character_name: "Character Name".to_string(),
character: Character::random(), character_body: HumanoidBody::random(),
creation_state: CreationState::Race, creation_state: CreationState::Race,
} }
} }
@ -597,14 +598,14 @@ impl CharSelectionUi {
// for alignment // for alignment
Rectangle::fill_with([151.0, 68.0], color::TRANSPARENT) Rectangle::fill_with([151.0, 68.0], color::TRANSPARENT)
.mid_top_with_margin_on(self.ids.creation_window, 210.0) .mid_top_with_margin_on(self.ids.creation_window, 210.0)
.set(self.ids.gender_bg, ui_widgets); .set(self.ids.body_type_bg, ui_widgets);
// Male // Male
Image::new(self.imgs.male) Image::new(self.imgs.male)
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.mid_left_of(self.ids.gender_bg) .mid_left_of(self.ids.body_type_bg)
.set(self.ids.male, ui_widgets); .set(self.ids.male, ui_widgets);
if Button::image(if let Gender::Male = self.character.gender { if Button::image(if let BodyType::Male = self.character_body.body_type {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -612,17 +613,17 @@ impl CharSelectionUi {
.middle_of(self.ids.male) .middle_of(self.ids.male)
.hover_image(self.imgs.icon_border_mo) .hover_image(self.imgs.icon_border_mo)
.press_image(self.imgs.icon_border_press) .press_image(self.imgs.icon_border_press)
.set(self.ids.gender_1, ui_widgets) .set(self.ids.body_type_1, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.gender = Gender::Male; self.character_body.body_type = BodyType::Male;
} }
// Female // Female
Image::new(self.imgs.female) Image::new(self.imgs.female)
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.right_from(self.ids.male, 16.0) .right_from(self.ids.male, 16.0)
.set(self.ids.female, ui_widgets); .set(self.ids.female, ui_widgets);
if Button::image(if let Gender::Female = self.character.gender { if Button::image(if let BodyType::Female = self.character_body.body_type {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -630,10 +631,10 @@ impl CharSelectionUi {
.middle_of(self.ids.female) .middle_of(self.ids.female)
.hover_image(self.imgs.icon_border_mo) .hover_image(self.imgs.icon_border_mo)
.press_image(self.imgs.icon_border_press) .press_image(self.imgs.icon_border_press)
.set(self.ids.gender_2, ui_widgets) .set(self.ids.body_type_2, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.gender = Gender::Female; self.character_body.body_type = BodyType::Female;
} }
// for alignment // for alignment
Rectangle::fill_with([458.0, 68.0], color::TRANSPARENT) Rectangle::fill_with([458.0, 68.0], color::TRANSPARENT)
@ -641,7 +642,7 @@ impl CharSelectionUi {
.set(self.ids.races_bg, ui_widgets); .set(self.ids.races_bg, ui_widgets);
// TODO: If races where in some sort of array format we could do this in a loop // TODO: If races where in some sort of array format we could do this in a loop
// Human // Human
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.human_m self.imgs.human_m
} else { } else {
self.imgs.human_f self.imgs.human_f
@ -649,7 +650,7 @@ impl CharSelectionUi {
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.mid_left_of(self.ids.races_bg) .mid_left_of(self.ids.races_bg)
.set(self.ids.human, ui_widgets); .set(self.ids.human, ui_widgets);
if Button::image(if let Race::Human = self.character.race { if Button::image(if let Race::Human = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -660,11 +661,11 @@ impl CharSelectionUi {
.set(self.ids.race_1, ui_widgets) .set(self.ids.race_1, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Human; self.character_body.race = Race::Human;
} }
// Orc // Orc
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.orc_m self.imgs.orc_m
} else { } else {
self.imgs.orc_f self.imgs.orc_f
@ -672,7 +673,7 @@ impl CharSelectionUi {
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.right_from(self.ids.human, 10.0) .right_from(self.ids.human, 10.0)
.set(self.ids.orc, ui_widgets); .set(self.ids.orc, ui_widgets);
if Button::image(if let Race::Orc = self.character.race { if Button::image(if let Race::Orc = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -683,10 +684,10 @@ impl CharSelectionUi {
.set(self.ids.race_2, ui_widgets) .set(self.ids.race_2, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Orc; self.character_body.race = Race::Orc;
} }
// Dwarf // Dwarf
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.dwarf_m self.imgs.dwarf_m
} else { } else {
self.imgs.dwarf_f self.imgs.dwarf_f
@ -694,7 +695,7 @@ impl CharSelectionUi {
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.right_from(self.ids.human, 10.0 * 2.0 + 68.0) .right_from(self.ids.human, 10.0 * 2.0 + 68.0)
.set(self.ids.dwarf, ui_widgets); .set(self.ids.dwarf, ui_widgets);
if Button::image(if let Race::Dwarf = self.character.race { if Button::image(if let Race::Dwarf = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -705,10 +706,10 @@ impl CharSelectionUi {
.set(self.ids.race_3, ui_widgets) .set(self.ids.race_3, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Dwarf; self.character_body.race = Race::Dwarf;
} }
// Elf // Elf
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.elf_m self.imgs.elf_m
} else { } else {
self.imgs.elf_f self.imgs.elf_f
@ -716,7 +717,7 @@ impl CharSelectionUi {
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.right_from(self.ids.human, 10.0 * 3.0 + 68.0 * 2.0) .right_from(self.ids.human, 10.0 * 3.0 + 68.0 * 2.0)
.set(self.ids.elf, ui_widgets); .set(self.ids.elf, ui_widgets);
if Button::image(if let Race::Elf = self.character.race { if Button::image(if let Race::Elf = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -727,10 +728,10 @@ impl CharSelectionUi {
.set(self.ids.race_4, ui_widgets) .set(self.ids.race_4, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Elf; self.character_body.race = Race::Elf;
} }
// Undead // Undead
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.undead_m self.imgs.undead_m
} else { } else {
self.imgs.undead_f self.imgs.undead_f
@ -738,7 +739,7 @@ impl CharSelectionUi {
.w_h(68.0, 68.0) .w_h(68.0, 68.0)
.right_from(self.ids.human, 10.0 * 4.0 + 68.0 * 3.0) .right_from(self.ids.human, 10.0 * 4.0 + 68.0 * 3.0)
.set(self.ids.undead, ui_widgets); .set(self.ids.undead, ui_widgets);
if Button::image(if let Race::Undead = self.character.race { if Button::image(if let Race::Undead = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -749,17 +750,17 @@ impl CharSelectionUi {
.set(self.ids.race_5, ui_widgets) .set(self.ids.race_5, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Undead; self.character_body.race = Race::Undead;
} }
// Danari // Danari
Image::new(if let Gender::Male = self.character.gender { Image::new(if let BodyType::Male = self.character_body.body_type {
self.imgs.danari_m self.imgs.danari_m
} else { } else {
self.imgs.danari_f self.imgs.danari_f
}) })
.right_from(self.ids.human, 10.0 * 5.0 + 68.0 * 4.0) .right_from(self.ids.human, 10.0 * 5.0 + 68.0 * 4.0)
.set(self.ids.danari, ui_widgets); .set(self.ids.danari, ui_widgets);
if Button::image(if let Race::Danari = self.character.race { if Button::image(if let Race::Danari = self.character_body.race {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -771,7 +772,7 @@ impl CharSelectionUi {
.set(self.ids.race_6, ui_widgets) .set(self.ids.race_6, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.race = Race::Danari; self.character_body.race = Race::Danari;
} }
// Description Headline and Text // Description Headline and Text
@ -832,7 +833,7 @@ impl CharSelectionUi {
\n\ \n\
Outcast communities consisting of these Blessed Danari have formed all over the land."; Outcast communities consisting of these Blessed Danari have formed all over the land.";
let (race_str, race_desc) = match self.character.race { let (race_str, race_desc) = match self.character_body.race {
Race::Human => ("Humans", HUMAN_DESC), Race::Human => ("Humans", HUMAN_DESC),
Race::Orc => ("Orcs", ORC_DESC), Race::Orc => ("Orcs", ORC_DESC),
Race::Dwarf => ("Dwarves", DWARF_DESC), Race::Dwarf => ("Dwarves", DWARF_DESC),
@ -872,7 +873,7 @@ impl CharSelectionUi {
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.mid_left_of(self.ids.weapon_bg) .mid_left_of(self.ids.weapon_bg)
.set(self.ids.sword_shield, ui_widgets); .set(self.ids.sword_shield, ui_widgets);
if Button::image(if let Weapon::SwordShield = self.character.weapon { if Button::image(if let Weapon::SwordShield = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -883,7 +884,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_1, ui_widgets) .set(self.ids.weapon_1, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::SwordShield; self.character_body.weapon = Weapon::SwordShield;
} }
// Daggers // Daggers
@ -891,7 +892,7 @@ impl CharSelectionUi {
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0) .right_from(self.ids.sword_shield, 8.0)
.set(self.ids.daggers, ui_widgets); .set(self.ids.daggers, ui_widgets);
if Button::image(if let Weapon::Daggers = self.character.weapon { if Button::image(if let Weapon::Daggers = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -902,7 +903,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_2, ui_widgets) .set(self.ids.weapon_2, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Daggers; self.character_body.weapon = Weapon::Daggers;
} }
// Sword // Sword
@ -910,7 +911,7 @@ impl CharSelectionUi {
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0 * 2.0 + 60.0 * 1.0) .right_from(self.ids.sword_shield, 8.0 * 2.0 + 60.0 * 1.0)
.set(self.ids.sword, ui_widgets); .set(self.ids.sword, ui_widgets);
if Button::image(if let Weapon::Sword = self.character.weapon { if Button::image(if let Weapon::Sword = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -921,14 +922,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_3, ui_widgets) .set(self.ids.weapon_3, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Sword; self.character_body.weapon = Weapon::Sword;
} }
// Axe // Axe
Image::new(self.imgs.axe) Image::new(self.imgs.axe)
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0 * 3.0 + 60.0 * 2.0) .right_from(self.ids.sword_shield, 8.0 * 3.0 + 60.0 * 2.0)
.set(self.ids.axe, ui_widgets); .set(self.ids.axe, ui_widgets);
if Button::image(if let Weapon::Axe = self.character.weapon { if Button::image(if let Weapon::Axe = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -939,14 +940,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_4, ui_widgets) .set(self.ids.weapon_4, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Axe; self.character_body.weapon = Weapon::Axe;
} }
// Hammer // Hammer
Image::new(self.imgs.hammer) Image::new(self.imgs.hammer)
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0 * 4.0 + 60.0 * 3.0) .right_from(self.ids.sword_shield, 8.0 * 4.0 + 60.0 * 3.0)
.set(self.ids.hammer, ui_widgets); .set(self.ids.hammer, ui_widgets);
if Button::image(if let Weapon::Hammer = self.character.weapon { if Button::image(if let Weapon::Hammer = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -957,14 +958,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_5, ui_widgets) .set(self.ids.weapon_5, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Hammer; self.character_body.weapon = Weapon::Hammer;
} }
// Bow // Bow
Image::new(self.imgs.bow) Image::new(self.imgs.bow)
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0 * 5.0 + 60.0 * 4.0) .right_from(self.ids.sword_shield, 8.0 * 5.0 + 60.0 * 4.0)
.set(self.ids.bow, ui_widgets); .set(self.ids.bow, ui_widgets);
if Button::image(if let Weapon::Bow = self.character.weapon { if Button::image(if let Weapon::Bow = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -975,14 +976,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_6, ui_widgets) .set(self.ids.weapon_6, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Bow; self.character_body.weapon = Weapon::Bow;
} }
// Staff // Staff
Image::new(self.imgs.staff) Image::new(self.imgs.staff)
.w_h(60.0, 60.0) .w_h(60.0, 60.0)
.right_from(self.ids.sword_shield, 8.0 * 6.0 + 60.0 * 5.0) .right_from(self.ids.sword_shield, 8.0 * 6.0 + 60.0 * 5.0)
.set(self.ids.staff, ui_widgets); .set(self.ids.staff, ui_widgets);
if Button::image(if let Weapon::Staff = self.character.weapon { if Button::image(if let Weapon::Staff = self.character_body.weapon {
self.imgs.icon_border_pressed self.imgs.icon_border_pressed
} else { } else {
self.imgs.icon_border self.imgs.icon_border
@ -993,7 +994,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_7, ui_widgets) .set(self.ids.weapon_7, ui_widgets)
.was_clicked() .was_clicked()
{ {
self.character.weapon = Weapon::Staff; self.character_body.weapon = Weapon::Staff;
} }
// TODO: Load these from files (or from the server???) // TODO: Load these from files (or from the server???)
@ -1005,7 +1006,7 @@ impl CharSelectionUi {
const BOW_DESC: &str = " MISSING "; const BOW_DESC: &str = " MISSING ";
const STAFF_DESC: &str = " MISSING "; const STAFF_DESC: &str = " MISSING ";
let (weapon_str, weapon_desc) = match self.character.weapon { let (weapon_str, weapon_desc) = match self.character_body.weapon {
Weapon::SwordShield => ("Sword and Shield", SWORDSHIELD_DESC), Weapon::SwordShield => ("Sword and Shield", SWORDSHIELD_DESC),
Weapon::Daggers => ("Daggers", DAGGERS_DESC), Weapon::Daggers => ("Daggers", DAGGERS_DESC),
Weapon::Sword => ("Sword", SWORD_DESC), Weapon::Sword => ("Sword", SWORD_DESC),
@ -1352,7 +1353,7 @@ impl CharSelectionUi {
.was_clicked() .was_clicked()
{}; {};
// Beard -> Only active when "male" was chosen // Beard -> Only active when "male" was chosen
if let Gender::Male = self.character.gender { if let BodyType::Male = self.character_body.body_type {
Text::new("Beard Style") Text::new("Beard Style")
.mid_top_with_margin_on(self.ids.hair_window, 340.0) .mid_top_with_margin_on(self.ids.hair_window, 340.0)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -1383,7 +1384,7 @@ impl CharSelectionUi {
// Color -> Picker // Color -> Picker
// Brightness -> Slider // Brightness -> Slider
BodyPart::Accessories => { BodyPart::Accessories => {
match self.character.race { match self.character_body.race {
Race::Human => { Race::Human => {
Text::new("Head Band") Text::new("Head Band")
.mid_top_with_margin_on(self.ids.accessories_window, 60.0) .mid_top_with_margin_on(self.ids.accessories_window, 60.0)

View File

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

View File

@ -14,7 +14,8 @@ use common::{
assets, assets,
comp::{ comp::{
self, self,
character::{Belt, Character, Chest, Draw, Foot, Hand, Head, Pants, Shoulder, Weapon}, actor::{Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Weapon},
Body, HumanoidBody,
}, },
figure::Segment, figure::Segment,
msg, msg,
@ -25,7 +26,7 @@ use std::{collections::HashMap, f32};
use vek::*; use vek::*;
pub struct FigureModelCache { pub struct FigureModelCache {
models: HashMap<Character, (Model<FigurePipeline>, u64)>, models: HashMap<HumanoidBody, (Model<FigurePipeline>, u64)>,
} }
impl FigureModelCache { impl FigureModelCache {
@ -38,31 +39,30 @@ impl FigureModelCache {
pub fn get_or_create_model( pub fn get_or_create_model(
&mut self, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
character: Character, body: HumanoidBody,
tick: u64, tick: u64,
) -> &Model<FigurePipeline> { ) -> &Model<FigurePipeline> {
match self.models.get_mut(&character) { match self.models.get_mut(&body) {
Some((model, last_used)) => { Some((model, last_used)) => {
*last_used = tick; *last_used = tick;
} }
None => { None => {
self.models.insert( self.models.insert(
character, body,
( (
{ {
let bone_meshes = [ let bone_meshes = [
Some(Self::load_head(character.head)), Some(Self::load_head(body.head)),
Some(Self::load_chest(character.chest)), Some(Self::load_chest(body.chest)),
Some(Self::load_belt(character.belt)), Some(Self::load_belt(body.belt)),
Some(Self::load_pants(character.pants)), Some(Self::load_pants(body.pants)),
Some(Self::load_left_hand(character.hand)), Some(Self::load_left_hand(body.hand)),
Some(Self::load_right_hand(character.hand)), Some(Self::load_right_hand(body.hand)),
Some(Self::load_left_foot(character.foot)), Some(Self::load_left_foot(body.foot)),
Some(Self::load_right_foot(character.foot)), Some(Self::load_right_foot(body.foot)),
Some(Self::load_weapon(character.weapon)), Some(Self::load_weapon(body.weapon)),
Some(Self::load_left_shoulder(character.shoulder)), Some(Self::load_left_shoulder(body.shoulder)),
Some(Self::load_right_shoulder(character.shoulder)), Some(Self::load_right_shoulder(body.shoulder)),
//Some(Self::load_draw(character.draw)),
None, None,
None, None,
None, None,
@ -89,7 +89,7 @@ impl FigureModelCache {
} }
} }
&self.models[&character].0 &self.models[&body].0
} }
pub fn clean(&mut self, tick: u64) { pub fn clean(&mut self, tick: u64) {
@ -108,7 +108,7 @@ impl FigureModelCache {
fn load_head(head: Head) -> Mesh<FigurePipeline> { fn load_head(head: Head) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match head { match head {
Head::DefaultHead => "head.vox", Head::Default => "head.vox",
}, },
Vec3::new(-7.0, -5.5, -6.0), Vec3::new(-7.0, -5.5, -6.0),
) )
@ -117,7 +117,7 @@ impl FigureModelCache {
fn load_chest(chest: Chest) -> Mesh<FigurePipeline> { fn load_chest(chest: Chest) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match chest { match chest {
Chest::DefaultChest => "chest.vox", Chest::Default => "chest.vox",
}, },
Vec3::new(-6.0, -3.5, 0.0), Vec3::new(-6.0, -3.5, 0.0),
) )
@ -126,7 +126,7 @@ impl FigureModelCache {
fn load_belt(belt: Belt) -> Mesh<FigurePipeline> { fn load_belt(belt: Belt) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match belt { match belt {
Belt::DefaultBelt => "belt.vox", Belt::Default => "belt.vox",
}, },
Vec3::new(-5.0, -3.5, 0.0), Vec3::new(-5.0, -3.5, 0.0),
) )
@ -135,7 +135,7 @@ impl FigureModelCache {
fn load_pants(pants: Pants) -> Mesh<FigurePipeline> { fn load_pants(pants: Pants) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match pants { match pants {
Pants::DefaultPants => "pants.vox", Pants::Default => "pants.vox",
}, },
Vec3::new(-5.0, -3.5, 0.0), Vec3::new(-5.0, -3.5, 0.0),
) )
@ -144,7 +144,7 @@ impl FigureModelCache {
fn load_left_hand(hand: Hand) -> Mesh<FigurePipeline> { fn load_left_hand(hand: Hand) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match hand { match hand {
Hand::DefaultHand => "hand.vox", Hand::Default => "hand.vox",
}, },
Vec3::new(2.0, 0.0, -7.0), Vec3::new(2.0, 0.0, -7.0),
) )
@ -153,7 +153,7 @@ impl FigureModelCache {
fn load_right_hand(hand: Hand) -> Mesh<FigurePipeline> { fn load_right_hand(hand: Hand) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match hand { match hand {
Hand::DefaultHand => "hand.vox", Hand::Default => "hand.vox",
}, },
Vec3::new(2.0, 0.0, -7.0), Vec3::new(2.0, 0.0, -7.0),
) )
@ -162,7 +162,7 @@ impl FigureModelCache {
fn load_left_foot(foot: Foot) -> Mesh<FigurePipeline> { fn load_left_foot(foot: Foot) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match foot { match foot {
Foot::DefaultFoot => "foot.vox", Foot::Default => "foot.vox",
}, },
Vec3::new(2.5, -3.5, -9.0), Vec3::new(2.5, -3.5, -9.0),
) )
@ -171,7 +171,7 @@ impl FigureModelCache {
fn load_right_foot(foot: Foot) -> Mesh<FigurePipeline> { fn load_right_foot(foot: Foot) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match foot { match foot {
Foot::DefaultFoot => "foot.vox", Foot::Default => "foot.vox",
}, },
Vec3::new(2.5, -3.5, -9.0), Vec3::new(2.5, -3.5, -9.0),
) )
@ -191,7 +191,7 @@ impl FigureModelCache {
fn load_left_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> { fn load_left_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match shoulder { match shoulder {
Shoulder::DefaultShoulder => "shoulder_l.vox", Shoulder::Default => "shoulder_l.vox",
}, },
Vec3::new(2.5, 0.0, 0.0), Vec3::new(2.5, 0.0, 0.0),
) )
@ -200,7 +200,7 @@ impl FigureModelCache {
fn load_right_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> { fn load_right_shoulder(shoulder: Shoulder) -> Mesh<FigurePipeline> {
Self::load_mesh( Self::load_mesh(
match shoulder { match shoulder {
Shoulder::DefaultShoulder => "shoulder_r.vox", Shoulder::Default => "shoulder_r.vox",
}, },
Vec3::new(2.5, 0.0, 0.0), Vec3::new(2.5, 0.0, 0.0),
) )
@ -238,42 +238,48 @@ impl FigureMgr {
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let time = client.state().get_time(); let time = client.state().get_time();
let ecs = client.state().ecs(); let ecs = client.state().ecs();
for (entity, pos, vel, dir, character, animation_history) in ( for (entity, pos, vel, dir, actor, animation_history) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(), &ecs.read_storage::<comp::phys::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(), &ecs.read_storage::<comp::phys::Vel>(),
&ecs.read_storage::<comp::phys::Dir>(), &ecs.read_storage::<comp::phys::Dir>(),
&ecs.read_storage::<comp::Character>(), &ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::AnimationHistory>(), &ecs.read_storage::<comp::AnimationHistory>(),
) )
.join() .join()
{ {
let state = self match actor {
.states comp::Actor::Character { body, .. } => match body {
.entry(entity) Body::Humanoid(body) => {
.or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new())); let state = self.states.entry(entity).or_insert_with(|| {
FigureState::new(renderer, CharacterSkeleton::new())
});
let target_skeleton = match animation_history.current { let target_skeleton = match animation_history.current {
comp::character::Animation::Idle => IdleAnimation::update_skeleton( comp::Animation::Idle => IdleAnimation::update_skeleton(
state.skeleton_mut(), state.skeleton_mut(),
time, time,
animation_history.time, animation_history.time,
), ),
comp::character::Animation::Run => RunAnimation::update_skeleton( comp::Animation::Run => RunAnimation::update_skeleton(
state.skeleton_mut(), state.skeleton_mut(),
(vel.0.magnitude(), time), (vel.0.magnitude(), time),
animation_history.time, animation_history.time,
), ),
comp::character::Animation::Jump => JumpAnimation::update_skeleton( comp::Animation::Jump => JumpAnimation::update_skeleton(
state.skeleton_mut(), state.skeleton_mut(),
time, time,
animation_history.time, animation_history.time,
), ),
}; };
state.skeleton.interpolate(&target_skeleton); state.skeleton.interpolate(&target_skeleton);
state.update(renderer, pos.0, dir.0); state.update(renderer, pos.0, dir.0);
} // TODO: Non-humanoid bodies
},
// TODO: Non-character actors
}
} }
self.states self.states
@ -289,13 +295,22 @@ impl FigureMgr {
let tick = client.get_tick(); let tick = client.get_tick();
let ecs = client.state().ecs(); let ecs = client.state().ecs();
for (entity, &character) in (&ecs.entities(), &ecs.read_storage::<comp::Character>()).join() for (entity, actor) in (&ecs.entities(), &ecs.read_storage::<comp::Actor>()).join() {
{ match actor {
if let Some(state) = self.states.get(&entity) { comp::Actor::Character { body, .. } => match body {
let model = self Body::Humanoid(body) => {
.model_cache if let Some(state) = self.states.get(&entity) {
.get_or_create_model(renderer, character, tick); let model = self.model_cache.get_or_create_model(renderer, *body, tick);
renderer.render_figure(model, globals, &state.locals(), state.bone_consts()); renderer.render_figure(
model,
globals,
&state.locals(),
state.bone_consts(),
);
}
} // TODO: Non-humanoid bodies
},
// TODO: Non-character actors
} }
} }
} }

View File

@ -1,9 +1,5 @@
// Standard // Standard
use std::{ use std::{collections::HashMap, sync::mpsc, time::Duration};
collections::{HashMap, LinkedList},
sync::mpsc,
time::Duration,
};
// Library // Library
use vek::*; use vek::*;
@ -42,10 +38,11 @@ fn mesh_worker(
pos: Vec3<i32>, pos: Vec3<i32>,
started_tick: u64, started_tick: u64,
volume: <TerrainMap as SampleVol>::Sample, volume: <TerrainMap as SampleVol>::Sample,
supplement: Aabb<i32>,
) -> MeshWorkerResponse { ) -> MeshWorkerResponse {
MeshWorkerResponse { MeshWorkerResponse {
pos, pos,
mesh: volume.generate_mesh(()), mesh: volume.generate_mesh(supplement),
started_tick, started_tick,
} }
} }
@ -57,7 +54,7 @@ pub struct Terrain {
// We keep the sender component for no reason othe than to clone it and send it to new workers. // 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_send_tmp: mpsc::Sender<MeshWorkerResponse>,
mesh_recv: mpsc::Receiver<MeshWorkerResponse>, mesh_recv: mpsc::Receiver<MeshWorkerResponse>,
mesh_todo: LinkedList<ChunkMeshState>, mesh_todo: HashMap<Vec3<i32>, ChunkMeshState>,
} }
impl Terrain { impl Terrain {
@ -71,7 +68,7 @@ impl Terrain {
mesh_send_tmp: send, mesh_send_tmp: send,
mesh_recv: recv, mesh_recv: recv,
mesh_todo: LinkedList::new(), mesh_todo: HashMap::new(),
} }
} }
@ -97,75 +94,70 @@ impl Terrain {
let pos = pos + Vec3::new(i, j, k); let pos = pos + Vec3::new(i, j, k);
if client.state().terrain().get_key(pos).is_some() { if client.state().terrain().get_key(pos).is_some() {
match self.mesh_todo.iter_mut().find(|todo| todo.pos == pos) { // re-mesh loaded chunks that border new/changed chunks
//Some(todo) => todo.started_tick = current_tick, if self.chunks.contains_key(&pos) || (i, j, k) == (0, 0, 0) {
// The chunk it's queued yet, add it to the queue self.mesh_todo.entry(pos).or_insert(ChunkMeshState {
_ /* None */ => self.mesh_todo.push_back(ChunkMeshState {
pos, pos,
started_tick: current_tick, started_tick: current_tick,
active_worker: false, active_worker: false,
}), });
} }
} }
} }
} }
} }
} }
// Remove any models for chunks that have been recently removed // Remove any models for chunks that have been recently removed
for pos in &client.state().changes().removed_chunks { for pos in &client.state().changes().removed_chunks {
self.chunks.remove(pos); self.chunks.remove(pos);
self.mesh_todo.drain_filter(|todo| todo.pos == *pos); self.mesh_todo.remove(pos);
} }
// Clone the sender to the thread can send us the chunk data back for todo in self
// TODO: It's a bit hacky cloning it here and then cloning it again below. Fix this. .mesh_todo
let send = self.mesh_send_tmp.clone(); .values_mut()
self.mesh_todo
.iter_mut()
// Only spawn workers for meshing jobs without an active worker already // Only spawn workers for meshing jobs without an active worker already
.filter(|todo| !todo.active_worker) .filter(|todo| !todo.active_worker)
.for_each(|todo| { {
// Find the area of the terrain we want. Because meshing needs to compute things like // 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 // ambient occlusion and edge elision, we also need to borders of the chunk's
// neighbours too (hence the `- 1` and `+ 1`). // neighbours too (hence the `- 1` and `+ 1`).
let aabb = Aabb { let aabb = Aabb {
min: todo min: todo
.pos .pos
.map2(TerrainMap::chunk_size(), |e, sz| e * sz as i32 - 1), .map2(TerrainMap::chunk_size(), |e, sz| e * sz as i32 - 1),
max: todo max: todo
.pos .pos
.map2(TerrainMap::chunk_size(), |e, sz| (e + 1) * sz as i32 + 1), .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 // 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 // sample of the terrain that includes both the chunk we want and
let volume = match client.state().terrain().sample(aabb) { let volume = match client.state().terrain().sample(aabb) {
Ok(sample) => sample, Ok(sample) => sample,
// If either this chunk or its neighbours doesn't yet exist, so we keep it in the // 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. // todo queue to be processed at a later date when we have its neighbours.
Err(VolMapErr::NoSuchChunk) => return, Err(VolMapErr::NoSuchChunk) => return,
_ => panic!("Unhandled edge case"), _ => panic!("Unhandled edge case"),
}; };
// Clone various things to that they can be moved into the thread // Clone various things to that they can be moved into the thread
let send = send.clone(); let send = self.mesh_send_tmp.clone();
let pos = todo.pos; let pos = todo.pos;
// Queue the worker thread // Queue the worker thread
client.thread_pool().execute(move || { client.thread_pool().execute(move || {
send.send(mesh_worker(pos, current_tick, volume)) send.send(mesh_worker(pos, current_tick, volume, aabb))
.expect("Failed to send chunk mesh to main thread"); .expect("Failed to send chunk mesh to main thread");
});
todo.active_worker = true;
}); });
todo.active_worker = true;
}
// Receive a chunk mesh from a worker thread, upload it to the GPU and then store it // Receive a chunk mesh from a worker thread, upload it to the GPU and then store it
// Only pull out one chunk per frame to avoid an unacceptable amount of blocking lag due // Only pull out one chunk per frame to avoid an unacceptable amount of blocking lag due
// to the GPU upload. That still gives us a 60 chunks / second budget to play with. // to the GPU upload. That still gives us a 60 chunks / second budget to play with.
if let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) { if let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) {
match self.mesh_todo.iter().find(|todo| todo.pos == response.pos) { match self.mesh_todo.get(&response.pos) {
// It's the mesh we want, insert the newly finished model into the terrain model // 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) // data structure (convert the mesh to a model first of course)
Some(todo) if response.started_tick == todo.started_tick => { Some(todo) if response.started_tick == todo.started_tick => {