use super::math_util::*;
use hiero_pack::*;
use super::{array::*, buffer::*, program::*, texture::*, OglIncomplete, *};
use crate::{*,window::*,console::*};
static DEFAULT_WHITE_SPACE_LEN: f32 = 24.0;
static CHARACTER_BUFFER_LEN: usize = 256;
static DEFAULT_PROGRAM: &'static str = "
#ifndef HEADER
#version 300 es
precision mediump float;
#endif
#ifndef UNIFORMS
uniform sampler2D page;
uniform vec4 text_color;
uniform mat4 projection;
uniform mat4 model;
#endif
#ifndef VERTEX_ATTRIBUTES
layout(location = 1) in vec2 vert_in;
layout(location = 2) in vec2 uv_in;
#endif
#ifndef VERTEX_SHADER
out vec2 tex_coord;
void main(){
tex_coord = uv_in;
gl_Position = projection*model*vec4(vert_in.xy,0.0,1.0);
}
#endif
#ifndef FRAGMENT_SHADER
in vec2 tex_coord;
out vec4 color;
void main(){
vec4 page = texture(page,tex_coord);
float dist = page.w;
vec2 grad = vec2( dFdx(dist), dFdy(dist));
float grad_mag = length(grad)*1.1;
color = vec4(1.)*smoothstep(0.5-grad_mag,0.5+grad_mag,dist);
}
#endif";
struct WriterState {
pen_x: f32,
pen_y: f32,
comps_filled: usize,
chars_filled: usize,
vert_len: usize,
text_len: usize,
}
impl WriterState {
fn advance_pen(&mut self, dx: f32, dy: f32) {
self.pen_x += dx;
self.pen_y += dy;
}
fn push_glyph(
&mut self,
verts: &mut [f32],
uvs: &mut [f32],
xoff: f32,
yoff: f32,
glyph_bounds: AABB,
) {
TextWriter::set_glyph(
verts,
uvs,
AABB::new(
self.pen_x + xoff,
self.pen_y + yoff,
glyph_bounds.w,
glyph_bounds.h,
),
glyph_bounds,
self.comps_filled,
);
self.count_glyph();
}
fn count_glyph(&mut self) {
self.chars_filled += 1;
self.comps_filled += 12;
}
fn buffers_are_full(&self, char_index: usize) -> bool {
self.comps_filled >= self.vert_len || char_index >= self.text_len - 1
}
fn flush(
&mut self,
gl: &GlState,
vert_buffer: &mut Box<dyn HasBufferObj>,
uv_buffer: &mut Box<dyn HasBufferObj>,
) {
uv_buffer.update();
vert_buffer.update();
unsafe {
gl.draw_arrays(TRIANGLES, 0, 6 * self.chars_filled as i32);
}
self.comps_filled = 0;
self.chars_filled = 0;
}
}
pub struct TextWriter {
gl: GlState,
text_geometry: OglArray,
renderer: OglProg,
atlas: Option<HieroAtlas>,
projection_mat_loc: Option<UniformLocation>,
model_loc: Option<UniformLocation>,
page_loc: Option<UniformLocation>,
page_texture: Option<OglTexture>,
whitespace_len: Option<f32>,
page_history: [usize; 4],
page_index: usize,
}
impl TextWriter {
pub fn new(gl: &GlState) -> OglIncomplete<Self> {
let renderer = match OglProg::compile_program(gl, DEFAULT_PROGRAM) {
Ok(prog) => prog,
Err(comp_err) => {
match comp_err {
CompilationError::ShaderError {
ogl_error,
faulty_source,
} => {
console_log!("ogl_error:\n{}\nsource:{}\n", ogl_error, faulty_source);
}
_ => (),
};
panic!("shader compiler error");
}
};
let vert_data: Vec<f32> = vec![0.; CHARACTER_BUFFER_LEN * 6 * 2];
let uv_data = vert_data.clone();
let array = OglArray::new(gl).init(vec![
BufferPair::new(
"verts",
OglBuf::new(gl)
.with_usage(DYNAMIC_DRAW)
.with_data(vert_data)
.with_index(1)
.with_num_comps(2)
.build()
.into(),
),
BufferPair::new(
"uvs",
OglBuf::new(&gl)
.with_usage(DYNAMIC_DRAW)
.with_data(uv_data)
.with_index(2)
.with_num_comps(2)
.build()
.into(),
),
]);
unsafe {
renderer.bind(true);
let proj_loc = gl.get_uniform_location(renderer.prog(), "projection");
let page_loc = gl.get_uniform_location(renderer.prog(), "page");
let model_loc = gl.get_uniform_location(renderer.prog(), "model");
OglIncomplete::new(Self {
gl: gl.clone(),
renderer: renderer,
atlas: None,
model_loc,
projection_mat_loc: proj_loc,
text_geometry: array,
page_loc,
page_texture: None,
whitespace_len: None,
page_history: [99999; 4],
page_index: 0,
})
}
}
pub fn draw_text_line(
&mut self,
text: &str,
x0: f32,
y0: f32,
size: f32,
screen_bounds: Option<(u32, u32)>,
) {
if text.len() == 0 {
return;
}
let gl = self.gl.clone();
let (screen_w, screen_h) = screen_bounds.unwrap_or((800, 600));
let proj_mat = Self::calc_proj(screen_w as f32, screen_h as f32);
let src_bb = self.calculate_bounding_box(x0, y0, text);
let aspect_ratio = src_bb.w / src_bb.h;
let resize_matrix = resize_region(src_bb, AABB::new(x0, y0, aspect_ratio * size, size));
let whitespace = self.whitespace();
self.renderer.bind(true);
self.text_geometry.bind(true);
self.page_texture
.as_ref()
.map(|texture| texture.bind(0, self.page_loc.as_ref()));
let uv_buffer: &mut Box<dyn HasBufferObj> = unsafe {
let ptr = self.text_geometry.get_mut("uvs").unwrap() as *mut Box<dyn HasBufferObj>;
&mut *ptr
};
let vert_buffer: &mut Box<dyn HasBufferObj> = unsafe {
let ptr = self.text_geometry.get_mut("verts").unwrap() as *mut Box<dyn HasBufferObj>;
&mut *ptr
};
let mut writer_state = WriterState {
pen_x: x0,
pen_y: y0,
comps_filled: 0,
chars_filled: 0,
vert_len: Self::to_float_slice(vert_buffer.raw_bytes_mut()).len(),
text_len: text.len(),
};
let first_char = text.chars().next().unwrap();
let first_page = self
.atlas
.as_ref()
.map(|atlas| {
atlas
.bitmap_table
.get(&first_char)
.map(|bitmap| bitmap.page)
})
.flatten();
first_page.map(|index| {
if self.cur_page() != index as usize {
self.decode_page(index as usize);
self.new_page(index as usize);
}
});
unsafe {
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
gl.uniform_matrix_4_f32_slice(self.projection_mat_loc.as_ref(), false, &proj_mat[..]);
gl.uniform_matrix_4_f32_slice(self.model_loc.as_ref(), true, &resize_matrix[..]);
}
for (k, character) in text.char_indices() {
let bitmap = self
.atlas
.as_ref()
.map(|atlas| atlas.bitmap_table.get(&character))
.flatten()
.map(|&a| a);
let adv = bitmap.map_or((whitespace, 0.), |bitmap| {
let new_page = bitmap.page as usize;
let cur_page = self.cur_page();
if new_page != cur_page {
writer_state.flush(&gl, vert_buffer, uv_buffer);
self.decode_page(new_page);
}
let hiero_bounds = AABB::new(
bitmap.x as f32,
bitmap.y as f32,
bitmap.width as f32,
bitmap.height as f32,
);
let uvs = Self::to_float_slice(uv_buffer.raw_bytes_mut());
let verts = Self::to_float_slice(vert_buffer.raw_bytes_mut());
writer_state.push_glyph(
verts,
uvs,
bitmap.xoffset as f32,
bitmap.yoffset as f32,
hiero_bounds,
);
self.new_page(new_page);
(bitmap.xadvance as f32, 0.0)
});
writer_state.advance_pen(adv.0, adv.1);
if writer_state.buffers_are_full(k) {
writer_state.flush(&gl, vert_buffer, uv_buffer);
}
}
unsafe {
gl.disable(glow::BLEND);
}
}
fn decode_page(&mut self, new_page: usize) {
self.atlas.as_ref().map(|atlas| {
atlas
.try_unpack_page(new_page)
.map(|page| {
let pixel_slice = &page.pixels()[..];
self.page_texture.as_ref().map(|page_texture| {
page_texture.copy_image(512, 512, pixel_slice);
})
})
.map_err(|err| {
panic!("Error: {}", err);
})
.unwrap();
});
}
fn calculate_bounding_box(&self, x0: f32, y0: f32, text: &str) -> AABB {
let mut minx = std::f32::INFINITY;
let mut miny = std::f32::INFINITY;
let mut maxx = std::f32::NEG_INFINITY;
let mut maxy = std::f32::NEG_INFINITY;
let mut pen_x = x0;
let pen_y = y0;
let whitespace = self.whitespace();
self.atlas.as_ref().map(|atlas| {
text.chars().for_each(|c| {
let x_adv = atlas.bitmap_table.get(&c).map_or_else(
|| whitespace,
|bitmap| {
let xoff = bitmap.xoffset as f32;
let yoff = bitmap.yoffset as f32;
let x = xoff + pen_x;
let y = yoff + pen_y;
let w = bitmap.width as f32;
let h = bitmap.height as f32;
[(x, y), (x + w, y), (x, y + h), (x + w, y + h)]
.iter()
.for_each(|&(x, y)| {
minx = minx.min(x);
maxx = maxx.max(x);
miny = miny.min(y);
maxy = maxy.max(y);
});
bitmap.xadvance as f32
},
);
pen_x += x_adv;
});
});
AABB {
x: minx,
y: miny,
w: maxx - minx,
h: maxy - miny,
}
}
fn calc_proj(w: f32, h: f32) -> [f32; 16] {
let PointInterceptLine { m: m1, b: b1 } = map_coefs(0., w, -1., 1.);
let PointInterceptLine { m: m2, b: b2 } = map_coefs(0., h, 1., -1.);
[
m1, 0., 0., 0., 0., m2, 0., 0., 0., 0., 1., 0., b1, b2, 0., 1.,
]
}
fn set_glyph(vert: &mut [f32], uvs: &mut [f32], vb: AABB, hb: AABB, offset: usize) {
const INV_PAGE_DIM: f32 = 1.0 / 512.;
let get_pos = |rel_index: usize| -> usize { (2 * rel_index) + offset };
let mut uv_helper = |rel_index: usize, x, y| {
uvs[get_pos(rel_index) + 0] = x * INV_PAGE_DIM;
uvs[get_pos(rel_index) + 1] = y * INV_PAGE_DIM;
};
let mut vert_helper = |rel_index: usize, x, y| {
vert[get_pos(rel_index) + 0] = x;
vert[get_pos(rel_index) + 1] = y;
};
let uvs = [
(hb.x, hb.y),
(hb.x + hb.w, hb.y),
(hb.x + hb.w, hb.y + hb.h),
(hb.x, hb.y),
(hb.x + hb.w, hb.y + hb.h),
(hb.x, hb.y + hb.h),
];
let verts = [
(vb.x, vb.y),
(vb.x + vb.w, vb.y),
(vb.x + vb.w, vb.y + vb.h),
(vb.x, vb.y),
(vb.x + vb.w, vb.y + vb.h),
(vb.x, vb.y + vb.h),
];
for (k, (&(vx, vy), (uvx, uvy))) in verts.iter().zip(uvs.iter()).enumerate() {
uv_helper(k, uvx, uvy);
vert_helper(k, vx, vy);
}
}
fn to_float_slice(slice: &mut [u8]) -> &mut [f32] {
let bytes = slice.len();
unsafe {
std::slice::from_raw_parts_mut(
slice.as_mut_ptr() as *mut f32,
bytes / std::mem::size_of::<f32>(),
)
}
}
fn whitespace(&self) -> f32 {
self.whitespace_len
.unwrap_or_else(|| DEFAULT_WHITE_SPACE_LEN)
}
#[allow(dead_code)]
fn prev_page(&self) -> usize {
let len = self.page_history.len();
self.page_history[(self.page_index + len - 1) % len]
}
fn cur_page(&self) -> usize {
self.page_history[self.page_index]
}
fn new_page(&mut self, page: usize) {
let new_index = (self.page_index + 1) % self.page_history.len();
self.page_history[new_index] = page;
self.page_index = new_index;
}
}
pub trait HasTextWriterBuilder {
type WriterType;
fn with_atlas(self, atlus: HieroAtlas) -> Self;
fn build(self) -> Self::WriterType;
}
impl HasTextWriterBuilder for OglIncomplete<TextWriter> {
type WriterType = TextWriter;
fn with_atlas(mut self, atlus: HieroAtlas) -> Self {
let gl = &self.inner.gl;
self.inner.page_texture = atlus.try_unpack_page(0).ok().map(|page| {
let info = page.info();
TextureObj::<u8>::new(gl)
.with_width(info.width)
.with_height(info.height)
.with_format(glow::RGBA)
.build()
.into()
});
self.inner.atlas = Some(atlus);
self
}
fn build(self) -> Self::WriterType {
self.inner
}
}