Former-commit-id: 9306d50f9ba615dd6ca091753ff26688767b6291
This commit is contained in:
jshipsey 2019-05-12 21:52:44 -04:00
commit e8c91f753e
65 changed files with 1413 additions and 650 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ voxygen/keybinds.toml
settings.toml settings.toml
*.rar *.rar
*.log *.log
run.sh

View File

@ -28,6 +28,9 @@ before_script:
- if [ ! -z "${SOURCE_PROJECT}" -a "${SOURCE_PROJECT}" != " " ]; then - if [ ! -z "${SOURCE_PROJECT}" -a "${SOURCE_PROJECT}" != " " ]; then
echo "THIS SEEMS TO BE A MERGE PIPELINE FROM ${SOURCE_PROJECT}/${SOURCE_BRANCH}"; 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 pull "https://gitlab.com/${SOURCE_PROJECT}/veloren.git" "${SOURCE_BRANCH}";
git lfs install;
git lfs fetch;
git lfs checkout;
fi; fi;
- git status - git status
- if [ -d target ]; then - if [ -d target ]; then

View File

@ -10,6 +10,10 @@ members = [
] ]
[profile.dev] [profile.dev]
opt-level = 2
overflow-checks = false
[profile.release] [profile.release]
debug = true debug = true
codegen-units = 1
lto = true

BIN
assets/voxygen/element/buttons/grid.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/x.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/frames/divider_charwindow.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/tab_bg.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/tab_small_closed.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/tab_small_open.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/window2.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/frames/window_3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/xp_charwindow.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/back.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/belt.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/chest.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/feet.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/gem.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/hands.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/head.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/legs.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/mainhand.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/necklace.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/offhand.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/ring.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/shoulders.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/tabard.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/crosshair.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/Wood Training 2h.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sword.vox (Stored with Git LFS)

Binary file not shown.

View File

