stevenarella/src/render/ui.rs

547 lines
18 KiB
Rust

// Copyright 2016 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::collections::HashMap;
use resources;
use gl;
use render;
use render::glsl;
use render::shaders;
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<RwLock<render::TextureManager>>,
resources: Arc<RwLock<resources::Manager>>,
pub version: usize,
data: Vec<u8>,
prev_size: usize,
count: usize,
array: gl::VertexArray,
buffer: gl::Buffer,
index_buffer: gl::Buffer,
index_type: gl::Type,
max_index: usize,
shader: UIShader,
// Font
font_pages: Vec<Option<render::Texture>>,
font_character_info: Vec<(i32, i32)>,
char_map: HashMap<char, char>,
page_width: f64,
page_height: f64,
}
init_shader! {
Program UIShader {
vert = "ui_vertex",
frag = "ui_frag",
attribute = {
position => "aPosition",
texture_info => "aTextureInfo",
texture_offset => "aTextureOffset",
color => "aColor",
},
uniform = {
texture => "textures",
screensize => "screenSize",
},
}
}
impl UIState {
pub fn new(glsl: &glsl::Registry,
textures: Arc<RwLock<render::TextureManager>>,
res: Arc<RwLock<resources::Manager>>)
-> UIState {
let shader = UIShader::new(glsl);
let array = gl::VertexArray::new();
array.bind();
let buffer = gl::Buffer::new();
buffer.bind(gl::ARRAY_BUFFER);
shader.position.enable();
shader.texture_info.enable();
shader.texture_offset.enable();
shader.color.enable();
shader.position.vertex_pointer_int(3, gl::SHORT, 28, 0);
shader.texture_info.vertex_pointer(4, gl::UNSIGNED_SHORT, false, 28, 8);
shader.texture_offset.vertex_pointer_int(3, gl::SHORT, 28, 16);
shader.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{|}~ \
ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞\
╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■";
for (pos, c) in ascii_chars.chars().enumerate() {
char_map.insert(c, ::std::char::from_u32(pos as u32).unwrap());
}
let mut state = 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,
// Font
font_pages: pages,
font_character_info: vec![(0, 0); 0x10000],
char_map: char_map,
page_width: 0.0,
page_height: 0.0,
};
state.load_font();
state
}
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);
gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
self.shader.program.use_program();
self.shader.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.shader.screensize.set_float2(width as f32, height as f32);
self.buffer.bind(gl::ARRAY_BUFFER);
self.index_buffer.bind(gl::ELEMENT_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 {
self.buffer.re_set_data(gl::ARRAY_BUFFER, &self.data);
}
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: &[u8]) {
self.data.extend_from_slice(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.get(&c).unwrap_or(&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))
}
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.get(&c).unwrap_or(&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];
(info.1 - info.0) as f64
}
fn load_font(&mut self) {
for page in &mut self.font_pages {
*page = None;
}
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, info) in self.font_character_info.iter_mut().enumerate() {
// Top nibble - start position
// Bottom nibble - end position
info.0 = (data[i] >> 4) as i32;
info.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 w = self.size_of_char(ch);
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<UIElement>,
pub width: f64,
}
impl UIText {
pub fn bytes(&self, width: f64, height: f64) -> Vec<u8> {
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<u8> {
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<u8>,
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::<NativeEndian>((dx * width + 0.5).floor() as i16);
buf.write_i16::<NativeEndian>((dy * height + 0.5).floor() as i16);
buf.write_i16::<NativeEndian>((self.layer * 256) as i16);
buf.write_i16::<NativeEndian>(0);
buf.write_u16::<NativeEndian>(self.t_x);
buf.write_u16::<NativeEndian>(self.t_y);
buf.write_u16::<NativeEndian>(self.t_w);
buf.write_u16::<NativeEndian>(self.t_h);
buf.write_i16::<NativeEndian>(tx);
buf.write_i16::<NativeEndian>(ty);
buf.write_i16::<NativeEndian>(self.t_atlas);
buf.write_i16::<NativeEndian>(0);
buf.write_u8(self.r);
buf.write_u8(self.g);
buf.write_u8(self.b);
buf.write_u8(self.a);
}
}