// Copyright 2015 Matthew Collins // // 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. 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); } }