stevenarella/src/render/mod.rs

1348 lines
47 KiB
Rust
Raw Normal View History

2016-03-16 14:25:35 -04:00
// Copyright 2016 Matthew Collins
2015-09-17 11:21:56 -04:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-09-17 11:04:25 -04:00
mod atlas;
pub mod glsl;
2015-10-06 18:49:52 -04:00
#[macro_use]
pub mod shaders;
2015-09-17 11:04:25 -04:00
pub mod ui;
2016-03-27 08:27:31 -04:00
pub mod model;
2016-04-01 15:00:13 -04:00
pub mod clouds;
2015-09-17 11:04:25 -04:00
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
2016-03-19 13:34:12 -04:00
use std::io::Write;
use crate::resources;
use crate::gl;
2015-09-17 11:04:25 -04:00
use image;
2018-09-30 20:21:05 -04:00
use image::{GenericImage, GenericImageView};
2015-09-17 11:04:25 -04:00
use byteorder::{WriteBytesExt, NativeEndian};
use serde_json;
use cgmath::prelude::*;
use crate::world;
2016-03-22 07:47:02 -04:00
use collision;
use log::{error, trace};
2015-09-17 11:04:25 -04:00
2016-03-24 11:39:57 -04:00
use std::hash::BuildHasherDefault;
use crate::types::hash::FNVHash;
2016-04-07 10:55:03 -04:00
use std::sync::atomic::{AtomicIsize, Ordering};
use std::thread;
use std::sync::mpsc;
2016-03-24 11:39:57 -04:00
2015-09-17 11:04:25 -04:00
const ATLAS_SIZE: usize = 1024;
2016-03-18 13:16:03 -04:00
// TEMP
const NUM_SAMPLES: i32 = 2;
2016-03-18 13:16:03 -04:00
pub struct Camera {
pub pos: cgmath::Point3<f64>,
pub yaw: f64,
pub pitch: f64,
}
2015-09-17 11:04:25 -04:00
pub struct Renderer {
2015-10-07 14:36:59 -04:00
resource_version: usize,
pub resources: Arc<RwLock<resources::Manager>>,
textures: Arc<RwLock<TextureManager>>,
pub ui: ui::UIState,
2016-03-27 18:31:57 -04:00
pub model: model::Manager,
2016-04-01 15:00:13 -04:00
pub clouds: clouds::Clouds,
2015-09-17 11:04:25 -04:00
2015-10-07 14:36:59 -04:00
gl_texture: gl::Texture,
texture_layers: usize,
2015-09-17 11:04:25 -04:00
2016-03-16 13:33:06 -04:00
chunk_shader: ChunkShader,
chunk_shader_alpha: ChunkShaderAlpha,
2016-03-18 11:19:21 -04:00
trans_shader: TransShader,
2016-03-16 13:33:06 -04:00
2016-03-19 13:34:12 -04:00
element_buffer: gl::Buffer,
element_buffer_size: usize,
element_buffer_type: gl::Type,
pub camera: Camera,
2016-03-16 13:33:06 -04:00
perspective_matrix: cgmath::Matrix4<f32>,
2016-03-24 17:47:11 -04:00
camera_matrix: cgmath::Matrix4<f32>,
pub frustum: collision::Frustum<f32>,
pub view_vector: cgmath::Vector3<f32>,
2016-03-16 13:33:06 -04:00
2016-03-24 19:27:22 -04:00
pub frame_id: u32,
2016-03-16 13:33:06 -04:00
trans: Option<TransInfo>,
pub width: u32,
pub height: u32,
// Light renderering
pub light_level: f32,
pub sky_offset: f32,
2016-04-07 10:55:03 -04:00
skin_request: mpsc::Sender<String>,
skin_reply: mpsc::Receiver<(String, Option<image::DynamicImage>)>,
2015-09-17 11:04:25 -04:00
}
#[derive(Default)]
2016-03-24 17:13:24 -04:00
pub struct ChunkBuffer {
2016-03-19 13:34:12 -04:00
solid: Option<ChunkRenderInfo>,
trans: Option<ChunkRenderInfo>,
}
2016-03-24 17:13:24 -04:00
impl ChunkBuffer {
pub fn new() -> ChunkBuffer { Default::default() }
2016-03-24 17:13:24 -04:00
}
2016-03-19 13:34:12 -04:00
struct ChunkRenderInfo {
array: gl::VertexArray,
buffer: gl::Buffer,
buffer_size: usize,
count: usize,
}
2016-03-16 13:33:06 -04:00
init_shader! {
Program ChunkShader {
vert = "chunk_vertex",
frag = "chunk_frag",
attribute = {
required position => "aPosition",
required texture_info => "aTextureInfo",
required texture_offset => "aTextureOffset",
required color => "aColor",
required lighting => "aLighting",
2016-03-16 13:33:06 -04:00
},
uniform = {
required perspective_matrix => "perspectiveMatrix",
required camera_matrix => "cameraMatrix",
required offset => "offset",
required texture => "textures",
required light_level => "lightLevel",
required sky_offset => "skyOffset",
2016-03-16 13:33:06 -04:00
},
}
}
init_shader! {
Program ChunkShaderAlpha {
vert = "chunk_vertex",
frag = "chunk_frag", #alpha
attribute = {
required position => "aPosition",
required texture_info => "aTextureInfo",
required texture_offset => "aTextureOffset",
required color => "aColor",
required lighting => "aLighting",
2016-03-16 13:33:06 -04:00
},
uniform = {
required perspective_matrix => "perspectiveMatrix",
required camera_matrix => "cameraMatrix",
required offset => "offset",
required texture => "textures",
required light_level => "lightLevel",
required sky_offset => "skyOffset",
2016-03-16 13:33:06 -04:00
},
}
}
2015-09-17 11:04:25 -04:00
impl Renderer {
2015-10-07 14:36:59 -04:00
pub fn new(res: Arc<RwLock<resources::Manager>>) -> Renderer {
let version = {
res.read().unwrap().version()
};
let tex = gl::Texture::new();
tex.bind(gl::TEXTURE_2D_ARRAY);
tex.image_3d(gl::TEXTURE_2D_ARRAY,
0,
ATLAS_SIZE as u32,
ATLAS_SIZE as u32,
1,
gl::RGBA,
gl::UNSIGNED_BYTE,
&[0; ATLAS_SIZE * ATLAS_SIZE * 4]);
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
tex.set_parameter(gl::TEXTURE_2D_ARRAY, gl::TEXTURE_MIN_FILTER, gl::NEAREST);
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);
2016-04-07 10:55:03 -04:00
let (textures, skin_req, skin_reply) = TextureManager::new(res.clone());
let textures = Arc::new(RwLock::new(textures));
2015-10-07 14:36:59 -04:00
let mut greg = glsl::Registry::new();
shaders::add_shaders(&mut greg);
let ui = ui::UIState::new(&greg, textures.clone(), res.clone());
gl::enable(gl::DEPTH_TEST);
gl::enable(gl::CULL_FACE_FLAG);
gl::cull_face(gl::BACK);
gl::front_face(gl::CLOCK_WISE);
2015-10-06 18:49:52 -04:00
2016-03-16 13:33:06 -04:00
// Shaders
let chunk_shader = ChunkShader::new(&greg);
let chunk_shader_alpha = ChunkShaderAlpha::new(&greg);
2016-03-18 11:19:21 -04:00
let trans_shader = TransShader::new(&greg);
2015-10-06 18:49:52 -04:00
2016-03-16 13:33:06 -04:00
// UI
// Line Drawer
// Clouds
2015-10-06 18:49:52 -04:00
2015-10-07 14:36:59 -04:00
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
2016-03-27 19:50:30 -04:00
gl::depth_func(gl::LESS_OR_EQUAL);
2015-10-07 14:36:59 -04:00
Renderer {
resource_version: version,
2016-03-27 18:31:57 -04:00
model: model::Manager::new(&greg),
2016-04-01 15:00:13 -04:00
clouds: clouds::Clouds::new(&greg, textures.clone()),
textures,
ui,
2015-10-07 14:36:59 -04:00
resources: res,
gl_texture: tex,
texture_layers: 1,
2016-03-16 13:33:06 -04:00
chunk_shader,
chunk_shader_alpha,
trans_shader,
2016-03-16 13:33:06 -04:00
2016-03-19 13:34:12 -04:00
element_buffer: gl::Buffer::new(),
element_buffer_size: 0,
element_buffer_type: gl::UNSIGNED_BYTE,
width: 0,
height: 0,
2016-03-16 13:33:06 -04:00
2016-03-18 13:16:03 -04:00
camera: Camera {
pos: cgmath::Point3::new(0.0, 0.0, 0.0),
yaw: 0.0,
pitch: ::std::f64::consts::PI,
},
2016-03-24 17:47:11 -04:00
perspective_matrix: cgmath::Matrix4::identity(),
camera_matrix: cgmath::Matrix4::identity(),
frustum: collision::Frustum::from_matrix4(cgmath::Matrix4::identity()).unwrap(),
view_vector: cgmath::Vector3::zero(),
2016-03-16 13:33:06 -04:00
2016-03-24 19:27:22 -04:00
frame_id: 1,
2016-03-16 13:33:06 -04:00
trans: None,
light_level: 0.8,
sky_offset: 1.0,
2016-04-07 10:55:03 -04:00
skin_request: skin_req,
skin_reply,
2015-10-07 14:36:59 -04:00
}
}
2016-03-24 17:47:11 -04:00
pub fn update_camera(&mut self, width: u32, height: u32) {
2016-03-18 13:16:03 -04:00
use std::f64::consts::PI as PI64;
2016-03-24 20:25:34 -04:00
// Not a sane place to put this but it works
{
let rm = self.resources.read().unwrap();
if rm.version() != self.resource_version {
self.resource_version = rm.version();
trace!("Updating textures to {}", self.resource_version);
self.textures.write().unwrap().update_textures(self.resource_version);
2016-03-27 18:31:57 -04:00
self.model.rebuild_models(self.resource_version, &self.textures);
2016-03-24 20:25:34 -04:00
}
}
2015-10-07 14:36:59 -04:00
if self.height != height || self.width != width {
self.width = width;
self.height = height;
2015-10-07 14:36:59 -04:00
gl::viewport(0, 0, width as i32, height as i32);
2016-03-16 13:33:06 -04:00
self.perspective_matrix = cgmath::Matrix4::from(
cgmath::PerspectiveFov {
fovy: cgmath::Rad::from(cgmath::Deg(90f32)),
2016-03-22 07:27:57 -04:00
aspect: (width as f32 / height as f32),
2016-03-16 13:33:06 -04:00
near: 0.1f32,
far: 500.0f32,
}
);
self.init_trans(width, height);
2015-10-07 14:36:59 -04:00
}
2016-03-24 17:47:11 -04:00
self.view_vector = cgmath::Vector3::new(
((self.camera.yaw - PI64/2.0).cos() * -self.camera.pitch.cos()) as f32,
(-self.camera.pitch.sin()) as f32,
(-(self.camera.yaw - PI64/2.0).sin() * -self.camera.pitch.cos()) as f32
);
let camera = cgmath::Point3::new(-self.camera.pos.x as f32, -self.camera.pos.y as f32, self.camera.pos.z as f32);
let camera_matrix = cgmath::Matrix4::look_at(
camera,
camera + cgmath::Point3::new(-self.view_vector.x, -self.view_vector.y, self.view_vector.z).to_vec(),
cgmath::Vector3::new(0.0, -1.0, 0.0)
);
self.camera_matrix = camera_matrix * cgmath::Matrix4::from_nonuniform_scale(-1.0, 1.0, 1.0);
self.frustum = collision::Frustum::from_matrix4(self.perspective_matrix * self.camera_matrix).unwrap();
}
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
pub fn tick(&mut self, world: &mut world::World, delta: f64, width: u32, height: u32, physical_width: u32, physical_height: u32) {
2016-03-24 17:47:11 -04:00
self.update_textures(delta);
2016-03-18 11:19:21 -04:00
let trans = self.trans.as_mut().unwrap();
trans.main.bind();
2015-10-07 14:36:59 -04:00
gl::active_texture(0);
self.gl_texture.bind(gl::TEXTURE_2D_ARRAY);
2016-03-16 13:33:06 -04:00
gl::enable(gl::MULTISAMPLE);
let time_offset = self.sky_offset * 0.9;
gl::clear_color(
(122.0 / 255.0) * time_offset,
(165.0 / 255.0) * time_offset,
(247.0 / 255.0) * time_offset,
1.0
);
2015-09-17 11:04:25 -04:00
gl::clear(gl::ClearFlags::Color | gl::ClearFlags::Depth);
2016-03-18 11:19:21 -04:00
// Chunk rendering
2016-03-18 13:16:03 -04:00
self.chunk_shader.program.use_program();
self.chunk_shader.perspective_matrix.set_matrix4(&self.perspective_matrix);
2016-03-24 17:47:11 -04:00
self.chunk_shader.camera_matrix.set_matrix4(&self.camera_matrix);
2016-03-18 13:16:03 -04:00
self.chunk_shader.texture.set_int(0);
self.chunk_shader.light_level.set_float(self.light_level);
self.chunk_shader.sky_offset.set_float(self.sky_offset);
2016-03-18 11:19:21 -04:00
2016-03-24 17:47:11 -04:00
for (pos, info) in world.get_render_list() {
2016-03-19 13:34:12 -04:00
if let Some(solid) = info.solid.as_ref() {
2016-03-24 19:27:22 -04:00
if solid.count > 0 {
self.chunk_shader.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
solid.array.bind();
2016-03-27 18:31:57 -04:00
gl::draw_elements(gl::TRIANGLES, solid.count as i32, self.element_buffer_type, 0);
2016-03-24 19:27:22 -04:00
}
2016-03-19 13:34:12 -04:00
}
}
2016-03-18 11:19:21 -04:00
// Line rendering
2016-03-18 13:16:03 -04:00
// Model rendering
2016-03-27 18:31:57 -04:00
self.model.draw(&self.frustum, &self.perspective_matrix, &self.camera_matrix, self.light_level, self.sky_offset);
2016-04-01 15:00:13 -04:00
if world.copy_cloud_heightmap(&mut self.clouds.heightmap_data) {
self.clouds.dirty = true;
}
self.clouds.draw(&self.camera.pos, &self.perspective_matrix, &self.camera_matrix, self.light_level, self.sky_offset, delta);
2016-03-18 11:19:21 -04:00
// Trans chunk rendering
2016-03-24 17:13:24 -04:00
self.chunk_shader_alpha.program.use_program();
self.chunk_shader_alpha.perspective_matrix.set_matrix4(&self.perspective_matrix);
2016-03-24 17:47:11 -04:00
self.chunk_shader_alpha.camera_matrix.set_matrix4(&self.camera_matrix);
2016-03-24 17:13:24 -04:00
self.chunk_shader_alpha.texture.set_int(0);
self.chunk_shader_alpha.light_level.set_float(self.light_level);
self.chunk_shader_alpha.sky_offset.set_float(self.sky_offset);
2016-03-18 11:19:21 -04:00
// Copy the depth buffer
trans.main.bind_read();
trans.trans.bind_draw();
gl::blit_framebuffer(
Use glutin to replace sdl2 (#35) * Add glutin dependency * Create a glutin window * Use the glutin window, basics work * Store DPI factor on game object, update on Resized * Use physical size for rendering only. Fixes UI scaled too small Fixes https://github.com/iceiix/steven/pull/35#issuecomment-442683373 See also https://github.com/iceiix/steven/issues/22 * Begin adding mouse input events * Listen for DeviceEvents * Call hover_at on mouse motion * Listen for CursorMoved window event, hovering works Glutin has separate WindowEvent::CursorMoved and DeviceEvent::MouseMotion events, for absolute cursor and relative mouse motion, respectively, instead of SDL's Event::MouseMotion for both. * Use tuple pattern matching instead of nested if for MouseInput * Implement left clicking * Use grab_cursor() to capture the cursor * Hide the cursor when grabbing * Implement MouseWheel event * Listen for keyboard input, escape key release * Keyboard input: console toggling, glutin calls backquote 'grave' * Implement fullscreen in glutin, updates https://github.com/iceiix/steven/pull/31 * Update settings for glutin VirtualKeyCode * Keyboard controls (note: must clear conf.cfg to use correct bindings) * Move DeviceEvent match arm up higher for clarity * Remove SDL * Pass physical dimensions to renderer tick so blit_framebuffer can use full size but the ui is still sized logically. * Listen for DeviceEvent::Text * Implement text input using ReceivedCharacter window event, works https://github.com/iceiix/steven/pull/35#issuecomment-443247267 * Request specific version of OpenGL, version 3.2 * Request OpenGL 3.2 but fallback to OpenGL ES 2.0 if available (not tested) * Set core profile and depth 24-bits, stencil 0-bits * Allow changing vsync, but require restarting (until https://github.com/tomaka/glutin/issues/693) * Clarify specific Rust version requirement * Import glutin::* in handle_window_event() to avoid overly repetitive code * Linux in VM fix: manually calculate delta in MouseMotion For the third issue on https://github.com/iceiix/steven/pull/35#issuecomment-443084458 https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM * Heuristic to detect absolute/relative MouseMotion from delta:(xrel, yrel); use a higher scaling factor * Add clipboard pasting with clipboard crate instead of sdl2 https://github.com/iceiix/steven/pull/35#issuecomment-443307295
2018-11-30 14:35:35 -05:00
0, 0, physical_width as i32, physical_height as i32,
0, 0, physical_width as i32, physical_height as i32,
2016-03-18 11:19:21 -04:00
gl::ClearFlags::Depth, gl::NEAREST
);
gl::enable(gl::BLEND);
gl::depth_mask(false);
2016-03-24 21:17:03 -04:00
trans.trans.bind();
2016-03-18 11:19:21 -04:00
gl::clear_color(0.0, 0.0, 0.0, 1.0);
gl::clear(gl::ClearFlags::Color);
gl::clear_buffer(gl::COLOR, 0, &[0.0, 0.0, 0.0, 1.0]);
gl::clear_buffer(gl::COLOR, 1, &[0.0, 0.0, 0.0, 0.0]);
gl::blend_func_separate(gl::ONE_FACTOR, gl::ONE_FACTOR, gl::ZERO_FACTOR, gl::ONE_MINUS_SRC_ALPHA);
2016-03-24 21:17:03 -04:00
for (pos, info) in world.get_render_list().into_iter().rev() {
if let Some(trans) = info.trans.as_ref() {
if trans.count > 0 {
self.chunk_shader_alpha.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
trans.array.bind();
2016-03-27 18:31:57 -04:00
gl::draw_elements(gl::TRIANGLES, trans.count as i32, self.element_buffer_type, 0);
2016-03-24 21:17:03 -04:00
}
}
}
2016-03-18 11:19:21 -04:00
gl::check_framebuffer_status();
2016-03-18 11:19:21 -04:00
gl::unbind_framebuffer();
gl::disable(gl::DEPTH_TEST);
gl::clear(gl::ClearFlags::Color);
gl::disable(gl::BLEND);
trans.draw(&self.trans_shader);
gl::enable(gl::DEPTH_TEST);
gl::depth_mask(true);
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::disable(gl::MULTISAMPLE);
2015-09-17 11:04:25 -04:00
self.ui.tick(width, height);
2016-03-24 19:27:22 -04:00
gl::check_gl_error();
2016-03-24 19:27:22 -04:00
self.frame_id = self.frame_id.wrapping_add(1);
2015-10-07 14:36:59 -04:00
}
2016-03-19 13:34:12 -04:00
fn ensure_element_buffer(&mut self, size: usize) {
if self.element_buffer_size < size {
let (data, ty) = self::generate_element_buffer(size);
self.element_buffer_type = ty;
self.element_buffer.bind(gl::ELEMENT_ARRAY_BUFFER);
self.element_buffer.set_data(gl::ELEMENT_ARRAY_BUFFER, &data, gl::DYNAMIC_DRAW);
self.element_buffer_size = size;
}
}
2016-03-24 17:13:24 -04:00
pub fn update_chunk_solid(&mut self, buffer: &mut ChunkBuffer, data: &[u8], count: usize) {
2016-03-19 13:34:12 -04:00
self.ensure_element_buffer(count);
if count == 0 {
if buffer.solid.is_some() {
buffer.solid = None;
}
return;
}
let new = buffer.solid.is_none();
if buffer.solid.is_none() {
buffer.solid = Some(ChunkRenderInfo {
array: gl::VertexArray::new(),
buffer: gl::Buffer::new(),
buffer_size: 0,
count: 0,
});
}
let info = buffer.solid.as_mut().unwrap();
info.array.bind();
self.chunk_shader.position.enable();
self.chunk_shader.texture_info.enable();
self.chunk_shader.texture_offset.enable();
self.chunk_shader.color.enable();
self.chunk_shader.lighting.enable();
self.element_buffer.bind(gl::ELEMENT_ARRAY_BUFFER);
info.buffer.bind(gl::ARRAY_BUFFER);
if new || info.buffer_size < data.len() {
info.buffer_size = data.len();
info.buffer.set_data(gl::ARRAY_BUFFER, data, gl::DYNAMIC_DRAW);
} else {
2016-03-19 16:35:31 -04:00
info.buffer.re_set_data(gl::ARRAY_BUFFER, data);
2016-03-19 13:34:12 -04:00
}
self.chunk_shader.position.vertex_pointer(3, gl::FLOAT, false, 40, 0);
self.chunk_shader.texture_info.vertex_pointer(4, gl::UNSIGNED_SHORT, false, 40, 12);
self.chunk_shader.texture_offset.vertex_pointer(3, gl::SHORT, false, 40, 20);
self.chunk_shader.color.vertex_pointer(3, gl::UNSIGNED_BYTE, true, 40, 28);
self.chunk_shader.lighting.vertex_pointer(2, gl::UNSIGNED_SHORT, false, 40, 32);
info.count = count;
}
2016-03-24 21:17:03 -04:00
pub fn update_chunk_trans(&mut self, buffer: &mut ChunkBuffer, data: &[u8], count: usize) {
self.ensure_element_buffer(count);
if count == 0 {
if buffer.trans.is_some() {
buffer.trans = None;
}
return;
}
let new = buffer.trans.is_none();
if buffer.trans.is_none() {
buffer.trans = Some(ChunkRenderInfo {
array: gl::VertexArray::new(),
buffer: gl::Buffer::new(),
buffer_size: 0,
count: 0,
});
}
let info = buffer.trans.as_mut().unwrap();
info.array.bind();
self.chunk_shader_alpha.position.enable();
self.chunk_shader_alpha.texture_info.enable();
self.chunk_shader_alpha.texture_offset.enable();
self.chunk_shader_alpha.color.enable();
self.chunk_shader_alpha.lighting.enable();
self.element_buffer.bind(gl::ELEMENT_ARRAY_BUFFER);
info.buffer.bind(gl::ARRAY_BUFFER);
if new || info.buffer_size < data.len() {
info.buffer_size = data.len();
info.buffer.set_data(gl::ARRAY_BUFFER, data, gl::DYNAMIC_DRAW);
} else {
info.buffer.re_set_data(gl::ARRAY_BUFFER, data);
}
self.chunk_shader_alpha.position.vertex_pointer(3, gl::FLOAT, false, 40, 0);
self.chunk_shader_alpha.texture_info.vertex_pointer(4, gl::UNSIGNED_SHORT, false, 40, 12);
self.chunk_shader_alpha.texture_offset.vertex_pointer(3, gl::SHORT, false, 40, 20);
self.chunk_shader_alpha.color.vertex_pointer(3, gl::UNSIGNED_BYTE, true, 40, 28);
self.chunk_shader_alpha.lighting.vertex_pointer(2, gl::UNSIGNED_SHORT, false, 40, 32);
info.count = count;
}
2016-03-16 13:33:06 -04:00
fn do_pending_textures(&mut self) {
2015-10-07 14:36:59 -04:00
let len = {
let tex = self.textures.read().unwrap();
2016-03-16 13:33:06 -04:00
// Rebuild the texture if it needs resizing
2015-10-07 14:36:59 -04:00
if self.texture_layers != tex.atlases.len() {
let len = ATLAS_SIZE * ATLAS_SIZE * 4 * tex.atlases.len();
let mut data = Vec::with_capacity(len);
unsafe {
data.set_len(len);
}
self.gl_texture.get_pixels(gl::TEXTURE_2D_ARRAY,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
&mut data[..]);
self.gl_texture.image_3d(gl::TEXTURE_2D_ARRAY,
0,
ATLAS_SIZE as u32,
ATLAS_SIZE as u32,
tex.atlases.len() as u32,
gl::RGBA,
gl::UNSIGNED_BYTE,
&data[..]);
self.texture_layers = tex.atlases.len();
}
tex.pending_uploads.len()
};
if len > 0 {
2016-03-16 13:33:06 -04:00
// Upload pending changes
2015-10-07 14:36:59 -04:00
let mut tex = self.textures.write().unwrap();
for upload in &tex.pending_uploads {
let atlas = upload.0;
let rect = upload.1;
let img = &upload.2;
self.gl_texture.sub_image_3d(gl::TEXTURE_2D_ARRAY,
0,
rect.x as u32,
rect.y as u32,
atlas as u32,
rect.width as u32,
rect.height as u32,
1,
gl::RGBA,
gl::UNSIGNED_BYTE,
&img[..]);
}
tex.pending_uploads.clear();
}
2016-03-16 13:33:06 -04:00
}
fn update_textures(&mut self, delta: f64) {
2016-04-07 10:55:03 -04:00
{
let mut tex = self.textures.write().unwrap();
while let Ok((hash, img)) = self.skin_reply.try_recv() {
if let Some(img) = img {
2016-04-07 10:55:03 -04:00
tex.update_skin(hash, img);
}
}
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));
}
}
2016-03-16 13:33:06 -04:00
self.gl_texture.bind(gl::TEXTURE_2D_ARRAY);
self.do_pending_textures();
2015-10-07 14:36:59 -04:00
for ani in &mut self.textures.write().unwrap().animated_textures {
if ani.remaining_time <= 0.0 {
ani.current_frame = (ani.current_frame + 1) % ani.frames.len();
ani.remaining_time += ani.frames[ani.current_frame].time as f64;
let offset = ani.texture.width * ani.texture.width *
ani.frames[ani.current_frame].index * 4;
let offset2 = offset + ani.texture.width * ani.texture.width * 4;
self.gl_texture.sub_image_3d(gl::TEXTURE_2D_ARRAY,
0,
ani.texture.get_x() as u32,
ani.texture.get_y() as u32,
ani.texture.atlas as u32,
ani.texture.get_width() as u32,
ani.texture.get_height() as u32,
1,
gl::RGBA,
gl::UNSIGNED_BYTE,
&ani.data[offset..offset2]);
} else {
ani.remaining_time -= delta / 3.0;
}
}
}
2016-03-16 13:33:06 -04:00
fn init_trans(&mut self, width: u32, height: u32) {
self.trans = None;
2016-03-24 21:17:03 -04:00
self.trans = Some(TransInfo::new(width, height, &self.chunk_shader_alpha, &self.trans_shader));
2016-03-16 13:33:06 -04:00
}
2015-10-07 14:36:59 -04:00
pub fn get_textures(&self) -> Arc<RwLock<TextureManager>> {
self.textures.clone()
}
pub fn get_textures_ref(&self) -> &RwLock<TextureManager> {
&self.textures
}
pub fn check_texture(&self, tex: Texture) -> Texture {
if tex.version == self.resource_version {
tex
} else {
let mut new = Renderer::get_texture(&self.textures, &tex.name);
new.rel_x = tex.rel_x;
new.rel_y = tex.rel_y;
new.rel_width = tex.rel_width;
new.rel_height = tex.rel_height;
new.is_rel = tex.is_rel;
new
}
}
pub fn get_texture(textures: &RwLock<TextureManager>, name: &str) -> Texture {
let tex = {
textures.read().unwrap().get_texture(name)
};
match tex {
Some(val) => val,
None => {
let mut t = textures.write().unwrap();
2016-03-16 13:33:06 -04:00
// Make sure it hasn't already been loaded since we switched
// locks.
2015-10-07 14:36:59 -04:00
if let Some(val) = t.get_texture(name) {
val
} else {
t.load_texture(name);
t.get_texture(name).unwrap()
}
}
}
}
2016-04-07 10:55:03 -04:00
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()
}
}
}
}
2015-09-17 11:04:25 -04:00
}
2016-03-16 13:33:06 -04:00
struct TransInfo {
main: gl::Framebuffer,
2016-03-16 14:01:33 -04:00
fb_color: gl::Texture,
2016-03-26 09:28:14 -04:00
_fb_depth: gl::Texture,
2016-03-16 13:33:06 -04:00
trans: gl::Framebuffer,
accum: gl::Texture,
revealage: gl::Texture,
2016-03-26 09:28:14 -04:00
_depth: gl::Texture,
2016-03-16 13:33:06 -04:00
array: gl::VertexArray,
2016-03-26 09:28:14 -04:00
_buffer: gl::Buffer,
2016-03-16 13:33:06 -04:00
}
init_shader! {
Program TransShader {
vert = "trans_vertex",
frag = "trans_frag",
attribute = {
required position => "aPosition",
2016-03-16 13:33:06 -04:00
},
uniform = {
required accum => "taccum",
required revealage => "trevealage",
required color => "tcolor",
required samples => "samples",
2016-03-16 13:33:06 -04:00
},
}
}
impl TransInfo {
2016-03-24 21:17:03 -04:00
pub fn new(width: u32, height: u32, chunk_shader: &ChunkShaderAlpha, shader: &TransShader) -> TransInfo {
2016-03-16 13:33:06 -04:00
let trans = gl::Framebuffer::new();
trans.bind();
let accum = gl::Texture::new();
accum.bind(gl::TEXTURE_2D);
2016-03-18 11:19:21 -04:00
accum.image_2d_ex(gl::TEXTURE_2D, 0, width, height, gl::RGBA16F, gl::RGBA, gl::FLOAT, None);
accum.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
accum.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MAX_LEVEL, gl::LINEAR);
trans.texture_2d(gl::COLOR_ATTACHMENT_0, gl::TEXTURE_2D, &accum, 0);
let revealage = gl::Texture::new();
revealage.bind(gl::TEXTURE_2D);
revealage.image_2d_ex(gl::TEXTURE_2D, 0, width, height, gl::R16F, gl::RED, gl::FLOAT, None);
revealage.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
revealage.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MAX_LEVEL, gl::LINEAR);
trans.texture_2d(gl::COLOR_ATTACHMENT_1, gl::TEXTURE_2D, &revealage, 0);
let trans_depth = gl::Texture::new();
trans_depth.bind(gl::TEXTURE_2D);
trans_depth.image_2d_ex(gl::TEXTURE_2D, 0, width, height, gl::DEPTH_COMPONENT24, gl::DEPTH_COMPONENT, gl::UNSIGNED_BYTE, None);
trans_depth.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
trans_depth.set_parameter(gl::TEXTURE_2D, gl::TEXTURE_MAX_LEVEL, gl::LINEAR);
trans.texture_2d(gl::DEPTH_ATTACHMENT, gl::TEXTURE_2D, &trans_depth, 0);
2016-03-24 21:17:03 -04:00
chunk_shader.program.use_program();
gl::bind_frag_data_location(&chunk_shader.program, 0, "accum");
gl::bind_frag_data_location(&chunk_shader.program, 1, "revealage");
gl::check_framebuffer_status();
gl::draw_buffers(&[gl::COLOR_ATTACHMENT_0, gl::COLOR_ATTACHMENT_1]);
2016-03-18 11:19:21 -04:00
let main = gl::Framebuffer::new();
main.bind();
let fb_color = gl::Texture::new();
fb_color.bind(gl::TEXTURE_2D_MULTISAMPLE);
fb_color.image_2d_sample(gl::TEXTURE_2D_MULTISAMPLE, NUM_SAMPLES, width, height, gl::RGBA8, false);
main.texture_2d(gl::COLOR_ATTACHMENT_0, gl::TEXTURE_2D_MULTISAMPLE, &fb_color, 0);
let fb_depth = gl::Texture::new();
fb_depth.bind(gl::TEXTURE_2D_MULTISAMPLE);
fb_depth.image_2d_sample(gl::TEXTURE_2D_MULTISAMPLE, NUM_SAMPLES, width, height, gl::DEPTH_COMPONENT24, false);
main.texture_2d(gl::DEPTH_ATTACHMENT, gl::TEXTURE_2D_MULTISAMPLE, &fb_depth, 0);
gl::check_framebuffer_status();
2016-03-18 11:19:21 -04:00
gl::unbind_framebuffer();
2016-03-24 21:17:03 -04:00
shader.program.use_program();
2016-03-18 11:19:21 -04:00
let array = gl::VertexArray::new();
array.bind();
let buffer = gl::Buffer::new();
buffer.bind(gl::ARRAY_BUFFER);
let mut data = vec![];
for f in [-1.0, 1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0].into_iter() {
data.write_f32::<NativeEndian>(*f).unwrap();
}
buffer.set_data(gl::ARRAY_BUFFER, &data, gl::STATIC_DRAW);
shader.position.enable();
shader.position.vertex_pointer(2, gl::FLOAT, false, 8, 0);
TransInfo {
main,
fb_color,
2016-03-26 09:28:14 -04:00
_fb_depth: fb_depth,
trans,
accum,
revealage,
2016-03-26 09:28:14 -04:00
_depth: trans_depth,
2016-03-18 11:19:21 -04:00
array,
2016-03-26 09:28:14 -04:00
_buffer: buffer,
2016-03-18 11:19:21 -04:00
}
}
2016-03-16 13:33:06 -04:00
2016-03-18 11:19:21 -04:00
fn draw(&mut self, shader: &TransShader) {
gl::active_texture(0);
self.accum.bind(gl::TEXTURE_2D);
gl::active_texture(1);
self.revealage.bind(gl::TEXTURE_2D);
gl::active_texture(2);
self.fb_color.bind(gl::TEXTURE_2D_MULTISAMPLE);
shader.program.use_program();
shader.accum.set_int(0);
shader.revealage.set_int(1);
shader.color.set_int(2);
shader.samples.set_int(NUM_SAMPLES);
self.array.bind();
gl::draw_arrays(gl::TRIANGLES, 0, 6);
2016-03-16 13:33:06 -04:00
}
}
2015-09-17 11:04:25 -04:00
pub struct TextureManager {
2016-03-24 11:39:57 -04:00
textures: HashMap<String, Texture, BuildHasherDefault<FNVHash>>,
2015-10-07 14:36:59 -04:00
version: usize,
resources: Arc<RwLock<resources::Manager>>,
atlases: Vec<atlas::Atlas>,
2015-09-17 11:04:25 -04:00
2015-10-07 14:36:59 -04:00
animated_textures: Vec<AnimatedTexture>,
pending_uploads: Vec<(i32, atlas::Rect, Vec<u8>)>,
2015-09-25 09:00:49 -04:00
dynamic_textures: HashMap<String, (Texture, image::DynamicImage), BuildHasherDefault<FNVHash>>,
free_dynamics: Vec<Texture>,
2016-04-07 10:55:03 -04:00
skins: HashMap<String, AtomicIsize, BuildHasherDefault<FNVHash>>,
_skin_thread: thread::JoinHandle<()>,
2015-09-17 11:04:25 -04:00
}
impl TextureManager {
2016-04-07 10:55:03 -04:00
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));
2015-10-07 14:36:59 -04:00
let mut tm = TextureManager {
2016-03-24 11:39:57 -04:00
textures: HashMap::with_hasher(BuildHasherDefault::default()),
2016-04-07 10:55:03 -04:00
version: {
let ver = res.read().unwrap().version();
ver
},
2015-10-07 14:36:59 -04:00
resources: res,
atlases: Vec::new(),
animated_textures: Vec::new(),
pending_uploads: Vec::new(),
2016-03-24 11:39:57 -04:00
dynamic_textures: HashMap::with_hasher(BuildHasherDefault::default()),
2015-10-07 14:36:59 -04:00
free_dynamics: Vec::new(),
2016-04-07 10:55:03 -04:00
skins: HashMap::with_hasher(BuildHasherDefault::default()),
_skin_thread: skin_thread,
2015-10-07 14:36:59 -04:00
};
tm.add_defaults();
2016-04-07 10:55:03 -04:00
(tm, stx, rx)
2015-10-07 14:36:59 -04:00
}
fn add_defaults(&mut self) {
self.put_texture("steven",
"missing_texture",
2,
2,
vec![
2016-03-16 13:33:06 -04:00
0, 0, 0, 255,
255, 0, 255, 255,
255, 0, 255, 255,
0, 0, 0, 255,
]);
2015-10-07 14:36:59 -04:00
self.put_texture("steven",
"solid",
1,
1,
vec![
2016-03-16 13:33:06 -04:00
255, 255, 255, 255,
]);
2015-10-07 14:36:59 -04:00
}
Add support for compiling WebAssembly wasm32-unknown-unknown target (#92) Note this only is the first step in web support, although the project compiles, it doesn't run! Merging now to avoid branch divergence, until dependencies can be updated for wasm support. * Add instructions to build for wasm32-unknown-unknown with wasm-pack in www/ * Update to rust-clipboard fork to compile with emscripten https://github.com/aweinstock314/rust-clipboard/pull/62 * Exclude reqwest dependency in wasm32 * Exclude compiling clipboard pasting on wasm32 * Exclude reqwest-using code from wasm32 * Install wasm target with rustup in Travis CI * Update to collision 0.19.0 Fixes wasm incompatibility in deprecated rustc-serialize crate: https://github.com/rustgd/collision-rs/issues/106 error[E0046]: not all trait items implemented, missing: `encode` --> github.com-1ecc6299db9ec823/rustc-serialize-0.3.24/src/serialize.rs:1358:1 * Increase travis_wait time even further, try 120 minutes * Set RUST_BACKTRACE=1 in main * Remove unused unneeded bzip2 features in zip crate To fix wasm32-unknown-unknown target compile error: error[E0432]: unresolved imports `libc::c_int`, `libc::c_uint`, `libc::c_void`, `libc::c_char` --> src/github.com-1ecc6299db9ec823/bzip2-sys-0.1.7/lib.rs:5:12 | 5 | use libc::{c_int, c_uint, c_void, c_char}; | ^^^^^ ^^^^^^ ^^^^^^ ^^^^^^ no `c_char` in the root | | | | | | | no `c_void` in the root | | no `c_uint` in the root | no `c_int` in the root * flate2 use Rust backend * Add console_error_panic_hook module for wasm backtraces * Build using wasm-pack, wasm-bindgen, run with wasm-app * Update to miniz_oxide 0.2.1, remove patch for https://github.com/Frommi/miniz_oxide/issues/42 * Update to official clipboard crate since https://github.com/aweinstock314/rust-clipboard/pull/62 was merged, but git revision pending release * Update to branch of glutin attempting to build for wasm https://github.com/iceiix/glutin/pull/1 * Update winit dependency of glutin to git master https://github.com/iceiix/winit/pull/2 * Update to glutin branch with working (compiles, doesn't run) wasm_stub * Add app name in title on web page * Add wasm to Travis-CI test matrix * Update glutin to fix Windows EGL compilation on AppVeyor https://github.com/iceiix/glutin/pull/1/commits/97797352b5242436cb82d8ecfb44242b69766e4c
2019-03-03 11:32:36 -05:00
#[cfg(target_arch = "wasm32")]
fn process_skins(recv: mpsc::Receiver<String>, reply: mpsc::Sender<(String, Option<image::DynamicImage>)>) {
}
#[cfg(not(target_arch = "wasm32"))]
2016-04-07 10:55:03 -04:00
fn process_skins(recv: mpsc::Receiver<String>, reply: mpsc::Sender<(String, Option<image::DynamicImage>)>) {
Replace hyper with reqwest (#7) An old version of hyper was used before (0.8.0), in the process of updating to hyper 0.12.11, found this higher-level replacement/wrapper, reqwest 0.9.4 which is simpler to use than the latest hyper and serves the purpose of a simple HTTP client well * Begin updating to hyper 0.12.11 https://github.com/iceiix/steven/issues/4#issuecomment-425759778 * Use type variables for hyper::Client * Fix setting header syntax, Content-Type: application/json, 17->13 * Parse strings into URLs with url.parse::<hyper::Uri>().unwrap() https://github.com/hyperium/hyper/blob/b20971cb4e5f158844aec5829eea1854e5b7d4b6/examples/client.rs#L25 * Use hyper::Request::post() then client.request() since client.post() removed * wait() on the ResponseFuture to get the Result * try! to unwrap the Result * status() is now a method * Concatenate body chunks unwrap into bytes, then parse JSON from byte slice, instead of from_reader which didn't compile * Replace send() with wait() on ResponseFuture * Parse HeaderValue to u64 * Slices implement std::io::Read trait * Read into_bytes() instead of read_to_end() * Disable boxed logger for now to workaround 'expected function, found macro' * Remove unnecessary mutability, warnings * Hack to parse twice to avoid double move * Use hyper-rustls pure Rust implementation for TLS for HTTPS in hyper * Start converting to reqwest: add Protocol::Error and reqwest::Error conversion * Use reqwest, replacing hyper, in protocol * Convert resources to use reqwest instead of hyper * Convert skin download to reqwest, instead of hyper * Remove hyper * Revert unnecessary variable name change req/body to reduce diff * Revert unnecessary whitespace change to reduce diff, align indentation on . * Fix authenticating to server, wrong method and join URL * Update Cargo.lock
2018-10-27 20:03:34 -04:00
use reqwest;
let client = reqwest::Client::new();
2016-04-07 10:55:03 -04:00
loop {
let hash = match recv.recv() {
Ok(val) => val,
Err(_) => return, // Most likely shutting down
};
match Self::obtain_skin(&client, &hash) {
Ok(img) => {
let _ = reply.send((hash, Some(img)));
},
Err(err) => {
error!("Failed to get skin {:?}: {}", hash, err);
let _ = reply.send((hash, None));
},
2016-04-07 11:12:33 -04:00
}
}
}
Add support for compiling WebAssembly wasm32-unknown-unknown target (#92) Note this only is the first step in web support, although the project compiles, it doesn't run! Merging now to avoid branch divergence, until dependencies can be updated for wasm support. * Add instructions to build for wasm32-unknown-unknown with wasm-pack in www/ * Update to rust-clipboard fork to compile with emscripten https://github.com/aweinstock314/rust-clipboard/pull/62 * Exclude reqwest dependency in wasm32 * Exclude compiling clipboard pasting on wasm32 * Exclude reqwest-using code from wasm32 * Install wasm target with rustup in Travis CI * Update to collision 0.19.0 Fixes wasm incompatibility in deprecated rustc-serialize crate: https://github.com/rustgd/collision-rs/issues/106 error[E0046]: not all trait items implemented, missing: `encode` --> github.com-1ecc6299db9ec823/rustc-serialize-0.3.24/src/serialize.rs:1358:1 * Increase travis_wait time even further, try 120 minutes * Set RUST_BACKTRACE=1 in main * Remove unused unneeded bzip2 features in zip crate To fix wasm32-unknown-unknown target compile error: error[E0432]: unresolved imports `libc::c_int`, `libc::c_uint`, `libc::c_void`, `libc::c_char` --> src/github.com-1ecc6299db9ec823/bzip2-sys-0.1.7/lib.rs:5:12 | 5 | use libc::{c_int, c_uint, c_void, c_char}; | ^^^^^ ^^^^^^ ^^^^^^ ^^^^^^ no `c_char` in the root | | | | | | | no `c_void` in the root | | no `c_uint` in the root | no `c_int` in the root * flate2 use Rust backend * Add console_error_panic_hook module for wasm backtraces * Build using wasm-pack, wasm-bindgen, run with wasm-app * Update to miniz_oxide 0.2.1, remove patch for https://github.com/Frommi/miniz_oxide/issues/42 * Update to official clipboard crate since https://github.com/aweinstock314/rust-clipboard/pull/62 was merged, but git revision pending release * Update to branch of glutin attempting to build for wasm https://github.com/iceiix/glutin/pull/1 * Update winit dependency of glutin to git master https://github.com/iceiix/winit/pull/2 * Update to glutin branch with working (compiles, doesn't run) wasm_stub * Add app name in title on web page * Add wasm to Travis-CI test matrix * Update glutin to fix Windows EGL compilation on AppVeyor https://github.com/iceiix/glutin/pull/1/commits/97797352b5242436cb82d8ecfb44242b69766e4c
2019-03-03 11:32:36 -05:00
#[cfg(not(target_arch = "wasm32"))]
Replace hyper with reqwest (#7) An old version of hyper was used before (0.8.0), in the process of updating to hyper 0.12.11, found this higher-level replacement/wrapper, reqwest 0.9.4 which is simpler to use than the latest hyper and serves the purpose of a simple HTTP client well * Begin updating to hyper 0.12.11 https://github.com/iceiix/steven/issues/4#issuecomment-425759778 * Use type variables for hyper::Client * Fix setting header syntax, Content-Type: application/json, 17->13 * Parse strings into URLs with url.parse::<hyper::Uri>().unwrap() https://github.com/hyperium/hyper/blob/b20971cb4e5f158844aec5829eea1854e5b7d4b6/examples/client.rs#L25 * Use hyper::Request::post() then client.request() since client.post() removed * wait() on the ResponseFuture to get the Result * try! to unwrap the Result * status() is now a method * Concatenate body chunks unwrap into bytes, then parse JSON from byte slice, instead of from_reader which didn't compile * Replace send() with wait() on ResponseFuture * Parse HeaderValue to u64 * Slices implement std::io::Read trait * Read into_bytes() instead of read_to_end() * Disable boxed logger for now to workaround 'expected function, found macro' * Remove unnecessary mutability, warnings * Hack to parse twice to avoid double move * Use hyper-rustls pure Rust implementation for TLS for HTTPS in hyper * Start converting to reqwest: add Protocol::Error and reqwest::Error conversion * Use reqwest, replacing hyper, in protocol * Convert resources to use reqwest instead of hyper * Convert skin download to reqwest, instead of hyper * Remove hyper * Revert unnecessary variable name change req/body to reduce diff * Revert unnecessary whitespace change to reduce diff, align indentation on . * Fix authenticating to server, wrong method and join URL * Update Cargo.lock
2018-10-27 20:03:34 -04:00
fn obtain_skin(client: &::reqwest::Client, hash: &str) -> Result<image::DynamicImage, ::std::io::Error> {
use std::io::Read;
use std::fs;
use std::path::Path;
use std::io::{Error, ErrorKind};
let path = format!("skin-cache/{}/{}.png", &hash[..2], hash);
let cache_path = Path::new(&path);
fs::create_dir_all(cache_path.parent().unwrap())?;
let mut buf = vec![];
if fs::metadata(cache_path).is_ok() {
// We have a cached image
let mut file = fs::File::open(cache_path)?;
file.read_to_end(&mut buf)?;
} else {
// Need to download it
Replace hyper with reqwest (#7) An old version of hyper was used before (0.8.0), in the process of updating to hyper 0.12.11, found this higher-level replacement/wrapper, reqwest 0.9.4 which is simpler to use than the latest hyper and serves the purpose of a simple HTTP client well * Begin updating to hyper 0.12.11 https://github.com/iceiix/steven/issues/4#issuecomment-425759778 * Use type variables for hyper::Client * Fix setting header syntax, Content-Type: application/json, 17->13 * Parse strings into URLs with url.parse::<hyper::Uri>().unwrap() https://github.com/hyperium/hyper/blob/b20971cb4e5f158844aec5829eea1854e5b7d4b6/examples/client.rs#L25 * Use hyper::Request::post() then client.request() since client.post() removed * wait() on the ResponseFuture to get the Result * try! to unwrap the Result * status() is now a method * Concatenate body chunks unwrap into bytes, then parse JSON from byte slice, instead of from_reader which didn't compile * Replace send() with wait() on ResponseFuture * Parse HeaderValue to u64 * Slices implement std::io::Read trait * Read into_bytes() instead of read_to_end() * Disable boxed logger for now to workaround 'expected function, found macro' * Remove unnecessary mutability, warnings * Hack to parse twice to avoid double move * Use hyper-rustls pure Rust implementation for TLS for HTTPS in hyper * Start converting to reqwest: add Protocol::Error and reqwest::Error conversion * Use reqwest, replacing hyper, in protocol * Convert resources to use reqwest instead of hyper * Convert skin download to reqwest, instead of hyper * Remove hyper * Revert unnecessary variable name change req/body to reduce diff * Revert unnecessary whitespace change to reduce diff, align indentation on . * Fix authenticating to server, wrong method and join URL * Update Cargo.lock
2018-10-27 20:03:34 -04:00
let url = &format!("http://textures.minecraft.net/texture/{}", hash);
let mut res = match client.get(url).send() {
2016-04-07 10:55:03 -04:00
Ok(val) => val,
Err(err) => {
return Err(Error::new(ErrorKind::ConnectionAborted, err));
2016-04-07 10:55:03 -04:00
}
};
Replace hyper with reqwest (#7) An old version of hyper was used before (0.8.0), in the process of updating to hyper 0.12.11, found this higher-level replacement/wrapper, reqwest 0.9.4 which is simpler to use than the latest hyper and serves the purpose of a simple HTTP client well * Begin updating to hyper 0.12.11 https://github.com/iceiix/steven/issues/4#issuecomment-425759778 * Use type variables for hyper::Client * Fix setting header syntax, Content-Type: application/json, 17->13 * Parse strings into URLs with url.parse::<hyper::Uri>().unwrap() https://github.com/hyperium/hyper/blob/b20971cb4e5f158844aec5829eea1854e5b7d4b6/examples/client.rs#L25 * Use hyper::Request::post() then client.request() since client.post() removed * wait() on the ResponseFuture to get the Result * try! to unwrap the Result * status() is now a method * Concatenate body chunks unwrap into bytes, then parse JSON from byte slice, instead of from_reader which didn't compile * Replace send() with wait() on ResponseFuture * Parse HeaderValue to u64 * Slices implement std::io::Read trait * Read into_bytes() instead of read_to_end() * Disable boxed logger for now to workaround 'expected function, found macro' * Remove unnecessary mutability, warnings * Hack to parse twice to avoid double move * Use hyper-rustls pure Rust implementation for TLS for HTTPS in hyper * Start converting to reqwest: add Protocol::Error and reqwest::Error conversion * Use reqwest, replacing hyper, in protocol * Convert resources to use reqwest instead of hyper * Convert skin download to reqwest, instead of hyper * Remove hyper * Revert unnecessary variable name change req/body to reduce diff * Revert unnecessary whitespace change to reduce diff, align indentation on . * Fix authenticating to server, wrong method and join URL * Update Cargo.lock
2018-10-27 20:03:34 -04:00
let mut buf = vec![];
match res.read_to_end(&mut buf) {
Ok(_) => {},
Err(err) => {
// TODO: different error for failure to read?
return Err(Error::new(ErrorKind::InvalidData, err));
}
}
// Save to cache
let mut file = fs::File::create(cache_path)?;
file.write_all(&buf)?;
}
let mut img = match image::load_from_memory(&buf) {
Ok(val) => val,
Err(err) => {
return Err(Error::new(ErrorKind::InvalidData, err));
}
};
let (_, height) = img.dimensions();
if height == 32 {
// Needs changing to the new format
let mut new = image::DynamicImage::new_rgba8(64, 64);
new.copy_from(&img, 0, 0);
for xx in 0 .. 4 {
for yy in 0 .. 16 {
for section in 0 .. 4 {
let os = match section {
0 => 2,
1 => 1,
2 => 0,
3 => 3,
_ => unreachable!(),
};
new.put_pixel(16 + (3 - xx) + section * 4, 48 + yy, img.get_pixel(xx + os * 4, 16 + yy));
new.put_pixel(32 + (3 - xx) + section * 4, 48 + yy, img.get_pixel(xx + 40 + os * 4, 16 + yy));
}
}
2016-04-07 15:39:48 -04:00
}
img = new;
}
// Block transparent pixels in blacklisted areas
let blacklist = [
// X, Y, W, H
(0, 0, 32, 16),
(16, 16, 24, 16),
(0, 16, 16, 16),
(16, 48, 16, 16),
(32, 48, 16, 16),
(40, 16, 16, 16),
];
for bl in blacklist.into_iter() {
for x in bl.0 .. (bl.0 + bl.2) {
for y in bl.1 .. (bl.1 + bl.3) {
let mut col = img.get_pixel(x, y);
col.data[3] = 255;
img.put_pixel(x, y, col);
2016-04-07 15:39:48 -04:00
}
}
2016-04-07 10:55:03 -04:00
}
Ok(img)
2016-04-07 10:55:03 -04:00
}
2015-10-07 14:36:59 -04:00
fn update_textures(&mut self, version: usize) {
self.pending_uploads.clear();
self.atlases.clear();
self.animated_textures.clear();
self.version = version;
let map = self.textures.clone();
self.textures.clear();
self.free_dynamics.clear();
2015-10-07 14:36:59 -04:00
self.add_defaults();
for name in map.keys() {
if name.starts_with("steven-dynamic:") {
let n = &name["steven-dynamic:".len()..];
let (width, height, data) = {
let dynamic_texture = match self.dynamic_textures.get(n) {
Some(val) => val,
None => continue,
};
let img = &dynamic_texture.1;
let (width, height) = img.dimensions();
(width, height, img.to_rgba().into_vec())
};
let new_tex = self.put_texture("steven-dynamic", n, width as u32, height as u32, data);
self.dynamic_textures.get_mut(n).unwrap().0 = new_tex;
} else if !self.textures.contains_key(name) {
self.load_texture(name);
}
2015-10-07 14:36:59 -04:00
}
}
2016-04-07 10:55:03 -04:00
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 img = if let Some(mut val) = res.read().unwrap().open("minecraft", "textures/entity/steve.png") {
let mut data = Vec::new();
val.read_to_end(&mut data).unwrap();
image::load_from_memory(&data).unwrap()
} else {
image::DynamicImage::new_rgba8(64, 64)
};
2016-04-07 10:55:03 -04:00
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 name = format!("steven-dynamic:skin-{}", hash);
let tex = self.get_texture(&name).unwrap();
2016-04-07 10:55:03 -04:00
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()));
self.dynamic_textures.get_mut(&format!("skin-{}", hash)).unwrap().1 = img;
2016-04-07 10:55:03 -04:00
}
2015-10-07 14:36:59 -04:00
fn get_texture(&self, name: &str) -> Option<Texture> {
2016-03-19 12:32:13 -04:00
if let Some(_) = name.find(':') {
2016-03-26 10:24:26 -04:00
self.textures.get(name).cloned()
2016-03-19 12:32:13 -04:00
} else {
2016-03-26 10:24:26 -04:00
self.textures.get(&format!("minecraft:{}", name)).cloned()
2016-03-19 12:32:13 -04:00
}
2015-10-07 14:36:59 -04:00
}
fn load_texture(&mut self, name: &str) {
let (plugin, name) = if let Some(pos) = name.find(':') {
(&name[..pos], &name[pos + 1..])
} else {
("minecraft", name)
};
let path = format!("textures/{}.png", name);
let res = self.resources.clone();
if let Some(mut val) = res.read().unwrap().open(plugin, &path) {
let mut data = Vec::new();
val.read_to_end(&mut data).unwrap();
if let Ok(img) = image::load_from_memory(&data) {
let (width, height) = img.dimensions();
2016-03-16 13:33:06 -04:00
// Might be animated
2015-10-07 14:36:59 -04:00
if (name.starts_with("blocks/") || name.starts_with("items/")) && width != height {
let id = img.to_rgba().into_vec();
let frame = id[..(width * width * 4) as usize].to_owned();
if let Some(mut ani) = self.load_animation(plugin, name, &img, id) {
ani.texture = self.put_texture(plugin, name, width, width, frame);
self.animated_textures.push(ani);
return;
}
}
self.put_texture(plugin, name, width, height, img.to_rgba().into_vec());
return;
}
}
self.insert_texture_dummy(plugin, name);
}
fn load_animation(&mut self,
plugin: &str,
name: &str,
img: &image::DynamicImage,
data: Vec<u8>)
-> Option<AnimatedTexture> {
let path = format!("textures/{}.png.mcmeta", name);
let res = self.resources.clone();
if let Some(val) = res.read().unwrap().open(plugin, &path) {
let meta: serde_json::Value = serde_json::from_reader(val).unwrap();
let animation = meta.get("animation").unwrap();
let frame_time = animation.get("frametime").and_then(|v| v.as_i64()).unwrap_or(1);
let interpolate = animation.get("interpolate")
.and_then(|v| v.as_bool())
2015-10-07 14:36:59 -04:00
.unwrap_or(false);
let frames = if let Some(frames) = animation.get("frames")
2015-10-07 14:36:59 -04:00
.and_then(|v| v.as_array()) {
let mut out = Vec::with_capacity(frames.len());
for frame in frames {
if let Some(index) = frame.as_i64() {
out.push(AnimationFrame {
index: index as usize,
time: frame_time,
})
} else {
out.push(AnimationFrame{
index: frame.get("index").unwrap().as_i64().unwrap() as usize,
time: frame_time * frame.get("frameTime").unwrap().as_i64().unwrap(),
2016-03-16 13:33:06 -04:00
})
2015-10-07 14:36:59 -04:00
}
}
out
} else {
let (width, height) = img.dimensions();
let count = height / width;
let mut frames = Vec::with_capacity(count as usize);
for i in 0..count {
frames.push(AnimationFrame {
index: i as usize,
time: frame_time,
})
}
frames
};
return Some(AnimatedTexture {
frames,
data,
interpolate,
2015-10-07 14:36:59 -04:00
current_frame: 0,
remaining_time: 0.0,
texture: self.get_texture("steven:missing_texture").unwrap(),
});
}
None
}
fn put_texture(&mut self,
plugin: &str,
name: &str,
width: u32,
height: u32,
data: Vec<u8>)
-> Texture {
let (atlas, rect) = self.find_free(width as usize, height as usize);
self.pending_uploads.push((atlas, rect, data));
let mut full_name = String::new();
2016-03-19 12:32:13 -04:00
full_name.push_str(plugin);
full_name.push_str(":");
2015-10-07 14:36:59 -04:00
full_name.push_str(name);
let tex = Texture {
name: full_name.clone(),
version: self.version,
atlas,
2015-10-07 14:36:59 -04:00
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
rel_x: 0.0,
rel_y: 0.0,
rel_width: 1.0,
rel_height: 1.0,
is_rel: false,
};
self.textures.insert(full_name, tex.clone());
tex
}
fn find_free(&mut self, width: usize, height: usize) -> (i32, atlas::Rect) {
let mut index = 0;
for atlas in &mut self.atlases {
if let Some(rect) = atlas.add(width, height) {
return (index, rect);
}
index += 1;
}
let mut atlas = atlas::Atlas::new(ATLAS_SIZE, ATLAS_SIZE);
let rect = atlas.add(width, height);
self.atlases.push(atlas);
(index, rect.unwrap())
}
fn insert_texture_dummy(&mut self, plugin: &str, name: &str) -> Texture {
let missing = self.get_texture("steven:missing_texture").unwrap();
let mut full_name = String::new();
2016-03-19 13:34:12 -04:00
full_name.push_str(plugin);
full_name.push_str(":");
2015-10-07 14:36:59 -04:00
full_name.push_str(name);
let t = Texture {
name: full_name.to_owned(),
version: self.version,
atlas: missing.atlas,
x: missing.x,
y: missing.y,
width: missing.width,
height: missing.height,
rel_x: 0.0,
rel_y: 0.0,
rel_width: 1.0,
rel_height: 1.0,
is_rel: false,
};
self.textures.insert(full_name.to_owned(), t.clone());
t
}
pub fn put_dynamic(&mut self, name: &str, img: image::DynamicImage) -> Texture {
2016-04-07 14:51:05 -04:00
use std::mem;
2015-10-07 14:36:59 -04:00
let (width, height) = img.dimensions();
let (width, height) = (width as usize, height as usize);
2016-04-07 14:51:05 -04:00
let mut rect_pos = None;
2015-10-07 14:36:59 -04:00
for (i, r) in self.free_dynamics.iter().enumerate() {
if r.width == width && r.height == height {
2016-04-07 14:51:05 -04:00
rect_pos = Some(i);
2015-10-07 14:36:59 -04:00
break;
} else if r.width >= width && r.height >= height {
2016-04-07 14:51:05 -04:00
rect_pos = Some(i);
2015-10-07 14:36:59 -04:00
}
}
let data = img.to_rgba().into_vec();
2016-04-07 14:51:05 -04:00
if let Some(rect_pos) = rect_pos {
let mut tex = self.free_dynamics.remove(rect_pos);
2015-10-07 14:36:59 -04:00
let rect = atlas::Rect {
x: tex.x,
y: tex.y,
width,
height,
2015-10-07 14:36:59 -04:00
};
self.pending_uploads.push((tex.atlas, rect, data));
2016-04-24 07:22:04 -04:00
let mut t = tex.relative(0.0, 0.0, (width as f32) / (tex.width as f32), (height as f32) / (tex.height as f32));
2016-04-07 14:51:05 -04:00
let old_name = mem::replace(&mut tex.name, format!("steven-dynamic:{}", name));
2016-04-07 10:55:03 -04:00
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
2016-04-07 14:51:05 -04:00
let mut old = self.textures.remove(&old_name).unwrap();
old.name = format!("steven-dynamic:{}", name);
2016-04-24 07:22:04 -04:00
t.name = old.name.clone();
2016-04-07 10:55:03 -04:00
self.textures.insert(format!("steven-dynamic:{}", name), old);
2015-10-07 14:36:59 -04:00
t
} else {
let tex = self.put_texture("steven-dynamic", name, width as u32, height as u32, data);
self.dynamic_textures.insert(name.to_owned(), (tex.clone(), img));
tex
2015-10-07 14:36:59 -04:00
}
}
pub fn remove_dynamic(&mut self, name: &str) {
let desc = self.dynamic_textures.remove(name).unwrap();
self.free_dynamics.push(desc.0);
2015-10-07 14:36:59 -04:00
}
2015-09-17 11:04:25 -04:00
}
2015-09-25 10:20:55 -04:00
#[allow(dead_code)]
2015-09-17 11:04:25 -04:00
struct AnimatedTexture {
2015-10-07 14:36:59 -04:00
frames: Vec<AnimationFrame>,
data: Vec<u8>,
interpolate: bool,
current_frame: usize,
remaining_time: f64,
texture: Texture,
2015-09-17 11:04:25 -04:00
}
struct AnimationFrame {
2015-10-07 14:36:59 -04:00
index: usize,
time: i64,
2015-09-17 11:04:25 -04:00
}
#[derive(Clone, Debug)]
pub struct Texture {
2016-04-24 07:22:04 -04:00
pub name: String,
2015-10-07 14:36:59 -04:00
version: usize,
pub atlas: i32,
x: usize,
y: usize,
width: usize,
height: usize,
2016-03-22 10:42:10 -04:00
is_rel: bool, // Save some cycles for non-relative textures
2015-10-07 14:36:59 -04:00
rel_x: f32,
rel_y: f32,
rel_width: f32,
rel_height: f32,
2015-09-17 11:04:25 -04:00
}
impl Texture {
2015-10-07 14:36:59 -04:00
pub fn get_x(&self) -> usize {
if self.is_rel {
self.x + ((self.width as f32) * self.rel_x) as usize
} else {
self.x
}
}
pub fn get_y(&self) -> usize {
if self.is_rel {
self.y + ((self.height as f32) * self.rel_y) as usize
} else {
self.y
}
}
pub fn get_width(&self) -> usize {
if self.is_rel {
((self.width as f32) * self.rel_width) as usize
} else {
self.width
}
}
pub fn get_height(&self) -> usize {
if self.is_rel {
((self.height as f32) * self.rel_height) as usize
} else {
self.height
}
}
pub fn relative(&self, x: f32, y: f32, width: f32, height: f32) -> Texture {
Texture {
name: self.name.clone(),
version: self.version,
x: self.x,
y: self.y,
atlas: self.atlas,
width: self.width,
height: self.height,
is_rel: true,
rel_x: self.rel_x + x * self.rel_width,
rel_y: self.rel_y + y * self.rel_height,
rel_width: width * self.rel_width,
rel_height: height * self.rel_height,
}
}
2015-09-17 11:04:25 -04:00
}
#[allow(unused_must_use)]
pub fn generate_element_buffer(size: usize) -> (Vec<u8>, gl::Type) {
2015-10-07 14:36:59 -04:00
let mut ty = gl::UNSIGNED_SHORT;
let mut data = if (size / 6) * 4 * 3 >= u16::max_value() as usize {
ty = gl::UNSIGNED_INT;
Vec::with_capacity(size * 4)
} else {
Vec::with_capacity(size * 2)
};
for i in 0..size / 6 {
for val in &[0, 1, 2, 2, 1, 3] {
if ty == gl::UNSIGNED_INT {
data.write_u32::<NativeEndian>((i as u32) * 4 + val);
} else {
data.write_u16::<NativeEndian>((i as u16) * 4 + (*val as u16));
}
}
}
(data, ty)
}