Initial skin support
This commit is contained in:
parent
0bbb10918e
commit
8469b32061
|
@ -48,6 +48,8 @@ pub fn create_local(m: &mut ecs::Manager) -> ecs::Entity {
|
||||||
|
|
||||||
pub struct PlayerModel {
|
pub struct PlayerModel {
|
||||||
model: Option<model::ModelKey>,
|
model: Option<model::ModelKey>,
|
||||||
|
skin_url: Option<String>,
|
||||||
|
dirty: bool,
|
||||||
|
|
||||||
has_head: bool,
|
has_head: bool,
|
||||||
has_name_tag: bool,
|
has_name_tag: bool,
|
||||||
|
@ -64,6 +66,8 @@ impl PlayerModel {
|
||||||
pub fn new(has_head: bool, has_name_tag: bool, first_person: bool) -> PlayerModel {
|
pub fn new(has_head: bool, has_name_tag: bool, first_person: bool) -> PlayerModel {
|
||||||
PlayerModel {
|
PlayerModel {
|
||||||
model: None,
|
model: None,
|
||||||
|
skin_url: None,
|
||||||
|
dirty: false,
|
||||||
|
|
||||||
has_head: has_head,
|
has_head: has_head,
|
||||||
has_name_tag: has_name_tag,
|
has_name_tag: has_name_tag,
|
||||||
|
@ -76,6 +80,11 @@ impl PlayerModel {
|
||||||
arm_time: 0.0,
|
arm_time: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_skin(&mut self, skin: Option<String>) {
|
||||||
|
self.skin_url = skin;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PlayerRenderer {
|
struct PlayerRenderer {
|
||||||
|
@ -122,7 +131,7 @@ impl ecs::System for PlayerRenderer {
|
||||||
&self.filter
|
&self.filter
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, m: &mut ecs::Manager, _: &mut world::World, renderer: &mut render::Renderer) {
|
fn update(&mut self, m: &mut ecs::Manager, world: &mut world::World, renderer: &mut render::Renderer) {
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::f64::consts::PI as PI64;
|
use std::f64::consts::PI as PI64;
|
||||||
let world_entity = m.get_world();
|
let world_entity = m.get_world();
|
||||||
|
@ -132,6 +141,11 @@ impl ecs::System for PlayerRenderer {
|
||||||
let position = m.get_component_mut(e, self.position).unwrap();
|
let position = m.get_component_mut(e, self.position).unwrap();
|
||||||
let rotation = m.get_component_mut(e, self.rotation).unwrap();
|
let rotation = m.get_component_mut(e, self.rotation).unwrap();
|
||||||
|
|
||||||
|
if player_model.dirty {
|
||||||
|
self.entity_removed(m, e, world, renderer);
|
||||||
|
self.entity_added(m, e, world, renderer);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(pmodel) = player_model.model {
|
if let Some(pmodel) = player_model.model {
|
||||||
let mdl = renderer.model.get_model(pmodel).unwrap();
|
let mdl = renderer.model.get_model(pmodel).unwrap();
|
||||||
let offset = if player_model.first_person {
|
let offset = if player_model.first_person {
|
||||||
|
@ -242,7 +256,13 @@ impl ecs::System for PlayerRenderer {
|
||||||
fn entity_added(&mut self, m: &mut ecs::Manager, e: ecs::Entity, _: &mut world::World, renderer: &mut render::Renderer) {
|
fn entity_added(&mut self, m: &mut ecs::Manager, e: ecs::Entity, _: &mut world::World, renderer: &mut render::Renderer) {
|
||||||
let player_model = m.get_component_mut(e, self.player_model).unwrap();
|
let player_model = m.get_component_mut(e, self.player_model).unwrap();
|
||||||
|
|
||||||
let skin = render::Renderer::get_texture(renderer.get_textures_ref(), "entity/steve");
|
player_model.dirty = false;
|
||||||
|
|
||||||
|
let skin = if let Some(url) = player_model.skin_url.as_ref() {
|
||||||
|
renderer.get_skin(renderer.get_textures_ref(), &url)
|
||||||
|
} else {
|
||||||
|
render::Renderer::get_texture(renderer.get_textures_ref(), "entity/steve")
|
||||||
|
};
|
||||||
|
|
||||||
macro_rules! srel {
|
macro_rules! srel {
|
||||||
($x:expr, $y:expr, $w:expr, $h:expr) => (
|
($x:expr, $y:expr, $w:expr, $h:expr) => (
|
||||||
|
@ -335,6 +355,9 @@ impl ecs::System for PlayerRenderer {
|
||||||
if let Some(model) = player_model.model.take() {
|
if let Some(model) = player_model.model.take() {
|
||||||
renderer.model.remove_model(model);
|
renderer.model.remove_model(model);
|
||||||
}
|
}
|
||||||
|
if let Some(url) = player_model.skin_url.take() {
|
||||||
|
renderer.get_textures_ref().read().unwrap().release_skin(&url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ use collision;
|
||||||
|
|
||||||
use std::hash::BuildHasherDefault;
|
use std::hash::BuildHasherDefault;
|
||||||
use types::hash::FNVHash;
|
use types::hash::FNVHash;
|
||||||
|
use std::sync::atomic::{AtomicIsize, Ordering};
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
const ATLAS_SIZE: usize = 1024;
|
const ATLAS_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
@ -82,6 +85,8 @@ pub struct Renderer {
|
||||||
// Light renderering
|
// Light renderering
|
||||||
pub light_level: f32,
|
pub light_level: f32,
|
||||||
pub sky_offset: f32,
|
pub sky_offset: f32,
|
||||||
|
skin_request: mpsc::Sender<String>,
|
||||||
|
skin_reply: mpsc::Receiver<(String, Option<image::DynamicImage>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChunkBuffer {
|
pub struct ChunkBuffer {
|
||||||
|
@ -170,7 +175,8 @@ impl Renderer {
|
||||||
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE);
|
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE);
|
||||||
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE);
|
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE);
|
||||||
|
|
||||||
let textures = Arc::new(RwLock::new(TextureManager::new(res.clone())));
|
let (textures, skin_req, skin_reply) = TextureManager::new(res.clone());
|
||||||
|
let textures = Arc::new(RwLock::new(textures));
|
||||||
|
|
||||||
let mut greg = glsl::Registry::new();
|
let mut greg = glsl::Registry::new();
|
||||||
shaders::add_shaders(&mut greg);
|
shaders::add_shaders(&mut greg);
|
||||||
|
@ -230,6 +236,8 @@ impl Renderer {
|
||||||
|
|
||||||
light_level: 0.8,
|
light_level: 0.8,
|
||||||
sky_offset: 1.0,
|
sky_offset: 1.0,
|
||||||
|
skin_request: skin_req,
|
||||||
|
skin_reply: skin_reply,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +537,27 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_textures(&mut self, delta: f64) {
|
fn update_textures(&mut self, delta: f64) {
|
||||||
|
{
|
||||||
|
let mut tex = self.textures.write().unwrap();
|
||||||
|
while let Ok((hash, img)) = self.skin_reply.try_recv() {
|
||||||
|
if let Some(img) = img {
|
||||||
|
debug!("Got completed skin: {:?}", hash);
|
||||||
|
tex.update_skin(hash, img);
|
||||||
|
} else {
|
||||||
|
debug!("Failed to get skin for {:?}", hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut old_skins = vec![];
|
||||||
|
for (skin, refcount) in &tex.skins {
|
||||||
|
if refcount.load(Ordering::Relaxed) == 0 {
|
||||||
|
old_skins.push(skin.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for skin in old_skins {
|
||||||
|
tex.skins.remove(&skin);
|
||||||
|
tex.remove_dynamic(&format!("skin-{}", skin));
|
||||||
|
}
|
||||||
|
}
|
||||||
self.gl_texture.bind(gl::TEXTURE_2D_ARRAY);
|
self.gl_texture.bind(gl::TEXTURE_2D_ARRAY);
|
||||||
self.do_pending_textures();
|
self.do_pending_textures();
|
||||||
|
|
||||||
|
@ -603,6 +632,26 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_skin(&self, textures: &RwLock<TextureManager>, url: &str) -> Texture {
|
||||||
|
let tex = {
|
||||||
|
textures.read().unwrap().get_skin(url)
|
||||||
|
};
|
||||||
|
match tex {
|
||||||
|
Some(val) => val,
|
||||||
|
None => {
|
||||||
|
let mut t = textures.write().unwrap();
|
||||||
|
// Make sure it hasn't already been loaded since we switched
|
||||||
|
// locks.
|
||||||
|
if let Some(val) = t.get_skin(url) {
|
||||||
|
val
|
||||||
|
} else {
|
||||||
|
t.load_skin(self, url);
|
||||||
|
t.get_skin(url).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransInfo {
|
struct TransInfo {
|
||||||
|
@ -739,13 +788,23 @@ pub struct TextureManager {
|
||||||
|
|
||||||
dynamic_textures: HashMap<String, (Texture, image::DynamicImage), BuildHasherDefault<FNVHash>>,
|
dynamic_textures: HashMap<String, (Texture, image::DynamicImage), BuildHasherDefault<FNVHash>>,
|
||||||
free_dynamics: Vec<Texture>,
|
free_dynamics: Vec<Texture>,
|
||||||
|
|
||||||
|
skins: HashMap<String, AtomicIsize, BuildHasherDefault<FNVHash>>,
|
||||||
|
|
||||||
|
_skin_thread: thread::JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureManager {
|
impl TextureManager {
|
||||||
fn new(res: Arc<RwLock<resources::Manager>>) -> TextureManager {
|
fn new(res: Arc<RwLock<resources::Manager>>) -> (TextureManager, mpsc::Sender<String>, mpsc::Receiver<(String, Option<image::DynamicImage>)>) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let (stx, srx) = mpsc::channel();
|
||||||
|
let skin_thread = thread::spawn(|| Self::process_skins(srx, tx));
|
||||||
let mut tm = TextureManager {
|
let mut tm = TextureManager {
|
||||||
textures: HashMap::with_hasher(BuildHasherDefault::default()),
|
textures: HashMap::with_hasher(BuildHasherDefault::default()),
|
||||||
version: 0xFFFF,
|
version: {
|
||||||
|
let ver = res.read().unwrap().version();
|
||||||
|
ver
|
||||||
|
},
|
||||||
resources: res,
|
resources: res,
|
||||||
atlases: Vec::new(),
|
atlases: Vec::new(),
|
||||||
animated_textures: Vec::new(),
|
animated_textures: Vec::new(),
|
||||||
|
@ -753,9 +812,12 @@ impl TextureManager {
|
||||||
|
|
||||||
dynamic_textures: HashMap::with_hasher(BuildHasherDefault::default()),
|
dynamic_textures: HashMap::with_hasher(BuildHasherDefault::default()),
|
||||||
free_dynamics: Vec::new(),
|
free_dynamics: Vec::new(),
|
||||||
|
skins: HashMap::with_hasher(BuildHasherDefault::default()),
|
||||||
|
|
||||||
|
_skin_thread: skin_thread,
|
||||||
};
|
};
|
||||||
tm.add_defaults();
|
tm.add_defaults();
|
||||||
tm
|
(tm, stx, rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_defaults(&mut self) {
|
fn add_defaults(&mut self) {
|
||||||
|
@ -778,6 +840,29 @@ impl TextureManager {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_skins(recv: mpsc::Receiver<String>, reply: mpsc::Sender<(String, Option<image::DynamicImage>)>) {
|
||||||
|
use hyper;
|
||||||
|
use std::io::Read;
|
||||||
|
let client = hyper::Client::new();
|
||||||
|
loop {
|
||||||
|
// TODO: Cache
|
||||||
|
let hash = recv.recv().unwrap();
|
||||||
|
trace!("Fetching skin {:?}", hash);
|
||||||
|
let url = format!("http://textures.minecraft.net/texture/{}", hash);
|
||||||
|
let mut res = client.get(&url).send().unwrap();
|
||||||
|
let mut buf = vec![];
|
||||||
|
res.read_to_end(&mut buf).unwrap();
|
||||||
|
let img = match image::load_from_memory(&buf) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
reply.send((hash, None)).unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reply.send((hash, Some(img))).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_textures(&mut self, version: usize) {
|
fn update_textures(&mut self, version: usize) {
|
||||||
self.pending_uploads.clear();
|
self.pending_uploads.clear();
|
||||||
self.atlases.clear();
|
self.atlases.clear();
|
||||||
|
@ -810,6 +895,48 @@ impl TextureManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_skin(&self, url: &str) -> Option<Texture> {
|
||||||
|
let hash = &url["http://textures.minecraft.net/texture/".len()..];
|
||||||
|
if let Some(skin) = self.skins.get(hash) {
|
||||||
|
skin.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
self.get_texture(&format!("steven-dynamic:skin-{}", hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_skin(&self, url: &str) {
|
||||||
|
let hash = &url["http://textures.minecraft.net/texture/".len()..];
|
||||||
|
if let Some(skin) = self.skins.get(hash) {
|
||||||
|
skin.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_skin(&mut self, renderer: &Renderer, url: &str) {
|
||||||
|
let hash = &url["http://textures.minecraft.net/texture/".len()..];
|
||||||
|
let res = self.resources.clone();
|
||||||
|
// TODO: This shouldn't be hardcoded to steve but instead
|
||||||
|
// have a way to select alex as a default.
|
||||||
|
let mut val = res.read().unwrap().open("minecraft", "textures/entity/steve.png").unwrap();
|
||||||
|
let mut data = Vec::new();
|
||||||
|
val.read_to_end(&mut data).unwrap();
|
||||||
|
let img = image::load_from_memory(&data).unwrap();
|
||||||
|
self.put_dynamic(&format!("skin-{}", hash), img);
|
||||||
|
self.skins.insert(hash.to_owned(), AtomicIsize::new(0));
|
||||||
|
renderer.skin_request.send(hash.to_owned()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_skin(&mut self, hash: String, img: image::DynamicImage) {
|
||||||
|
if !self.skins.contains_key(&hash) { return; }
|
||||||
|
let tex = self.get_texture(&format!("steven-dynamic:skin-{}", hash)).unwrap();
|
||||||
|
let rect = atlas::Rect {
|
||||||
|
x: tex.x,
|
||||||
|
y: tex.y,
|
||||||
|
width: tex.width,
|
||||||
|
height: tex.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pending_uploads.push((tex.atlas, rect, img.to_rgba().into_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
fn get_texture(&self, name: &str) -> Option<Texture> {
|
fn get_texture(&self, name: &str) -> Option<Texture> {
|
||||||
if let Some(_) = name.find(':') {
|
if let Some(_) = name.find(':') {
|
||||||
self.textures.get(name).cloned()
|
self.textures.get(name).cloned()
|
||||||
|
@ -1005,7 +1132,12 @@ impl TextureManager {
|
||||||
};
|
};
|
||||||
self.pending_uploads.push((tex.atlas, rect, data));
|
self.pending_uploads.push((tex.atlas, rect, data));
|
||||||
let t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32));
|
let t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32));
|
||||||
self.dynamic_textures.insert(name.to_owned(), (tex, img));
|
self.dynamic_textures.insert(name.to_owned(), (tex.clone(), img));
|
||||||
|
// We need to rename the texture itself so that get_texture calls
|
||||||
|
// work with the new name
|
||||||
|
let mut old = self.textures.remove(&tex.name).unwrap();
|
||||||
|
old.name = name.to_owned();
|
||||||
|
self.textures.insert(format!("steven-dynamic:{}", name), old);
|
||||||
t
|
t
|
||||||
} else {
|
} else {
|
||||||
let tex = self.put_texture("steven-dynamic", name, width as u32, height as u32, data);
|
let tex = self.put_texture("steven-dynamic", name, width as u32, height as u32, data);
|
||||||
|
|
|
@ -488,6 +488,10 @@ impl Server {
|
||||||
fn on_game_join(&mut self, join: packet::play::clientbound::JoinGame) {
|
fn on_game_join(&mut self, join: packet::play::clientbound::JoinGame) {
|
||||||
let gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32);
|
let gamemode = Gamemode::from_int((join.gamemode & 0x7) as i32);
|
||||||
let player = entity::player::create_local(&mut self.entities);
|
let player = entity::player::create_local(&mut self.entities);
|
||||||
|
if let Some(info) = self.players.get(&self.uuid) {
|
||||||
|
let model = self.entities.get_component_mut_direct::<entity::player::PlayerModel>(player).unwrap();
|
||||||
|
model.set_skin(info.skin_url.clone());
|
||||||
|
}
|
||||||
*self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode;
|
*self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode;
|
||||||
// TODO: Temp
|
// TODO: Temp
|
||||||
self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly();
|
self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly();
|
||||||
|
@ -610,6 +614,16 @@ impl Server {
|
||||||
info.skin_url = Some(skin_url.to_owned());
|
info.skin_url = Some(skin_url.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh our own skin when the server sends it to us.
|
||||||
|
// The join game packet can come before this packet meaning
|
||||||
|
// we may not have the skin in time for spawning ourselves.
|
||||||
|
// This isn't an issue for other players because this packet
|
||||||
|
// must come before the spawn player packet.
|
||||||
|
if info.uuid == self.uuid {
|
||||||
|
let model = self.entities.get_component_mut_direct::<entity::player::PlayerModel>(self.player.unwrap()).unwrap();
|
||||||
|
model.set_skin(info.skin_url.clone());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
UpdateGamemode { uuid, gamemode } => {
|
UpdateGamemode { uuid, gamemode } => {
|
||||||
if let Some(info) = self.players.get_mut(&uuid) {
|
if let Some(info) = self.players.get_mut(&uuid) {
|
||||||
|
|
Loading…
Reference in New Issue