diff --git a/.gitignore b/.gitignore index 26094c130e..abc6feeddb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ voxygen/keybinds.toml settings.toml *.rar *.log +run.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47688d76e6..3b865a9084 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,9 @@ before_script: - if [ ! -z "${SOURCE_PROJECT}" -a "${SOURCE_PROJECT}" != " " ]; then echo "THIS SEEMS TO BE A MERGE PIPELINE FROM ${SOURCE_PROJECT}/${SOURCE_BRANCH}"; git pull "https://gitlab.com/${SOURCE_PROJECT}/veloren.git" "${SOURCE_BRANCH}"; + git lfs install; + git lfs fetch; + git lfs checkout; fi; - git status - if [ -d target ]; then diff --git a/Cargo.toml b/Cargo.toml index 533a0add9c..435991041f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ members = [ ] [profile.dev] +opt-level = 2 +overflow-checks = false [profile.release] debug = true +codegen-units = 1 +lto = true diff --git a/assets/voxygen/element/buttons/grid.vox b/assets/voxygen/element/buttons/grid.vox new file mode 100644 index 0000000000..ed50eae910 --- /dev/null +++ b/assets/voxygen/element/buttons/grid.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8fbdc3f10eb9c1710259ea3e304a418fee8d176af850350016c1be256798462 +size 45134 diff --git a/assets/voxygen/element/buttons/x.vox b/assets/voxygen/element/buttons/x.vox index e45fc41417..9086a70699 100644 --- a/assets/voxygen/element/buttons/x.vox +++ b/assets/voxygen/element/buttons/x.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab197899725e8f34376b1e74c318611580bb9ac3e6a5ebbb64e6d7705cbb0bb1 +oid sha256:5f604ac7fa307a80bed9dff02e5bb631d537e83e2081ff881bb0821a101dd4cd size 237548 diff --git a/assets/voxygen/element/frames/divider_charwindow.vox b/assets/voxygen/element/frames/divider_charwindow.vox new file mode 100644 index 0000000000..6c0d05b1e8 --- /dev/null +++ b/assets/voxygen/element/frames/divider_charwindow.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01261b4c1122d200a2c633e1d02949b89d569e5bb4071005cdd57e173bfba238 +size 2000 diff --git a/assets/voxygen/element/frames/tab_bg.vox b/assets/voxygen/element/frames/tab_bg.vox new file mode 100644 index 0000000000..25ee6a6d31 --- /dev/null +++ b/assets/voxygen/element/frames/tab_bg.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:472e5b6b123a61e9462b487d95608961dd5375497d505c973a5bfabd69574938 +size 70424 diff --git a/assets/voxygen/element/frames/tab_small_closed.vox b/assets/voxygen/element/frames/tab_small_closed.vox new file mode 100644 index 0000000000..3e318262d9 --- /dev/null +++ b/assets/voxygen/element/frames/tab_small_closed.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d39eb0235217529f28f60172f1d5df9c5e5285a1bb23f3bb80482d9a692a86f5 +size 1408 diff --git a/assets/voxygen/element/frames/tab_small_open.vox b/assets/voxygen/element/frames/tab_small_open.vox new file mode 100644 index 0000000000..834ed2d131 --- /dev/null +++ b/assets/voxygen/element/frames/tab_small_open.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd222b358e97e6ada43f9473543423e4266f248f72c490c8b9d660e2840200c +size 1388 diff --git a/assets/voxygen/element/frames/window2.vox b/assets/voxygen/element/frames/window2.vox index 1c403cfecb..0f7a118d72 100644 --- a/assets/voxygen/element/frames/window2.vox +++ b/assets/voxygen/element/frames/window2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edf569e8a729c66728f36d9040fd54c7a694d9545957cd3a583c77338d3f7e4f -size 197528 +oid sha256:c26453e1da7b3f4f32f3016f0456cbc4864298abb12d714db3c08a1f867c52fb +size 267279 diff --git a/assets/voxygen/element/frames/window_3.vox b/assets/voxygen/element/frames/window_3.vox new file mode 100644 index 0000000000..c9ef3917af --- /dev/null +++ b/assets/voxygen/element/frames/window_3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee3d7c5a94372b9ec1f5544151112deadcbfc0bc2571c8cf2efba350e448b126 +size 239600 diff --git a/assets/voxygen/element/frames/xp_charwindow.vox b/assets/voxygen/element/frames/xp_charwindow.vox new file mode 100644 index 0000000000..d4444a0e1f --- /dev/null +++ b/assets/voxygen/element/frames/xp_charwindow.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cb5b82b72589a02b57c64ec5c27db5fed738cfbf94f1e84eb68b7ef494f8823 +size 6148 diff --git a/assets/voxygen/element/icons/back.vox b/assets/voxygen/element/icons/back.vox new file mode 100644 index 0000000000..a9f8eee678 --- /dev/null +++ b/assets/voxygen/element/icons/back.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0fed53b953e06948cf233f3d4f3f27bac06c12f1b9c1687e1b47a3f66d5bc35 +size 62604 diff --git a/assets/voxygen/element/icons/belt.vox b/assets/voxygen/element/icons/belt.vox new file mode 100644 index 0000000000..49d0bff7cb --- /dev/null +++ b/assets/voxygen/element/icons/belt.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa92f8ffc54d7838fa7bb90ff76df7a9e67d95bcbf1c339b7563f5e7b57befe1 +size 61268 diff --git a/assets/voxygen/element/icons/chest.vox b/assets/voxygen/element/icons/chest.vox new file mode 100644 index 0000000000..e7593a2382 --- /dev/null +++ b/assets/voxygen/element/icons/chest.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3446d88d65fd7bdbb5b195dbe45809a56c27e527c59360a4be32795c4f034f0d +size 61612 diff --git a/assets/voxygen/element/icons/feet.vox b/assets/voxygen/element/icons/feet.vox new file mode 100644 index 0000000000..20127bc7e0 --- /dev/null +++ b/assets/voxygen/element/icons/feet.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a20ba6cc66e585069fddeacdafd89746504ee6ac81e57fcfa2a34293b81cc24 +size 61212 diff --git a/assets/voxygen/element/icons/gem.vox b/assets/voxygen/element/icons/gem.vox new file mode 100644 index 0000000000..4a346ed2bf --- /dev/null +++ b/assets/voxygen/element/icons/gem.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0eeda19e2850ef3b521cb6a4d7d2ea9c85bc7c141ef01abf589880cc3738b234 +size 62236 diff --git a/assets/voxygen/element/icons/hands.vox b/assets/voxygen/element/icons/hands.vox new file mode 100644 index 0000000000..3f47c0c3a3 --- /dev/null +++ b/assets/voxygen/element/icons/hands.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cc139fac19e456c4ed91df21fd41f5c8ef868bfd968055ff66d4ff8acb7c3b5 +size 61644 diff --git a/assets/voxygen/element/icons/head.vox b/assets/voxygen/element/icons/head.vox new file mode 100644 index 0000000000..de41bf0327 --- /dev/null +++ b/assets/voxygen/element/icons/head.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5beedc43d289152ecbe622f36bb203fa0b39ff51cf1c77e7ef59fddfa075b4ee +size 63728 diff --git a/assets/voxygen/element/icons/legs.vox b/assets/voxygen/element/icons/legs.vox new file mode 100644 index 0000000000..85375ee204 --- /dev/null +++ b/assets/voxygen/element/icons/legs.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62817a15ea83e0af343bce8eac4260cd0a1c5386cea227ad1ca1b7ae488b1439 +size 60140 diff --git a/assets/voxygen/element/icons/mainhand.vox b/assets/voxygen/element/icons/mainhand.vox new file mode 100644 index 0000000000..68d68bf9d7 --- /dev/null +++ b/assets/voxygen/element/icons/mainhand.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b29577ae26134a468e767001ac69929e1ad656af694bda38b75e4bee83da673d +size 15844 diff --git a/assets/voxygen/element/icons/necklace.vox b/assets/voxygen/element/icons/necklace.vox new file mode 100644 index 0000000000..7712027800 --- /dev/null +++ b/assets/voxygen/element/icons/necklace.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f6c06b002a1d836fe8921dbe2888232c47040a120efb8617f26846cd9a1af21 +size 59564 diff --git a/assets/voxygen/element/icons/offhand.vox b/assets/voxygen/element/icons/offhand.vox new file mode 100644 index 0000000000..4e65802558 --- /dev/null +++ b/assets/voxygen/element/icons/offhand.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fc29c53975102582cf938181311f0ada6a2c7c9e044f9a4d0883ab9c899fc4e +size 19088 diff --git a/assets/voxygen/element/icons/ring.vox b/assets/voxygen/element/icons/ring.vox new file mode 100644 index 0000000000..cddf5b7483 --- /dev/null +++ b/assets/voxygen/element/icons/ring.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c73f9198c986bce203d62afaeeb5690a4009cb9b0b731eb99775a36b3932466 +size 19496 diff --git a/assets/voxygen/element/icons/shoulders.vox b/assets/voxygen/element/icons/shoulders.vox new file mode 100644 index 0000000000..bc260ac9f8 --- /dev/null +++ b/assets/voxygen/element/icons/shoulders.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b277676fd12242532f0002c074df50066da54accf6e2b54d94c65ca3ac0b2107 +size 60428 diff --git a/assets/voxygen/element/icons/tabard.vox b/assets/voxygen/element/icons/tabard.vox new file mode 100644 index 0000000000..69999e3228 --- /dev/null +++ b/assets/voxygen/element/icons/tabard.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fae087fbbddc39faf126b7a7ae80301f331c048677fbd047d790918043c29e9 +size 62188 diff --git a/assets/voxygen/element/misc_bg/charwindow.png b/assets/voxygen/element/misc_bg/charwindow.png index de990cdea6..41d47b209c 100644 --- a/assets/voxygen/element/misc_bg/charwindow.png +++ b/assets/voxygen/element/misc_bg/charwindow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88b682a60928a48fc38bd9e0f83bf9ac2d5976c40d2750583167cc1461e8c27c -size 120638 +oid sha256:312e8e5ce70dff58ab12b91bf695071aee35890ddf9881d6c42db121df2c5d9f +size 2484 diff --git a/assets/voxygen/element/misc_bg/crosshair.vox b/assets/voxygen/element/misc_bg/crosshair.vox new file mode 100644 index 0000000000..37f0d80d5b --- /dev/null +++ b/assets/voxygen/element/misc_bg/crosshair.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ebf4d14b5deaf1e4a5645ebe143024522d66991026328400245f7d4f3e07580 +size 5352 diff --git a/assets/voxygen/voxel/Wood Training 2h.vox b/assets/voxygen/voxel/Wood Training 2h.vox new file mode 100644 index 0000000000..5394e57cac --- /dev/null +++ b/assets/voxygen/voxel/Wood Training 2h.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65b30ab854142755ca70727b42c32deff7a0dfd5e619c8bf73a2cd4685ecc079 +size 44628 diff --git a/assets/voxygen/voxel/sword.vox b/assets/voxygen/voxel/sword.vox index 3cc01d1cbc..be6c69ae07 100644 --- a/assets/voxygen/voxel/sword.vox +++ b/assets/voxygen/voxel/sword.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e458a391915d1381c5c8f995da96310e521a441d0e14e67bcacad423ffc68857 -size 1568 +oid sha256:f76c933f8a40237cbca3db5dea9baf11f2a23282f981fdada49318a62b3da3c9 +size 1520 diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 1c299b8ba3..f3ff742700 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -1,16 +1,20 @@ -use specs::{Component, VecStorage}; +use specs::{Component, Entity as EcsEntity, VecStorage}; use vek::*; -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug)] pub enum Agent { Wanderer(Vec2), + Pet { + target: EcsEntity, + offset: Vec2, + }, } impl Component for Agent { type Storage = VecStorage; } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug)] pub struct Control { pub move_dir: Vec2, pub jumping: bool, diff --git a/common/src/comp/character.rs b/common/src/comp/character.rs index 9c47827055..7b4e4f9d65 100644 --- a/common/src/comp/character.rs +++ b/common/src/comp/character.rs @@ -140,6 +140,16 @@ pub struct AnimationHistory { pub time: f64, } +impl AnimationHistory { + pub fn new(animation: Animation) -> Self { + Self { + last: None, + current: animation, + time: 0.0, + } + } +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Animation { Idle, diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index ea466ac8bb..3c720f67d9 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -226,7 +226,7 @@ impl PostBox { for _ in 0..100 { match outgoing_chunks.pop_front() { Some(mut chunk) => match stream.write(&chunk) { - Ok(n) => if n == chunk.len() {}, + Ok(n) if n == chunk.len() => {} Ok(n) => { outgoing_chunks.push_front(chunk.split_off(n)); break; diff --git a/common/src/ray.rs b/common/src/ray.rs index c577980f54..568dedc4c4 100644 --- a/common/src/ray.rs +++ b/common/src/ray.rs @@ -9,6 +9,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil> { to: Vec3, until: F, max_iter: usize, + ignore_error: bool, } impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { @@ -19,6 +20,7 @@ impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { to, until, max_iter: 100, + ignore_error: false, } } @@ -31,6 +33,11 @@ impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { self } + pub fn ignore_error(mut self) -> Self { + self.ignore_error = true; + self + } + pub fn cast(mut self) -> (f32, Result, V::Err>) { // TODO: Fully test this! @@ -47,17 +54,17 @@ impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { pos = self.from + dir * dist; ipos = pos.map(|e| e.floor() as i32); - match self.vol.get(ipos).map(|vox| (vox, (self.until)(vox))) { - Ok((vox, true)) => return (dist, Ok(Some(vox))), - Ok((_, false)) => {} - Err(err) => return (dist, Err(err)), - } - // Allow one iteration above max if dist > max { break; } + match self.vol.get(ipos).map(|vox| (vox, (self.until)(vox))) { + Ok((vox, true)) => return (dist, Ok(Some(vox))), + Err(err) if !self.ignore_error => return (dist, Err(err)), + _ => {} + } + let deltas = (dir.map(|e| if e < 0.0 { 0.0 } else { 1.0 }) - pos.map(|e| e.abs().fract())) / dir; diff --git a/common/src/state.rs b/common/src/state.rs index 82b28adfcf..34eb33df50 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -36,7 +36,7 @@ pub struct DeltaTime(pub f32); /// too fast, we'd skip important physics events like collisions. This constant determines what /// the upper limit is. If delta time exceeds this value, the game's physics will begin to produce /// time lag. Ideally, we'd avoid such a situation. -const MAX_DELTA_TIME: f32 = 0.2; +const MAX_DELTA_TIME: f32 = 0.05; pub struct Changes { pub new_chunks: HashSet>, diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 0a04e995ec..d13d6cc3df 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -15,14 +15,12 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Control>, ); - fn run(&mut self, (mut agents, pos, mut controls): Self::SystemData) { - for (mut agent, pos, mut control) in (&mut agents, &pos, &mut controls).join() { + fn run(&mut self, (mut agents, positions, mut controls): Self::SystemData) { + for (mut agent, pos, mut control) in (&mut agents, &positions, &mut controls).join() { match agent { Agent::Wanderer(bearing) => { - *bearing += Vec2::new( - rand::random::().fract() - 0.5, - rand::random::().fract() - 0.5, - ) * 0.1 + *bearing += Vec2::new(rand::random::() - 0.5, rand::random::() - 0.5) + * 0.1 - *bearing * 0.01 - pos.0 * 0.0002; @@ -30,6 +28,35 @@ impl<'a> System<'a> for Sys { control.move_dir = bearing.normalized(); } } + Agent::Pet { target, offset } => { + // Run towards target + match positions.get(*target) { + Some(tgt_pos) => { + let tgt_pos = tgt_pos.0 + *offset; + + // Jump with target + control.jumping = tgt_pos.z > pos.0.z + 1.0; + + // Move towards the target + let dist = tgt_pos.distance(pos.0); + control.move_dir = if dist > 5.0 { + Vec2::from(tgt_pos - pos.0).normalized() + } else if dist < 1.5 && pos.0 != tgt_pos { + Vec2::from(pos.0 - tgt_pos).normalized() + } else { + Vec2::zero() + }; + } + _ => control.move_dir = Vec2::zero(), + } + + // Change offset occasionally + if rand::random::() < 0.003 { + *offset = + Vec2::new(rand::random::() - 0.5, rand::random::() - 0.5) + * 10.0; + } + } } } } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 0de3578576..2728ffe02e 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -28,6 +28,13 @@ impl<'a> System<'a> for Sys { // Movement pos.0 += vel.0 * dt.0; + // Don't fall into the void + // TODO: This shouldn't be needed when we have proper physics and chunk loading + if pos.0.z < 0.0 { + pos.0.z = 0.0; + vel.0.z = 0.0; + } + // Basic collision with terrain let mut i = 0; while terrain diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 4f02f455c5..e585165d9b 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -4,7 +4,7 @@ use crate::Server; use common::{comp, msg::ServerMsg}; -use specs::{join::Join, Entity as EcsEntity}; +use specs::{Builder, Entity as EcsEntity, Join}; use vek::*; use lazy_static::lazy_static; @@ -72,9 +72,15 @@ lazy_static! { ChatCommand::new( "tp", "{}", - "/tp : Teleport to another player", + "/tp : Teleport to another player", handle_tp ), + ChatCommand::new( + "pet", + "{}", + "/pet : Spawn a test pet NPC", + handle_pet + ), ChatCommand::new("help", "", "/help: Display this message", handle_help) ]; } @@ -179,6 +185,35 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat } } +fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { + match server + .state + .read_component_cloned::(entity) + { + Some(pos) => { + let mut current = entity; + + for _ in 0..1 { + current = server + .create_npc(comp::Character::random()) + .with(comp::Control::default()) + .with(comp::Agent::Pet { + target: current, + offset: Vec2::zero(), + }) + .with(pos) + .build(); + } + server + .clients + .notify(entity, ServerMsg::Chat("Pet spawned!".to_owned())); + } + None => server + .clients + .notify(entity, ServerMsg::Chat("You have no position!".to_owned())), + } +} + fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { for cmd in CHAT_COMMANDS.iter() { server diff --git a/server/src/lib.rs b/server/src/lib.rs index 4a70ffdbe4..55c755c26f 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -82,8 +82,8 @@ impl Server { for i in 0..4 { this.create_npc(comp::Character::random()) - .with(comp::Agent::Wanderer(Vec2::zero())) .with(comp::Control::default()) + .with(comp::Agent::Wanderer(Vec2::zero())) .build(); } @@ -121,6 +121,7 @@ impl Server { .with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))) .with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Dir(Vec3::unit_y())) + .with(comp::AnimationHistory::new(Animation::Idle)) .with(character) } @@ -138,14 +139,7 @@ impl Server { state.write_component(entity, comp::phys::ForceUpdate); // Set initial animation - state.write_component( - entity, - comp::AnimationHistory { - last: None, - current: Animation::Idle, - time: 0.0, - }, - ); + state.write_component(entity, comp::AnimationHistory::new(Animation::Idle)); // Tell the client his request was successful client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character))); @@ -230,7 +224,9 @@ impl Server { .join() { let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); - let dist = (chunk_pos - key).map(|e| e.abs()).reduce_max(); + let dist = Vec2::from(chunk_pos - key) + .map(|e: i32| e.abs()) + .reduce_max(); min_dist = min_dist.min(dist); } @@ -272,7 +268,7 @@ impl Server { // (All components Sphynx tracks) client.notify(ServerMsg::InitialSync { ecs_state: self.state.ecs().gen_state_package(), - entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), + entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail }); self.clients.add(entity, client); diff --git a/voxygen/shaders/ui.frag b/voxygen/shaders/ui.frag index 778d28d28c..7ef02c68b5 100644 --- a/voxygen/shaders/ui.frag +++ b/voxygen/shaders/ui.frag @@ -9,14 +9,14 @@ uniform sampler2D u_tex; out vec4 tgt_color; void main() { - // Text - if (f_mode == uint(0)) { - tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); - // Image - } else if (f_mode == uint(1)) { - tgt_color = texture(u_tex, f_uv); - // 2D Geometry - } else if (f_mode == uint(2)) { - tgt_color = f_color; - } + // Text + if (f_mode == uint(0)) { + tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); + // Image + } else if (f_mode == uint(1)) { + tgt_color = f_color * texture(u_tex, f_uv); + // 2D Geometry + } else if (f_mode == uint(2)) { + tgt_color = f_color; + } } diff --git a/voxygen/shaders/ui.vert b/voxygen/shaders/ui.vert index f2e956e62f..ab90588fc3 100644 --- a/voxygen/shaders/ui.vert +++ b/voxygen/shaders/ui.vert @@ -12,8 +12,8 @@ flat out uint f_mode; out vec4 f_color; void main() { - f_uv = v_uv; - f_color = v_color; - gl_Position = vec4(v_pos, 0.0, 1.0); - f_mode = v_mode; + f_uv = v_uv; + f_color = v_color; + gl_Position = vec4(v_pos, 0.0, 1.0); + f_mode = v_mode; } diff --git a/voxygen/src/anim/character/idle.rs b/voxygen/src/anim/character/idle.rs index cc6b97cef4..613f12fcbe 100644 --- a/voxygen/src/anim/character/idle.rs +++ b/voxygen/src/anim/character/idle.rs @@ -32,12 +32,12 @@ impl Animation for IdleAnimation { let wave_dip = (wave_slow.abs() - 0.5).abs(); let head_look = Vec2::new( - ((global_time + anim_time) as f32 / 5.0) + ((global_time + anim_time) as f32 / 8.0) .floor() .mul(7331.0) .sin() * 0.5, - ((global_time + anim_time) as f32 / 5.0) + ((global_time + anim_time) as f32 / 8.0) .floor() .mul(1337.0) .sin() diff --git a/voxygen/src/hud/character_window.rs b/voxygen/src/hud/character_window.rs index 6ef209784d..8aaf39de3f 100644 --- a/voxygen/src/hud/character_window.rs +++ b/voxygen/src/hud/character_window.rs @@ -2,18 +2,18 @@ use super::{img_ids::Imgs, Fonts, TEXT_COLOR, XP_COLOR}; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Text}, - widget_ids, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; widget_ids! { pub struct Ids { charwindow, - charwindow_bg, + charwindow_gradient, charwindow_close, charwindow_exp_progress_rectangle, charwindow_exp_rectangle, charwindow_frame, - charwindow_icon, + content_align, charwindow_rectangle, charwindow_tab1, charwindow_tab1_exp, @@ -23,6 +23,45 @@ widget_ids! { charwindow_tab1_stats, charwindow_tab_bg, charwindow_title, + window_3, + tab_bg, + tab_small_open, + tab_small_closed, + xp_charwindow, + divider, + head_bg, + shoulders_bg, + hands_bg, + belt_bg, + legs_bg, + feet_bg, + ring_r_bg, + ring_l_bg, + tabard_bg, + chest_bg, + back_bg, + gem_bg, + necklace_bg, + mainhand_bg, + offhand_bg, + charwindow_bg, + head_grid, + shoulders_grid, + hands_grid, + belt_grid, + legs_grid, + feet_grid, + ring_r_grid, + ring_l_grid, + tabard_grid, + chest_grid, + back_grid, + gem_grid, + necklace_grid, + mainhand_grid, + offhand_grid, + + } } @@ -69,24 +108,24 @@ impl<'a> Widget for CharacterWindow<'a> { let xp_percentage = 0.4; // Frame - Image::new(self.imgs.window_frame) + Image::new(self.imgs.window_3) .middle_of(id) - .top_left_with_margins_on(ui.window, 200.0, 215.0) - .w_h(107.0 * 4.0, 125.0 * 4.0) + .top_left_with_margins_on(ui.window, 212.0, 215.0) + .w_h(103.0 * 4.0, 122.0 * 4.0) .set(state.charwindow_frame, ui); // Icon - Image::new(self.imgs.charwindow_icon) - .w_h(40.0, 40.0) - .top_left_with_margins_on(state.charwindow_frame, 4.0, 4.0) - .set(state.charwindow_icon, ui); + //Image::new(self.imgs.charwindow_icon) + //.w_h(40.0, 40.0) + //.top_left_with_margins_on(state.charwindow_frame, 4.0, 4.0) + //.set(state.charwindow_icon, ui); // X-Button if Button::image(self.imgs.close_button) .w_h(28.0, 28.0) .hover_image(self.imgs.close_button_hover) .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(state.charwindow_frame, 12.0, 0.0) + .top_right_with_margins_on(state.charwindow_frame, 0.0, 0.0) .set(state.charwindow_close, ui) .was_clicked() { @@ -95,31 +134,199 @@ impl<'a> Widget for CharacterWindow<'a> { // Title Text::new("Character Name") // Add in actual Character Name - .mid_top_with_margin_on(state.charwindow_frame, 17.0) + .mid_top_with_margin_on(state.charwindow_frame, 6.0) .font_id(self.fonts.metamorph) .font_size(14) .color(TEXT_COLOR) .set(state.charwindow_title, ui); + // Content Alignment + Rectangle::fill_with([95.0 * 4.0, 108.0 * 4.0], color::TRANSPARENT) + .mid_top_with_margin_on(state.charwindow_frame, 40.0) + .set(state.content_align, ui); + + // Gradient BG + Image::new(self.imgs.charwindow_gradient) + .w_h(95.0 * 4.0, 108.0 * 4.0) + .middle_of(state.content_align) + .set(state.charwindow_gradient, ui); + + // Contents + + //Head + Image::new(self.imgs.head_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .mid_top_with_margin_on(state.content_align, 5.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.head_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.head_bg) + .set(state.head_grid, ui); + + // Ring R + Image::new(self.imgs.ring_r_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .bottom_right_with_margins_on(state.content_align, 20.0, 20.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.ring_r_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.ring_r_bg) + .set(state.ring_r_grid, ui); + // Feet + Image::new(self.imgs.feet_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.ring_r_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.feet_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.feet_bg) + .set(state.feet_grid, ui); + // Legs + Image::new(self.imgs.legs_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.feet_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.legs_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.legs_bg) + .set(state.legs_grid, ui); + // Belt + Image::new(self.imgs.belt_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.legs_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.belt_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.belt_bg) + .set(state.belt_grid, ui); + // Hands + Image::new(self.imgs.hands_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.belt_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.hands_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.hands_bg) + .set(state.hands_grid, ui); + // Shoulders + Image::new(self.imgs.shoulders_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.hands_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.shoulders_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.shoulders_bg) + .set(state.shoulders_grid, ui); + // Ring L + Image::new(self.imgs.ring_l_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .bottom_left_with_margins_on(state.content_align, 20.0, 20.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.ring_l_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.ring_l_bg) + .set(state.ring_l_grid, ui); + // Tabard + Image::new(self.imgs.tabard_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.ring_l_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.tabard_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.tabard_bg) + .set(state.tabard_grid, ui); + // Chest + Image::new(self.imgs.chest_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.tabard_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.chest_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.chest_bg) + .set(state.chest_grid, ui); + // Back + Image::new(self.imgs.back_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.chest_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.back_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.back_bg) + .set(state.back_grid, ui); + // Gem + Image::new(self.imgs.gem_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.back_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.gem_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.gem_bg) + .set(state.gem_grid, ui); + // Necklace + Image::new(self.imgs.necklace_bg) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .up_from(state.gem_bg, 10.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.necklace_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 1.8, 28.0 * 1.8) + .middle_of(state.necklace_bg) + .set(state.necklace_grid, ui); + + // Weapon Main Hand + Image::new(self.imgs.mainhand_bg) + .w_h(28.0 * 2.2, 28.0 * 2.2) + .bottom_right_with_margins_on(state.ring_l_bg, 0.0, -115.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.mainhand_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 2.2, 28.0 * 2.2) + .middle_of(state.mainhand_bg) + .set(state.mainhand_grid, ui); + // Weapon Off-Hand + Image::new(self.imgs.offhand_bg) + .w_h(28.0 * 2.2, 28.0 * 2.2) + .bottom_left_with_margins_on(state.ring_r_bg, 0.0, -115.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.1))) + .set(state.offhand_bg, ui); + Button::image(self.imgs.grid) + .w_h(28.0 * 2.2, 28.0 * 2.2) + .middle_of(state.offhand_bg) + .set(state.offhand_grid, ui); + + // Stats Tab + // Tab BG - Image::new(self.imgs.charwindow_tab_bg) - .w_h(205.0, 412.0) - .mid_left_with_margin_on(state.charwindow_frame, -205.0) + Image::new(self.imgs.tab_bg) + .w_h(51.0 * 4.0, 115.0 * 4.0) + .top_left_with_margins_on(state.charwindow_frame, 28.0, -200.0) .set(state.charwindow_tab_bg, ui); // Tab Rectangle - Rectangle::fill_with([192.0, 371.0], color::rgba(0.0, 0.0, 0.0, 0.8)) - .top_right_with_margins_on(state.charwindow_tab_bg, 20.0, 0.0) + Rectangle::fill_with([45.0 * 4.0, 104.0 * 4.0], color::TRANSPARENT) + .top_left_with_margins_on(state.charwindow_tab_bg, 7.0 * 4.0, 4.0 * 4.0) .set(state.charwindow_rectangle, ui); - // Tab Button - Button::image(self.imgs.charwindow_tab) - .w_h(65.0, 23.0) - .top_left_with_margins_on(state.charwindow_tab_bg, -18.0, 2.0) - .label("Stats") - .label_color(TEXT_COLOR) - .label_font_size(14) - .set(state.charwindow_tab1, ui); + // Tab Button -> Add that back in when we have multiple tabs + // Button::image(self.imgs.charwindow_tab) + //.w_h(65.0, 23.0) + //.top_left_with_margins_on(state.charwindow_tab_bg, -18.0, 1.8) + //.label("Stats") + //.label_color(TEXT_COLOR) + //.label_font_size(14) + //.set(state.charwindow_tab1, ui); Text::new("1") //Add in actual Character Level .mid_top_with_margin_on(state.charwindow_rectangle, 10.0) @@ -152,6 +359,13 @@ impl<'a> Widget for CharacterWindow<'a> { .color(TEXT_COLOR) .set(state.charwindow_tab1_exp, ui); + // Divider + + Image::new(self.imgs.divider) + .w_h(38.0 * 4.0, 5.0 * 4.0) + .mid_top_with_margin_on(state.charwindow_tab1_exp, 30.0) + .set(state.divider, ui); + // Stats Text::new( "Stamina\n\ @@ -162,7 +376,7 @@ impl<'a> Widget for CharacterWindow<'a> { \n\ Intelligence", ) - .top_left_with_margins_on(state.charwindow_rectangle, 100.0, 20.0) + .top_left_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) .font_id(self.fonts.opensans) .font_size(16) .color(TEXT_COLOR) @@ -177,7 +391,7 @@ impl<'a> Widget for CharacterWindow<'a> { \n\ 124124", ) - .right_from(state.charwindow_tab1_statnames, 10.0) + .top_right_with_margins_on(state.charwindow_rectangle, 140.0, 5.0) .font_id(self.fonts.opensans) .font_size(16) .color(TEXT_COLOR) diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 3acd09b085..f32b405ad5 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -1,13 +1,59 @@ -use crate::ui::{BlankGraphic, ImageGraphic, VoxelGraphic}; +use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelMs9Graphic}; image_ids! { pub struct Imgs { + // Bag bag_contents: "/voxygen/element/frames/bag.vox", inv_grid: "/voxygen/element/frames/inv_grid.vox", inv_slot: "/voxygen/element/buttons/inv_slot.vox", + // Window Parts + window_3: "/voxygen/element/frames/window_3.vox", + tab_bg: "/voxygen/element/frames/tab_bg.vox", + tab_small_open: "/voxygen/element/frames/tab_small_open.vox", + tab_small_closed: "/voxygen/element/frames/tab_small_closed.vox", + + // MiniMap + mmap_frame: "/voxygen/element/frames/mmap.vox", + mmap_frame_closed: "/voxygen/element/frames/mmap_closed.vox", + + // Missing: Buff Frame Animation .gif ?! we could do animation in ui.maintain, or in shader? + window_frame: "/voxygen/element/frames/window2.vox", + + // Settings Window + settings_frame_r: "/voxygen/element/frames/settings_r.vox", + settings_frame_l: "/voxygen/element/frames/settings_l.vox", + settings_button: "/voxygen/element/buttons/settings_button.vox", + settings_button_pressed: "/voxygen/element/buttons/settings_button_pressed.vox", + settings_button_hover: "/voxygen/element/buttons/settings_button_hover.vox", + settings_button_press: "/voxygen/element/buttons/settings_button_press.vox", + check: "/voxygen/element/buttons/check/no.vox", + check_mo: "/voxygen/element/buttons/check/no_mo.vox", + check_press: "/voxygen/element/buttons/check/press.vox", + check_checked: "/voxygen/element/buttons/check/yes.vox", + check_checked_mo: "/voxygen/element/buttons/check/yes_mo.vox", + slider: "/voxygen/element/slider/track.vox", + slider_indicator: "/voxygen/element/slider/indicator.vox", + + // Map Window + map_frame_l: "/voxygen/element/frames/map_l.vox", + map_frame_r: "/voxygen/element/frames/map_r.vox", + map_frame_bl: "/voxygen/element/frames/map_bl.vox", + map_frame_br: "/voxygen/element/frames/map_br.vox", + + // Chat-Arrows + chat_arrow: "/voxygen/element/buttons/arrow_down.vox", + chat_arrow_mo: "/voxygen/element/buttons/arrow_down_hover.vox", + chat_arrow_press: "/voxygen/element/buttons/arrow_down_press.vox", + + // Crosshair + crosshair: "/voxygen/element/misc_bg/crosshair.vox", + + + + // Buttons mmap_closed: "/voxygen/element/buttons/button_mmap_closed.vox", mmap_closed_hover: "/voxygen/element/buttons/button_mmap_closed_hover.vox", @@ -16,6 +62,11 @@ image_ids! { mmap_open_hover: "/voxygen/element/buttons/button_mmap_open_hover.vox", mmap_open_press: "/voxygen/element/buttons/button_mmap_open_press.vox", + // Grid + grid: "/voxygen/element/buttons/grid.vox", + grid_hover: "/voxygen/element/buttons/grid.vox", + grid_press: "/voxygen/element/buttons/grid.vox", + settings: "/voxygen/element/buttons/settings.vox", settings_hover: "/voxygen/element/buttons/settings_hover.vox", settings_press: "/voxygen/element/buttons/settings_press.vox", @@ -40,6 +91,24 @@ image_ids! { qlog_hover: "/voxygen/element/buttons/qlog_hover.vox", qlog_press: "/voxygen/element/buttons/qlog_press.vox", + // Charwindow + xp_charwindow: "/voxygen/element/frames/xp_charwindow.vox", + divider: "/voxygen/element/frames/divider_charwindow.vox", + head_bg: "/voxygen/element/icons/head.vox", + shoulders_bg: "/voxygen/element/icons/shoulders.vox", + hands_bg: "/voxygen/element/icons/hands.vox", + belt_bg: "/voxygen/element/icons/belt.vox", + legs_bg: "/voxygen/element/icons/legs.vox", + feet_bg: "/voxygen/element/icons/feet.vox", + ring_r_bg: "/voxygen/element/icons/ring.vox", + ring_l_bg: "/voxygen/element/icons/ring.vox", + tabard_bg: "/voxygen/element/icons/tabard.vox", + chest_bg: "/voxygen/element/icons/chest.vox", + back_bg: "/voxygen/element/icons/back.vox", + gem_bg: "/voxygen/element/icons/gem.vox", + necklace_bg: "/voxygen/element/icons/necklace.vox", + mainhand_bg: "/voxygen/element/icons/mainhand.vox", + offhand_bg: "/voxygen/element/icons/offhand.vox", // Close button close_button: "/voxygen/element/buttons/x.vox", @@ -52,45 +121,12 @@ image_ids! { button_hover: "/voxygen/element/buttons/button_hover.vox", button_press: "/voxygen/element/buttons/button_press.vox", - // MiniMap - mmap_frame: "/voxygen/element/frames/mmap.vox", - mmap_frame_closed: "/voxygen/element/frames/mmap_closed.vox", - - - // Missing: Buff Frame Animation .gif ?! we could do animation in ui.maintain, or in shader? - window_frame: "/voxygen/element/frames/window2.vox", - - // Settings Window - settings_frame_r: "/voxygen/element/frames/settings_r.vox", - settings_frame_l: "/voxygen/element/frames/settings_l.vox", - settings_button: "/voxygen/element/buttons/settings_button.vox", - settings_button_pressed: "/voxygen/element/buttons/settings_button_pressed.vox", - settings_button_hover: "/voxygen/element/buttons/settings_button_hover.vox", - settings_button_press: "/voxygen/element/buttons/settings_button_press.vox", - check: "/voxygen/element/buttons/check/no.vox", - check_mo: "/voxygen/element/buttons/check/no_mo.vox", - check_press: "/voxygen/element/buttons/check/press.vox", - check_checked: "/voxygen/element/buttons/check/yes.vox", - check_checked_mo: "/voxygen/element/buttons/check/yes_mo.vox", - slider: "/voxygen/element/slider/track.vox", - slider_indicator: "/voxygen/element/slider/indicator.vox", - - - // Map Window - map_frame_l: "/voxygen/element/frames/map_l.vox", - map_frame_r: "/voxygen/element/frames/map_r.vox", - map_frame_bl: "/voxygen/element/frames/map_bl.vox", - map_frame_br: "/voxygen/element/frames/map_br.vox", - - - // Chat-Arrows - chat_arrow: "/voxygen/element/buttons/arrow_down.vox", - chat_arrow_mo: "/voxygen/element/buttons/arrow_down_hover.vox", - chat_arrow_press: "/voxygen/element/buttons/arrow_down_press.vox", - // Spell Book Window + charwindow_gradient:"/voxygen/element/misc_bg/charwindow.png", + + // Spell Book Window spellbook_bg: "/voxygen/element/misc_bg/small_bg.png", spellbook_icon: "/voxygen/element/icons/spellbook.png", diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index f6aeceaab0..a21a5f2b7f 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -71,6 +71,7 @@ impl<'a> Widget for Map<'a> { .scroll_kids() .scroll_kids_vertically() .set(state.ids.map_bg, ui); + // Frame Image::new(self.imgs.map_frame_l) .top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0) @@ -95,12 +96,6 @@ impl<'a> Widget for Map<'a> { .top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0) .set(state.ids.map_icon, ui); - // Icon - Image::new(self.imgs.map_icon) - .w_h(224.0 / 3.0, 224.0 / 3.0) - .top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0) - .set(state.ids.map_icon, ui); - // X-Button if Button::image(self.imgs.close_button) .w_h(28.0, 28.0) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 83bf5d1079..84a02613cd 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -306,7 +306,7 @@ impl Hud { .w_h(100.0 * 0.2, 100.0 * 0.2) .hover_image(self.imgs.close_button_hover) .press_image(self.imgs.close_button_press) - .top_right_with_margins_on(self.ids.help_bg, 8.0, 3.0) + .top_right_with_margins_on(self.ids.help_bg, 4.0, 4.0) .set(self.ids.button_help2, ui_widgets) .was_clicked() { @@ -475,6 +475,13 @@ impl Hud { self.show.toggle_ui(); true } + WinEvent::KeyDown(Key::ToggleCursor) => { + self.force_ungrab = !self.force_ungrab; + if self.force_ungrab { + global_state.window.grab_cursor(false); + } + true + } _ if !self.show.ui => false, WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(), WinEvent::KeyDown(Key::Enter) => { @@ -527,13 +534,6 @@ impl Hud { self.show.toggle_help(); true } - Key::ToggleCursor => { - self.force_ungrab = !self.force_ungrab; - if self.force_ungrab { - global_state.window.grab_cursor(false); - } - true - } _ => false, }, WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key { diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 5fc20576d4..3eed2c9172 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -21,6 +21,7 @@ widget_ids! { test, xp_bar, xp_bar_progress, + crosshair, } } @@ -69,8 +70,14 @@ impl<'a> Widget for Skillbar<'a> { // TODO: Read from parameter / character struct let xp_percentage = 0.4; - let hp_percentage = 0.4; - let mana_percentage = 0.4; + let hp_percentage = 1.0; + let mana_percentage = 1.0; + + // Crosshair TODO: Only show while aiming with a bow or when casting a spell + // Image::new(self.imgs.crosshair) + // .w_h(101.0 * 0.5, 101.0 * 0.5) + // .mid_top_with_margin_on(ui.window, 500.0) + // .set(state.ids.crosshair, ui); // Experience-Bar Image::new(self.imgs.xp_bar) diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 1754854313..f57958ba4e 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -136,7 +136,7 @@ Voxygen has logged information about the problem (including this message) to the The information below is intended for developers and testers. Panic Payload: {:?} -PanicInfo: {:?}", settings_clone.log.file, reason, panic_info); +PanicInfo: {}", settings_clone.log.file, reason, panic_info); log::error!("VOXYGEN HAS PANICKED\n\n{}", msg); diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 565c114527..13f430d082 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -1,3 +1,4 @@ +mod scene; mod ui; use crate::{ @@ -7,6 +8,7 @@ use crate::{ }; use client::{self, Client}; use common::{clock::Clock, msg::ClientMsg}; +use scene::Scene; use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; use vek::*; @@ -16,6 +18,7 @@ const FPS: u64 = 60; pub struct CharSelectionState { char_selection_ui: CharSelectionUi, client: Rc>, + scene: Scene, } impl CharSelectionState { @@ -24,6 +27,7 @@ impl CharSelectionState { Self { char_selection_ui: CharSelectionUi::new(window), client, + scene: Scene::new(window.renderer_mut()), } } } @@ -82,6 +86,14 @@ impl PlayState for CharSelectionState { } } + // Maintain the scene + self.scene + .maintain(global_state.window.renderer_mut(), &self.client.borrow()); + + // Render the scene + self.scene + .render(global_state.window.renderer_mut(), &self.client.borrow()); + // Draw the UI to the screen self.char_selection_ui .render(global_state.window.renderer_mut()); diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/menu/char_selection/scene.rs new file mode 100644 index 0000000000..6c999afa4b --- /dev/null +++ b/voxygen/src/menu/char_selection/scene.rs @@ -0,0 +1,134 @@ +use crate::{ + anim::{ + character::{CharacterSkeleton, IdleAnimation}, + Animation, Skeleton, + }, + render::{ + create_pp_mesh, create_skybox_mesh, Consts, FigurePipeline, Globals, Model, + PostProcessLocals, PostProcessPipeline, Renderer, SkyboxLocals, SkyboxPipeline, + }, + scene::{ + camera::Camera, + figure::{FigureModelCache, FigureState}, + }, +}; +use client::Client; +use common::{comp::Character, figure::Segment}; +use vek::*; + +struct Skybox { + model: Model, + locals: Consts, +} + +struct PostProcess { + model: Model, + locals: Consts, +} + +pub struct Scene { + globals: Consts, + camera: Camera, + + skybox: Skybox, + postprocess: PostProcess, + backdrop_model: Model, + backdrop_state: FigureState, + + figure_model_cache: FigureModelCache, + figure_state: FigureState, +} + +impl Scene { + pub fn new(renderer: &mut Renderer) -> Self { + let resolution = renderer.get_resolution().map(|e| e as f32); + + Self { + globals: renderer.create_consts(&[Globals::default()]).unwrap(), + camera: Camera::new(resolution.x / resolution.y), + + skybox: Skybox { + model: renderer.create_model(&create_skybox_mesh()).unwrap(), + locals: renderer.create_consts(&[SkyboxLocals::default()]).unwrap(), + }, + postprocess: PostProcess { + model: renderer.create_model(&create_pp_mesh()).unwrap(), + locals: renderer + .create_consts(&[PostProcessLocals::default()]) + .unwrap(), + }, + figure_model_cache: FigureModelCache::new(), + figure_state: FigureState::new(renderer, CharacterSkeleton::new()), + + backdrop_model: renderer + .create_model(&FigureModelCache::load_mesh("knight.vox", Vec3::zero())) + .unwrap(), + backdrop_state: FigureState::new(renderer, CharacterSkeleton::new()), + } + } + + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + self.camera.set_focus_pos(Vec3::unit_z() * 1.75); + self.camera.update(client.state().get_time()); + self.camera.set_distance(4.0); + self.camera + .set_orientation(Vec3::new(client.state().get_time() as f32 * 0.2, 0.3, 0.0)); + + let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client); + + renderer.update_consts( + &mut self.globals, + &[Globals::new( + view_mat, + proj_mat, + cam_pos, + self.camera.get_focus_pos(), + 100.0, + client.state().get_time_of_day(), + client.state().get_time(), + renderer.get_resolution(), + )], + ); + + self.figure_model_cache.clean(client.get_tick()); + + let tgt_skeleton = IdleAnimation::update_skeleton( + self.figure_state.skeleton_mut(), + client.state().get_time(), + client.state().get_time(), + ); + self.figure_state.skeleton_mut().interpolate(&tgt_skeleton); + + self.figure_state + .update(renderer, Vec3::zero(), -Vec3::unit_y()); + } + + pub fn render(&mut self, renderer: &mut Renderer, client: &Client) { + renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); + + let model = self.figure_model_cache.get_or_create_model( + renderer, + Character::random(), + client.get_tick(), + ); + renderer.render_figure( + model, + &self.globals, + self.figure_state.locals(), + self.figure_state.bone_consts(), + ); + + renderer.render_figure( + &self.backdrop_model, + &self.globals, + self.backdrop_state.locals(), + self.backdrop_state.bone_consts(), + ); + + renderer.render_post_process( + &self.postprocess.model, + &self.globals, + &self.postprocess.locals, + ); + } +} diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 10f1591648..d53f9f0ffa 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -1,17 +1,18 @@ use crate::{ render::Renderer, - ui::{self, BlankGraphic, Graphic, ImageGraphic, ScaleMode, Ui, VoxelGraphic}, + ui::{ + self, + img_ids::{ImageGraphic, VoxelGraphic}, + ScaleMode, Ui, + }, window::Window, }; -use common::{ - assets, - comp::character::{Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon}, +use common::comp::character::{ + Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon, }; use conrod_core::{ color, color::TRANSPARENT, - image::Id as ImgId, - text::font::Id as FontId, widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; @@ -154,7 +155,6 @@ widget_ids! { warpaint_slider_indicator, warpaint_slider_range, warpaint_slider_text, - } } @@ -315,13 +315,13 @@ impl CharSelectionUi { // Background Image if !self.character_creation { - Image::new(self.imgs.bg_selection) - .middle_of(ui_widgets.window) - .set(self.ids.bg_selection, ui_widgets); + //Image::new(self.imgs.bg_selection) + // .middle_of(ui_widgets.window) + // .set(self.ids.bg_selection, ui_widgets); // Logout_Button if Button::image(self.imgs.button) - .bottom_left_with_margins_on(self.ids.bg_selection, 10.0, 10.0) + .bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0) .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -337,7 +337,7 @@ impl CharSelectionUi { // Create Character Button if Button::image(self.imgs.button) - .mid_bottom_with_margin_on(self.ids.bg_selection, 10.0) + .mid_bottom_with_margin_on(ui_widgets.window, 10.0) .w_h(270.0, 50.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -353,7 +353,7 @@ impl CharSelectionUi { } // Test Characters if Button::image(self.imgs.test_char_l_button) - .bottom_left_with_margins_on(self.ids.bg_selection, 395.0, 716.0) + .bottom_left_with_margins_on(ui_widgets.window, 395.0, 716.0) .w_h(95.0, 130.0) .hover_image(self.imgs.test_char_l_button) .press_image(self.imgs.test_char_l_button) @@ -375,7 +375,7 @@ impl CharSelectionUi { .set(self.ids.version, ui_widgets); // Click Character to Login <-- Temporary! Image::new(self.imgs.window_frame_2) - .mid_top_with_margin_on(self.ids.bg_selection, 60.0) + .mid_top_with_margin_on(ui_widgets.window, 60.0) .w_h(700.0, 70.0) .set(self.ids.help_text_bg, ui_widgets); Text::new("Click character to select it") @@ -445,12 +445,13 @@ impl CharSelectionUi { // Character_Creation ////////////// else { // Background - Image::new(self.imgs.bg_creation) - .middle_of(ui_widgets.window) - .set(self.ids.bg_creation, ui_widgets); + //Image::new(self.imgs.bg_creation) + // .middle_of(ui_widgets.window) + // .set(self.ids.bg_creation, ui_widgets); + // Back Button if Button::image(self.imgs.button) - .bottom_left_with_margins_on(self.ids.bg_creation, 10.0, 10.0) + .bottom_left_with_margins_on(ui_widgets.window, 10.0, 10.0) .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -465,7 +466,7 @@ impl CharSelectionUi { } // Create Button if Button::image(self.imgs.button) - .bottom_right_with_margins_on(self.ids.bg_creation, 10.0, 10.0) + .bottom_right_with_margins_on(ui_widgets.window, 10.0, 10.0) .w_h(150.0, 40.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) @@ -481,7 +482,7 @@ impl CharSelectionUi { } // Character Name Input Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.99)) - .mid_bottom_with_margin_on(self.ids.bg_creation, 20.0) + .mid_bottom_with_margin_on(ui_widgets.window, 20.0) .set(self.ids.name_input_bg, ui_widgets); Button::image(self.imgs.name_input) .w_h(337.0, 67.0) @@ -513,7 +514,7 @@ impl CharSelectionUi { self.imgs.creation_window }) .w_h(628.0, 814.0) - .top_left_with_margins_on(self.ids.bg_creation, 60.0, 30.0) + .top_left_with_margins_on(ui_widgets.window, 60.0, 30.0) .set(self.ids.creation_window, ui_widgets); // Arrows diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index c8f4f078b2..431b3673cf 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -1,15 +1,16 @@ use crate::{ render::Renderer, - ui::{self, BlankGraphic, Graphic, ImageGraphic, ScaleMode, Ui, VoxelGraphic}, + ui::{ + self, + img_ids::{ImageGraphic, VoxelGraphic}, + ScaleMode, Ui, + }, GlobalState, DEFAULT_PUBLIC_SERVER, }; -use common::assets; use conrod_core::{ color, color::TRANSPARENT, - image::Id as ImgId, position::Relative, - text::font::Id as FontId, widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 511bdd2fc5..7c9e50f42c 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -10,7 +10,7 @@ mod util; pub use self::{ consts::Consts, mesh::{Mesh, Quad, Tri}, - model::Model, + model::{DynamicModel, Model}, pipelines::{ figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals}, postprocess::{ @@ -40,6 +40,7 @@ pub enum RenderError { UpdateError(gfx::UpdateError), TexUpdateError(gfx::UpdateError<[u16; 3]>), CombinedError(gfx::CombinedError), + BufferCreationError(gfx::buffer::CreationError), } /// Used to represent a specific rendering configuration. diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs index df7a3d3958..ea5e645230 100644 --- a/voxygen/src/render/model.rs +++ b/voxygen/src/render/model.rs @@ -1,8 +1,11 @@ -// Library -use gfx::{self, traits::FactoryExt}; - -// Local -use super::{gfx_backend, mesh::Mesh, Pipeline}; +use super::{gfx_backend, mesh::Mesh, Pipeline, RenderError}; +use gfx::{ + buffer::Role, + memory::{Bind, Usage}, + traits::FactoryExt, + Factory, +}; +use std::ops::Range; /// Represents a mesh that has been sent to the GPU. pub struct Model { @@ -24,3 +27,43 @@ impl Model

{ } } } + +/// Represents a mesh on the GPU which can be updated dynamically +pub struct DynamicModel { + pub vbuf: gfx::handle::Buffer, +} + +impl DynamicModel

{ + pub fn new(factory: &mut gfx_backend::Factory, size: usize) -> Result { + Ok(Self { + vbuf: factory + .create_buffer(size, Role::Vertex, Usage::Dynamic, Bind::empty()) + .map_err(|err| RenderError::BufferCreationError(err))?, + }) + } + + /// Create a model with a slice of a portion of this model to send to the renderer + pub fn submodel(&self, range: Range) -> Model

{ + Model { + vbuf: self.vbuf.clone(), + slice: gfx::Slice { + start: range.start as u32, + end: range.end as u32, + base_vertex: 0, + instances: None, + buffer: gfx::IndexBuffer::Auto, + }, + } + } + + pub fn update( + &self, + encoder: &mut gfx::Encoder, + mesh: &Mesh

, + offset: usize, + ) -> Result<(), RenderError> { + encoder + .update_buffer(&self.vbuf, mesh.vertices(), offset) + .map_err(|err| RenderError::UpdateError(err)) + } +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index bc553ae536..6c860ca069 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -2,7 +2,7 @@ use super::{ consts::Consts, gfx_backend, mesh::Mesh, - model::Model, + model::{DynamicModel, Model}, pipelines::{figure, postprocess, skybox, terrain, ui, Globals}, texture::Texture, Pipeline, RenderError, @@ -12,7 +12,6 @@ use gfx::{ handle::Sampler, traits::{Device, Factory, FactoryExt}, }; -use image; use vek::*; /// Represents the format of the pre-processed color target. @@ -246,6 +245,24 @@ impl Renderer { Ok(Model::new(&mut self.factory, mesh)) } + /// Create a new dynamic model with the specified size + pub fn create_dynamic_model( + &mut self, + size: usize, + ) -> Result, RenderError> { + DynamicModel::new(&mut self.factory, size) + } + + /// Update a dynamic model with a mesh and a offset + pub fn update_model( + &mut self, + model: &DynamicModel

, + mesh: &Mesh

, + offset: usize, + ) -> Result<(), RenderError> { + model.update(&mut self.encoder, mesh, offset) + } + /// Create a new texture from the provided image. pub fn create_texture( &mut self, diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index c72da5ec54..afdac9606b 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -54,7 +54,14 @@ impl Camera { ) * self.dist), ); - match client.state().terrain().ray(start, end).cast() { + match client + .state() + .terrain() + .ray(start, end) + .ignore_error() + .max_iter(500) + .cast() + { (d, Ok(Some(_))) => f32::min(d - 1.0, self.dist), (_, Ok(None)) => self.dist, (_, Err(_)) => self.dist, @@ -88,12 +95,27 @@ impl Camera { self.ori.z = (self.ori.z + delta.z) % (2.0 * PI); } + /// Set the orientation of the camera about its focus + pub fn set_orientation(&mut self, orientation: Vec3) { + // Wrap camera yaw + self.ori.x = orientation.x % (2.0 * PI); + // Clamp camera pitch to the vertical limits + self.ori.y = orientation.y.min(PI / 2.0).max(-PI / 2.0); + // Wrap camera roll + self.ori.z = orientation.z % (2.0 * PI); + } + /// 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.tgt_dist = (self.tgt_dist + delta).max(0.0); } + /// Set the distance of the camera from the target (i.e: zoom) + pub fn set_distance(&mut self, dist: f32) { + self.tgt_dist = dist; + } + pub fn update(&mut self, time: f64) { // This is horribly frame time dependent, but so is most of the game let delta = self.last_time.replace(time).map_or(0.0, |t| time - t); diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 59f11f827d..0cfbcc9e5b 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -24,31 +24,29 @@ use specs::{Component, Entity as EcsEntity, Join, VecStorage}; use std::{collections::HashMap, f32}; use vek::*; -pub struct FigureCache { +pub struct FigureModelCache { models: HashMap, u64)>, - states: HashMap>, } -impl FigureCache { +impl FigureModelCache { pub fn new() -> Self { Self { models: HashMap::new(), - states: HashMap::new(), } } - pub fn get_or_create_model<'a>( - models: &'a mut HashMap, u64)>, + pub fn get_or_create_model( + &mut self, renderer: &mut Renderer, - tick: u64, character: Character, - ) -> &'a (Model, u64) { - match models.get_mut(&character) { + tick: u64, + ) -> &Model { + match self.models.get_mut(&character) { Some((model, last_used)) => { *last_used = tick; } None => { - models.insert( + self.models.insert( character, ( { @@ -91,7 +89,7 @@ impl FigureCache { } } - &models[&character] + &self.models[&character].0 } pub fn clean(&mut self, tick: u64) { @@ -100,7 +98,8 @@ impl FigureCache { .retain(|_, (_, last_used)| *last_used + 60 > tick); } - fn load_mesh(filename: &str, position: Vec3) -> Mesh { + // TODO: Don't make this public + pub fn load_mesh(filename: &str, position: Vec3) -> Mesh { let fullpath: String = ["/voxygen/voxel/", filename].concat(); Segment::from(assets::load_expect::(fullpath.as_str()).as_ref()) .generate_mesh(position) @@ -218,9 +217,28 @@ impl FigureCache { // ) // } - pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) { +} + +pub struct FigureMgr { + model_cache: FigureModelCache, + states: HashMap>, +} + +impl FigureMgr { + pub fn new() -> Self { + Self { + model_cache: FigureModelCache::new(), + states: HashMap::new(), + } + } + + pub fn clean(&mut self, tick: u64) { + self.model_cache.clean(tick); + } + + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { let time = client.state().get_time(); - let ecs = client.state_mut().ecs_mut(); + let ecs = client.state().ecs(); for (entity, pos, vel, dir, character, animation_history) in ( &ecs.entities(), &ecs.read_storage::(), @@ -238,17 +256,17 @@ impl FigureCache { let target_skeleton = match animation_history.current { comp::character::Animation::Idle => IdleAnimation::update_skeleton( - &mut state.skeleton, + state.skeleton_mut(), time, animation_history.time, ), comp::character::Animation::Run => RunAnimation::update_skeleton( - &mut state.skeleton, + state.skeleton_mut(), (vel.0.magnitude(), time), animation_history.time, ), comp::character::Animation::Jump => JumpAnimation::update_skeleton( - &mut state.skeleton, + state.skeleton_mut(), time, animation_history.time, ), @@ -271,13 +289,15 @@ impl FigureCache { ) { let tick = client.get_tick(); let ecs = client.state().ecs(); - let models = &mut self.models; for (entity, &character) in (&ecs.entities(), &ecs.read_storage::()).join() { - let model = Self::get_or_create_model(models, renderer, tick, character); - let state = self.states.get(&entity).unwrap(); - renderer.render_figure(&model.0, globals, &state.locals, &state.bone_consts); + if let Some(state) = self.states.get(&entity) { + let model = self + .model_cache + .get_or_create_model(renderer, character, tick); + renderer.render_figure(model, globals, &state.locals(), state.bone_consts()); + } } } } @@ -299,7 +319,7 @@ impl FigureState { } } - fn update(&mut self, renderer: &mut Renderer, pos: Vec3, dir: Vec3) { + pub fn update(&mut self, renderer: &mut Renderer, pos: Vec3, dir: Vec3) { let mat = Mat4::::identity() * Mat4::translation_3d(pos) * Mat4::rotation_z(-dir.x.atan2(dir.y)); // + f32::consts::PI / 2.0); @@ -311,4 +331,16 @@ impl FigureState { .update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()) .unwrap(); } + + pub fn locals(&self) -> &Consts { + &self.locals + } + + pub fn bone_consts(&self) -> &Consts { + &self.bone_consts + } + + pub fn skeleton_mut(&mut self) -> &mut S { + &mut self.skeleton + } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index e445495cf3..806c0d3df2 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -2,7 +2,7 @@ pub mod camera; pub mod figure; pub mod terrain; -use self::{camera::Camera, figure::FigureCache, terrain::Terrain}; +use self::{camera::Camera, figure::FigureMgr, terrain::Terrain}; use crate::{ anim::{ character::{CharacterSkeleton, RunAnimation}, @@ -41,7 +41,7 @@ pub struct Scene { postprocess: PostProcess, terrain: Terrain, - figure_cache: FigureCache, + figure_mgr: FigureMgr, } impl Scene { @@ -64,7 +64,7 @@ impl Scene { .unwrap(), }, terrain: Terrain::new(), - figure_cache: FigureCache::new(), + figure_mgr: FigureMgr::new(), } } @@ -104,7 +104,7 @@ impl Scene { } /// Maintain data such as GPU constant buffers, models, etc. To be called once per tick. - pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) { + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { // Get player position let player_pos = client .state() @@ -144,10 +144,10 @@ impl Scene { self.terrain.maintain(renderer, client); // Maintain the figures - self.figure_cache.maintain(renderer, client); + self.figure_mgr.maintain(renderer, client); // Remove unused figures - self.figure_cache.clean(client.get_tick()); + self.figure_mgr.clean(client.get_tick()); } /// Render the scene using the provided `Renderer` @@ -157,7 +157,7 @@ impl Scene { // Render terrain and figures self.terrain.render(renderer, &self.globals); - self.figure_cache.render(renderer, client, &self.globals); + self.figure_mgr.render(renderer, client, &self.globals); renderer.render_post_process( &self.postprocess.model, diff --git a/voxygen/src/ui/cache.rs b/voxygen/src/ui/cache.rs new file mode 100644 index 0000000000..b111222768 --- /dev/null +++ b/voxygen/src/ui/cache.rs @@ -0,0 +1,54 @@ +use super::graphic::{Graphic, GraphicCache, Id as GraphicId}; +use crate::{ + render::{Renderer, Texture, UiPipeline}, + Error, +}; +use conrod_core::text::GlyphCache; +use vek::*; + +pub struct Cache { + glyph_cache: GlyphCache<'static>, + glyph_cache_tex: Texture, + graphic_cache: GraphicCache, + graphic_cache_tex: Texture, +} + +// TODO: Should functions be returning UiError instead of Error? +impl Cache { + pub fn new(renderer: &mut Renderer) -> Result { + let (w, h) = renderer.get_resolution().into_tuple(); + const SCALE_TOLERANCE: f32 = 0.1; + const POSITION_TOLERANCE: f32 = 0.1; + + let graphic_cache_dims = Vec2::new(w * 4, h * 4); + Ok(Self { + glyph_cache: GlyphCache::builder() + .dimensions(w as u32, h as u32) + .scale_tolerance(SCALE_TOLERANCE) + .position_tolerance(POSITION_TOLERANCE) + .build(), + glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?, + graphic_cache: GraphicCache::new(graphic_cache_dims), + graphic_cache_tex: renderer.create_dynamic_texture(graphic_cache_dims)?, + }) + } + pub fn glyph_cache_tex(&self) -> &Texture { + &self.glyph_cache_tex + } + pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture) { + (&mut self.glyph_cache, &self.glyph_cache_tex) + } + pub fn graphic_cache_tex(&self) -> &Texture { + &self.graphic_cache_tex + } + pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture) { + (&mut self.graphic_cache, &self.graphic_cache_tex) + } + pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId { + self.graphic_cache.add_graphic(graphic) + } + pub fn clear_graphic_cache(&mut self, renderer: &mut Renderer, new_size: Vec2) { + self.graphic_cache.clear_cache(new_size); + self.graphic_cache_tex = renderer.create_dynamic_texture(new_size).unwrap(); + } +} diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs new file mode 100644 index 0000000000..161df0ab64 --- /dev/null +++ b/voxygen/src/ui/event.rs @@ -0,0 +1,47 @@ +use conrod_core::{event::Input, input::Button}; +use vek::*; + +#[derive(Clone)] +pub struct Event(pub Input); +impl Event { + pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option { + use conrod_winit::*; + use winit; + // A wrapper around the winit window that allows us to implement the trait necessary for enabling + // the winit <-> conrod conversion functions. + struct WindowRef<'a>(&'a winit::Window); + + // Implement the `WinitWindow` trait for `WindowRef` to allow for generating compatible conversion + // functions. + impl<'a> conrod_winit::WinitWindow for WindowRef<'a> { + fn get_inner_size(&self) -> Option<(u32, u32)> { + winit::Window::get_inner_size(&self.0).map(Into::into) + } + fn hidpi_factor(&self) -> f32 { + winit::Window::get_hidpi_factor(&self.0) as _ + } + } + convert_event!(event, &WindowRef(window.window())).map(|input| Self(input)) + } + pub fn is_keyboard_or_mouse(&self) -> bool { + match self.0 { + Input::Press(_) + | Input::Release(_) + | Input::Motion(_) + | Input::Touch(_) + | Input::Text(_) => true, + _ => false, + } + } + pub fn is_keyboard(&self) -> bool { + match self.0 { + Input::Press(Button::Keyboard(_)) + | Input::Release(Button::Keyboard(_)) + | Input::Text(_) => true, + _ => false, + } + } + pub fn new_resize(dims: Vec2) -> Self { + Self(Input::Resize(dims.x, dims.y)) + } +} diff --git a/voxygen/src/ui/graphic/graphic.rs b/voxygen/src/ui/graphic/graphic.rs index 38b80a08e4..71748def81 100644 --- a/voxygen/src/ui/graphic/graphic.rs +++ b/voxygen/src/ui/graphic/graphic.rs @@ -7,7 +7,7 @@ use vek::*; pub enum Graphic { Image(Arc), - Voxel(Arc), + Voxel(Arc, Option), Blank, } @@ -94,9 +94,8 @@ impl GraphicCache { .pixels() .map(|p| p.data) .collect::>(), - Graphic::Voxel(ref vox) => { - super::renderer::draw_vox(&vox.as_ref().into(), aabr.size().into()) - } + Graphic::Voxel(ref vox, min_samples) => + super::renderer::draw_vox(&vox.as_ref().into(), aabr.size().into(), *min_samples), Graphic::Blank => return None, }; diff --git a/voxygen/src/ui/graphic/renderer.rs b/voxygen/src/ui/graphic/renderer.rs index 54945f8b96..ec3118e496 100644 --- a/voxygen/src/ui/graphic/renderer.rs +++ b/voxygen/src/ui/graphic/renderer.rs @@ -4,6 +4,7 @@ use common::{ vol::{ReadVol, SizedVol, Vox}, }; use euc::{buffer::Buffer2d, rasterizer, Pipeline}; +use image::{DynamicImage, RgbaImage}; use vek::*; struct Voxel { @@ -56,8 +57,13 @@ impl<'a> Pipeline for Voxel { } } -pub fn draw_vox(segment: &Segment, output_size: Vec2) -> Vec<[u8; 4]> { - let dims = output_size.map(|e| e as usize).into_array(); +pub fn draw_vox( + segment: &Segment, + output_size: Vec2, + min_samples: Option, +) -> Vec<[u8; 4]> { + let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize; + let dims = output_size.map(|e| e as usize * scale).into_array(); let mut color = Buffer2d::new(dims, [0; 4]); let mut depth = Buffer2d::new(dims, 1.0); @@ -79,8 +85,33 @@ pub fn draw_vox(segment: &Segment, output_size: Vec2) -> Vec<[u8; 4]> { &mut depth, ); - // TODO: remove this clone - color.as_ref().to_vec() + if scale > 1 { + DynamicImage::ImageRgba8( + RgbaImage::from_vec( + dims[0] as u32, + dims[1] as u32, + color + .as_ref() + .iter() + .flatten() + .cloned() + .collect::>(), + ) + .unwrap(), + ) + .resize_exact( + output_size.x as u32, + output_size.y as u32, + image::FilterType::Triangle, + ) + .to_rgba() + .pixels() + .map(|p| p.data) + .collect::>() + } else { + // TODO: remove clone + color.as_ref().to_vec() + } } fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 { diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs index 04167078d6..133dafb689 100644 --- a/voxygen/src/ui/img_ids.rs +++ b/voxygen/src/ui/img_ids.rs @@ -3,9 +3,8 @@ use common::assets::{load, Error}; use dot_vox::DotVoxData; use image::DynamicImage; -pub struct BlankGraphic; -pub struct ImageGraphic; -pub struct VoxelGraphic; +pub enum BlankGraphic {} +pub enum ImageGraphic {} pub trait GraphicCreator<'a> { type Specifier; @@ -23,10 +22,37 @@ impl<'a> GraphicCreator<'a> for ImageGraphic { Ok(Graphic::Image(load::(specifier)?)) } } + +pub enum VoxelGraphic {} +pub enum VoxelMsGraphic {} +pub enum VoxelMs4Graphic {} +pub enum VoxelMs9Graphic {} + impl<'a> GraphicCreator<'a> for VoxelGraphic { type Specifier = &'a str; fn new_graphic(specifier: Self::Specifier) -> Result { - Ok(Graphic::Voxel(load::(specifier)?)) + Ok(Graphic::Voxel(load::(specifier)?, None)) + } +} +impl<'a> GraphicCreator<'a> for VoxelMsGraphic { + type Specifier = (&'a str, u8); + fn new_graphic(specifier: Self::Specifier) -> Result { + Ok(Graphic::Voxel( + load::(specifier.0)?, + Some(specifier.1), + )) + } +} +impl<'a> GraphicCreator<'a> for VoxelMs4Graphic { + type Specifier = &'a str; + fn new_graphic(specifier: Self::Specifier) -> Result { + Ok(Graphic::Voxel(load::(specifier)?, Some(4))) + } +} +impl<'a> GraphicCreator<'a> for VoxelMs9Graphic { + type Specifier = &'a str; + fn new_graphic(specifier: Self::Specifier) -> Result { + Ok(Graphic::Voxel(load::(specifier)?, Some(9))) } } @@ -59,9 +85,9 @@ macro_rules! image_ids { impl $Ids { pub fn load(ui: &mut crate::ui::Ui) -> Result { - use crate::ui::GraphicCreator; + use crate::ui::img_ids::GraphicCreator; Ok(Self { - $($( $name: ui.add_graphic(<$T>::new_graphic($specifier)?), )*)* + $($( $name: ui.add_graphic(<$T as GraphicCreator>::new_graphic($specifier)?), )*)* }) } } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 71c2d2db64..f151142e53 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -1,134 +1,50 @@ +mod cache; +mod event; mod graphic; +mod scale; mod util; mod widgets; #[macro_use] -mod img_ids; +pub mod img_ids; #[macro_use] mod font_ids; +pub use event::Event; pub use graphic::Graphic; -pub use img_ids::{BlankGraphic, GraphicCreator, ImageGraphic, VoxelGraphic}; -pub(self) use util::{linear_to_srgb, srgb_to_linear}; +pub use scale::ScaleMode; pub use widgets::toggle_button::ToggleButton; use crate::{ render::{ - create_ui_quad, create_ui_tri, Mesh, Model, RenderError, Renderer, Texture, UiMode, + create_ui_quad, create_ui_tri, DynamicModel, Mesh, RenderError, Renderer, UiMode, UiPipeline, }, window::Window, Error, }; +use cache::Cache; use common::assets; use conrod_core::{ event::Input, graph::Graph, image::{Id as ImgId, Map}, - input::{touch::Touch, Button, Motion, Widget}, + input::{touch::Touch, Motion, Widget}, render::Primitive, - text::{self, GlyphCache}, + text::{self, font}, widget::{id::Generator, Id as WidgId}, Ui as CrUi, UiBuilder, UiCell, }; -use graphic::{GraphicCache, Id as GraphicId}; +use graphic::Id as GraphicId; +use scale::Scale; +use std::ops::Range; use std::sync::Arc; +use util::{linear_to_srgb, srgb_to_linear}; use vek::*; #[derive(Debug)] pub enum UiError { RenderError(RenderError), } -#[derive(Clone)] -pub struct Event(Input); -impl Event { - pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option { - use conrod_winit::*; - use winit; - // A wrapper around the winit window that allows us to implement the trait necessary for enabling - // the winit <-> conrod conversion functions. - struct WindowRef<'a>(&'a winit::Window); - - // Implement the `WinitWindow` trait for `WindowRef` to allow for generating compatible conversion - // functions. - impl<'a> conrod_winit::WinitWindow for WindowRef<'a> { - fn get_inner_size(&self) -> Option<(u32, u32)> { - winit::Window::get_inner_size(&self.0).map(Into::into) - } - fn hidpi_factor(&self) -> f32 { - winit::Window::get_hidpi_factor(&self.0) as _ - } - } - convert_event!(event, &WindowRef(window.window())).map(|input| Self(input)) - } - pub fn is_keyboard_or_mouse(&self) -> bool { - match self.0 { - Input::Press(_) - | Input::Release(_) - | Input::Motion(_) - | Input::Touch(_) - | Input::Text(_) => true, - _ => false, - } - } - pub fn is_keyboard(&self) -> bool { - match self.0 { - Input::Press(Button::Keyboard(_)) - | Input::Release(Button::Keyboard(_)) - | Input::Text(_) => true, - _ => false, - } - } - pub fn new_resize(dims: Vec2) -> Self { - Self(Input::Resize(dims.x, dims.y)) - } -} - -pub struct Cache { - glyph_cache: GlyphCache<'static>, - glyph_cache_tex: Texture, - graphic_cache: graphic::GraphicCache, - graphic_cache_tex: Texture, -} - -// TODO: Should functions be returning UiError instead of Error? -impl Cache { - pub fn new(renderer: &mut Renderer) -> Result { - let (w, h) = renderer.get_resolution().into_tuple(); - const SCALE_TOLERANCE: f32 = 0.1; - const POSITION_TOLERANCE: f32 = 0.1; - - let graphic_cache_dims = Vec2::new(w * 4, h * 4); - Ok(Self { - glyph_cache: GlyphCache::builder() - .dimensions(w as u32, h as u32) - .scale_tolerance(SCALE_TOLERANCE) - .position_tolerance(POSITION_TOLERANCE) - .build(), - glyph_cache_tex: renderer.create_dynamic_texture((w, h).into())?, - graphic_cache: GraphicCache::new(graphic_cache_dims), - graphic_cache_tex: renderer.create_dynamic_texture(graphic_cache_dims)?, - }) - } - pub fn glyph_cache_tex(&self) -> &Texture { - &self.glyph_cache_tex - } - pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture) { - (&mut self.glyph_cache, &self.glyph_cache_tex) - } - pub fn graphic_cache_tex(&self) -> &Texture { - &self.graphic_cache_tex - } - pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture) { - (&mut self.graphic_cache, &self.graphic_cache_tex) - } - pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId { - self.graphic_cache.add_graphic(graphic) - } - pub fn clear_graphic_cache(&mut self, renderer: &mut Renderer, new_size: Vec2) { - self.graphic_cache.clear_cache(new_size); - self.graphic_cache_tex = renderer.create_dynamic_texture(new_size).unwrap(); - } -} enum DrawKind { Image, @@ -136,90 +52,24 @@ enum DrawKind { Plain, } enum DrawCommand { - Draw { - kind: DrawKind, - model: Model, - }, + Draw { kind: DrawKind, verts: Range }, Scissor(Aabr), } impl DrawCommand { - fn image(model: Model) -> DrawCommand { + fn image(verts: Range) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Image, - model, + verts, } } - fn plain(model: Model) -> DrawCommand { + fn plain(verts: Range) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Plain, - model, + verts, } } } -// How to scale the ui -pub enum ScaleMode { - // Scale against physical size - Absolute(f64), - // Use the dpi factor provided by the windowing system (i.e. use logical size) - DpiFactor, - // Scale based on the window's physical size, but maintain aspect ratio of widgets - // Contains width and height of the "default" window size (ie where there should be no scaling) - RelativeToWindow(Vec2), -} - -struct Scale { - // Type of scaling to use - mode: ScaleMode, - // Current dpi factor - dpi_factor: f64, - // Current logical window size - window_dims: Vec2, -} - -impl Scale { - fn new(window: &Window, mode: ScaleMode) -> Self { - let window_dims = window.logical_size(); - let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x; - Scale { - mode, - dpi_factor, - window_dims, - } - } - // Change the scaling mode - pub fn scaling_mode(&mut self, mode: ScaleMode) { - self.mode = mode; - } - // Calculate factor to transform between logical coordinates and our scaled coordinates - fn scale_factor_logical(&self) -> f64 { - match self.mode { - ScaleMode::Absolute(scale) => scale / self.dpi_factor, - ScaleMode::DpiFactor => 1.0, - ScaleMode::RelativeToWindow(dims) => { - (self.window_dims.x / dims.x).min(self.window_dims.y / dims.y) - } - } - } - // Calculate factor to transform between physical coordinates and our scaled coordinates - fn scale_factor_physical(&self) -> f64 { - self.scale_factor_logical() * self.dpi_factor - } - // Updates internal window size (and/or dpi_factor) - fn window_resized(&mut self, new_dims: Vec2, renderer: &Renderer) { - self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x; - self.window_dims = new_dims; - } - // Get scaled window size - fn scaled_window_size(&self) -> Vec2 { - self.window_dims / self.scale_factor_logical() - } - // Transform point from logical to scaled coordinates - fn scale_point(&self, point: Vec2) -> Vec2 { - point / self.scale_factor_logical() - } -} - pub struct Font(text::Font); impl assets::Asset for Font { fn load(specifier: &str) -> Result { @@ -235,6 +85,8 @@ pub struct Ui { cache: Cache, // Draw commands for the next render draw_commands: Vec, + // Model for drawing the ui + model: DynamicModel, // Stores new window size for updating scaling window_resized: Option>, // Scaling of the ui @@ -250,8 +102,9 @@ impl Ui { ui: UiBuilder::new(win_dims).build(), image_map: Map::new(), cache: Cache::new(window.renderer_mut())?, - window_resized: None, draw_commands: vec![], + model: window.renderer_mut().create_dynamic_model(100)?, + window_resized: None, scale, }) } @@ -268,7 +121,7 @@ impl Ui { self.image_map.insert(self.cache.add_graphic(graphic)) } - pub fn new_font(&mut self, mut font: Arc) -> text::font::Id { + pub fn new_font(&mut self, mut font: Arc) -> font::Id { self.ui.fonts.insert(font.as_ref().0.clone()) } @@ -309,7 +162,9 @@ impl Ui { } pub fn handle_event(&mut self, event: Event) { match event.0 { - Input::Resize(w, h) => self.window_resized = Some(Vec2::new(w, h)), + Input::Resize(w, h) if w > 1.0 && h > 1.0 => { + self.window_resized = Some(Vec2::new(w, h)) + } Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch { xy: self.scale.scale_point(touch.xy.into()).into_array(), ..touch @@ -338,308 +193,315 @@ impl Ui { } pub fn maintain(&mut self, renderer: &mut Renderer) { - let ref mut ui = self.ui; // Regenerate draw commands and associated models only if the ui changed - if let Some(mut primitives) = ui.draw_if_changed() { - self.draw_commands.clear(); - let mut mesh = Mesh::new(); + let mut primitives = match self.ui.draw_if_changed() { + Some(primitives) => primitives, + None => return, + }; - // TODO: this could be removed entirely if the draw call just used both textures - // however this allows for flexibility if we want to interleave other draw calls later - enum State { - Image, - Plain, + self.draw_commands.clear(); + let mut mesh = Mesh::new(); + + // TODO: this could be removed entirely if the draw call just used both textures + // however this allows for flexibility if we want to interleave other draw calls later + enum State { + Image, + Plain, + }; + + let mut current_state = State::Plain; + let mut start = 0; + + let window_scizzor = default_scissor(renderer); + let mut current_scizzor = window_scizzor; + + // Switches to the `Plain` state and completes the previous `Command` if not already in the + // `Plain` state. + macro_rules! switch_to_plain_state { + () => { + if let State::Image = current_state { + self.draw_commands + .push(DrawCommand::image(start..mesh.vertices().len())); + current_state = State::Plain; + start = mesh.vertices().len(); + } }; + } - let mut current_state = State::Plain; + let p_scale_factor = self.scale.scale_factor_physical(); - let window_scizzor = default_scissor(renderer); - let mut current_scizzor = window_scizzor; + while let Some(prim) = primitives.next() { + let Primitive { + kind, + scizzor, + rect, + .. + } = prim; - // Switches to the `Plain` state and completes the previous `Command` if not already in the - // `Plain` state. - macro_rules! switch_to_plain_state { - () => { - if let State::Image = current_state { - self.draw_commands - .push(DrawCommand::image(renderer.create_model(&mesh).unwrap())); - mesh.clear(); - current_state = State::Plain; - } - }; + // Check for a change in the scizzor + let new_scizzor = { + let (l, b, w, h) = scizzor.l_b_w_h(); + // Calculate minimum x and y coordinates while + // - flipping y axis (from +up to +down) + // - moving origin to top-left corner (from middle) + let min_x = self.ui.win_w / 2.0 + l; + let min_y = self.ui.win_h / 2.0 - b - h; + Aabr { + min: Vec2 { + x: (min_x * p_scale_factor) as u16, + y: (min_y * p_scale_factor) as u16, + }, + max: Vec2 { + x: ((min_x + w) * p_scale_factor) as u16, + y: ((min_y + h) * p_scale_factor) as u16, + }, + } + .intersection(window_scizzor) + }; + if new_scizzor != current_scizzor { + // Finish the current command + self.draw_commands.push(match current_state { + State::Plain => DrawCommand::plain(start..mesh.vertices().len()), + State::Image => DrawCommand::image(start..mesh.vertices().len()), + }); + start = mesh.vertices().len(); + + // Update the scizzor and produce a command. + current_scizzor = new_scizzor; + self.draw_commands.push(DrawCommand::Scissor(new_scizzor)); } - let p_scale_factor = self.scale.scale_factor_physical(); - - while let Some(prim) = primitives.next() { - let Primitive { - kind, - scizzor, - id: _id, - rect, - } = prim; - - // Check for a change in the scizzor - let new_scizzor = { - let (l, b, w, h) = scizzor.l_b_w_h(); - // Calculate minimum x and y coordinates while - // - flipping y axis (from +up to +down) - // - moving origin to top-left corner (from middle) - let min_x = ui.win_w / 2.0 + l; - let min_y = ui.win_h / 2.0 - b - h; - Aabr { - min: Vec2 { - x: (min_x * p_scale_factor) as u16, - y: (min_y * p_scale_factor) as u16, - }, - max: Vec2 { - x: ((min_x + w) * p_scale_factor) as u16, - y: ((min_y + h) * p_scale_factor) as u16, - }, - } - .intersection(window_scizzor) - }; - if new_scizzor != current_scizzor { - // Finish the current command - self.draw_commands.push(match current_state { - State::Plain => DrawCommand::plain(renderer.create_model(&mesh).unwrap()), - State::Image => DrawCommand::image(renderer.create_model(&mesh).unwrap()), - }); - mesh.clear(); - - // Update the scizzor and produce a command. - current_scizzor = new_scizzor; - self.draw_commands.push(DrawCommand::Scissor(new_scizzor)); + // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0) + let ui_win_w = self.ui.win_w; + let ui_win_h = self.ui.win_h; + let vx = |x: f64| (x / ui_win_w * 2.0) as f32; + let vy = |y: f64| (y / ui_win_h * 2.0) as f32; + let gl_aabr = |rect: conrod_core::Rect| { + let (l, r, b, t) = rect.l_r_b_t(); + Aabr { + min: Vec2::new(vx(l), vy(b)), + max: Vec2::new(vx(r), vy(t)), } + }; - // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0) - let vx = |x: f64| (x / ui.win_w * 2.0) as f32; - let vy = |y: f64| (y / ui.win_h * 2.0) as f32; - let gl_aabr = |rect: conrod_core::Rect| { - let (l, r, b, t) = rect.l_r_b_t(); - Aabr { - min: Vec2::new(vx(l), vy(b)), - max: Vec2::new(vx(r), vy(t)), + use conrod_core::render::PrimitiveKind; + match kind { + PrimitiveKind::Image { + image_id, + color, + source_rect, + } => { + let graphic_id = self + .image_map + .get(&image_id) + .expect("Image does not exist in image map"); + let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex(); + + match graphic_cache.get_graphic(*graphic_id) { + Some(Graphic::Blank) | None => continue, + _ => {} } - }; - use conrod_core::render::PrimitiveKind; - match kind { - PrimitiveKind::Image { - image_id, - color, - source_rect, - } => { - let graphic_id = self - .image_map - .get(&image_id) - .expect("Image does not exist in image map"); - let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex(); + // Switch to the image state if we are not in it already + if let State::Plain = current_state { + self.draw_commands + .push(DrawCommand::plain(start..mesh.vertices().len())); + start = mesh.vertices().len(); + current_state = State::Image; + } - match graphic_cache.get_graphic(*graphic_id) { - Some(Graphic::Blank) | None => continue, - _ => {} + let color = + srgb_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into()); + + let resolution = Vec2::new( + (rect.w() * p_scale_factor).round() as u16, + (rect.h() * p_scale_factor).round() as u16, + ); + // Transform the source rectangle into uv coordinate + // TODO: make sure this is right + let source_aabr = { + let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); /*match source_rect { + Some(src_rect) => { + let (l, r, b, t) = src_rect.l_r_b_t(); + ((l / image_w) as f32, + (r / image_w) as f32, + (b / image_h) as f32, + (t / image_h) as f32) + } + None => (0.0, 1.0, 0.0, 1.0), + };*/ + Aabr { + min: Vec2::new(uv_l, uv_b), + max: Vec2::new(uv_r, uv_t), } + }; + let (cache_w, cache_h) = + cache_tex.get_dimensions().map(|e| e as f32).into_tuple(); - // Switch to the `Image` state for this image if we're not in it already. - if let State::Plain = current_state { - self.draw_commands - .push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())); - mesh.clear(); - current_state = State::Image; - } + // Cache graphic at particular resolution + let uv_aabr = match graphic_cache.cache_res( + *graphic_id, + resolution, + source_aabr, + |aabr, data| { + let offset = aabr.min.into_array(); + let size = aabr.size().into_array(); + renderer.update_texture(cache_tex, offset, size, &data); + }, + ) { + Some(aabr) => Aabr { + min: Vec2::new( + aabr.min.x as f32 / cache_w, + aabr.max.y as f32 / cache_h, + ), + max: Vec2::new( + aabr.max.x as f32 / cache_w, + aabr.min.y as f32 / cache_h, + ), + }, + None => continue, + }; - let color = srgb_to_linear( - color.unwrap_or(conrod_core::color::WHITE).to_fsa().into(), - ); + mesh.push_quad(create_ui_quad(gl_aabr(rect), uv_aabr, color, UiMode::Image)); + } + PrimitiveKind::Text { + color, + text, + font_id, + } => { + switch_to_plain_state!(); + // Get screen width and height + let (screen_w, screen_h) = + renderer.get_resolution().map(|e| e as f32).into_tuple(); + // Calculate dpi factor + let dpi_factor = screen_w / ui_win_w as f32; - let resolution = Vec2::new( - (rect.w() * p_scale_factor) as u16, - (rect.h() * p_scale_factor) as u16, - ); - // Transform the source rectangle into uv coordinate - // TODO: make sure this is right - let source_aabr = { - let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); /*match source_rect { - Some(src_rect) => { - let (l, r, b, t) = src_rect.l_r_b_t(); - ((l / image_w) as f32, - (r / image_w) as f32, - (b / image_h) as f32, - (t / image_h) as f32) - } - None => (0.0, 1.0, 0.0, 1.0), - };*/ - Aabr { - min: Vec2::new(uv_l, uv_b), - max: Vec2::new(uv_r, uv_t), - } - }; - let (cache_w, cache_h) = - cache_tex.get_dimensions().map(|e| e as f32).into_tuple(); + let positioned_glyphs = text.positioned_glyphs(dpi_factor); + let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex(); + // Queue the glyphs to be cached + for glyph in positioned_glyphs { + glyph_cache.queue_glyph(font_id.index(), glyph.clone()); + } - // Cache graphic at particular resolution - let uv_aabr = match graphic_cache.cache_res( - *graphic_id, - resolution, - source_aabr, - |aabr, data| { - let offset = aabr.min.into_array(); - let size = aabr.size().into_array(); - renderer.update_texture(cache_tex, offset, size, &data); - }, - ) { - Some(aabr) => Aabr { + glyph_cache + .cache_queued(|rect, data| { + let offset = [rect.min.x as u16, rect.min.y as u16]; + let size = [rect.width() as u16, rect.height() as u16]; + + let new_data = data + .iter() + .map(|x| [255, 255, 255, *x]) + .collect::>(); + + renderer.update_texture(cache_tex, offset, size, &new_data); + }) + .unwrap(); + + let color = srgb_to_linear(color.to_fsa().into()); + + for g in positioned_glyphs { + if let Ok(Some((uv_rect, screen_rect))) = + glyph_cache.rect_for(font_id.index(), g) + { + let uv = Aabr { + min: Vec2::new(uv_rect.min.x, uv_rect.max.y), + max: Vec2::new(uv_rect.max.x, uv_rect.min.y), + }; + let rect = Aabr { min: Vec2::new( - aabr.min.x as f32 / cache_w, - aabr.max.y as f32 / cache_h, + (screen_rect.min.x as f32 / screen_w - 0.5) * 2.0, + (screen_rect.max.y as f32 / screen_h - 0.5) * -2.0, ), max: Vec2::new( - aabr.max.x as f32 / cache_w, - aabr.min.y as f32 / cache_h, + (screen_rect.max.x as f32 / screen_w - 0.5) * 2.0, + (screen_rect.min.y as f32 / screen_h - 0.5) * -2.0, ), - }, - None => continue, - }; - - mesh.push_quad(create_ui_quad( - gl_aabr(rect), - uv_aabr, - color, - UiMode::Image, - )); + }; + mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text)); + } } - PrimitiveKind::Text { + } + PrimitiveKind::Rectangle { color } => { + let color = srgb_to_linear(color.to_fsa().into()); + // Don't draw a transparent rectangle + if color[3] == 0.0 { + continue; + } + + switch_to_plain_state!(); + + mesh.push_quad(create_ui_quad( + gl_aabr(rect), + Aabr { + min: Vec2::new(0.0, 0.0), + max: Vec2::new(0.0, 0.0), + }, color, - text, - font_id, - } => { - switch_to_plain_state!(); - // Get screen width and height - let (screen_w, screen_h) = - renderer.get_resolution().map(|e| e as f32).into_tuple(); - // Calculate dpi factor - let dpi_factor = screen_w / ui.win_w as f32; - - let positioned_glyphs = text.positioned_glyphs(dpi_factor); - let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex(); - // Queue the glyphs to be cached - for glyph in positioned_glyphs { - glyph_cache.queue_glyph(font_id.index(), glyph.clone()); - } - - glyph_cache - .cache_queued(|rect, data| { - let offset = [rect.min.x as u16, rect.min.y as u16]; - let size = [rect.width() as u16, rect.height() as u16]; - - let new_data = data - .iter() - .map(|x| [255, 255, 255, *x]) - .collect::>(); - - renderer.update_texture(cache_tex, offset, size, &new_data); - }) - .unwrap(); - - let color = srgb_to_linear(color.to_fsa().into()); - - for g in positioned_glyphs { - if let Ok(Some((uv_rect, screen_rect))) = - glyph_cache.rect_for(font_id.index(), g) - { - let uv = Aabr { - min: Vec2::new(uv_rect.min.x, uv_rect.max.y), - max: Vec2::new(uv_rect.max.x, uv_rect.min.y), - }; - let rect = Aabr { - min: Vec2::new( - (screen_rect.min.x as f32 / screen_w - 0.5) * 2.0, - (screen_rect.max.y as f32 / screen_h - 0.5) * -2.0, - ), - max: Vec2::new( - (screen_rect.max.x as f32 / screen_w - 0.5) * 2.0, - (screen_rect.min.y as f32 / screen_h - 0.5) * -2.0, - ), - }; - mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text)); - } - } + UiMode::Geometry, + )); + } + PrimitiveKind::TrianglesSingleColor { color, triangles } => { + // Don't draw transparent triangle or switch state if there are actually no triangles + let color = srgb_to_linear(Rgba::from(Into::<[f32; 4]>::into(color))); + if triangles.is_empty() || color[3] == 0.0 { + continue; } - PrimitiveKind::Rectangle { color } => { - let color = srgb_to_linear(color.to_fsa().into()); - // Don't draw a transparent rectangle - if color[3] == 0.0 { - continue; - } - switch_to_plain_state!(); + switch_to_plain_state!(); - mesh.push_quad(create_ui_quad( - gl_aabr(rect), - Aabr { - min: Vec2::new(0.0, 0.0), - max: Vec2::new(0.0, 0.0), - }, + for tri in triangles { + let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1])); + let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1])); + let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1])); + // If triangle is clockwise reverse it + let (v1, v2): (Vec3, Vec3) = ((p2 - p1).into(), (p3 - p1).into()); + let triangle = if v1.cross(v2).z > 0.0 { + [p1.into_array(), p2.into_array(), p3.into_array()] + } else { + [p2.into_array(), p1.into_array(), p3.into_array()] + }; + mesh.push_tri(create_ui_tri( + triangle, + [[0.0; 2]; 3], color, UiMode::Geometry, )); } - PrimitiveKind::TrianglesSingleColor { color, triangles } => { - // Don't draw transparent triangle or switch state if there are actually no triangles - let color = srgb_to_linear(Rgba::from(Into::<[f32; 4]>::into(color))); - if triangles.is_empty() || color[3] == 0.0 { - continue; - } - - switch_to_plain_state!(); - - for tri in triangles { - let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1])); - let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1])); - let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1])); - // If triangle is clockwise reverse it - let (v1, v2): (Vec3, Vec3) = - ((p2 - p1).into(), (p3 - p1).into()); - let triangle = if v1.cross(v2).z > 0.0 { - [p1.into_array(), p2.into_array(), p3.into_array()] - } else { - [p2.into_array(), p1.into_array(), p3.into_array()] - }; - mesh.push_tri(create_ui_tri( - triangle, - [[0.0; 2]; 3], - color, - UiMode::Geometry, - )); - } - } - _ => {} // TODO: Add this - //PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);} - // Other uneeded for now - //PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);} } + _ => {} // TODO: Add this + //PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);} + // Other uneeded for now + //PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);} } - // Enter the final command - self.draw_commands.push(match current_state { - State::Plain => DrawCommand::plain(renderer.create_model(&mesh).unwrap()), - State::Image => DrawCommand::image(renderer.create_model(&mesh).unwrap()), - }); + } + // Enter the final command + self.draw_commands.push(match current_state { + State::Plain => DrawCommand::plain(start..mesh.vertices().len()), + State::Image => DrawCommand::image(start..mesh.vertices().len()), + }); - // Handle window resizing - if let Some(new_dims) = self.window_resized.take() { - self.scale.window_resized(new_dims, renderer); - let (w, h) = self.scale.scaled_window_size().into_tuple(); - self.ui.handle_event(Input::Resize(w, h)); + // create a larger dynamic model if the mesh is larger than the current model size + if self.model.vbuf.len() < mesh.vertices().len() { + self.model = renderer + .create_dynamic_model(mesh.vertices().len() * 4 / 3) + .unwrap(); + } + renderer.update_model(&self.model, &mesh, 0).unwrap(); + // Update model with new mesh - let res = renderer.get_resolution(); - // Avoid panic in graphic cache when minimizing - if res.x > 0 && res.y > 0 { - self.cache - .clear_graphic_cache(renderer, renderer.get_resolution().map(|e| e * 4)); - } - // TODO: probably need to resize glyph cache, see conrod's gfx backend for reference + // Handle window resizing + if let Some(new_dims) = self.window_resized.take() { + self.scale.window_resized(new_dims, renderer); + let (w, h) = self.scale.scaled_window_size().into_tuple(); + self.ui.handle_event(Input::Resize(w, h)); + + let res = renderer.get_resolution(); + // Avoid panic in graphic cache when minimizing + if res.x > 0 && res.y > 0 { + self.cache + .clear_graphic_cache(renderer, renderer.get_resolution().map(|e| e * 4)); } + // TODO: probably need to resize glyph cache, see conrod's gfx backend for reference } } @@ -650,11 +512,12 @@ impl Ui { DrawCommand::Scissor(scizzor) => { scissor = *scizzor; } - DrawCommand::Draw { kind, model } => { + DrawCommand::Draw { kind, verts } => { let tex = match kind { DrawKind::Image => self.cache.graphic_cache_tex(), DrawKind::Plain => self.cache.glyph_cache_tex(), }; + let model = self.model.submodel(verts.clone()); renderer.render_ui_element(&model, &tex, scissor); } } diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs new file mode 100644 index 0000000000..2daae0506a --- /dev/null +++ b/voxygen/src/ui/scale.rs @@ -0,0 +1,65 @@ +use crate::{render::Renderer, window::Window}; +use vek::*; + +// How the ui is scaled +pub enum ScaleMode { + // Scale against physical size + Absolute(f64), + // Use the dpi factor provided by the windowing system (i.e. use logical size) + DpiFactor, + // Scale based on the window's physical size, but maintain aspect ratio of widgets + // Contains width and height of the "default" window size (ie where there should be no scaling) + RelativeToWindow(Vec2), +} + +pub struct Scale { + // Type of scaling to use + mode: ScaleMode, + // Current dpi factor + dpi_factor: f64, + // Current logical window size + window_dims: Vec2, +} + +impl Scale { + pub fn new(window: &Window, mode: ScaleMode) -> Self { + let window_dims = window.logical_size(); + let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x; + Scale { + mode, + dpi_factor, + window_dims, + } + } + // Change the scaling mode + pub fn scaling_mode(&mut self, mode: ScaleMode) { + self.mode = mode; + } + // Calculate factor to transform between logical coordinates and our scaled coordinates + pub fn scale_factor_logical(&self) -> f64 { + match self.mode { + ScaleMode::Absolute(scale) => scale / self.dpi_factor, + ScaleMode::DpiFactor => 1.0, + ScaleMode::RelativeToWindow(dims) => { + (self.window_dims.x / dims.x).min(self.window_dims.y / dims.y) + } + } + } + // Calculate factor to transform between physical coordinates and our scaled coordinates + pub fn scale_factor_physical(&self) -> f64 { + self.scale_factor_logical() * self.dpi_factor + } + // Updates internal window size (and/or dpi_factor) + pub fn window_resized(&mut self, new_dims: Vec2, renderer: &Renderer) { + self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x; + self.window_dims = new_dims; + } + // Get scaled window size + pub fn scaled_window_size(&self) -> Vec2 { + self.window_dims / self.scale_factor_logical() + } + // Transform point from logical to scaled coordinates + pub fn scale_point(&self, point: Vec2) -> Vec2 { + point / self.scale_factor_logical() + } +}