From 5aef272d439f2dd80807392be7d80750bbc03cff Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Thu, 17 Sep 2015 16:04:25 +0100 Subject: [PATCH] Base of ui complete --- .gitignore | 2 + Cargo.lock | 117 +++++- Cargo.toml | 14 +- gl/Cargo.lock | 59 +++ openssl/Cargo.lock | 79 ++++ openssl/Cargo.toml | 1 + openssl/src/lib.rs | 1 - src/format.rs | 2 +- src/gl/mod.rs | 484 ++++++++++++++++++++++ src/item.rs | 3 +- src/lib.rs | 8 - src/{bin/steven.rs => main.rs} | 40 +- src/nbt/mod.rs | 3 +- src/protocol/mod.rs | 13 +- src/protocol/mojang.rs | 8 +- src/render/atlas.rs | 85 ++++ src/render/glsl.rs | 51 +++ src/render/mod.rs | 536 +++++++++++++++++++++++++ src/render/shaders.rs | 10 + src/render/shaders/get_light.glsl | 28 ++ src/render/shaders/lookup_texture.glsl | 8 + src/render/shaders/ui_frag.glsl | 17 + src/render/shaders/ui_vertex.glsl | 20 + src/render/ui.rs | 456 +++++++++++++++++++++ src/resources.rs | 170 ++++++++ src/types/metadata.rs | 1 + 26 files changed, 2176 insertions(+), 40 deletions(-) create mode 100644 gl/Cargo.lock delete mode 100644 src/lib.rs rename src/{bin/steven.rs => main.rs} (54%) create mode 100644 src/render/atlas.rs create mode 100644 src/render/glsl.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/shaders.rs create mode 100644 src/render/shaders/get_light.glsl create mode 100644 src/render/shaders/lookup_texture.glsl create mode 100644 src/render/shaders/ui_frag.glsl create mode 100644 src/render/shaders/ui_vertex.glsl create mode 100644 src/render/ui.rs create mode 100644 src/resources.rs diff --git a/.gitignore b/.gitignore index 2f7896d..f778df6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ target/ +.rust/ +working/ diff --git a/Cargo.lock b/Cargo.lock index cafdac8..7938d1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,11 +6,14 @@ dependencies = [ "flate2 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "glfw 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "steven_gl 0.0.1", "steven_openssl 0.0.1", + "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "zip 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -18,7 +21,7 @@ name = "advapi32-sys" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -32,6 +35,29 @@ name = "byteorder" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bzip2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bzip2-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "color_quant" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cookie" version = "0.1.21" @@ -43,6 +69,14 @@ dependencies = [ "url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "enum_primitive" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "enum_primitive" version = "0.1.0" @@ -66,7 +100,16 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gif" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lzw 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -94,7 +137,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glfw-sys 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -102,8 +144,8 @@ dependencies = [ ] [[package]] -name = "glfw-sys" -version = "3.1.3" +name = "glob" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -140,12 +182,25 @@ dependencies = [ "url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "image" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gif 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -185,6 +240,11 @@ dependencies = [ "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lzw" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "matches" version = "0.1.2" @@ -207,6 +267,16 @@ dependencies = [ "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "msdos_time" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.1.27" @@ -259,6 +329,22 @@ dependencies = [ "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "png" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "podio" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand" version = "0.3.11" @@ -266,7 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -320,6 +406,7 @@ name = "steven_openssl" version = "0.0.1" dependencies = [ "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -337,7 +424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -366,7 +453,7 @@ dependencies = [ [[package]] name = "winapi" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -382,3 +469,15 @@ dependencies = [ "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "zip" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bzip2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "msdos_time 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "podio 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/Cargo.toml b/Cargo.toml index 4f0ce64..471bbda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ hyper = "0.6.13" serde = "0.6.0" serde_json = "0.6.0" flate2 = "0.2.9" +zip = "0.1.12" +image = "0.3.12" +time = "0.1.32" [dependencies.steven_gl] path = "./gl" @@ -20,5 +23,12 @@ version = "0" path = "./openssl" version = "0" -[[bin]] -name = "steven" +[target.x86_64-pc-windows-gnu.dependencies] +glfw = { version = "0.1.0", default-features = false } + +[target.x86_64-pc-windows-msvc.dependencies] +glfw = { version = "0.1.0", default-features = false } + +[target.i686-pc-windows-gnu.dependencies] +glfw = { version = "0.1.0", default-features = false } + diff --git a/gl/Cargo.lock b/gl/Cargo.lock new file mode 100644 index 0000000..b2ef40f --- /dev/null +++ b/gl/Cargo.lock @@ -0,0 +1,59 @@ +[root] +name = "steven_gl" +version = "0.0.1" +dependencies = [ + "gl_common 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "gl_generator 0.0.27 (registry+https://github.com/rust-lang/crates.io-index)", + "khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gl_common" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gl_generator" +version = "0.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "khronos_api 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "khronos_api" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xml-rs" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/openssl/Cargo.lock b/openssl/Cargo.lock index 827b76c..a495041 100644 --- a/openssl/Cargo.lock +++ b/openssl/Cargo.lock @@ -3,6 +3,25 @@ name = "steven_openssl" version = "0.0.1" dependencies = [ "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "advapi32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gcc" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -10,3 +29,63 @@ name = "libc" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libressl-pnacl-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pnacl-build-helper" +version = "1.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempdir" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/openssl/Cargo.toml b/openssl/Cargo.toml index 333c22a..0df310c 100644 --- a/openssl/Cargo.toml +++ b/openssl/Cargo.toml @@ -5,3 +5,4 @@ authors = [ "Thinkofdeath " ] [dependencies] libc = "*" +openssl-sys = "0.6.5" \ No newline at end of file diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 0b54092..ee46955 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -31,7 +31,6 @@ struct ENGINE; const RSA_PKCS1_PADDING : libc::c_int = 1; -#[link(name = "crypto")] extern { fn SHA1_Init(c: *mut SHA_CTX) -> libc::c_int; fn SHA1_Update(c: *mut SHA_CTX, data: *const u8, len: libc::size_t) -> libc::c_int; diff --git a/src/format.rs b/src/format.rs index 26ec1e7..8cef4af 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,5 +1,5 @@ -extern crate serde_json; +use serde_json; use std::fmt; #[derive(Debug)] diff --git a/src/gl/mod.rs b/src/gl/mod.rs index ad5095c..e5e9964 100644 --- a/src/gl/mod.rs +++ b/src/gl/mod.rs @@ -2,17 +2,53 @@ extern crate steven_gl as gl; extern crate glfw; use std::ops::BitOr; +use std::ffi; +use std::mem; +use std::ptr; +use std::ops::{Deref, DerefMut}; +/// Inits the gl library. This should be called once a context is ready. pub fn init(window: &mut glfw::Window) { gl::load_with(|s| window.get_proc_address(s)); } +/// Dsed to specify how the vertices will be handled +/// to draw. +pub type DrawType = u32; + +/// Treats each set of 3 vertices as a triangle +pub const TRIANGLES: DrawType = gl::TRIANGLES; +/// Means the previous vertex connects to the next +/// one in a continuous strip. +pub const LINE_STRIP: DrawType = gl::LINE_STRIP; +/// Treats each set of 2 vertices as a line +pub const LINES: DrawType = gl::LINES; +/// Treats each vertex as a point +pub const POINTS: DrawType = gl::POINTS; + +pub fn draw_elements(ty: DrawType, count: usize, dty: Type, offset: usize) { + unsafe { + gl::DrawElements(ty, count as i32, dty, offset as *const gl::types::GLvoid); + } +} + +/// Sets the size of the viewport of this context. +pub fn viewport(x: i32, y: i32, w: i32, h: i32) { + unsafe { gl::Viewport(x, y, w, h); } +} + +/// Sets the color the color buffer should be cleared to +/// when Clear is called with the color flag. pub fn clear_color(r: f32, g: f32, b: f32, a: f32) { unsafe { gl::ClearColor(r, g, b, a); } } +/// ClearFlags is a set of flags to mark what should be cleared during +/// a Clear call. pub enum ClearFlags { + /// Marks the color buffer to be cleared Color, + /// Marks the depth buffer to be cleared Depth, Internal(u32) } @@ -35,6 +71,454 @@ impl BitOr for ClearFlags { } } +/// Clears the buffers specified by the passed flags. pub fn clear(flags: ClearFlags) { unsafe { gl::Clear(flags.internal()) } } + +/// Func is a function to be preformed on two values. +pub type Func = u32; + +pub const NEVER: Func = gl::NEVER; +pub const LESS: Func = gl::LESS; +pub const LESS_OR_EQUAL: Func = gl::LEQUAL; +pub const GREATER: Func = gl::GREATER; +pub const ALWAYS: Func = gl::ALWAYS; +pub const EQUAL: Func = gl::EQUAL; + +pub fn depth_func(f: Func) { + unsafe { gl::DepthFunc(f); } +} + +/// Flag is a setting that can be enabled or disabled on the context. +pub type Flag = u32; + +pub const DEPTH_TEST: Flag = gl::DEPTH_TEST; +pub const CULL_FACE_FLAG: Flag = gl::CULL_FACE; +pub const STENCIL_TEST: Flag = gl::STENCIL_TEST; +pub const BLEND: Flag = gl::BLEND; +pub const MULTISAMPLE: Flag = gl::MULTISAMPLE; + +/// Enables the passed flag. +pub fn enable(f: Flag) { + unsafe { gl::Enable(f); } +} + +/// Disables the passed flag. +pub fn disable(f: Flag) { + unsafe { gl::Disable(f); } +} + +/// Sets the texture slot with the passed id as the +/// currently active one. +pub fn active_texture(id: u32) { + unsafe { gl::ActiveTexture(gl::TEXTURE0 + id); } +} + +/// Type is a type of data used by various operations. +pub type Type = u32; +pub const UNSIGNED_BYTE: Type = gl::UNSIGNED_BYTE; +pub const UNSIGNED_SHORT: Type = gl::UNSIGNED_SHORT; +pub const UNSIGNED_INT: Type = gl::UNSIGNED_INT; +pub const SHORT: Type = gl::SHORT; +pub const FLOAT: Type = gl::FLOAT; + +/// TextureTarget is a target were a texture can be bound to +pub type TextureTarget = u32; + +pub const TEXTURE_2D: TextureTarget = gl::TEXTURE_2D; +pub const TEXTURE_2D_MULTISAMPLE: TextureTarget = gl::TEXTURE_2D_MULTISAMPLE; +pub const TEXTURE_2D_ARRAY: TextureTarget = gl::TEXTURE_2D_ARRAY; +pub const TEXTURE_3D: TextureTarget = gl::TEXTURE_3D; + +/// TextureFormat is the format of a texture either internally or +/// to be uploaded. +pub type TextureFormat = u32; + +pub const RED: TextureFormat = gl::RED; +pub const RGB: TextureFormat = gl::RGB; +pub const RGBA: TextureFormat = gl::RGBA; +pub const RGBA8: TextureFormat = gl::RGBA8; +pub const RGBA16F: TextureFormat = gl::RGBA16F; +pub const R16F: TextureFormat = gl::R16F; +pub const DEPTH_COMPONENT24: TextureFormat = gl::DEPTH_COMPONENT24; +pub const DEPTH_COMPONENT: TextureFormat = gl::DEPTH_COMPONENT; + +/// TextureParameter is a parameter that can be read or set on a texture. +pub type TextureParameter = u32; + +pub const TEXTURE_MIN_FILTER: TextureParameter = gl::TEXTURE_MIN_FILTER; +pub const TEXTURE_MAG_FILTER: TextureParameter = gl::TEXTURE_MAG_FILTER; +pub const TEXTURE_WRAP_S: TextureParameter = gl::TEXTURE_WRAP_S; +pub const TEXTURE_WRAP_T: TextureParameter = gl::TEXTURE_WRAP_T; +pub const TEXTURE_MAX_LEVEL: TextureParameter = gl::TEXTURE_MAX_LEVEL; + +/// TextureValue is a value that be set on a texture's parameter. +pub type TextureValue = i32; + +pub const NEAREST: TextureValue = gl::NEAREST as TextureValue; +pub const LINEAR: TextureValue = gl::LINEAR as TextureValue; +pub const LINEAR_MIPMAP_LINEAR: TextureValue = gl::LINEAR_MIPMAP_LINEAR as TextureValue; +pub const LINEAR_MIPMAP_NEAREST: TextureValue = gl::LINEAR_MIPMAP_NEAREST as TextureValue; +pub const NEAREST_MIPMAP_NEAREST: TextureValue = gl::NEAREST_MIPMAP_NEAREST as TextureValue; +pub const NEAREST_MIPMAP_LINEAR: TextureValue = gl::NEAREST_MIPMAP_LINEAR as TextureValue; +pub const CLAMP_TO_EDGE: TextureValue = gl::CLAMP_TO_EDGE as TextureValue; + +/// Texture is a buffer of data used by fragment shaders. +pub struct Texture { + internal: u32, +} + +impl Texture { + // Allocates a new texture. + pub fn new() -> Texture { + let mut t = Texture{ internal: 0 }; + unsafe { gl::GenTextures(1, &mut t.internal); } + t + } + + /// Binds the texture to the passed target. + pub fn bind(&self, target: TextureTarget) { + unsafe { + gl::BindTexture(target, self.internal); + } + } + + pub fn get_pixels(&self, target: TextureTarget, level: i32, format: TextureFormat, ty: Type, pixels: &mut [u8]) { + unsafe { + gl::GetTexImage(target, level, format, ty, pixels.as_mut_ptr() as *mut gl::types::GLvoid); + } + } + + pub fn image_3d(&self, target: TextureTarget, level: i32, width: u32, height: u32, depth: u32, format: TextureFormat, ty: Type, pix: &[u8]) { + unsafe { + gl::TexImage3D(target, level, format as i32, width as i32, height as i32, depth as i32, 0, format, ty, pix.as_ptr() as *const gl::types::GLvoid); + } + } + + pub fn sub_image_3d(&self, target: TextureTarget, level: i32, x: u32, y: u32, z: u32, width: u32, height: u32, depth: u32, format: TextureFormat, ty: Type, pix: &[u8]) { + unsafe { + gl::TexSubImage3D(target, level, x as i32, y as i32, z as i32, width as i32, height as i32, depth as i32, format, ty, pix.as_ptr() as *const gl::types::GLvoid); + } + } + + pub fn set_parameter(&self, target: TextureTarget, param: TextureParameter, value: TextureValue) { + unsafe { + gl::TexParameteri(target, param, value); + } + } +} + +impl Drop for Texture { + fn drop(&mut self) { + unsafe { gl::DeleteTextures(1, &self.internal); } + } +} + +pub type ShaderType = u32; + +pub const VERTEX_SHADER: ShaderType = gl::VERTEX_SHADER; +pub const FRAGMENT_SHADER: ShaderType = gl::FRAGMENT_SHADER; +pub const GEOMETRY_SHADER: ShaderType = gl::GEOMETRY_SHADER; + +pub type ShaderParameter = u32; + +pub const COMPILE_STATUS: ShaderParameter = gl::COMPILE_STATUS; +pub const INFO_LOG_LENGTH: ShaderParameter = gl::INFO_LOG_LENGTH; + +pub struct Program { + internal: u32, +} + +impl Program { + pub fn new() -> Program { + Program { + internal: unsafe { gl::CreateProgram() } + } + } + + pub fn attach_shader(&self, shader: Shader) { + unsafe { + gl::AttachShader(self.internal, shader.internal); + } + } + + pub fn link(&self) { + unsafe { + gl::LinkProgram(self.internal); + } + } + + pub fn use_program(&self) { + unsafe { + gl::UseProgram(self.internal); + } + } + + pub fn uniform_location(&self, name: &str) -> Uniform { + Uniform { + internal: unsafe { gl::GetUniformLocation(self.internal, ffi::CString::new(name).unwrap().as_ptr()) } + } + } + + pub fn attribute_location(&self, name: &str) -> Attribute { + Attribute { + internal: unsafe { gl::GetAttribLocation(self.internal, ffi::CString::new(name).unwrap().as_ptr()) } + } + } +} + +impl Drop for Program { + fn drop(&mut self) { + unsafe { gl::DeleteProgram(self.internal); } + } +} + +pub struct Shader { + internal: u32, +} + +impl Shader { + pub fn new(ty: ShaderType) -> Shader { + Shader { + internal: unsafe { gl::CreateShader(ty) } + } + } + + pub fn set_source(&self, src: &str) { + unsafe { + gl::ShaderSource(self.internal, 1, &ffi::CString::new(src).unwrap().as_ptr(), ptr::null()); + } + } + + pub fn compile(&self) { + unsafe { + gl::CompileShader(self.internal); + } + } + + pub fn get_parameter(&self, param: ShaderParameter) -> i32 { + let mut ret : i32 = 0; + unsafe { gl::GetShaderiv(self.internal, param, &mut ret); } + return ret; + } + + pub fn get_info_log(&self) -> String { + let len = self.get_parameter(INFO_LOG_LENGTH); + + let mut data = Vec::::with_capacity(len as usize); + unsafe { + data.set_len(len as usize); + gl::GetShaderInfoLog(self.internal, len, ptr::null_mut(), data.as_mut_ptr() as *mut i8); + } + String::from_utf8(data).unwrap() + } +} + +pub struct Uniform { + internal: i32, +} + +impl Uniform { + pub fn set_int(&self, val: i32) { + unsafe { + gl::Uniform1i(self.internal, val); + } + } + + pub fn set_int3(&self, x: i32, y: i32, z: i32) { + unsafe { + gl::Uniform3i(self.internal, x, y, z); + } + } + + pub fn set_float(&self, val: f32) { + unsafe { + gl::Uniform1f(self.internal, val); + } + } + + pub fn set_float2(&self, x: f32, y: f32) { + unsafe { + gl::Uniform2f(self.internal, x, y); + } + } + + pub fn set_float3(&self, x: f32, y: f32, z: f32) { + unsafe { + gl::Uniform3f(self.internal, x, y, z); + } + } + + pub fn set_float4(&self, x: f32, y: f32, z: f32, w: f32) { + unsafe { + gl::Uniform4f(self.internal, x, y, z, w); + } + } +} + +pub struct Attribute { + internal: i32, +} + +impl Attribute { + pub fn enable(&self) { + unsafe { + gl::EnableVertexAttribArray(self.internal as u32); + } + } + + pub fn disable(&self) { + unsafe { + gl::DisableVertexAttribArray(self.internal as u32); + } + } + + pub fn vertex_pointer(&self, size: i32, ty: Type, normalized: bool, stride: i32, offset: i32) { + unsafe { + gl::VertexAttribPointer(self.internal as u32, size, ty, if normalized { 1 } else { 0 }, stride, offset as *const gl::types::GLvoid); + } + } + + pub fn vertex_pointer_int(&self, size: i32, ty: Type, stride: i32, offset: i32) { + unsafe { + gl::VertexAttribIPointer(self.internal as u32, size, ty, stride, offset as *const gl::types::GLvoid); + } + } +} + +// VertexArray is used to store state needed to render vertices. +// This includes buffers, the format of the buffers and enabled +// attributes. +pub struct VertexArray { + internal: u32, +} + +impl VertexArray { + /// Allocates a new VertexArray. + pub fn new() -> VertexArray { + let mut va = VertexArray { + internal: 0, + }; + unsafe { gl::GenVertexArrays(1, &mut va.internal); } + va + } + + /// Marks the VertexArray as the currently active one, this + /// means buffers/the format of the buffers etc will be bound to + /// this VertexArray. + pub fn bind(&self) { + unsafe { gl::BindVertexArray(self.internal); } + } +} + +impl Drop for VertexArray { + fn drop(&mut self) { + unsafe { gl::DeleteVertexArrays(1, &self.internal); } + self.internal = 0; + } +} + +/// BufferTarget is a target for a buffer to be bound to. +pub type BufferTarget = u32; + +pub const ARRAY_BUFFER: BufferTarget = gl::ARRAY_BUFFER; +pub const ELEMENT_ARRAY_BUFFER: BufferTarget = gl::ELEMENT_ARRAY_BUFFER; + +/// BufferUsage states how a buffer is going to be used by the program. +pub type BufferUsage = u32; + +/// Marks the buffer as 'not going to change' after the +/// initial data upload to be rendered by the gpu. +pub const STATIC_DRAW: BufferUsage = gl::STATIC_DRAW; +/// Marks the buffer as 'changed frequently' during the +/// course of the program whilst being rendered by the gpu. +pub const DYNAMIC_DRAW: BufferUsage = gl::DYNAMIC_DRAW; +/// Marks the buffer as 'changed every frame' whilst being +/// rendered by the gpu. +pub const STREAM_DRAW: BufferUsage = gl::STREAM_DRAW; + +/// Access states how a value will be accesed by the program. +pub type Access = u32; + +/// States that the returned value will only be read. +pub const READ_ONLY: Access = gl::READ_ONLY; +/// States that the returned value will only be written +/// to. +pub const WRITE_ONLY: Access = gl::WRITE_ONLY; + +/// Buffer is a storage for vertex data. +pub struct Buffer { + internal: u32, +} + +impl Buffer { + /// Allocates a new Buffer. + pub fn new() -> Buffer { + let mut b = Buffer { + internal: 0, + }; + unsafe { gl::GenBuffers(1, &mut b.internal); } + b + } + + /// Makes the buffer the currently active one for the given target. + /// This will allow it to be the source of operations that act on a buffer + /// (Data, Map etc). + pub fn bind(&self, target: BufferTarget) { + unsafe { gl::BindBuffer(target, self.internal); } + } + + pub fn set_data(&self, target: BufferTarget, data: &[u8], usage: BufferUsage) { + unsafe { + gl::BufferData(target, data.len() as i64, data.as_ptr() as *const gl::types::GLvoid, usage); + } + } + + /// Maps the memory in the buffer on the gpu to memory which the program + /// can access. The access flag will specify how the program plans to use the + /// returned data. It'll unmap itself once the returned value is dropped. + /// + /// Warning: the passed length value is not checked in anyway so it is + /// possible to overrun the memory. It is up to the program to ensure this + /// length is valid. + pub fn map(&self, target: BufferTarget, access: Access, length: usize) -> MappedBuffer { + unsafe { + MappedBuffer{inner: Vec::from_raw_parts(gl::MapBuffer(target, access) as *mut u8, 0, length), target: target} + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { + gl::DeleteBuffers(1, &self.internal); + } + } +} + +pub struct MappedBuffer { + inner: Vec, + target: BufferTarget, +} + +impl Deref for MappedBuffer { + type Target = Vec; + + fn deref<'a>(&'a self) -> &'a Self::Target { + &self.inner + } +} + +impl DerefMut for MappedBuffer { + fn deref_mut<'a>(&'a mut self) -> &'a mut Self::Target { + &mut self.inner + } +} + +impl Drop for MappedBuffer { + fn drop(&mut self) { + unsafe { gl::UnmapBuffer(self.target); } + mem::forget(mem::replace(&mut self.inner, Vec::new())); + } +} diff --git a/src/item.rs b/src/item.rs index 27661f2..2cdd046 100644 --- a/src/item.rs +++ b/src/item.rs @@ -1,10 +1,9 @@ -extern crate byteorder; use nbt; use protocol::{Serializable}; use std::io; use std::io::{Read, Write}; -use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; #[derive(Debug)] pub struct Stack { diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 4653127..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![cfg_attr(test, allow(dead_code))] -pub mod bit; -pub mod protocol; -pub mod format; -pub mod nbt; -pub mod item; -pub mod gl; -pub mod types; diff --git a/src/bin/steven.rs b/src/main.rs similarity index 54% rename from src/bin/steven.rs rename to src/main.rs index 56ccb89..6ee8355 100644 --- a/src/bin/steven.rs +++ b/src/main.rs @@ -1,10 +1,29 @@ -extern crate glfw; -extern crate steven; +pub mod bit; +pub mod protocol; +pub mod format; +pub mod nbt; +pub mod item; +pub mod gl; +pub mod types; +pub mod resources; +pub mod render; -use steven::*; +extern crate glfw; +extern crate image; +extern crate time; +extern crate byteorder; +extern crate serde_json; +extern crate steven_openssl as openssl; +extern crate hyper; +extern crate flate2; + +use std::sync::{Arc, RwLock}; use glfw::{Action, Context, Key}; fn main() { + let resource_manager = Arc::new(RwLock::new(resources::Manager::new())); + { resource_manager.write().unwrap().tick(); } + let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); glfw.window_hint(glfw::WindowHint::ContextVersion(3, 2)); @@ -22,9 +41,20 @@ fn main() { window.make_current(); glfw.set_swap_interval(1); + let mut renderer = render::Renderer::new(resource_manager.clone()); + + let mut last_frame = time::now(); + let frame_time = (time::Duration::seconds(1).num_nanoseconds().unwrap() as f64) / 60.0; + while !window.should_close() { - gl::clear_color(1.0, 0.0, 0.0, 1.0); - gl::clear(gl::ClearFlags::Color | gl::ClearFlags::Depth); + { resource_manager.write().unwrap().tick(); } + let now = time::now(); + let diff = now - last_frame; + last_frame = now; + let delta = (diff.num_nanoseconds().unwrap() as f64) / frame_time; + + let (width, height) = window.get_framebuffer_size(); + renderer.tick(delta, width as u32, height as u32); window.swap_buffers(); glfw.poll_events(); diff --git a/src/nbt/mod.rs b/src/nbt/mod.rs index 7abfd9e..9bfd5f7 100644 --- a/src/nbt/mod.rs +++ b/src/nbt/mod.rs @@ -1,4 +1,3 @@ -extern crate byteorder; use std::collections::HashMap; use std::io; @@ -6,7 +5,7 @@ use std::io::{Read, Write}; use super::protocol::{Serializable}; use super::protocol; -use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; #[derive(Debug)] pub enum Tag { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 2f7496e..45d74ef 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,9 +1,7 @@ #![allow(dead_code)] -extern crate byteorder; -extern crate hyper; -extern crate steven_openssl as openssl; -extern crate flate2; -extern crate serde_json; + +use openssl; +use serde_json; pub mod mojang; @@ -15,8 +13,9 @@ use std::net::TcpStream; use std::io; use std::io::{Write, Read}; use std::convert; -use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; -use self::flate2::read::{ZlibDecoder, ZlibEncoder}; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use flate2::read::{ZlibDecoder, ZlibEncoder}; +use flate2; /// Helper macro for defining packets #[macro_export] diff --git a/src/protocol/mojang.rs b/src/protocol/mojang.rs index e126793..c692e30 100644 --- a/src/protocol/mojang.rs +++ b/src/protocol/mojang.rs @@ -1,6 +1,8 @@ -extern crate steven_openssl as openssl; -extern crate serde_json; -extern crate hyper; + + +use openssl; +use serde_json; +use hyper; pub struct Profile { pub username: String, diff --git a/src/render/atlas.rs b/src/render/atlas.rs new file mode 100644 index 0000000..de2b461 --- /dev/null +++ b/src/render/atlas.rs @@ -0,0 +1,85 @@ + +pub struct Atlas { + width: usize, + height: usize, + free_space: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub struct Rect { + pub x: usize, + pub y: usize, + pub width: usize, + pub height: usize, +} + +impl Atlas { + pub fn new(width: usize, height: usize) -> Atlas { + let mut a = Atlas { + width: width, + height: height, + free_space: Vec::new(), + }; + a.free_space.push(Rect{ + x: 0, y: 0, + width: width, height: height, + }); + a + } + + pub fn add(&mut self, width: usize, height: usize) -> Option { + let mut priority = usize::max_value(); + let mut target: Option = None; + let mut index = 0; + let mut target_index = 0; + // Search through and find the best fit for this texture + for free in &self.free_space { + if free.width >= width && free.height >= height { + let current_priority = (free.width - width) * (free.height - height); + if target.is_none() || current_priority < priority { + target = Some(*free); + priority = current_priority; + target_index = index; + } + // Perfect match, we can break early + if priority == 0 { + break; + } + } + index += 1; + } + if target.is_none() { + return None; + } + let mut t = target.unwrap(); + let ret = Rect{ + x: t.x, y: t.y, + width: width, height: height, + }; + + if width == t.width { + t.y += height; + t.height -= height; + if t.height == 0 { + // Remove empty sections + self.free_space.remove(target_index); + } else { + self.free_space[target_index] = t; + } + } else { + if t.height > height { + // Split by height + self.free_space.insert(0, Rect{ + x: t.x, y: t.y + height, + width: width, height: t.height - height, + }); + target_index += 1; + } + t.x += width; + t.width -= width; + self.free_space[target_index] = t; + } + + Some(ret) + } +} \ No newline at end of file diff --git a/src/render/glsl.rs b/src/render/glsl.rs new file mode 100644 index 0000000..1323e41 --- /dev/null +++ b/src/render/glsl.rs @@ -0,0 +1,51 @@ + +use std::collections::HashMap; + +pub struct Registry { + shaders: HashMap, +} + +impl Registry { + pub fn new() -> Registry { + Registry { + shaders: HashMap::new(), + } + } + + pub fn register(&mut self, name: &str, source: &str) { + if self.shaders.contains_key(name) { + panic!("shader {} is already defined", name); + } + self.shaders.insert(name.to_owned(), source.trim().to_owned()); + } + + pub fn get(&self, name: &str) -> String { + let mut out = String::new(); + out.push_str("#version 150\n"); + self.get_internal(&mut out, name); + out + } + + pub fn get_define(&self, name: &str, define: &str) -> String { + let mut out = String::new(); + out.push_str("#version 150\n"); + out.push_str("#define "); + out.push_str(define); + out.push_str("\n"); + self.get_internal(&mut out, name); + out + } + + fn get_internal(&self, out: &mut String, name: &str) { + let src = self.shaders.get(name).unwrap(); + for line in src.lines() { + if line.starts_with("#include ") { + let inc = line["#include ".len()..].trim(); + self.get_internal(out, &inc); + continue; + } + out.push_str(&line); + out.push_str("\n"); + } + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..6b06b2f --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,536 @@ + + +mod atlas; +pub mod glsl; +pub mod ui; +mod shaders; + +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; +use resources; +use gl; +use image; +use image::{GenericImage}; +use byteorder::{WriteBytesExt, NativeEndian}; +use serde_json; + +const ATLAS_SIZE: usize = 1024; + +pub struct Renderer { + resource_version: usize, + resources: Arc>, + textures: Arc>, + glsl: glsl::Registry, + ui: ui::UIState, + + gl_texture: gl::Texture, + texture_layers: usize, + + last_width: u32, + last_height: u32, + + temp_rot: f64, +} + +impl Renderer { + pub fn new(res: Arc>) -> 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*1*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); + + let textures = Arc::new(RwLock::new(TextureManager::new(res.clone()))); + + let mut greg = glsl::Registry::new(); + shaders::add_shaders(&mut greg); + let ui = ui::UIState::new(&greg, textures.clone(), res.clone()); + + Renderer { + resource_version: version, + textures: textures, + glsl: greg, + ui: ui, + resources: res, + gl_texture: tex, + texture_layers: 1, + last_width: 0, + last_height: 0, + + temp_rot: 0.0, + } + } + + pub fn tick(&mut self, delta: f64, width: u32, height: u32) { + { + let rm = self.resources.read().unwrap(); + if rm.version() != self.resource_version { + self.resource_version = rm.version(); + println!("Updating textures to {}", self.resource_version); + self.textures.write().unwrap().update_textures(self.resource_version); + } + } + + self.update_textures(delta); + + if self.last_height != height || self.last_width != width { + self.last_width = width; + self.last_height = height; + gl::viewport(0, 0, width as i32, height as i32); + } + + gl::active_texture(0); + self.gl_texture.bind(gl::TEXTURE_2D_ARRAY); + + gl::clear_color(14.0/255.0, 48.0/255.0, 92.0/255.0, 1.0); + gl::clear(gl::ClearFlags::Color | gl::ClearFlags::Depth); + + let test = self.ui.new_text("Hello world", 10.0, 10.0, 255, 255, 255); + let data = test.bytes(width as f64, height as f64); + self.ui.add_bytes(&data); + + let test = self.ui.new_text("Font rendering is complete! (ish)", 10.0, 30.0, 0, 255, 0); + let data = test.bytes(width as f64, height as f64); + self.ui.add_bytes(&data); + + self.temp_rot += delta * 0.05; + let test = self.ui.new_text_rotated("Yay! Progress!", 150.0, 150.0, 1.0, 1.0, self.temp_rot, 255, 0, 0); + let data = test.bytes(width as f64, height as f64); + self.ui.add_bytes(&data); + + self.ui.tick(width, height); + } + + fn update_textures(&mut self, delta: f64) { + self.gl_texture.bind(gl::TEXTURE_2D_ARRAY); + let len = { + let tex = self.textures.read().unwrap(); + 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 { + 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(); + } + + for ani in self.textures.write().unwrap().animated_textures.iter_mut() { + 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; + } + } + + } + + pub fn get_textures(&self) -> Arc> { + self.textures.clone() + } + + pub fn get_textures_ref(&self) -> &RwLock { + &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, name: &str) -> Texture { + let tex = { textures.read().unwrap().get_texture(name) }; + 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_texture(name) { + val + } else { + t.load_texture(name); + t.get_texture(name).unwrap() + } + } + } + } +} + +pub struct TextureManager { + textures: HashMap, + version: usize, + resources: Arc>, + atlases: Vec, + + animated_textures: Vec, + pending_uploads: Vec<(i32, atlas::Rect, Vec)>, +} + +impl TextureManager { + fn new(res: Arc>) -> TextureManager { + let mut tm = TextureManager { + textures: HashMap::new(), + version: 0xFFFF, + resources: res, + atlases: Vec::new(), + animated_textures: Vec::new(), + pending_uploads: Vec::new(), + }; + tm.add_defaults(); + tm + } + + fn add_defaults(&mut self) { + self.put_texture("steven", "missing_texture", 2, 2, vec![ + 0, 0, 0, 255, + 255, 0, 255, 255, + 255, 0, 255, 255, + 0, 0, 0, 255, + ]); + self.put_texture("steven", "solid", 1, 1, vec![ + 255, 255, 255, 255, + ]); + } + + 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.add_defaults(); + + for name in map.keys() { + self.load_texture(name); + } + } + + fn get_texture(&self, name: &str) -> Option { + self.textures.get(name).map(|v| v.clone()) + } + + // TODO: Animated textures + + 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(); + // Might be animated + 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(name); + } + + fn load_animation(&mut self, plugin: &str, name: &str, img: &image::DynamicImage, data: Vec) -> Option { + 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.find("animation").unwrap(); + let frame_time = animation.find("frameTime").and_then(|v| v.as_i64()).unwrap_or(1); + let interpolate = animation.find("interpolate").and_then(|v| v.as_boolean()).unwrap_or(false); + let frames = if let Some(frames) = animation.find("frames").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.find("index").unwrap().as_i64().unwrap() as usize, + time: frame_time * frame.find("frameTime").unwrap().as_i64().unwrap(), + }) + } + } + 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: frames, + data: data, + interpolate: interpolate, + 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) -> 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(); + if plugin != "minecraft" { + full_name.push_str(plugin); + full_name.push_str(":"); + } + full_name.push_str(name); + + let tex = Texture { + name: full_name.clone(), + version: self.version, + atlas: atlas, + 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 self.atlases.iter_mut() { + 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, name: &str) -> Texture { + let missing = self.get_texture("steven:missing_texture").unwrap(); + let t = Texture { + name: 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(name.to_owned(), t.clone()); + t + } +} + +struct AnimatedTexture { + frames: Vec, + data: Vec, + interpolate: bool, + current_frame: usize, + remaining_time: f64, + texture: Texture, +} + +struct AnimationFrame { + index: usize, + time: i64, +} + +#[derive(Clone, Debug)] +pub struct Texture { + name: String, + version: usize, + pub atlas: i32, + x: usize, + y: usize, + width: usize, + height: usize, + is_rel: bool, // Save some cycles for none relative textures + rel_x: f32, + rel_y: f32, + rel_width: f32, + rel_height: f32, +} + +impl Texture { + 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, + } + } +} + +pub fn create_program(vertex: &str, fragment: &str) -> gl::Program { + let program = gl::Program::new(); + + let v = gl::Shader::new(gl::VERTEX_SHADER); + v.set_source(vertex); + v.compile(); + + if v.get_parameter(gl::COMPILE_STATUS) == 0 { + println!("Src: {}", vertex); + panic!("Shader error: {}", v.get_info_log()); + } else { + let log = v.get_info_log(); + if log.len() > 0 { + println!("{}", log); + } + } + + let f = gl::Shader::new(gl::FRAGMENT_SHADER); + f.set_source(fragment); + f.compile(); + + if f.get_parameter(gl::COMPILE_STATUS) == 0 { + println!("Src: {}", fragment); + panic!("Shader error: {}", f.get_info_log()); + } else { + let log = f.get_info_log(); + if log.len() > 0 { + println!("{}", log); + } + } + + program.attach_shader(v); + program.attach_shader(f); + program.link(); + program.use_program(); + program +} + +#[allow(unused_must_use)] +pub fn generate_element_buffer(size: usize) -> (Vec, gl::Type) { + let mut ty = gl::UNSIGNED_SHORT; + let mut data = if (size/6)*4*3 >= u16::max_value() as usize { + ty = gl::UNSIGNED_INT; + let data = Vec::with_capacity(size*4); + data + } else { + let data = Vec::with_capacity(size*2); + data + }; + for i in 0 .. size/6 { + for val in &[0, 1, 2, 2, 1, 3] { + if ty == gl::UNSIGNED_INT { + data.write_u32::((i as u32) * 4 + val); + } else { + data.write_u16::((i as u16) * 4 + (*val as u16)); + } + } + } + + (data, ty) +} \ No newline at end of file diff --git a/src/render/shaders.rs b/src/render/shaders.rs new file mode 100644 index 0000000..de6d754 --- /dev/null +++ b/src/render/shaders.rs @@ -0,0 +1,10 @@ + +use render::glsl; + +pub fn add_shaders(reg: &mut glsl::Registry) { + reg.register("lookup_texture", include_str!("shaders/lookup_texture.glsl")); + reg.register("get_light", include_str!("shaders/get_light.glsl")); + + reg.register("ui_vertex", include_str!("shaders/ui_vertex.glsl")); + reg.register("ui_frag", include_str!("shaders/ui_frag.glsl")); +} \ No newline at end of file diff --git a/src/render/shaders/get_light.glsl b/src/render/shaders/get_light.glsl new file mode 100644 index 0000000..6d0e021 --- /dev/null +++ b/src/render/shaders/get_light.glsl @@ -0,0 +1,28 @@ + +vec3 getLight(vec2 light) { + vec2 li = pow(vec2(lightLevel), 15.0 - light); + float skyTint = skyOffset * 0.95 + 0.05; + float bl = li.x; + float sk = li.y * skyTint; + + float skyRed = sk * (skyOffset * 0.65 + 0.35); + float skyGreen = sk * (skyOffset * 0.65 + 0.35); + float blockGreen = bl * ((bl * 0.6 + 0.4) * 0.6 + 0.4); + float blockBlue = bl * (bl * bl * 0.6 + 0.4); + + vec3 col = vec3( + skyRed + bl, + skyGreen + blockGreen, + sk + blockBlue + ); + + col = col * 0.96 + 0.03; + + float gamma = 0.0; + vec3 invCol = 1.0 - col; + invCol = 1.0 - invCol * invCol * invCol * invCol; + col = col * (1.0 - gamma) + invCol * gamma; + col = col * 0.96 + 0.03; + + return clamp(col, 0.0, 1.0); +} \ No newline at end of file diff --git a/src/render/shaders/lookup_texture.glsl b/src/render/shaders/lookup_texture.glsl new file mode 100644 index 0000000..8d460b4 --- /dev/null +++ b/src/render/shaders/lookup_texture.glsl @@ -0,0 +1,8 @@ +const float invAtlasSize = 1.0 / 1024; +vec4 atlasTexture() { + vec2 tPos = vTextureOffset; + tPos = mod(tPos, vTextureInfo.zw); + tPos += vTextureInfo.xy; + tPos *= invAtlasSize; + return texture(textures, vec3(tPos, vAtlas)); +} \ No newline at end of file diff --git a/src/render/shaders/ui_frag.glsl b/src/render/shaders/ui_frag.glsl new file mode 100644 index 0000000..00a0a83 --- /dev/null +++ b/src/render/shaders/ui_frag.glsl @@ -0,0 +1,17 @@ +uniform sampler2DArray textures; + +in vec4 vColor; +in vec4 vTextureInfo; +in vec2 vTextureOffset; +in float vAtlas; + +out vec4 fragColor; + +#include lookup_texture + +void main() { + vec4 col = atlasTexture(); + col *= vColor; + if (col.a == 0.0) discard; + fragColor = col; +} \ No newline at end of file diff --git a/src/render/shaders/ui_vertex.glsl b/src/render/shaders/ui_vertex.glsl new file mode 100644 index 0000000..eb0ec9b --- /dev/null +++ b/src/render/shaders/ui_vertex.glsl @@ -0,0 +1,20 @@ +in ivec3 aPosition; +in vec4 aTextureInfo; +in ivec3 aTextureOffset; +in vec4 aColor; + +out vec4 vColor; +out vec4 vTextureInfo; +out vec2 vTextureOffset; +out float vAtlas; + +uniform vec2 screenSize; + +void main() { + vec2 pos = aPosition.xy / screenSize; + gl_Position = vec4((pos.x-0.5)*2.0, -(pos.y-0.5)*2.0, float(-aPosition.z) / float(0xFFFF-1), 1.0); + vColor = aColor; + vTextureInfo = aTextureInfo; + vTextureOffset = aTextureOffset.xy / 16.0; + vAtlas = aTextureOffset.z; +} \ No newline at end of file diff --git a/src/render/ui.rs b/src/render/ui.rs new file mode 100644 index 0000000..a02e9d4 --- /dev/null +++ b/src/render/ui.rs @@ -0,0 +1,456 @@ + +use std::sync::{Arc, RwLock}; +use std::io::Write; +use std::collections::HashMap; +use resources; +use gl; +use render; +use byteorder::{WriteBytesExt, NativeEndian}; +use image; +use image::{GenericImage}; + +const UI_WIDTH: f64 = 854.0; +const UI_HEIGHT: f64 = 480.0; + +pub struct UIState { + textures: Arc>, + resources: Arc>, + version: usize, + + data: Vec, + prev_size: usize, + count: usize, + + array: gl::VertexArray, + buffer: gl::Buffer, + index_buffer: gl::Buffer, + index_type: gl::Type, + max_index: usize, + + shader: gl::Program, + s_position: gl::Attribute, + s_texture_info: gl::Attribute, + s_texture_offset: gl::Attribute, + s_color: gl::Attribute, + s_texture: gl::Uniform, + s_screensize: gl::Uniform, + + // Font + font_pages: Vec>, + font_character_info: [(i32,i32); 0x10000], + char_map: HashMap, + page_width: f64, + page_height: f64, +} + +impl UIState { + pub fn new(glsl: &super::glsl::Registry, textures: Arc>, res: Arc>) -> UIState { + let v = glsl.get("ui_vertex"); + let f = glsl.get("ui_frag"); + let shader = super::create_program(&v, &f); + let s_position = shader.attribute_location("aPosition"); + let s_texture_info = shader.attribute_location("aTextureInfo"); + let s_texture_offset = shader.attribute_location("aTextureOffset"); + let s_color = shader.attribute_location("aColor"); + let s_texture = shader.uniform_location("textures"); + let s_screensize = shader.uniform_location("screenSize"); + + let array = gl::VertexArray::new(); + array.bind(); + let buffer = gl::Buffer::new(); + buffer.bind(gl::ARRAY_BUFFER); + s_position.enable(); + s_texture_info.enable(); + s_texture_offset.enable(); + s_color.enable(); + s_position.vertex_pointer_int(3, gl::SHORT, 28, 0); + s_texture_info.vertex_pointer(4, gl::UNSIGNED_SHORT, false, 28, 8); + s_texture_offset.vertex_pointer_int(3, gl::SHORT, 28, 16); + s_color.vertex_pointer(4, gl::UNSIGNED_BYTE, true, 28, 24); + + let index_buffer = gl::Buffer::new(); + index_buffer.bind(gl::ELEMENT_ARRAY_BUFFER); + + let mut pages = Vec::with_capacity(0x100); + for _ in 0 .. 0x100 { + pages.push(Option::None); + } + + let mut char_map = HashMap::new(); + let ascii_chars = "ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■"; + let mut pos = 0u32; + for c in ascii_chars.chars() { + char_map.insert(c, ::std::char::from_u32(pos).unwrap()); + pos += 1; + } + + UIState { + textures: textures, + resources: res, + version: 0xFFFF, + + data: Vec::new(), + count: 0, + prev_size: 0, + + index_type: gl::UNSIGNED_BYTE, + array: array, + buffer: buffer, + index_buffer: index_buffer, + max_index: 0, + + shader: shader, + s_position: s_position, + s_texture_info: s_texture_info, + s_texture_offset: s_texture_offset, + s_color: s_color, + s_texture: s_texture, + s_screensize: s_screensize, + + // Font + font_pages: pages, + font_character_info: [(0, 0); 0x10000], + char_map: char_map, + page_width: 0.0, + page_height: 0.0, + } + } + + pub fn tick(&mut self, width: u32, height: u32) { + { + let version = self.resources.read().unwrap().version(); + if self.version != version { + self.version = version; + self.load_font(); + } + } + // Prevent clipping with the world + gl::clear(gl::ClearFlags::Depth); + gl::depth_func(gl::LESS_OR_EQUAL); + gl::enable(gl::BLEND); + + self.shader.use_program(); + self.s_texture.set_int(0); + if self.count > 0 { + self.array.bind(); + if self.max_index < self.count { + let (data, ty) = render::generate_element_buffer(self.count); + self.index_type = ty; + self.index_buffer.bind(gl::ELEMENT_ARRAY_BUFFER); + self.index_buffer.set_data(gl::ELEMENT_ARRAY_BUFFER, &data, gl::DYNAMIC_DRAW); + self.max_index = self.count; + } + + self.s_screensize.set_float2(width as f32, height as f32); + + self.buffer.bind(gl::ARRAY_BUFFER); + if self.data.len() > self.prev_size { + self.prev_size = self.data.len(); + self.buffer.set_data(gl::ARRAY_BUFFER, &self.data, gl::STREAM_DRAW); + } else { + let mut target = self.buffer.map(gl::ARRAY_BUFFER, gl::WRITE_ONLY, self.data.len()); + target.write_all(&self.data[..]).unwrap(); + } + gl::draw_elements(gl::TRIANGLES, self.count, self.index_type, 0); + } + + + gl::disable(gl::BLEND); + self.data.clear(); + self.count = 0; + } + + pub fn add_bytes(&mut self, data: &Vec) { + self.data.extend(data); + self.count += (data.len() / (28 + 4)) * 6; + } + + pub fn character_texture(&mut self, c: char) -> render::Texture { + let raw = c as u32; + let page = raw >> 8; + // Lazy load fonts to size memory + if self.font_pages[page as usize].is_none() { + let name = if page == 0 { + "font/ascii".to_owned() + } else { + format!("font/unicode_page_{:02X}", page) + }; + let textures = self.textures.clone(); + self.font_pages[page as usize] = Some(render::Renderer::get_texture(&textures, &name)); + } + let p = self.font_pages[page as usize].clone().unwrap(); + + let raw = if page == 0 { + self.char_map[&c] as u32 + } else { + raw + }; + let ch = raw & 0xFF; + let cx = ch & 0xF; + let cy = ch >> 4; + let info = self.font_character_info[raw as usize]; + if page == 0 { + let sw = (self.page_width / 16.0) as u32; + let sh = (self.page_height / 16.0) as u32; + return p.relative( + (cx * sw + info.0 as u32) as f32 / (self.page_width as f32), + (cy * sh) as f32 / (self.page_height as f32), + (info.1 - info.0) as f32 / (self.page_width as f32), + (sh as f32) / (self.page_height as f32) + ) + } + return p.relative( + (cx * 16 + info.0 as u32) as f32 / 256.0, + (cy * 16) as f32 / 256.0, + (info.1 - info.0) as f32 / 256.0, + 16.0 / 256.0 + ) + } + + pub fn size_of_string(&self, val: &str) -> f64 { + let mut size = 0.0; + for c in val.chars() { + size += self.size_of_char(c) + 2.0; + } + size - 2.0 + } + + pub fn size_of_char(&self, c: char) -> f64 { + if c == ' ' { + return 4.0; + } + let r = c as u32; + if r >> 8 == 0 { + let r = self.char_map[&c] as u32; + let info = self.font_character_info[r as usize]; + let sw = self.page_width / 16.0; + return (((info.1 - info.0) as f64) / sw) * 16.0; + } + let info = self.font_character_info[c as usize]; + return (info.1 - info.0) as f64; + } + + fn load_font(&mut self) { + let res = self.resources.read().unwrap(); + if let Some(mut info) = res.open("minecraft", "font/glyph_sizes.bin") { + let mut data = Vec::with_capacity(0x10000); + info.read_to_end(&mut data).unwrap(); + for i in 0 .. self.font_character_info.len() { + // Top nibble - start position + // Bottom nibble - end position + self.font_character_info[i].0 = (data[i] >> 4) as i32; + self.font_character_info[i].1 = (data[i] & 0xF) as i32 + 1; + } + } + if let Some(mut val) = res.open("minecraft", "textures/font/ascii.png") { + 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(); + self.page_width = width as f64; + self.page_height = height as f64; + let sw = width / 16; + let sh = height / 16; + for i in 0 .. 256 { + let cx = (i & 0xF) * sw; + let cy = (i >> 4) * sh; + let mut start = true; + 'x_loop: for x in 0 .. sw { + for y in 0 .. sh { + let a = img.get_pixel(cx+x, cy+y).data[3]; + if start && a != 0 { + self.font_character_info[i as usize].0 = x as i32; + start = false; + continue 'x_loop; + } else if !start && a != 0 { + continue 'x_loop; + } + } + if !start { + self.font_character_info[i as usize].1 = x as i32; + break; + } + } + } + } + } + } + + pub fn new_text(&mut self, val: &str, x: f64, y: f64, r: u8, g: u8, b: u8) -> UIText { + self.new_text_scaled(val, x, y, 1.0, 1.0, r, g, b) + } + + pub fn new_text_scaled(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, r: u8, g: u8, b: u8) -> UIText { + self.create_text(val, x, y, sx, sy, 0.0, r, g, b) + } + + pub fn new_text_rotated(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, rotation: f64, r: u8, g: u8, b: u8) -> UIText { + self.create_text(val, x, y, sx, sy, rotation, r, g, b) + } + + fn create_text(&mut self, val: &str, x: f64, y: f64, sx: f64, sy: f64, rotation: f64, r: u8, g: u8, b: u8) -> UIText { + let mut elements = Vec::new(); + let mut offset = 0.0; + for ch in val.chars() { + if ch == ' ' { + offset += 6.0; + continue; + } + let texture = self.character_texture(ch); + let mut raw = ch as u32; + let page = raw >> 8; + + if page == 0 { + raw = self.char_map[&ch] as u32; + } + let info = self.font_character_info[raw as usize]; + let w = if page == 0 { + let sw = self.page_width / 16.0; + ((info.1 - info.0) as f64 / sw) * 16.0 + } else { + (info.1 - info.0) as f64 + }; + + let mut dsx = offset + 2.0; + let mut dsy = 2.0; + let mut dx = offset; + let mut dy = 0.0; + if rotation != 0.0 { + let c = rotation.cos(); + let s = rotation.sin(); + let tmpx = dsx - (w * 0.5); + let tmpy = dsy - (16.0 * 0.5); + dsx = (w * 0.5) + (tmpx*c - tmpy*s); + dsy = (16.0 * 0.5) + (tmpy*c + tmpx*s); + let tmpx = dx - (w * 0.5); + let tmpy = dy - (16.0 * 0.5); + dx = (w * 0.5) + (tmpx*c - tmpy*s); + dy = (16.0 * 0.5) + (tmpy*c + tmpx*s); + } + + let mut shadow = UIElement::new(&texture, x+dsx*sx, y+dsy*sy, w*sx, 16.0*sy, 0.0, 0.0, 1.0, 1.0); + shadow.r = ((r as f64) * 0.25) as u8; + shadow.g = ((g as f64) * 0.25) as u8; + shadow.b = ((b as f64) * 0.25) as u8; + shadow.rotation = rotation; + elements.push(shadow); + + let mut text = UIElement::new(&texture, x+dx*sx, y+dy*sy, w*sx, 16.0*sy, 0.0, 0.0, 1.0, 1.0); + text.r = r; + text.g = g; + text.b = b; + text.rotation = rotation; + elements.push(text); + offset += w + 2.0; + } + UIText { + elements: elements, + width: (offset - 2.0) * sx, + } + } +} + +pub struct UIText { + pub elements: Vec, + pub width: f64, +} + +impl UIText { + pub fn bytes(&self, width: f64, height: f64) -> Vec { + let mut buf = Vec::with_capacity(28*4*self.elements.len()); + for e in &self.elements { + buf.extend(e.bytes(width, height)); + } + buf + } +} + +pub struct UIElement { + pub x: f64, + pub y: f64, + pub w: f64, + pub h: f64, + pub layer: isize, + pub t_x: u16, + pub t_y: u16, + pub t_w: u16, + pub t_h: u16, + pub t_offsetx: i16, + pub t_offsety: i16, + pub t_atlas: i16, + pub t_sizew: i16, + pub t_sizeh: i16, + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, + pub rotation: f64, +} + +impl UIElement { + pub fn new(tex: &render::Texture, x: f64, y: f64, width: f64, height: f64, tx: f64, ty: f64, tw: f64, th: f64) -> UIElement { + let twidth = tex.get_width(); + let theight = tex.get_height(); + UIElement { + x: x / UI_WIDTH, + y: y / UI_HEIGHT, + w: width / UI_WIDTH, + h: height / UI_HEIGHT, + layer: 0, + t_x: tex.get_x() as u16, + t_y: tex.get_y() as u16, + t_w: twidth as u16, + t_h: theight as u16, + t_atlas: tex.atlas as i16, + t_offsetx: (tx * (twidth as f64) * 16.0) as i16, + t_offsety: (ty * (theight as f64) * 16.0) as i16, + t_sizew: (tw * (twidth as f64) * 16.0) as i16, + t_sizeh: (th * (theight as f64) * 16.0) as i16, + r: 255, + g: 255, + b: 255, + a: 255, + rotation: 0.0, + } + } + + pub fn bytes(&self, width: f64, height: f64) -> Vec { + let mut buf = Vec::with_capacity(28*4); + self.append_vertex(&mut buf, self.x, self.y, self.t_offsetx, self.t_offsety, width, height); + self.append_vertex(&mut buf, self.x + self.w, self.y, self.t_offsetx + self.t_sizew, self.t_offsety, width, height); + self.append_vertex(&mut buf, self.x, self.y + self.h, self.t_offsetx, self.t_offsety + self.t_sizeh, width, height); + self.append_vertex(&mut buf, self.x + self.w, self.y + self.h, self.t_offsetx + self.t_sizew, self.t_offsety + self.t_sizeh, width, height); + buf + } + + #[allow(unused_must_use)] + pub fn append_vertex(&self, buf: &mut Vec, x: f64, y: f64, tx: i16, ty: i16, width: f64, height: f64) { + let mut dx = x as f64; + let mut dy = y as f64; + if self.rotation != 0.0 { + let c = self.rotation.cos(); + let s = self.rotation.sin(); + let tmpx = dx - self.x - (self.w / 2.0); + let tmpy = dy - self.y - (self.h / 2.0); + dx = (self.w / 2.0) + (tmpx * c - tmpy * s) + self.x; + dy = (self.h / 2.0) + (tmpy * c + tmpx * s) + self.y; + } + + buf.write_i16::((dx*width+0.5).floor() as i16); + buf.write_i16::((dy*height+0.5).floor() as i16); + buf.write_i16::((self.layer * 256) as i16); + buf.write_i16::(0); + buf.write_u16::(self.t_x); + buf.write_u16::(self.t_y); + buf.write_u16::(self.t_w); + buf.write_u16::(self.t_h); + buf.write_i16::(tx); + buf.write_i16::(ty); + buf.write_i16::(self.t_atlas); + buf.write_i16::(0); + buf.write_u8(self.r); + buf.write_u8(self.g); + buf.write_u8(self.b); + buf.write_u8(self.a); + } +} \ No newline at end of file diff --git a/src/resources.rs b/src/resources.rs new file mode 100644 index 0000000..cc6cd4f --- /dev/null +++ b/src/resources.rs @@ -0,0 +1,170 @@ +extern crate hyper; +extern crate zip; + +use std::thread; +use std::path; +use std::io; +use std::fs; +use std::sync::mpsc; + +const RESOURCES_VERSION: &'static str = "15w37a"; + +pub trait Pack { + fn open(&self, name: &str) -> Option>; +} + +pub struct Manager { + packs: Vec>, + version: usize, + + vanilla_chan: Option>, +} + +impl Manager { + pub fn new() -> Manager { + let mut m = Manager { + packs: Vec::new(), + version: 0, + vanilla_chan: None, + }; + m.download_vanilla(); + m + } + + /// Returns the 'version' of the manager. The version is + /// increase everytime a pack is added or removed. + pub fn version(&self) -> usize { + self.version + } + + pub fn open(&self, plugin: &str, name: &str) -> Option> { + for pack in self.packs.iter().rev() { + let path = format!("assets/{}/{}", plugin, name); + match pack.open(&path) { + Some(val) => return Some(val), + None => {}, + } + } + None + } + + pub fn open_all(&self, plugin: &str, name: &str) -> Vec> { + let mut ret = Vec::new(); + for pack in self.packs.iter().rev() { + let path = format!("assets/{}/{}", plugin, name); + match pack.open(&path) { + Some(val) => ret.push(val), + None => {}, + } + } + ret + } + + pub fn tick(&mut self) { + // Check to see if the download of vanilla has completed + // (if it was started) + let mut done = false; + if let Some(ref recv) = self.vanilla_chan { + if let Ok(_) = recv.try_recv() { + done = true; + } + } + if done { + self.vanilla_chan = None; + self.load_vanilla(); + } + } + + fn add_pack(&mut self, pck: Box) { + self.packs.push(pck); + self.version += 1; + } + + fn load_vanilla(&mut self) { + let loc = format!("./resources-{}", RESOURCES_VERSION); + let location = path::Path::new(&loc); + self.add_pack(Box::new(DirPack{ + root: location.to_path_buf(), + })) + } + + fn download_vanilla(&mut self) { + let loc = format!("./resources-{}", RESOURCES_VERSION); + let location = path::Path::new(&loc); + if fs::metadata(location.join("steven.assets")).is_ok() { + self.load_vanilla(); + return; + } + let (send, recv) = mpsc::channel(); + self.vanilla_chan = Some(recv); + + println!("Vanilla assets missing, obtaining"); + thread::spawn(move || { + let client = hyper::Client::new(); + let url = format!("https://s3.amazonaws.com/Minecraft.Download/versions/{0}/{0}.jar", RESOURCES_VERSION); + let res = client.get(&url) + .send().unwrap(); + let mut file = fs::File::create(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); + + let length = *res.headers.get::().unwrap(); + let mut progress = ProgressRead { + read: res, + progress: 0, + total: *length, + }; + io::copy(&mut progress, &mut file).unwrap(); + + // Copy the resources from the zip + let file = fs::File::open(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); + let mut zip = zip::ZipArchive::new(file).unwrap(); + + let loc = format!("./resources-{}", RESOURCES_VERSION); + let location = path::Path::new(&loc); + let count = zip.len(); + for i in 0 .. count { + let mut file = zip.by_index(i).unwrap(); + if !file.name().starts_with("assets/") { + continue; + } + let path = location.join(file.name()); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + let mut out = fs::File::create(path).unwrap(); + io::copy(&mut file, &mut out).unwrap(); + } + + fs::File::create(location.join("steven.assets")).unwrap(); // Marker file + println!("Done"); + send.send(true).unwrap(); + + fs::remove_file(format!("{}.tmp", RESOURCES_VERSION)).unwrap(); + }); + } +} + +struct DirPack { + root: path::PathBuf, +} + +impl Pack for DirPack { + fn open(&self, name: &str) -> Option> { + match fs::File::open(self.root.join(name)) { + Ok(val) => Some(Box::new(val)), + Err(_) => None, + } + } +} + +struct ProgressRead { + read: T, + total: u64, + progress: u64, +} + +impl io::Read for ProgressRead { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let size = try!(self.read.read(buf)); + self.progress += size as u64; + println!("Progress: {:.2}", (self.progress as f64) / (self.total as f64)); + Ok(size) + } +} diff --git a/src/types/metadata.rs b/src/types/metadata.rs index 3b78b42..7ad261e 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -15,6 +15,7 @@ pub struct MetadataKey { } impl MetadataKey { + #[allow(dead_code)] // TODO: Make const later when possible /*const*/ fn new(index: i32) -> MetadataKey { MetadataKey {