@ -1,16 +1,20 @@
use specs::{Component, VecStorage}; use specs::{Component, Entity as EcsEntity, VecStorage};
use vek::*; use vek::*;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug)]
pub enum Agent { pub enum Agent {
Wanderer(Vec2<f32>), Wanderer(Vec2<f32>),
Pet {
target: EcsEntity,
offset: Vec2<f32>,
},
} }
impl Component for Agent { impl Component for Agent {
type Storage = VecStorage<Self>; type Storage = VecStorage<Self>;
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, Debug)]
pub struct Control { pub struct Control {
pub move_dir: Vec2<f32>, pub move_dir: Vec2<f32>,
pub jumping: bool, pub jumping: bool,

View File

@ -140,6 +140,16 @@ pub struct AnimationHistory {
pub time: f64, 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)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Animation { pub enum Animation {
Idle, Idle,

View File

@ -226,7 +226,7 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> {
for _ in 0..100 { for _ in 0..100 {
match outgoing_chunks.pop_front() { match outgoing_chunks.pop_front() {
Some(mut chunk) => match stream.write(&chunk) { Some(mut chunk) => match stream.write(&chunk) {
Ok(n) => if n == chunk.len() {}, Ok(n) if n == chunk.len() => {}
Ok(n) => { Ok(n) => {
outgoing_chunks.push_front(chunk.split_off(n)); outgoing_chunks.push_front(chunk.split_off(n));
break; break;

View File

@ -9,6 +9,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>> {
to: Vec3<f32>, to: Vec3<f32>,
until: F, until: F,
max_iter: usize, max_iter: usize,
ignore_error: bool,
} }
impl<'a, V: ReadVol, F: RayUntil<V::Vox>> Ray<'a, V, F> { impl<'a, V: ReadVol, F: RayUntil<V::Vox>> Ray<'a, V, F> {
@ -19,6 +20,7 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>> Ray<'a, V, F> {
to, to,
until, until,
max_iter: 100, max_iter: 100,
ignore_error: false,
} }
} }
@ -31,6 +33,11 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>> Ray<'a, V, F> {
self self
} }
pub fn ignore_error(mut self) -> Self {
self.ignore_error = true;
self
}
pub fn cast(mut self) -> (f32, Result<Option<&'a V::Vox>, V::Err>) { pub fn cast(mut self) -> (f32, Result<Option<&'a V::Vox>, V::Err>) {
// TODO: Fully test this! // TODO: Fully test this!
@ -47,17 +54,17 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>> Ray<'a, V, F> {
pos = self.from + dir * dist; pos = self.from + dir * dist;
ipos = pos.map(|e| e.floor() as i32); 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 // Allow one iteration above max
if dist > max { if dist > max {
break; 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 = let deltas =
(dir.map(|e| if e < 0.0 { 0.0 } else { 1.0 }) - pos.map(|e| e.abs().fract())) / dir; (dir.map(|e| if e < 0.0 { 0.0 } else { 1.0 }) - pos.map(|e| e.abs().fract())) / dir;

View File

@ -36,7 +36,7 @@ pub struct DeltaTime(pub f32);
/// too fast, we'd skip important physics events like collisions. This constant determines what /// 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 /// 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. /// 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 struct Changes {
pub new_chunks: HashSet<Vec3<i32>>, pub new_chunks: HashSet<Vec3<i32>>,

View File

@ -15,14 +15,12 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Control>, WriteStorage<'a, Control>,
); );
fn run(&mut self, (mut agents, pos, mut controls): Self::SystemData) { fn run(&mut self, (mut agents, positions, mut controls): Self::SystemData) {
for (mut agent, pos, mut control) in (&mut agents, &pos, &mut controls).join() { for (mut agent, pos, mut control) in (&mut agents, &positions, &mut controls).join() {
match agent { match agent {
Agent::Wanderer(bearing) => { Agent::Wanderer(bearing) => {
*bearing += Vec2::new( *bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
rand::random::<f32>().fract() - 0.5, * 0.1
rand::random::<f32>().fract() - 0.5,
) * 0.1
- *bearing * 0.01 - *bearing * 0.01
- pos.0 * 0.0002; - pos.0 * 0.0002;
@ -30,6 +28,35 @@ impl<'a> System<'a> for Sys {
control.move_dir = bearing.normalized(); 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::<f32>() < 0.003 {
*offset =
Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
* 10.0;
}
}
} }
} }
} }

View File

@ -28,6 +28,13 @@ impl<'a> System<'a> for Sys {
// Movement // Movement
pos.0 += vel.0 * dt.0; 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 // Basic collision with terrain
let mut i = 0; let mut i = 0;
while terrain while terrain

View File

@ -4,7 +4,7 @@
use crate::Server; use crate::Server;
use common::{comp, msg::ServerMsg}; use common::{comp, msg::ServerMsg};
use specs::{join::Join, Entity as EcsEntity}; use specs::{Builder, Entity as EcsEntity, Join};
use vek::*; use vek::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -72,9 +72,15 @@ lazy_static! {
ChatCommand::new( ChatCommand::new(
"tp", "tp",
"{}", "{}",
"/tp <alias>: Teleport to another player", "/tp <alias> : Teleport to another player",
handle_tp handle_tp
), ),
ChatCommand::new(
"pet",
"{}",
"/pet : Spawn a test pet NPC",
handle_pet
),
ChatCommand::new("help", "", "/help: Display this message", handle_help) 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::<comp::phys::Pos>(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) { fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
for cmd in CHAT_COMMANDS.iter() { for cmd in CHAT_COMMANDS.iter() {
server server

View File

@ -82,8 +82,8 @@ impl Server {
for i in 0..4 { for i in 0..4 {
this.create_npc(comp::Character::random()) this.create_npc(comp::Character::random())
.with(comp::Agent::Wanderer(Vec2::zero()))
.with(comp::Control::default()) .with(comp::Control::default())
.with(comp::Agent::Wanderer(Vec2::zero()))
.build(); .build();
} }
@ -121,6 +121,7 @@ impl Server {
.with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))) .with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)))
.with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Vel(Vec3::zero()))
.with(comp::phys::Dir(Vec3::unit_y())) .with(comp::phys::Dir(Vec3::unit_y()))
.with(comp::AnimationHistory::new(Animation::Idle))
.with(character) .with(character)
} }
@ -138,14 +139,7 @@ impl Server {
state.write_component(entity, comp::phys::ForceUpdate); state.write_component(entity, comp::phys::ForceUpdate);
// Set initial animation // Set initial animation
state.write_component( state.write_component(entity, comp::AnimationHistory::new(Animation::Idle));
entity,
comp::AnimationHistory {
last: None,
current: Animation::Idle,
time: 0.0,
},
);
// Tell the client his request was successful // Tell the client his request was successful
client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character))); client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character)));
@ -230,7 +224,9 @@ impl Server {
.join() .join()
{ {
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); 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); min_dist = min_dist.min(dist);
} }
@ -272,7 +268,7 @@ impl Server {
// (All components Sphynx tracks) // (All components Sphynx tracks)
client.notify(ServerMsg::InitialSync { client.notify(ServerMsg::InitialSync {
ecs_state: self.state.ecs().gen_state_package(), 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); self.clients.add(entity, client);

View File

@ -9,14 +9,14 @@ uniform sampler2D u_tex;
out vec4 tgt_color; out vec4 tgt_color;
void main() { void main() {
// Text // Text
if (f_mode == uint(0)) { if (f_mode == uint(0)) {
tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a); tgt_color = f_color * vec4(1.0, 1.0, 1.0, texture(u_tex, f_uv).a);
// Image // Image
} else if (f_mode == uint(1)) { } else if (f_mode == uint(1)) {
tgt_color = texture(u_tex, f_uv); tgt_color = f_color * texture(u_tex, f_uv);
// 2D Geometry // 2D Geometry
} else if (f_mode == uint(2)) { } else if (f_mode == uint(2)) {
tgt_color = f_color; tgt_color = f_color;
} }
} }

View File

@ -12,8 +12,8 @@ flat out uint f_mode;
out vec4 f_color; out vec4 f_color;
void main() { void main() {
f_uv = v_uv; f_uv = v_uv;
f_color = v_color; f_color = v_color;
gl_Position = vec4(v_pos, 0.0, 1.0); gl_Position = vec4(v_pos, 0.0, 1.0);
f_mode = v_mode; f_mode = v_mode;
} }

View File

@ -32,12 +32,12 @@ impl Animation for IdleAnimation {
let wave_dip = (wave_slow.abs() - 0.5).abs(); let wave_dip = (wave_slow.abs() - 0.5).abs();
let head_look = Vec2::new( let head_look = Vec2::new(
((global_time + anim_time) as f32 / 5.0) ((global_time + anim_time) as f32 / 8.0)
.floor() .floor()
.mul(7331.0) .mul(7331.0)
.sin() .sin()
* 0.5, * 0.5,
((global_time + anim_time) as f32 / 5.0) ((global_time + anim_time) as f32 / 8.0)
.floor() .floor()
.mul(1337.0) .mul(1337.0)
.sin() .sin()

View File

@ -2,18 +2,18 @@ use super::{img_ids::Imgs, Fonts, TEXT_COLOR, XP_COLOR};
use conrod_core::{ use conrod_core::{
color, color,
widget::{self, Button, Image, Rectangle, Text}, 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! { widget_ids! {
pub struct Ids { pub struct Ids {
charwindow, charwindow,
charwindow_bg, charwindow_gradient,
charwindow_close, charwindow_close,
charwindow_exp_progress_rectangle, charwindow_exp_progress_rectangle,
charwindow_exp_rectangle, charwindow_exp_rectangle,
charwindow_frame, charwindow_frame,
charwindow_icon, content_align,
charwindow_rectangle, charwindow_rectangle,
charwindow_tab1, charwindow_tab1,
charwindow_tab1_exp, charwindow_tab1_exp,
@ -23,6 +23,45 @@ widget_ids! {
charwindow_tab1_stats, charwindow_tab1_stats,
charwindow_tab_bg, charwindow_tab_bg,
charwindow_title, 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; let xp_percentage = 0.4;
// Frame // Frame
Image::new(self.imgs.window_frame) Image::new(self.imgs.window_3)
.middle_of(id) .middle_of(id)
.top_left_with_margins_on(ui.window, 200.0, 215.0) .top_left_with_margins_on(ui.window, 212.0, 215.0)
.w_h(107.0 * 4.0, 125.0 * 4.0) .w_h(103.0 * 4.0, 122.0 * 4.0)
.set(state.charwindow_frame, ui); .set(state.charwindow_frame, ui);
// Icon // Icon
Image::new(self.imgs.charwindow_icon) //Image::new(self.imgs.charwindow_icon)
.w_h(40.0, 40.0) //.w_h(40.0, 40.0)
.top_left_with_margins_on(state.charwindow_frame, 4.0, 4.0) //.top_left_with_margins_on(state.charwindow_frame, 4.0, 4.0)
.set(state.charwindow_icon, ui); //.set(state.charwindow_icon, ui);
// X-Button // X-Button
if Button::image(self.imgs.close_button) if Button::image(self.imgs.close_button)
.w_h(28.0, 28.0) .w_h(28.0, 28.0)
.hover_image(self.imgs.close_button_hover) .hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press) .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) .set(state.charwindow_close, ui)
.was_clicked() .was_clicked()
{ {
@ -95,31 +134,199 @@ impl<'a> Widget for CharacterWindow<'a> {
// Title // Title
Text::new("Character Name") // Add in actual Character Name 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_id(self.fonts.metamorph)
.font_size(14) .font_size(14)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_title, ui); .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 // Tab BG
Image::new(self.imgs.charwindow_tab_bg) Image::new(self.imgs.tab_bg)
.w_h(205.0, 412.0) .w_h(51.0 * 4.0, 115.0 * 4.0)
.mid_left_with_margin_on(state.charwindow_frame, -205.0) .top_left_with_margins_on(state.charwindow_frame, 28.0, -200.0)
.set(state.charwindow_tab_bg, ui); .set(state.charwindow_tab_bg, ui);
// Tab Rectangle // Tab Rectangle
Rectangle::fill_with([192.0, 371.0], color::rgba(0.0, 0.0, 0.0, 0.8)) Rectangle::fill_with([45.0 * 4.0, 104.0 * 4.0], color::TRANSPARENT)
.top_right_with_margins_on(state.charwindow_tab_bg, 20.0, 0.0) .top_left_with_margins_on(state.charwindow_tab_bg, 7.0 * 4.0, 4.0 * 4.0)
.set(state.charwindow_rectangle, ui); .set(state.charwindow_rectangle, ui);
// Tab Button // Tab Button -> Add that back in when we have multiple tabs
Button::image(self.imgs.charwindow_tab) // Button::image(self.imgs.charwindow_tab)
.w_h(65.0, 23.0) //.w_h(65.0, 23.0)
.top_left_with_margins_on(state.charwindow_tab_bg, -18.0, 2.0) //.top_left_with_margins_on(state.charwindow_tab_bg, -18.0, 1.8)
.label("Stats") //.label("Stats")
.label_color(TEXT_COLOR) //.label_color(TEXT_COLOR)
.label_font_size(14) //.label_font_size(14)
.set(state.charwindow_tab1, ui); //.set(state.charwindow_tab1, ui);
Text::new("1") //Add in actual Character Level Text::new("1") //Add in actual Character Level
.mid_top_with_margin_on(state.charwindow_rectangle, 10.0) .mid_top_with_margin_on(state.charwindow_rectangle, 10.0)
@ -152,6 +359,13 @@ impl<'a> Widget for CharacterWindow<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.charwindow_tab1_exp, ui); .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 // Stats
Text::new( Text::new(
"Stamina\n\ "Stamina\n\
@ -162,7 +376,7 @@ impl<'a> Widget for CharacterWindow<'a> {
\n\ \n\
Intelligence", 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_id(self.fonts.opensans)
.font_size(16) .font_size(16)
.color(TEXT_COLOR) .color(TEXT_COLOR)
@ -177,7 +391,7 @@ impl<'a> Widget for CharacterWindow<'a> {
\n\ \n\
124124", 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_id(self.fonts.opensans)
.font_size(16) .font_size(16)
.color(TEXT_COLOR) .color(TEXT_COLOR)

View File

@ -1,13 +1,59 @@
use crate::ui::{BlankGraphic, ImageGraphic, VoxelGraphic}; use crate::ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic, VoxelMs9Graphic};
image_ids! { image_ids! {
pub struct Imgs { pub struct Imgs {
<VoxelGraphic> <VoxelGraphic>
// Bag // Bag
bag_contents: "/voxygen/element/frames/bag.vox", bag_contents: "/voxygen/element/frames/bag.vox",
inv_grid: "/voxygen/element/frames/inv_grid.vox", inv_grid: "/voxygen/element/frames/inv_grid.vox",
inv_slot: "/voxygen/element/buttons/inv_slot.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",
<VoxelMs9Graphic>
// Buttons // Buttons
mmap_closed: "/voxygen/element/buttons/button_mmap_closed.vox", mmap_closed: "/voxygen/element/buttons/button_mmap_closed.vox",
mmap_closed_hover: "/voxygen/element/buttons/button_mmap_closed_hover.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_hover: "/voxygen/element/buttons/button_mmap_open_hover.vox",
mmap_open_press: "/voxygen/element/buttons/button_mmap_open_press.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: "/voxygen/element/buttons/settings.vox",
settings_hover: "/voxygen/element/buttons/settings_hover.vox", settings_hover: "/voxygen/element/buttons/settings_hover.vox",
settings_press: "/voxygen/element/buttons/settings_press.vox", settings_press: "/voxygen/element/buttons/settings_press.vox",
@ -40,6 +91,24 @@ image_ids! {
qlog_hover: "/voxygen/element/buttons/qlog_hover.vox", qlog_hover: "/voxygen/element/buttons/qlog_hover.vox",
qlog_press: "/voxygen/element/buttons/qlog_press.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
close_button: "/voxygen/element/buttons/x.vox", close_button: "/voxygen/element/buttons/x.vox",
@ -52,45 +121,12 @@ image_ids! {
button_hover: "/voxygen/element/buttons/button_hover.vox", button_hover: "/voxygen/element/buttons/button_hover.vox",
button_press: "/voxygen/element/buttons/button_press.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",
<ImageGraphic> <ImageGraphic>
// Spell Book Window charwindow_gradient:"/voxygen/element/misc_bg/charwindow.png",
// Spell Book Window
spellbook_bg: "/voxygen/element/misc_bg/small_bg.png", spellbook_bg: "/voxygen/element/misc_bg/small_bg.png",
spellbook_icon: "/voxygen/element/icons/spellbook.png", spellbook_icon: "/voxygen/element/icons/spellbook.png",

View File

@ -71,6 +71,7 @@ impl<'a> Widget for Map<'a> {
.scroll_kids() .scroll_kids()
.scroll_kids_vertically() .scroll_kids_vertically()
.set(state.ids.map_bg, ui); .set(state.ids.map_bg, ui);
// Frame // Frame
Image::new(self.imgs.map_frame_l) Image::new(self.imgs.map_frame_l)
.top_left_with_margins_on(state.ids.map_bg, 0.0, 0.0) .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) .top_left_with_margins_on(state.ids.map_frame, -10.0, -10.0)
.set(state.ids.map_icon, ui); .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 // X-Button
if Button::image(self.imgs.close_button) if Button::image(self.imgs.close_button)
.w_h(28.0, 28.0) .w_h(28.0, 28.0)

View File

@ -306,7 +306,7 @@ impl Hud {
.w_h(100.0 * 0.2, 100.0 * 0.2) .w_h(100.0 * 0.2, 100.0 * 0.2)
.hover_image(self.imgs.close_button_hover) .hover_image(self.imgs.close_button_hover)
.press_image(self.imgs.close_button_press) .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) .set(self.ids.button_help2, ui_widgets)
.was_clicked() .was_clicked()
{ {
@ -475,6 +475,13 @@ impl Hud {
self.show.toggle_ui(); self.show.toggle_ui();
true 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, _ if !self.show.ui => false,
WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(), WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(),
WinEvent::KeyDown(Key::Enter) => { WinEvent::KeyDown(Key::Enter) => {
@ -527,13 +534,6 @@ impl Hud {
self.show.toggle_help(); self.show.toggle_help();
true true
} }
Key::ToggleCursor => {
self.force_ungrab = !self.force_ungrab;
if self.force_ungrab {
global_state.window.grab_cursor(false);
}
true
}
_ => false, _ => false,
}, },
WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key { WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key {

View File

@ -21,6 +21,7 @@ widget_ids! {
test, test,
xp_bar, xp_bar,
xp_bar_progress, xp_bar_progress,
crosshair,
} }
} }
@ -69,8 +70,14 @@ impl<'a> Widget for Skillbar<'a> {
// TODO: Read from parameter / character struct // TODO: Read from parameter / character struct
let xp_percentage = 0.4; let xp_percentage = 0.4;
let hp_percentage = 0.4; let hp_percentage = 1.0;
let mana_percentage = 0.4; 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 // Experience-Bar
Image::new(self.imgs.xp_bar) Image::new(self.imgs.xp_bar)

View File

@ -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. The information below is intended for developers and testers.
Panic Payload: {:?} 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); log::error!("VOXYGEN HAS PANICKED\n\n{}", msg);

View File

@ -1,3 +1,4 @@
mod scene;
mod ui; mod ui;
use crate::{ use crate::{
@ -7,6 +8,7 @@ use crate::{
}; };
use client::{self, Client}; use client::{self, Client};
use common::{clock::Clock, msg::ClientMsg}; use common::{clock::Clock, msg::ClientMsg};
use scene::Scene;
use std::{cell::RefCell, rc::Rc, time::Duration}; use std::{cell::RefCell, rc::Rc, time::Duration};
use ui::CharSelectionUi; use ui::CharSelectionUi;
use vek::*; use vek::*;
@ -16,6 +18,7 @@ const FPS: u64 = 60;
pub struct CharSelectionState { pub struct CharSelectionState {
char_selection_ui: CharSelectionUi, char_selection_ui: CharSelectionUi,
client: Rc<RefCell<Client>>, client: Rc<RefCell<Client>>,
scene: Scene,
} }
impl CharSelectionState { impl CharSelectionState {
@ -24,6 +27,7 @@ impl CharSelectionState {
Self { Self {
char_selection_ui: CharSelectionUi::new(window), char_selection_ui: CharSelectionUi::new(window),
client, 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 // Draw the UI to the screen
self.char_selection_ui self.char_selection_ui
.render(global_state.window.renderer_mut()); .render(global_state.window.renderer_mut());

View File

@ -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<SkyboxPipeline>,
locals: Consts<SkyboxLocals>,
}
struct PostProcess {
model: Model<PostProcessPipeline>,
locals: Consts<PostProcessLocals>,
}
pub struct Scene {
globals: Consts<Globals>,
camera: Camera,
skybox: Skybox,
postprocess: PostProcess,
backdrop_model: Model<FigurePipeline>,
backdrop_state: FigureState<CharacterSkeleton>,
figure_model_cache: FigureModelCache,
figure_state: FigureState<CharacterSkeleton>,
}
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,
);
}
}

View File

@ -1,17 +1,18 @@
use crate::{ use crate::{
render::Renderer, render::Renderer,
ui::{self, BlankGraphic, Graphic, ImageGraphic, ScaleMode, Ui, VoxelGraphic}, ui::{
self,
img_ids::{ImageGraphic, VoxelGraphic},
ScaleMode, Ui,
},
window::Window, window::Window,
}; };
use common::{ use common::comp::character::{
assets, Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon,
comp::character::{Belt, Character, Chest, Foot, Gender, Hand, Head, Pants, Race, Weapon},
}; };
use conrod_core::{ use conrod_core::{
color, color,
color::TRANSPARENT, color::TRANSPARENT,
image::Id as ImgId,
text::font::Id as FontId,
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox}, widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };
@ -154,7 +155,6 @@ widget_ids! {
warpaint_slider_indicator, warpaint_slider_indicator,
warpaint_slider_range, warpaint_slider_range,
warpaint_slider_text, warpaint_slider_text,
} }
} }
@ -315,13 +315,13 @@ impl CharSelectionUi {
// Background Image // Background Image
if !self.character_creation { if !self.character_creation {
Image::new(self.imgs.bg_selection) //Image::new(self.imgs.bg_selection)
.middle_of(ui_widgets.window) // .middle_of(ui_widgets.window)
.set(self.ids.bg_selection, ui_widgets); // .set(self.ids.bg_selection, ui_widgets);
// Logout_Button // Logout_Button
if Button::image(self.imgs.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) .w_h(150.0, 40.0)
.hover_image(self.imgs.button_hover) .hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press) .press_image(self.imgs.button_press)
@ -337,7 +337,7 @@ impl CharSelectionUi {
// Create Character Button // Create Character Button
if Button::image(self.imgs.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) .w_h(270.0, 50.0)
.hover_image(self.imgs.button_hover) .hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press) .press_image(self.imgs.button_press)
@ -353,7 +353,7 @@ impl CharSelectionUi {
} }
// Test Characters // Test Characters
if Button::image(self.imgs.test_char_l_button) 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) .w_h(95.0, 130.0)
.hover_image(self.imgs.test_char_l_button) .hover_image(self.imgs.test_char_l_button)
.press_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); .set(self.ids.version, ui_widgets);
// Click Character to Login <-- Temporary! // Click Character to Login <-- Temporary!
Image::new(self.imgs.window_frame_2) 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) .w_h(700.0, 70.0)
.set(self.ids.help_text_bg, ui_widgets); .set(self.ids.help_text_bg, ui_widgets);
Text::new("Click character to select it") Text::new("Click character to select it")
@ -445,12 +445,13 @@ impl CharSelectionUi {
// Character_Creation ////////////// // Character_Creation //////////////
else { else {
// Background // Background
Image::new(self.imgs.bg_creation) //Image::new(self.imgs.bg_creation)
.middle_of(ui_widgets.window) // .middle_of(ui_widgets.window)
.set(self.ids.bg_creation, ui_widgets); // .set(self.ids.bg_creation, ui_widgets);
// Back Button // Back Button
if Button::image(self.imgs.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) .w_h(150.0, 40.0)
.hover_image(self.imgs.button_hover) .hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press) .press_image(self.imgs.button_press)
@ -465,7 +466,7 @@ impl CharSelectionUi {
} }
// Create Button // Create Button
if Button::image(self.imgs.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) .w_h(150.0, 40.0)
.hover_image(self.imgs.button_hover) .hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press) .press_image(self.imgs.button_press)
@ -481,7 +482,7 @@ impl CharSelectionUi {
} }
// Character Name Input // Character Name Input
Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.99)) 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); .set(self.ids.name_input_bg, ui_widgets);
Button::image(self.imgs.name_input) Button::image(self.imgs.name_input)
.w_h(337.0, 67.0) .w_h(337.0, 67.0)
@ -513,7 +514,7 @@ impl CharSelectionUi {
self.imgs.creation_window self.imgs.creation_window
}) })
.w_h(628.0, 814.0) .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); .set(self.ids.creation_window, ui_widgets);
// Arrows // Arrows

View File

@ -1,15 +1,16 @@
use crate::{ use crate::{
render::Renderer, render::Renderer,
ui::{self, BlankGraphic, Graphic, ImageGraphic, ScaleMode, Ui, VoxelGraphic}, ui::{
self,
img_ids::{ImageGraphic, VoxelGraphic},
ScaleMode, Ui,
},
GlobalState, DEFAULT_PUBLIC_SERVER, GlobalState, DEFAULT_PUBLIC_SERVER,
}; };
use common::assets;
use conrod_core::{ use conrod_core::{
color, color,
color::TRANSPARENT, color::TRANSPARENT,
image::Id as ImgId,
position::Relative, position::Relative,
text::font::Id as FontId,
widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox}, widget::{text_box::Event as TextBoxEvent, Button, Image, List, Rectangle, Text, TextBox},
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };

View File

@ -10,7 +10,7 @@ mod util;
pub use self::{ pub use self::{
consts::Consts, consts::Consts,
mesh::{Mesh, Quad, Tri}, mesh::{Mesh, Quad, Tri},
model::Model, model::{DynamicModel, Model},
pipelines::{ pipelines::{
figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals}, figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals},
postprocess::{ postprocess::{
@ -40,6 +40,7 @@ pub enum RenderError {
UpdateError(gfx::UpdateError<usize>), UpdateError(gfx::UpdateError<usize>),
TexUpdateError(gfx::UpdateError<[u16; 3]>), TexUpdateError(gfx::UpdateError<[u16; 3]>),
CombinedError(gfx::CombinedError), CombinedError(gfx::CombinedError),
BufferCreationError(gfx::buffer::CreationError),
} }
/// Used to represent a specific rendering configuration. /// Used to represent a specific rendering configuration.

View File

@ -1,8 +1,11 @@
// Library use super::{gfx_backend, mesh::Mesh, Pipeline, RenderError};
use gfx::{self, traits::FactoryExt}; use gfx::{
buffer::Role,
// Local memory::{Bind, Usage},
use super::{gfx_backend, mesh::Mesh, Pipeline}; traits::FactoryExt,
Factory,
};
use std::ops::Range;
/// Represents a mesh that has been sent to the GPU. /// Represents a mesh that has been sent to the GPU.
pub struct Model<P: Pipeline> { pub struct Model<P: Pipeline> {
@ -24,3 +27,43 @@ impl<P: Pipeline> Model<P> {
} }
} }
} }
/// Represents a mesh on the GPU which can be updated dynamically
pub struct DynamicModel<P: Pipeline> {
pub vbuf: gfx::handle::Buffer<gfx_backend::Resources, P::Vertex>,
}
impl<P: Pipeline> DynamicModel<P> {
pub fn new(factory: &mut gfx_backend::Factory, size: usize) -> Result<Self, RenderError> {
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<usize>) -> Model<P> {
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<gfx_backend::Resources, gfx_backend::CommandBuffer>,
mesh: &Mesh<P>,
offset: usize,
) -> Result<(), RenderError> {
encoder
.update_buffer(&self.vbuf, mesh.vertices(), offset)
.map_err(|err| RenderError::UpdateError(err))
}
}

View File

@ -2,7 +2,7 @@ use super::{
consts::Consts, consts::Consts,
gfx_backend, gfx_backend,
mesh::Mesh, mesh::Mesh,
model::Model, model::{DynamicModel, Model},
pipelines::{figure, postprocess, skybox, terrain, ui, Globals}, pipelines::{figure, postprocess, skybox, terrain, ui, Globals},
texture::Texture, texture::Texture,
Pipeline, RenderError, Pipeline, RenderError,
@ -12,7 +12,6 @@ use gfx::{
handle::Sampler, handle::Sampler,
traits::{Device, Factory, FactoryExt}, traits::{Device, Factory, FactoryExt},
}; };
use image;
use vek::*; use vek::*;
/// Represents the format of the pre-processed color target. /// Represents the format of the pre-processed color target.
@ -246,6 +245,24 @@ impl Renderer {
Ok(Model::new(&mut self.factory, mesh)) Ok(Model::new(&mut self.factory, mesh))
} }
/// Create a new dynamic model with the specified size
pub fn create_dynamic_model<P: Pipeline>(
&mut self,
size: usize,
) -> Result<DynamicModel<P>, RenderError> {
DynamicModel::new(&mut self.factory, size)
}
/// Update a dynamic model with a mesh and a offset
pub fn update_model<P: Pipeline>(
&mut self,
model: &DynamicModel<P>,
mesh: &Mesh<P>,
offset: usize,
) -> Result<(), RenderError> {
model.update(&mut self.encoder, mesh, offset)
}
/// Create a new texture from the provided image. /// Create a new texture from the provided image.
pub fn create_texture<P: Pipeline>( pub fn create_texture<P: Pipeline>(
&mut self, &mut self,

View File

@ -54,7 +54,14 @@ impl Camera {
) * self.dist), ) * 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), (d, Ok(Some(_))) => f32::min(d - 1.0, self.dist),
(_, Ok(None)) => self.dist, (_, Ok(None)) => self.dist,
(_, Err(_)) => self.dist, (_, Err(_)) => self.dist,
@ -88,12 +95,27 @@ impl Camera {
self.ori.z = (self.ori.z + delta.z) % (2.0 * PI); 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<f32>) {
// 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. /// Zoom the camera by the given delta, limiting the input accordingly.
pub fn zoom_by(&mut self, delta: f32) { pub fn zoom_by(&mut self, delta: f32) {
// Clamp camera dist to the 0 <= x <= infinity range // Clamp camera dist to the 0 <= x <= infinity range
self.tgt_dist = (self.tgt_dist + delta).max(0.0); 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) { pub fn update(&mut self, time: f64) {
// This is horribly frame time dependent, but so is most of the game // 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); let delta = self.last_time.replace(time).map_or(0.0, |t| time - t);

View File

@ -24,31 +24,29 @@ use specs::{Component, Entity as EcsEntity, Join, VecStorage};
use std::{collections::HashMap, f32}; use std::{collections::HashMap, f32};
use vek::*; use vek::*;
pub struct FigureCache { pub struct FigureModelCache {
models: HashMap<Character, (Model<FigurePipeline>, u64)>, models: HashMap<Character, (Model<FigurePipeline>, u64)>,
states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
} }
impl FigureCache { impl FigureModelCache {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
models: HashMap::new(), models: HashMap::new(),
states: HashMap::new(),
} }
} }
pub fn get_or_create_model<'a>( pub fn get_or_create_model(
models: &'a mut HashMap<Character, (Model<FigurePipeline>, u64)>, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
tick: u64,
character: Character, character: Character,
) -> &'a (Model<FigurePipeline>, u64) { tick: u64,
match models.get_mut(&character) { ) -> &Model<FigurePipeline> {
match self.models.get_mut(&character) {
Some((model, last_used)) => { Some((model, last_used)) => {
*last_used = tick; *last_used = tick;
} }
None => { None => {
models.insert( self.models.insert(
character, character,
( (
{ {
@ -91,7 +89,7 @@ impl FigureCache {
} }
} }
&models[&character] &self.models[&character].0
} }
pub fn clean(&mut self, tick: u64) { pub fn clean(&mut self, tick: u64) {
@ -100,7 +98,8 @@ impl FigureCache {
.retain(|_, (_, last_used)| *last_used + 60 > tick); .retain(|_, (_, last_used)| *last_used + 60 > tick);
} }
fn load_mesh(filename: &str, position: Vec3<f32>) -> Mesh<FigurePipeline> { // TODO: Don't make this public
pub fn load_mesh(filename: &str, position: Vec3<f32>) -> Mesh<FigurePipeline> {
let fullpath: String = ["/voxygen/voxel/", filename].concat(); let fullpath: String = ["/voxygen/voxel/", filename].concat();
Segment::from(assets::load_expect::<DotVoxData>(fullpath.as_str()).as_ref()) Segment::from(assets::load_expect::<DotVoxData>(fullpath.as_str()).as_ref())
.generate_mesh(position) .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<EcsEntity, FigureState<CharacterSkeleton>>,
}
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 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 ( for (entity, pos, vel, dir, character, animation_history) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(), &ecs.read_storage::<comp::phys::Pos>(),
@ -238,17 +256,17 @@ impl FigureCache {
let target_skeleton = match animation_history.current { let target_skeleton = match animation_history.current {
comp::character::Animation::Idle => IdleAnimation::update_skeleton( comp::character::Animation::Idle => IdleAnimation::update_skeleton(
&mut state.skeleton, state.skeleton_mut(),
time, time,
animation_history.time, animation_history.time,
), ),
comp::character::Animation::Run => RunAnimation::update_skeleton( comp::character::Animation::Run => RunAnimation::update_skeleton(
&mut state.skeleton, state.skeleton_mut(),
(vel.0.magnitude(), time), (vel.0.magnitude(), time),
animation_history.time, animation_history.time,
), ),
comp::character::Animation::Jump => JumpAnimation::update_skeleton( comp::character::Animation::Jump => JumpAnimation::update_skeleton(
&mut state.skeleton, state.skeleton_mut(),
time, time,
animation_history.time, animation_history.time,
), ),
@ -271,13 +289,15 @@ impl FigureCache {
) { ) {
let tick = client.get_tick(); let tick = client.get_tick();
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let models = &mut self.models;
for (entity, &character) in (&ecs.entities(), &ecs.read_storage::<comp::Character>()).join() for (entity, &character) in (&ecs.entities(), &ecs.read_storage::<comp::Character>()).join()
{ {
let model = Self::get_or_create_model(models, renderer, tick, character); if let Some(state) = self.states.get(&entity) {
let state = self.states.get(&entity).unwrap(); let model = self
renderer.render_figure(&model.0, globals, &state.locals, &state.bone_consts); .model_cache
.get_or_create_model(renderer, character, tick);
renderer.render_figure(model, globals, &state.locals(), state.bone_consts());
}
} }
} }
} }
@ -299,7 +319,7 @@ impl<S: Skeleton> FigureState<S> {
} }
} }
fn update(&mut self, renderer: &mut Renderer, pos: Vec3<f32>, dir: Vec3<f32>) { pub fn update(&mut self, renderer: &mut Renderer, pos: Vec3<f32>, dir: Vec3<f32>) {
let mat = Mat4::<f32>::identity() let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(pos) * Mat4::translation_3d(pos)
* Mat4::rotation_z(-dir.x.atan2(dir.y)); // + f32::consts::PI / 2.0); * Mat4::rotation_z(-dir.x.atan2(dir.y)); // + f32::consts::PI / 2.0);
@ -311,4 +331,16 @@ impl<S: Skeleton> FigureState<S> {
.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()) .update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())
.unwrap(); .unwrap();
} }
pub fn locals(&self) -> &Consts<FigureLocals> {
&self.locals
}
pub fn bone_consts(&self) -> &Consts<FigureBoneData> {
&self.bone_consts
}
pub fn skeleton_mut(&mut self) -> &mut S {
&mut self.skeleton
}
} }

View File

@ -2,7 +2,7 @@ pub mod camera;
pub mod figure; pub mod figure;
pub mod terrain; pub mod terrain;
use self::{camera::Camera, figure::FigureCache, terrain::Terrain}; use self::{camera::Camera, figure::FigureMgr, terrain::Terrain};
use crate::{ use crate::{
anim::{ anim::{
character::{CharacterSkeleton, RunAnimation}, character::{CharacterSkeleton, RunAnimation},
@ -41,7 +41,7 @@ pub struct Scene {
postprocess: PostProcess, postprocess: PostProcess,
terrain: Terrain, terrain: Terrain,
figure_cache: FigureCache, figure_mgr: FigureMgr,
} }
impl Scene { impl Scene {
@ -64,7 +64,7 @@ impl Scene {
.unwrap(), .unwrap(),
}, },
terrain: Terrain::new(), 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. /// 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 // Get player position
let player_pos = client let player_pos = client
.state() .state()
@ -144,10 +144,10 @@ impl Scene {
self.terrain.maintain(renderer, client); self.terrain.maintain(renderer, client);
// Maintain the figures // Maintain the figures
self.figure_cache.maintain(renderer, client); self.figure_mgr.maintain(renderer, client);
// Remove unused figures // Remove unused figures
self.figure_cache.clean(client.get_tick()); self.figure_mgr.clean(client.get_tick());
} }
/// Render the scene using the provided `Renderer` /// Render the scene using the provided `Renderer`
@ -157,7 +157,7 @@ impl Scene {
// Render terrain and figures // Render terrain and figures
self.terrain.render(renderer, &self.globals); 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( renderer.render_post_process(
&self.postprocess.model, &self.postprocess.model,

54
voxygen/src/ui/cache.rs Normal file
View File

@ -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<UiPipeline>,
graphic_cache: GraphicCache,
graphic_cache_tex: Texture<UiPipeline>,
}
// TODO: Should functions be returning UiError instead of Error?
impl Cache {
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
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<UiPipeline> {
&self.glyph_cache_tex
}
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) {
(&mut self.glyph_cache, &self.glyph_cache_tex)
}
pub fn graphic_cache_tex(&self) -> &Texture<UiPipeline> {
&self.graphic_cache_tex
}
pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture<UiPipeline>) {
(&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<u16>) {
self.graphic_cache.clear_cache(new_size);
self.graphic_cache_tex = renderer.create_dynamic_texture(new_size).unwrap();
}
}

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

@ -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<Self> {
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<f64>) -> Self {
Self(Input::Resize(dims.x, dims.y))
}
}

View File

@ -7,7 +7,7 @@ use vek::*;
pub enum Graphic { pub enum Graphic {
Image(Arc<DynamicImage>), Image(Arc<DynamicImage>),
Voxel(Arc<DotVoxData>), Voxel(Arc<DotVoxData>, Option<u8>),
Blank, Blank,
} }
@ -94,9 +94,8 @@ impl GraphicCache {
.pixels() .pixels()
.map(|p| p.data) .map(|p| p.data)
.collect::<Vec<[u8; 4]>>(), .collect::<Vec<[u8; 4]>>(),
Graphic::Voxel(ref vox) => { Graphic::Voxel(ref vox, min_samples) =>
super::renderer::draw_vox(&vox.as_ref().into(), aabr.size().into()) super::renderer::draw_vox(&vox.as_ref().into(), aabr.size().into(), *min_samples),
}
Graphic::Blank => return None, Graphic::Blank => return None,
}; };

View File

@ -4,6 +4,7 @@ use common::{
vol::{ReadVol, SizedVol, Vox}, vol::{ReadVol, SizedVol, Vox},
}; };
use euc::{buffer::Buffer2d, rasterizer, Pipeline}; use euc::{buffer::Buffer2d, rasterizer, Pipeline};
use image::{DynamicImage, RgbaImage};
use vek::*; use vek::*;
struct Voxel { struct Voxel {
@ -56,8 +57,13 @@ impl<'a> Pipeline for Voxel {
} }
} }
pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>) -> Vec<[u8; 4]> { pub fn draw_vox(
let dims = output_size.map(|e| e as usize).into_array(); segment: &Segment,
output_size: Vec2<u16>,
min_samples: Option<u8>,
) -> 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 color = Buffer2d::new(dims, [0; 4]);
let mut depth = Buffer2d::new(dims, 1.0); let mut depth = Buffer2d::new(dims, 1.0);
@ -79,8 +85,33 @@ pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>) -> Vec<[u8; 4]> {
&mut depth, &mut depth,
); );
// TODO: remove this clone if scale > 1 {
color.as_ref().to_vec() DynamicImage::ImageRgba8(
RgbaImage::from_vec(
dims[0] as u32,
dims[1] as u32,
color
.as_ref()
.iter()
.flatten()
.cloned()
.collect::<Vec<u8>>(),
)
.unwrap(),
)
.resize_exact(
output_size.x as u32,
output_size.y as u32,
image::FilterType::Triangle,
)
.to_rgba()
.pixels()
.map(|p| p.data)
.collect::<Vec<[u8; 4]>>()
} else {
// TODO: remove clone
color.as_ref().to_vec()
}
} }
fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 { fn ao_level(side1: bool, corner: bool, side2: bool) -> u8 {

View File

@ -3,9 +3,8 @@ use common::assets::{load, Error};
use dot_vox::DotVoxData; use dot_vox::DotVoxData;
use image::DynamicImage; use image::DynamicImage;
pub struct BlankGraphic; pub enum BlankGraphic {}
pub struct ImageGraphic; pub enum ImageGraphic {}
pub struct VoxelGraphic;
pub trait GraphicCreator<'a> { pub trait GraphicCreator<'a> {
type Specifier; type Specifier;
@ -23,10 +22,37 @@ impl<'a> GraphicCreator<'a> for ImageGraphic {
Ok(Graphic::Image(load::<DynamicImage>(specifier)?)) Ok(Graphic::Image(load::<DynamicImage>(specifier)?))
} }
} }
pub enum VoxelGraphic {}
pub enum VoxelMsGraphic {}
pub enum VoxelMs4Graphic {}
pub enum VoxelMs9Graphic {}
impl<'a> GraphicCreator<'a> for VoxelGraphic { impl<'a> GraphicCreator<'a> for VoxelGraphic {
type Specifier = &'a str; type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> { fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?)) Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None))
}
}
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
type Specifier = (&'a str, u8);
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier.0)?,
Some(specifier.1),
))
}
}
impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(4)))
}
}
impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(9)))
} }
} }
@ -59,9 +85,9 @@ macro_rules! image_ids {
impl $Ids { impl $Ids {
pub fn load(ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> { pub fn load(ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
use crate::ui::GraphicCreator; use crate::ui::img_ids::GraphicCreator;
Ok(Self { Ok(Self {
$($( $name: ui.add_graphic(<$T>::new_graphic($specifier)?), )*)* $($( $name: ui.add_graphic(<$T as GraphicCreator>::new_graphic($specifier)?), )*)*
}) })
} }
} }

View File

@ -1,134 +1,50 @@
mod cache;
mod event;
mod graphic; mod graphic;
mod scale;
mod util; mod util;
mod widgets; mod widgets;
#[macro_use] #[macro_use]
mod img_ids; pub mod img_ids;
#[macro_use] #[macro_use]
mod font_ids; mod font_ids;
pub use event::Event;
pub use graphic::Graphic; pub use graphic::Graphic;
pub use img_ids::{BlankGraphic, GraphicCreator, ImageGraphic, VoxelGraphic}; pub use scale::ScaleMode;
pub(self) use util::{linear_to_srgb, srgb_to_linear};
pub use widgets::toggle_button::ToggleButton; pub use widgets::toggle_button::ToggleButton;
use crate::{ use crate::{
render::{ 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, UiPipeline,
}, },
window::Window, window::Window,
Error, Error,
}; };
use cache::Cache;
use common::assets; use common::assets;
use conrod_core::{ use conrod_core::{
event::Input, event::Input,
graph::Graph, graph::Graph,
image::{Id as ImgId, Map}, image::{Id as ImgId, Map},
input::{touch::Touch, Button, Motion, Widget}, input::{touch::Touch, Motion, Widget},
render::Primitive, render::Primitive,
text::{self, GlyphCache}, text::{self, font},
widget::{id::Generator, Id as WidgId}, widget::{id::Generator, Id as WidgId},
Ui as CrUi, UiBuilder, UiCell, 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 std::sync::Arc;
use util::{linear_to_srgb, srgb_to_linear};
use vek::*; use vek::*;
#[derive(Debug)] #[derive(Debug)]
pub enum UiError { pub enum UiError {
RenderError(RenderError), RenderError(RenderError),
} }
#[derive(Clone)]
pub struct Event(Input);
impl Event {
pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option<Self> {
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<f64>) -> Self {
Self(Input::Resize(dims.x, dims.y))
}
}
pub struct Cache {
glyph_cache: GlyphCache<'static>,
glyph_cache_tex: Texture<UiPipeline>,
graphic_cache: graphic::GraphicCache,
graphic_cache_tex: Texture<UiPipeline>,
}
// TODO: Should functions be returning UiError instead of Error?
impl Cache {
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
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<UiPipeline> {
&self.glyph_cache_tex
}
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) {
(&mut self.glyph_cache, &self.glyph_cache_tex)
}
pub fn graphic_cache_tex(&self) -> &Texture<UiPipeline> {
&self.graphic_cache_tex
}
pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture<UiPipeline>) {
(&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<u16>) {
self.graphic_cache.clear_cache(new_size);
self.graphic_cache_tex = renderer.create_dynamic_texture(new_size).unwrap();
}
}
enum DrawKind { enum DrawKind {
Image, Image,
@ -136,90 +52,24 @@ enum DrawKind {
Plain, Plain,
} }
enum DrawCommand { enum DrawCommand {
Draw { Draw { kind: DrawKind, verts: Range<usize> },
kind: DrawKind,
model: Model<UiPipeline>,
},
Scissor(Aabr<u16>), Scissor(Aabr<u16>),
} }
impl DrawCommand { impl DrawCommand {
fn image(model: Model<UiPipeline>) -> DrawCommand { fn image(verts: Range<usize>) -> DrawCommand {
DrawCommand::Draw { DrawCommand::Draw {
kind: DrawKind::Image, kind: DrawKind::Image,
model, verts,
} }
} }
fn plain(model: Model<UiPipeline>) -> DrawCommand { fn plain(verts: Range<usize>) -> DrawCommand {
DrawCommand::Draw { DrawCommand::Draw {
kind: DrawKind::Plain, 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<f64>),
}
struct Scale {
// Type of scaling to use
mode: ScaleMode,
// Current dpi factor
dpi_factor: f64,
// Current logical window size
window_dims: Vec2<f64>,
}
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<f64>, 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<f64> {
self.window_dims / self.scale_factor_logical()
}
// Transform point from logical to scaled coordinates
fn scale_point(&self, point: Vec2<f64>) -> Vec2<f64> {
point / self.scale_factor_logical()
}
}
pub struct Font(text::Font); pub struct Font(text::Font);
impl assets::Asset for Font { impl assets::Asset for Font {
fn load(specifier: &str) -> Result<Self, assets::Error> { fn load(specifier: &str) -> Result<Self, assets::Error> {
@ -235,6 +85,8 @@ pub struct Ui {
cache: Cache, cache: Cache,
// Draw commands for the next render // Draw commands for the next render
draw_commands: Vec<DrawCommand>, draw_commands: Vec<DrawCommand>,
// Model for drawing the ui
model: DynamicModel<UiPipeline>,
// Stores new window size for updating scaling // Stores new window size for updating scaling
window_resized: Option<Vec2<f64>>, window_resized: Option<Vec2<f64>>,
// Scaling of the ui // Scaling of the ui
@ -250,8 +102,9 @@ impl Ui {
ui: UiBuilder::new(win_dims).build(), ui: UiBuilder::new(win_dims).build(),
image_map: Map::new(), image_map: Map::new(),
cache: Cache::new(window.renderer_mut())?, cache: Cache::new(window.renderer_mut())?,
window_resized: None,
draw_commands: vec![], draw_commands: vec![],
model: window.renderer_mut().create_dynamic_model(100)?,
window_resized: None,
scale, scale,
}) })
} }
@ -268,7 +121,7 @@ impl Ui {
self.image_map.insert(self.cache.add_graphic(graphic)) self.image_map.insert(self.cache.add_graphic(graphic))
} }
pub fn new_font(&mut self, mut font: Arc<Font>) -> text::font::Id { pub fn new_font(&mut self, mut font: Arc<Font>) -> font::Id {
self.ui.fonts.insert(font.as_ref().0.clone()) self.ui.fonts.insert(font.as_ref().0.clone())
} }
@ -309,7 +162,9 @@ impl Ui {
} }
pub fn handle_event(&mut self, event: Event) { pub fn handle_event(&mut self, event: Event) {
match event.0 { 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 { Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch {
xy: self.scale.scale_point(touch.xy.into()).into_array(), xy: self.scale.scale_point(touch.xy.into()).into_array(),
..touch ..touch
@ -338,308 +193,315 @@ impl Ui {
} }
pub fn maintain(&mut self, renderer: &mut Renderer) { 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 // Regenerate draw commands and associated models only if the ui changed
if let Some(mut primitives) = ui.draw_if_changed() { let mut primitives = match self.ui.draw_if_changed() {
self.draw_commands.clear(); Some(primitives) => primitives,
let mut mesh = Mesh::new(); None => return,
};
// TODO: this could be removed entirely if the draw call just used both textures self.draw_commands.clear();
// however this allows for flexibility if we want to interleave other draw calls later let mut mesh = Mesh::new();
enum State {
Image, // TODO: this could be removed entirely if the draw call just used both textures
Plain, // 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); while let Some(prim) = primitives.next() {
let mut current_scizzor = window_scizzor; let Primitive {
kind,
scizzor,
rect,
..
} = prim;
// Switches to the `Plain` state and completes the previous `Command` if not already in the // Check for a change in the scizzor
// `Plain` state. let new_scizzor = {
macro_rules! switch_to_plain_state { let (l, b, w, h) = scizzor.l_b_w_h();
() => { // Calculate minimum x and y coordinates while
if let State::Image = current_state { // - flipping y axis (from +up to +down)
self.draw_commands // - moving origin to top-left corner (from middle)
.push(DrawCommand::image(renderer.create_model(&mesh).unwrap())); let min_x = self.ui.win_w / 2.0 + l;
mesh.clear(); let min_y = self.ui.win_h / 2.0 - b - h;
current_state = State::Plain; 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(); // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0)
let ui_win_w = self.ui.win_w;
while let Some(prim) = primitives.next() { let ui_win_h = self.ui.win_h;
let Primitive { let vx = |x: f64| (x / ui_win_w * 2.0) as f32;
kind, let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
scizzor, let gl_aabr = |rect: conrod_core::Rect| {
id: _id, let (l, r, b, t) = rect.l_r_b_t();
rect, Aabr {
} = prim; min: Vec2::new(vx(l), vy(b)),
max: Vec2::new(vx(r), vy(t)),
// 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) use conrod_core::render::PrimitiveKind;
let vx = |x: f64| (x / ui.win_w * 2.0) as f32; match kind {
let vy = |y: f64| (y / ui.win_h * 2.0) as f32; PrimitiveKind::Image {
let gl_aabr = |rect: conrod_core::Rect| { image_id,
let (l, r, b, t) = rect.l_r_b_t(); color,
Aabr { source_rect,
min: Vec2::new(vx(l), vy(b)), } => {
max: Vec2::new(vx(r), vy(t)), 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; // Switch to the image state if we are not in it already
match kind { if let State::Plain = current_state {
PrimitiveKind::Image { self.draw_commands
image_id, .push(DrawCommand::plain(start..mesh.vertices().len()));
color, start = mesh.vertices().len();
source_rect, current_state = State::Image;
} => { }
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) { let color =
Some(Graphic::Blank) | None => continue, 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. // Cache graphic at particular resolution
if let State::Plain = current_state { let uv_aabr = match graphic_cache.cache_res(
self.draw_commands *graphic_id,
.push(DrawCommand::plain(renderer.create_model(&mesh).unwrap())); resolution,
mesh.clear(); source_aabr,
current_state = State::Image; |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( mesh.push_quad(create_ui_quad(gl_aabr(rect), uv_aabr, color, UiMode::Image));
color.unwrap_or(conrod_core::color::WHITE).to_fsa().into(), }
); 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( let positioned_glyphs = text.positioned_glyphs(dpi_factor);
(rect.w() * p_scale_factor) as u16, let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex();
(rect.h() * p_scale_factor) as u16, // Queue the glyphs to be cached
); for glyph in positioned_glyphs {
// Transform the source rectangle into uv coordinate glyph_cache.queue_glyph(font_id.index(), glyph.clone());
// 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();
// Cache graphic at particular resolution glyph_cache
let uv_aabr = match graphic_cache.cache_res( .cache_queued(|rect, data| {
*graphic_id, let offset = [rect.min.x as u16, rect.min.y as u16];
resolution, let size = [rect.width() as u16, rect.height() as u16];
source_aabr,
|aabr, data| { let new_data = data
let offset = aabr.min.into_array(); .iter()
let size = aabr.size().into_array(); .map(|x| [255, 255, 255, *x])
renderer.update_texture(cache_tex, offset, size, &data); .collect::<Vec<[u8; 4]>>();
},
) { renderer.update_texture(cache_tex, offset, size, &new_data);
Some(aabr) => Aabr { })
.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( min: Vec2::new(
aabr.min.x as f32 / cache_w, (screen_rect.min.x as f32 / screen_w - 0.5) * 2.0,
aabr.max.y as f32 / cache_h, (screen_rect.max.y as f32 / screen_h - 0.5) * -2.0,
), ),
max: Vec2::new( max: Vec2::new(
aabr.max.x as f32 / cache_w, (screen_rect.max.x as f32 / screen_w - 0.5) * 2.0,
aabr.min.y as f32 / cache_h, (screen_rect.min.y as f32 / screen_h - 0.5) * -2.0,
), ),
}, };
None => continue, mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text));
}; }
mesh.push_quad(create_ui_quad(
gl_aabr(rect),
uv_aabr,
color,
UiMode::Image,
));
} }
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, color,
text, UiMode::Geometry,
font_id, ));
} => { }
switch_to_plain_state!(); PrimitiveKind::TrianglesSingleColor { color, triangles } => {
// Get screen width and height // Don't draw transparent triangle or switch state if there are actually no triangles
let (screen_w, screen_h) = let color = srgb_to_linear(Rgba::from(Into::<[f32; 4]>::into(color)));
renderer.get_resolution().map(|e| e as f32).into_tuple(); if triangles.is_empty() || color[3] == 0.0 {
// Calculate dpi factor continue;
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::<Vec<[u8; 4]>>();
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));
}
}
} }
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( for tri in triangles {
gl_aabr(rect), let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1]));
Aabr { let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1]));
min: Vec2::new(0.0, 0.0), let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1]));
max: Vec2::new(0.0, 0.0), // If triangle is clockwise reverse it
}, let (v1, v2): (Vec3<f32>, Vec3<f32>) = ((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, color,
UiMode::Geometry, 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<f32>, Vec3<f32>) =
((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 { // Enter the final command
State::Plain => DrawCommand::plain(renderer.create_model(&mesh).unwrap()), self.draw_commands.push(match current_state {
State::Image => DrawCommand::image(renderer.create_model(&mesh).unwrap()), State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
}); State::Image => DrawCommand::image(start..mesh.vertices().len()),
});
// Handle window resizing // create a larger dynamic model if the mesh is larger than the current model size
if let Some(new_dims) = self.window_resized.take() { if self.model.vbuf.len() < mesh.vertices().len() {
self.scale.window_resized(new_dims, renderer); self.model = renderer
let (w, h) = self.scale.scaled_window_size().into_tuple(); .create_dynamic_model(mesh.vertices().len() * 4 / 3)
self.ui.handle_event(Input::Resize(w, h)); .unwrap();
}
renderer.update_model(&self.model, &mesh, 0).unwrap();
// Update model with new mesh
let res = renderer.get_resolution(); // Handle window resizing
// Avoid panic in graphic cache when minimizing if let Some(new_dims) = self.window_resized.take() {
if res.x > 0 && res.y > 0 { self.scale.window_resized(new_dims, renderer);
self.cache let (w, h) = self.scale.scaled_window_size().into_tuple();
.clear_graphic_cache(renderer, renderer.get_resolution().map(|e| e * 4)); self.ui.handle_event(Input::Resize(w, h));
}
// TODO: probably need to resize glyph cache, see conrod's gfx backend for reference 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) => { DrawCommand::Scissor(scizzor) => {
scissor = *scizzor; scissor = *scizzor;
} }
DrawCommand::Draw { kind, model } => { DrawCommand::Draw { kind, verts } => {
let tex = match kind { let tex = match kind {
DrawKind::Image => self.cache.graphic_cache_tex(), DrawKind::Image => self.cache.graphic_cache_tex(),
DrawKind::Plain => self.cache.glyph_cache_tex(), DrawKind::Plain => self.cache.glyph_cache_tex(),
}; };
let model = self.model.submodel(verts.clone());
renderer.render_ui_element(&model, &tex, scissor); renderer.render_ui_element(&model, &tex, scissor);
} }
} }

65
voxygen/src/ui/scale.rs Normal file
View File

@ -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<f64>),
}
pub struct Scale {
// Type of scaling to use
mode: ScaleMode,
// Current dpi factor
dpi_factor: f64,
// Current logical window size
window_dims: Vec2<f64>,
}
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<f64>, 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<f64> {
self.window_dims / self.scale_factor_logical()
}
// Transform point from logical to scaled coordinates
pub fn scale_point(&self, point: Vec2<f64>) -> Vec2<f64> {
point / self.scale_factor_logical()
}
}