Frontend/visualiser: enhancement show ship_count in visualiser (#23)

* make all fleets fly forward

* make things beautiful
This commit is contained in:
ajuvercr 2020-04-17 21:03:43 +02:00 committed by GitHub
parent 33abe9515f
commit 998cb3d535
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 518 additions and 193 deletions

View file

@ -14,6 +14,12 @@ mod types;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
@ -25,11 +31,17 @@ pub struct Circle {
r: f32,
x: f32,
y: f32,
a1: f32,
a2: f32,
a0: f32,
ad: f32,
distance: usize,
}
use std::f32::consts::PI;
fn spr(from: f32) -> f32 {
let pi2 = PI*2.;
((from % pi2) + pi2) % pi2
}
impl Circle {
pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
let x1 = p1.x;
@ -37,101 +49,55 @@ impl Circle {
let x2 = p2.x;
let y2 = p2.y;
// Distance between planets
let q = ((x2-x1).powi(2) + (y2-y1).powi(2)).sqrt();
// Center of between planets
let x3 = (x1+x2)/2.0;
let y3 = (y1+y2)/2.0;
let r = q * 1.1;
// Radius of circle
let r = q * 1.0;
let mut x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
let mut y = y3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
// Center of circle
let x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
let y = y3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
// console_log!("{},{} -> {},{} ({},{} r={})", x1, y1, x2, y2, x, y, r);
let a0 = spr((y - y1).atan2(x - x1));
let a2 = spr((y - y2).atan2(x - x2));
let mut a1 = (y - y1).atan2(x - x1);
let mut a2 = (y - y2).atan2(x - x2);
if a2 < a1 {
x = x3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
y = y3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
a1 = (y - y1).atan2(x - x1);
a2 = (y - y2).atan2(x - x2);
let mut ad = spr(a0 - a2);
if ad > PI {
ad = spr(a2 - a0);
}
// console_log!("a1 {} a2 {} ad {}", a0/PI * 180.0, a2/PI * 180.0, ad/PI*180.0);
let distance = q.ceil() as usize + 1;
Self {
r, x, y, a1, a2, distance
r, x, y, a0, ad, distance
}
}
pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
pub fn get_for_remaining(&self, remaining: usize) -> ((Mat3<f32>, f32), (Mat3<f32>, f32)) {
(
self.get_remaining(remaining),
self.get_remaining((remaining + 1).min(self.distance - 1)),
)
}
fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
let alpha = (self.a1 * remaining as f32 + (self.distance - remaining) as f32 * self.a2) / self.distance as f32;
fn get_remaining(&self, remaining: usize) -> (Mat3<f32>, f32) {
let alpha = self.a0 + (1.0 - (remaining as f32 / self.distance as f32)) * self.ad;
let cos = alpha.cos();
let sin = alpha.sin();
Mat3::new(
(Mat3::new(
0.3, 0.0, 0.0,
0.0, 0.4, 0.0,
0.0, 0.3, 0.0,
-self.x + cos * self.r, -self.y + sin * self.r, 0.3,
) * Mat3::rotate_z(alpha)
), alpha)
}
}
// struct Line {
// x1: f32,
// y1: f32,
// x2: f32,
// y2: f32,
// a: f32,
// d: usize,
// }
// impl Line {
// pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
// let dx = p1.x - p2.x;
// let dy = p1.y - p2.y;
// let a = dy.atan2(dx);
// // let a = (dy / dx).atan();
// let d = (dx * dx + dy * dy).sqrt().ceil() as usize + 1;
// Self {
// x1: p1.x,
// x2: p2.x,
// y1: p1.y,
// y2: p2.y,
// d, a,
// }
// }
// pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
// (
// self.get_remaining(remaining),
// self.get_remaining((remaining + 1).min(self.d - 1)),
// )
// }
// fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
// let x = (self.x1 * remaining as f32 + (self.d - remaining) as f32 * self.x2) / self.d as f32;
// // let x = self.x1 + (remaining as f32 / self.d as f32) * (self.x2 - self.x1);
// let y = (self.y1 * remaining as f32 + (self.d - remaining) as f32 * self.y2) / self.d as f32;
// // let y = self.y1 + (remaining as f32 / self.d as f32) * (self.y2 - self.y1);
// Mat3::new(
// 0.3, 0.0, 0.0,
// 0.0, 0.3, 0.0,
// x, y, 0.3,
// ) * Mat3::rotate_z(self.a)
// }
// }
fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, Vec<usize>) {
let mut verts: Vec<Vec2<f32>> = planets.iter().map(|p| Vec2::new(p.x, p.y)).collect();
let mut ids = Vec::new();
@ -173,9 +139,13 @@ pub struct Game {
view_box: Vec<f32>,
planets: Vec<Vec3<f32>>,
planet_ships: Vec<usize>,
ship_locations: Vec<[f32;9]>,
ship_label_locations: Vec<[f32;9]>,
ship_colours: Vec<Vec3<f32>>,
ship_counts: Vec<usize>,
current_planet_colours: Vec<Vec3<f32>>,
voronoi_vertices: Vec<Vec2<f32>>,
@ -188,6 +158,8 @@ impl Game {
pub fn new(file: &str) -> Self {
utils::set_panic_hook();
console_log!("Rust is busy being awesome!");
// First line is fucked but we just filter out things that cannot parse
let states: Vec<types::State> = file.split("\n").filter_map(|line|
serde_json::from_str(line).ok()
@ -210,13 +182,16 @@ impl Game {
Self {
planets: utils::get_planets(&states[0].planets, 2.0),
planet_ships: Vec::new(),
view_box,
planet_map,
turn: 0,
states,
ship_locations: Vec::new(),
ship_label_locations: Vec::new(),
ship_colours: Vec::new(),
ship_counts: Vec::new(),
current_planet_colours: Vec::new(),
voronoi_vertices,
@ -233,6 +208,10 @@ impl Game {
self.planets.as_ptr()
}
pub fn get_planet_ships(&self) -> *const usize {
self.planet_ships.as_ptr()
}
pub fn get_planet_colors(&self) -> *const Vec3<f32> {
self.current_planet_colours.as_ptr()
}
@ -248,13 +227,19 @@ impl Game {
pub fn update_turn(&mut self, turn: usize) -> usize {
self.turn = turn.min(self.states.len() -1);
self.update_planet_ships();
self.update_planet_colours();
self.update_voronoi_colors();
self.update_ship_locations();
self.update_ship_counts();
self.turn
}
fn update_planet_ships(&mut self) {
self.planet_ships = self.states[self.turn].planets.iter().map(|p| p.ship_count as usize).collect();
}
fn update_voronoi_colors(&mut self) {
for (i, p) in self.states[self.turn].planets.iter().enumerate() {
self.voronoi_colors[i] = utils::COLORS[p.owner.unwrap_or(0) as usize % utils::COLORS.len()].into()
@ -279,19 +264,32 @@ impl Game {
}
fn update_ship_locations(&mut self) {
let mut new_vec = Vec::new();
self.ship_locations = Vec::new();
self.ship_label_locations = Vec::new();
let t = Mat3::new(0.2, 0., 0.,
0., 0.2, 0.0,
0., -0.5, 0.2);
for ship in self.states[self.turn].expeditions.iter() {
let (o1, o2) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize);
new_vec.push(o1.to_array());
new_vec.push(o2.to_array());
let ((o1, a1), (o2, a2)) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize);
self.ship_locations.push((o1 * Mat3::rotate_z(a1)).to_array());
self.ship_locations.push((o2 * Mat3::rotate_z(a2)).to_array());
self.ship_label_locations.push((o1 + t).to_array());
self.ship_label_locations.push((o2 + t).to_array());
}
self.ship_locations = new_vec;
self.ship_colours = self.states[self.turn].expeditions.iter().map(|s| {
utils::COLORS[s.owner as usize % utils::COLORS.len()].into()
}).collect();
}
fn update_ship_counts(&mut self) {
self.ship_counts = self.states[self.turn].expeditions.iter().map(|s| {
s.ship_count as usize
}).collect();
}
pub fn get_max_ships(&self) -> usize {
self.states.iter().map(|s| s.expeditions.len()).max().unwrap()
}
@ -304,10 +302,18 @@ impl Game {
self.ship_locations.as_ptr()
}
pub fn get_ship_label_locations(&self) -> *const [f32;9] {
self.ship_label_locations.as_ptr()
}
pub fn get_ship_colours(&self) -> *const Vec3<f32> {
self.ship_colours.as_ptr()
}
pub fn get_ship_counts(&self) -> *const usize {
self.ship_counts.as_ptr()
}
pub fn get_voronoi_vert_count(&self) -> usize {
self.voronoi_vertices.len()
}
@ -331,6 +337,8 @@ impl Game {
#[wasm_bindgen]
extern {
extern "C" {
fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}

View file

@ -8,6 +8,12 @@ export function handle(loc, e) {
h(loc, e);
}
if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, {
print: function() {
console.log("Hello world");
}
});
import ("./index.js")
.then(e => {
h = e.handle;

View file

@ -102,3 +102,5 @@ function parse_ini(inifile: string) {
);
}
}
on_load();

View file

@ -5,7 +5,9 @@ import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Unifor
import { Renderer } from "./webgl/renderer";
import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
import { Texture } from "./webgl/texture";
import { callbackify } from "util";
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
function f32v(ptr: number, size: number): Float32Array {
return new Float32Array(memory.buffer, ptr, size);
@ -67,11 +69,23 @@ ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/vor.glsl", LOCATION + "static/shaders/vert/vor.glsl"
).then((e) => VOR_SHADER_FACTORY = e);
var IMAGE_SHADER_FACTORY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/image.glsl", LOCATION + "static/shaders/vert/simple.glsl"
).then((e) => IMAGE_SHADER_FACTORY = e);
class GameInstance {
resizer: Resizer;
game: Game;
shader: Shader;
vor_shader: Shader;
image_shader: Shader;
text_factory: LabelFactory;
planet_labels: Label[];
ship_labels: Label[];
renderer: Renderer;
planet_count: number;
@ -89,12 +103,21 @@ class GameInstance {
constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) {
this.game = game;
this.planet_count = this.game.get_planet_count();
this.shader = SHADERFACOTRY.create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count });
this.image_shader = IMAGE_SHADER_FACTORY.create_shader(GL);
this.vor_shader = VOR_SHADER_FACTORY.create_shader(GL, { "PLANETS": '' + this.planet_count });
this.text_factory = defaultLabelFactory(GL, this.image_shader);
this.planet_labels = [];
this.ship_labels = [];
this.resizer = new Resizer(CANVAS, [...f32v(game.get_viewbox(), 4)], true);
this.renderer = new Renderer();
this.game.update_turn(0);
const indexBuffer = new IndexBuffer(GL, [
0, 1, 2,
1, 2, 3,
@ -121,7 +144,7 @@ class GameInstance {
const planets = f32v(game.get_planets(), this.planet_count * 3);
for (let i = 0; i < this.planet_count; i++) {
{
const transform = new UniformMatrix3fv([
1, 0, 0,
0, 1, 0,
@ -147,6 +170,19 @@ class GameInstance {
);
}
{
const transform = new UniformMatrix3fv([
1., 0, 0,
0, 1., 0,
-planets[i * 3], -planets[i * 3 + 1] -1.2, 1.,
]);
const label = this.text_factory.build(GL, transform);
this.renderer.addRenderable(label);
this.planet_labels.push(label);
}
}
this.turn_count = game.turn_count();
this.ship_indices = [];
@ -166,6 +202,10 @@ class GameInstance {
{}
)
);
const label = this.text_factory.build(GL);
this.ship_labels.push(label);
this.renderer.addRenderable(label)
}
this.vor_shader.uniform(GL, "u_planets", new Uniform3fv(planets));
@ -180,24 +220,33 @@ class GameInstance {
_update_state() {
const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6);
const planet_ships = i32v(this.game.get_planet_ships(), this.planet_count);
this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours));
for (let i = 0; i < this.planet_count; i++) {
const u = new Uniform3f(colours[i * 6], colours[i * 6 + 1], colours[i * 6 + 2]);
this.renderer.updateUniform(i+1, (us) => us["u_color"] = u);
this.renderer.updateUniform(2 * i + 1, (us) => us["u_color"] = u);
const u2 = new Uniform3f(colours[i * 6 + 3], colours[i * 6 + 4], colours[i * 6 + 5]);
this.renderer.updateUniform(i+1, (us) => us["u_color_next"] = u2);
this.renderer.updateUniform(2 * i + 1, (us) => us["u_color_next"] = u2);
this.planet_labels[i].setText(GL, "*"+planet_ships[i], Align.Middle, Align.Begin);
}
const ships = f32v(this.game.get_ship_locations(), this.game.get_ship_count() * 9 * 2);
const ship_colours = f32v(this.game.get_ship_colours(), this.game.get_ship_count() * 3);
const ship_count = this.game.get_ship_count();
const ships = f32v(this.game.get_ship_locations(), ship_count * 9 * 2);
const labels = f32v(this.game.get_ship_label_locations(), ship_count * 9 * 2);
const ship_counts = i32v(this.game.get_ship_counts(), ship_count);
const ship_colours = f32v(this.game.get_ship_colours(), ship_count * 3);
for (let i = 0; i < this.game.get_max_ships(); i++) {
const index = this.ship_indices[i];
if (i < this.game.get_ship_count()) {
if (i < ship_count) {
this.ship_labels[i].setText(GL, ""+ship_counts[i], Align.Middle, Align.Middle);
this.renderer.enableRendershit(index);
this.renderer.enableRendershit(index+1);
const u = new Uniform3f(ship_colours[i * 3], ship_colours[i * 3 + 1], ship_colours[i * 3 + 2]);
// const t1 = new UniformMatrix3fv(new Float32Array(ships, i * 18, 9));
@ -206,14 +255,24 @@ class GameInstance {
const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9));
const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18));
const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9));
const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18));
this.renderer.updateUniform(index, (us) => {
us["u_color"] = u;
us["u_color_next"] = u;
us["u_trans"] = t1;
us["u_trans_next"] = t2;
});
this.renderer.updateUniform(index+1, (us) => {
us["u_trans"] = tl1;
us["u_trans_next"] = tl2;
});
} else {
this.renderer.disableRenderShift(index);
this.renderer.disableRenderShift(index+1);
}
}
}
@ -236,6 +295,7 @@ class GameInstance {
this.shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.vor_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.image_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.renderer.render(GL);
return;
@ -258,7 +318,11 @@ class GameInstance {
this.shader.uniform(GL, "u_mouse", new Uniform2f(this.resizer.get_mouse_pos()));
this.shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
this.shader.uniform(GL, "u_vor", new UniformBool(true));
this.image_shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / ms_per_frame));
this.image_shader.uniform(GL, "u_mouse", new Uniform2f(this.resizer.get_mouse_pos()));
this.image_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.image_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
this.renderer.render(GL);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -0,0 +1,14 @@
#ifdef GL_ES
precision mediump float;
#endif
// Passed in from the vertex shader.
varying vec2 v_texCoord;
// The texture.
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
// gl_FragColor = vec4(0.7, 0.7, 0.0, 1.0);
}

View file

@ -0,0 +1,33 @@
#ifdef GL_ES
precision mediump float;
#endif
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform float u_time;
uniform vec4 u_viewbox; // [x, y, width, height]
uniform vec2 u_resolution;
uniform mat3 u_trans;
varying vec2 v_pos;
varying vec2 v_texCoord;
void main() {
vec3 pos = vec3(a_position, 1.0);
pos = u_trans * pos;
vec2 uv = pos.xy;
// Viewbox's center is top left, a_position's is in the center to the screen
// So translate and scale the viewbox**
uv -= u_viewbox.xy + (u_viewbox.zw * 0.5);
uv /= u_viewbox.zw * 0.5;
v_pos = (uv.xy + 1.0) * 0.5;
gl_Position = vec4(uv.xy, 0.0, 1.0);
v_texCoord = a_texCoord;
}

View file

@ -3,6 +3,7 @@ precision mediump float;
#endif
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform float u_time;
@ -12,6 +13,7 @@ uniform mat3 u_trans;
uniform mat3 u_trans_next;
varying vec2 v_pos;
varying vec2 v_texCoord;
void main() {
vec3 pos = vec3(a_position, 1.0);
@ -30,4 +32,5 @@ void main() {
v_pos = (uv.xy + 1.0) * 0.5;
gl_Position = vec4(uv.xy, 0.0, 1.0);
v_texCoord = a_texCoord;
}

View file

@ -17,6 +17,8 @@ function createAndSetupTexture(gl: WebGLRenderingContext): WebGLTexture {
}
export class Foo implements Renderable {
uniforms: Dictionary<Uniform>;
stages: Stage[];
textures: WebGLTexture[];
@ -25,7 +27,9 @@ export class Foo implements Renderable {
width: number;
height: number;
constructor(gl: WebGLRenderingContext, width: number, height: number) {
this.uniforms = {};
this.width = width;
this.height = height;
@ -49,6 +53,10 @@ export class Foo implements Renderable {
}
}
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) {
this.stages.forEach( (item, i) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i%2]);
@ -62,6 +70,10 @@ class Stage implements Renderable {
program: Shader;
uniforms: Dictionary<Uniform>;
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) {
this.program.bind(gl);

View file

@ -6,18 +6,17 @@ import { Texture } from './texture';
import { Dictionary } from './util';
export interface Renderable {
getUniforms() : Dictionary<Uniform>;
render(gl: WebGLRenderingContext): void;
}
class RenderShit implements Renderable {
export class RenderShit implements Renderable {
ibo: IndexBuffer;
va: VertexArray;
shader: Shader;
textures: Texture[];
uniforms: Dictionary<Uniform>;
enabled: boolean;
constructor(
ibo: IndexBuffer,
va: VertexArray,
@ -25,7 +24,6 @@ class RenderShit implements Renderable {
textures: Texture[],
uniforms: Dictionary<Uniform>,
) {
this.enabled = true;
this.ibo = ibo;
this.va = va;
this.shader = shader;
@ -33,11 +31,12 @@ class RenderShit implements Renderable {
this.uniforms = uniforms;
}
render(gl: WebGLRenderingContext): void {
if (!this.enabled) {
return;
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext): void {
const indexBuffer = this.ibo;
const vertexArray = this.va;
const uniforms = this.uniforms;
@ -73,35 +72,34 @@ class RenderShit implements Renderable {
}
}
}
export class Renderer {
renderables: RenderShit[];
renderables: [Renderable, boolean][];
constructor() {
this.renderables = [];
}
updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void) {
f(this.renderables[i].uniforms);
f(this.renderables[i][0].getUniforms());
}
disableRenderShift(i: number) {
this.renderables[i].enabled = false;
this.renderables[i][1] = false;
}
enableRendershit(i: number) {
this.renderables[i].enabled = true;
this.renderables[i][1] = true;
}
// addRenderable(item: Renderable) {
// this.renderables.push(item);
// }
addRenderable(item: Renderable): number {
this.renderables.push([item, true]);
return this.renderables.length - 1;
}
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary<Uniform>, texture?: Texture[]): number {
this.renderables.push(
return this.addRenderable(
new RenderShit(
indexBuffer,
vertexArray,
@ -110,8 +108,6 @@ export class Renderer {
uniforms || {},
)
);
return this.renderables.length - 1;
}
render(gl: WebGLRenderingContext, frameBuffer?: WebGLFramebuffer, width?: number, height?: number) {
@ -121,7 +117,8 @@ export class Renderer {
const maxTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
for (let r of this.renderables) {
for (let [r, e] of this.renderables) {
if (!e) continue;
r.render(gl);
}

186
frontend/www/webgl/text.ts Normal file
View file

@ -0,0 +1,186 @@
import { Texture } from "./texture";
import { Dictionary } from "./util";
import { Renderable, RenderShit } from "./renderer";
import { Uniform, Shader, UniformMatrix3fv } from "./shader";
import { IndexBuffer, VertexBuffer } from "./buffer";
import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout";
export enum Align {
Begin,
End,
Middle,
}
export class GlypInfo {
x: number;
y: number;
width: number;
}
export class FontInfo {
letterHeight: number;
spaceWidth: number;
spacing: number;
textureWidth: number;
textureHeight: number;
glyphInfos: Dictionary<GlypInfo>;
}
export class LabelFactory {
texture: Texture;
font: FontInfo;
shader: Shader;
constructor(gl: WebGLRenderingContext, loc: string, font: FontInfo, shader: Shader) {
this.texture = Texture.fromImage(gl, loc, 'font');
this.font = font;
this.shader = shader;
}
build(gl: WebGLRenderingContext, transform?: UniformMatrix3fv): Label {
return new Label(gl, this.shader, this.texture, this.font, transform);
}
}
export class Label implements Renderable {
inner: Renderable;
ib: IndexBuffer;
vb: VertexBuffer;
font: FontInfo;
constructor(gl: WebGLRenderingContext, shader: Shader, tex: Texture, font: FontInfo, transform?: UniformMatrix3fv) {
this.font = font;
const uniforms = transform ? { "u_trans": transform, "u_trans_next": transform, } : {};
this.ib = new IndexBuffer(gl, []);
this.vb = new VertexBuffer(gl, []);
const layout = new VertexBufferLayout();
layout.push(gl.FLOAT, 2, 4, "a_position");
layout.push(gl.FLOAT, 2, 4, "a_texCoord");
const vao = new VertexArray();
vao.addBuffer(this.vb, layout);
this.inner = new RenderShit(this.ib, vao, shader, [tex], uniforms);
}
getUniforms(): Dictionary<Uniform> {
return this.inner.getUniforms();
}
render(gl: WebGLRenderingContext): void {
return this.inner.render(gl);
}
setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) {
const idxs = [];
const verts = [];
const letterHeight = this.font.letterHeight / this.font.textureHeight;
let xPos = 0;
switch (h_align) {
case Align.Begin:
break;
case Align.End:
xPos = -1 * [...text].map(n => this.font.glyphInfos[n] ? this.font.glyphInfos[n].width : this.font.spaceWidth).reduce((a, b) => a + b, 0) / this.font.letterHeight;
break;
case Align.Middle:
xPos = -1 * [...text].map(n => this.font.glyphInfos[n] ? this.font.glyphInfos[n].width : this.font.spaceWidth).reduce((a, b) => a + b, 0) / this.font.letterHeight / 2;
break;
}
let yStart = 0;
switch (v_align) {
case Align.Begin:
break;
case Align.End:
yStart = 1;
break;
case Align.Middle:
yStart = 0.5;
break;
}
let j = 0;
for (let i = 0; i < text.length; i++) {
const info = this.font.glyphInfos[text[i]];
if (info) {
const dx = info.width / this.font.letterHeight;
const letterWidth = info.width / this.font.textureWidth;
const x0 = info.x / this.font.textureWidth;
const y0 = info.y / this.font.textureHeight;
verts.push(xPos, yStart, x0, y0);
verts.push(xPos + dx, yStart, x0 + letterWidth, y0);
verts.push(xPos, yStart-1, x0, y0 + letterHeight);
verts.push(xPos + dx, yStart-1, x0 + letterWidth, y0 + letterHeight);
xPos += dx;
idxs.push(j+0, j+1, j+2, j+1, j+2, j+3);
j += 4;
} else {
// Just move xPos
xPos += this.font.spaceWidth / this.font.letterHeight;
}
}
this.ib.updateData(gl, idxs);
this.vb.updateData(gl, verts);
}
}
export function defaultLabelFactory(gl: WebGLRenderingContext, shader: Shader): LabelFactory {
const fontInfo = {
letterHeight: 8,
spaceWidth: 8,
spacing: -1,
textureWidth: 64,
textureHeight: 40,
glyphInfos: {
'a': { x: 0, y: 0, width: 8, },
'b': { x: 8, y: 0, width: 8, },
'c': { x: 16, y: 0, width: 8, },
'd': { x: 24, y: 0, width: 8, },
'e': { x: 32, y: 0, width: 8, },
'f': { x: 40, y: 0, width: 8, },
'g': { x: 48, y: 0, width: 8, },
'h': { x: 56, y: 0, width: 8, },
'i': { x: 0, y: 8, width: 8, },
'j': { x: 8, y: 8, width: 8, },
'k': { x: 16, y: 8, width: 8, },
'l': { x: 24, y: 8, width: 8, },
'm': { x: 32, y: 8, width: 8, },
'n': { x: 40, y: 8, width: 8, },
'o': { x: 48, y: 8, width: 8, },
'p': { x: 56, y: 8, width: 8, },
'q': { x: 0, y: 16, width: 8, },
'r': { x: 8, y: 16, width: 8, },
's': { x: 16, y: 16, width: 8, },
't': { x: 24, y: 16, width: 8, },
'u': { x: 32, y: 16, width: 8, },
'v': { x: 40, y: 16, width: 8, },
'w': { x: 48, y: 16, width: 8, },
'x': { x: 56, y: 16, width: 8, },
'y': { x: 0, y: 24, width: 8, },
'z': { x: 8, y: 24, width: 8, },
'0': { x: 16, y: 24, width: 8, },
'1': { x: 24, y: 24, width: 8, },
'2': { x: 32, y: 24, width: 8, },
'3': { x: 40, y: 24, width: 8, },
'4': { x: 48, y: 24, width: 8, },
'5': { x: 56, y: 24, width: 8, },
'6': { x: 0, y: 32, width: 8, },
'7': { x: 8, y: 32, width: 8, },
'8': { x: 16, y: 32, width: 8, },
'9': { x: 24, y: 32, width: 8, },
'-': { x: 32, y: 32, width: 8, },
'*': { x: 40, y: 32, width: 8, },
'!': { x: 48, y: 32, width: 8, },
'?': { x: 56, y: 32, width: 8, },
},
};
return new LabelFactory(gl, 'static/res/assets/font.png', fontInfo, shader);
}