From 998cb3d5354a892284c52aac48741ab7efaf175d Mon Sep 17 00:00:00 2001 From: ajuvercr Date: Fri, 17 Apr 2020 21:03:43 +0200 Subject: [PATCH] Frontend/visualiser: enhancement show ship_count in visualiser (#23) * make all fleets fly forward * make things beautiful --- frontend/src/lib.rs | 158 ++++++++-------- frontend/www/bootstrap.js | 14 +- frontend/www/games.ts | 2 + frontend/www/index.ts | 156 +++++++++++----- frontend/www/package.json | 90 ++++----- frontend/www/static/res/assets/font.png | Bin 0 -> 912 bytes frontend/www/static/res/assets/leaves.jpg | Bin 0 -> 36902 bytes frontend/www/static/shaders/frag/image.glsl | 14 ++ frontend/www/static/shaders/vert/image.glsl | 33 ++++ frontend/www/static/shaders/vert/simple.glsl | 3 + frontend/www/webgl/instance.ts | 18 +- frontend/www/webgl/renderer.ts | 37 ++-- frontend/www/webgl/text.ts | 186 +++++++++++++++++++ 13 files changed, 518 insertions(+), 193 deletions(-) create mode 100644 frontend/www/static/res/assets/font.png create mode 100644 frontend/www/static/res/assets/leaves.jpg create mode 100644 frontend/www/static/shaders/frag/image.glsl create mode 100644 frontend/www/static/shaders/vert/image.glsl create mode 100644 frontend/www/webgl/text.ts diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 86240ac..331931b 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -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, Mat3) { + pub fn get_for_remaining(&self, remaining: usize) -> ((Mat3, f32), (Mat3, f32)) { ( self.get_remaining(remaining), self.get_remaining((remaining + 1).min(self.distance - 1)), ) } - fn get_remaining(&self, remaining: usize) -> Mat3 { - 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) { + 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, Mat3) { -// ( -// self.get_remaining(remaining), -// self.get_remaining((remaining + 1).min(self.d - 1)), -// ) -// } - -// fn get_remaining(&self, remaining: usize) -> Mat3 { -// 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, bbox: f32) -> (Vec>, Vec) { let mut verts: Vec> = 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, planets: Vec>, + planet_ships: Vec, ship_locations: Vec<[f32;9]>, + ship_label_locations: Vec<[f32;9]>, ship_colours: Vec>, + ship_counts: Vec, + current_planet_colours: Vec>, voronoi_vertices: Vec>, @@ -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 = 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 { 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 { 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); } diff --git a/frontend/www/bootstrap.js b/frontend/www/bootstrap.js index 251d3e0..3a4b1f6 100644 --- a/frontend/www/bootstrap.js +++ b/frontend/www/bootstrap.js @@ -2,14 +2,20 @@ // asynchronously. This `bootstrap.js` file does the single async import, so // that no one else needs to worry about it again. // Import index.js that executes index.ts -var h = (_a, _b) => { } +var h = (_a, _b) => {} export function handle(loc, e) { h(loc, e); } -import("./index.js") - .then(e => { +if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, { + print: function() { + console.log("Hello world"); + } +}); + +import ("./index.js") +.then(e => { h = e.handle; }) - .catch(e => console.error("Error importing `index.js`:", e)); + .catch(e => console.error("Error importing `index.js`:", e)); \ No newline at end of file diff --git a/frontend/www/games.ts b/frontend/www/games.ts index ad5ae9b..5b5c065 100644 --- a/frontend/www/games.ts +++ b/frontend/www/games.ts @@ -102,3 +102,5 @@ function parse_ini(inifile: string) { ); } } + +on_load(); diff --git a/frontend/www/index.ts b/frontend/www/index.ts index 6d71601..f02ccbd 100644 --- a/frontend/www/index.ts +++ b/frontend/www/index.ts @@ -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); @@ -27,8 +29,8 @@ const COUNTER = new FPSCounter(); const LOADER = document.getElementById("main"); const SLIDER = document.getElementById("turnSlider"); -const FILESELECTOR = document.getElementById("fileselect"); -const SPEED = document.getElementById("speed"); +const FILESELECTOR = document.getElementById("fileselect"); +const SPEED = document.getElementById("speed"); export function set_loading(loading: boolean) { if (loading) { @@ -40,7 +42,7 @@ export function set_loading(loading: boolean) { } } -const URL = window.location.origin+window.location.pathname; +const URL = window.location.origin + window.location.pathname; export const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1); const CANVAS = document.getElementById("c"); const RESOLUTION = [CANVAS.width, CANVAS.height]; @@ -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; @@ -86,15 +100,24 @@ class GameInstance { turn_count = 0; - constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) { + 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.vor_shader = VOR_SHADER_FACTORY.create_shader(GL, {"PLANETS": ''+this.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, @@ -120,31 +143,44 @@ class GameInstance { const planets = f32v(game.get_planets(), this.planet_count * 3); - for(let i=0; i < this.planet_count; i++){ + for (let i = 0; i < this.planet_count; i++) { + { + const transform = new UniformMatrix3fv([ + 1, 0, 0, + 0, 1, 0, + -planets[i * 3], -planets[i * 3 + 1], 1, + ]); - const transform = new UniformMatrix3fv([ - 1, 0, 0, - 0, 1, 0, - -planets[i*3], -planets[i*3+1], 1, - ]); + const indexBuffer = new IndexBuffer(GL, meshes[i % meshes.length].cells); + const positionBuffer = new VertexBuffer(GL, meshes[i % meshes.length].positions); - const indexBuffer = new IndexBuffer(GL, meshes[i % meshes.length].cells); - const positionBuffer = new VertexBuffer(GL, meshes[i % meshes.length].positions); + const layout = new VertexBufferLayout(); + layout.push(GL.FLOAT, 3, 4, "a_position"); + const vao = new VertexArray(); + vao.addBuffer(positionBuffer, layout); - const layout = new VertexBufferLayout(); - layout.push(GL.FLOAT, 3, 4, "a_position"); - const vao = new VertexArray(); - vao.addBuffer(positionBuffer, layout); + this.renderer.addToDraw( + indexBuffer, + vao, + this.shader, + { + "u_trans": transform, + "u_trans_next": transform, + } + ); + } - this.renderer.addToDraw( - indexBuffer, - vao, - this.shader, - { - "u_trans": transform, - "u_trans_next": transform, - } - ); + { + 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(); @@ -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,40 +220,59 @@ 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); - 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); + 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(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(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++) { + 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 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)); // const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9)); 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); } @@ -332,33 +396,33 @@ export async function set_instance(source: string) { set_loading(false); } -window.addEventListener('resize', function() { +window.addEventListener('resize', function () { resizeCanvasToDisplaySize(CANVAS); if (game_instance) { game_instance.on_resize(); } -}, { capture: false, passive: true}) +}, { capture: false, passive: true }) -SLIDER.oninput = function() { +SLIDER.oninput = function () { if (game_instance) { game_instance.updateTurn(parseInt(SLIDER.value)); } } -FILESELECTOR.onchange = function(){ +FILESELECTOR.onchange = function () { const file = FILESELECTOR.files[0]; - if(!file) { return; } + if (!file) { return; } var reader = new FileReader(); - reader.onload = function() { - set_instance( reader.result); + reader.onload = function () { + set_instance(reader.result); } reader.readAsText(file); } -SPEED.onchange = function() { +SPEED.onchange = function () { ms_per_frame = parseInt(SPEED.value); } diff --git a/frontend/www/package.json b/frontend/www/package.json index 0754458..0819adf 100644 --- a/frontend/www/package.json +++ b/frontend/www/package.json @@ -1,46 +1,46 @@ { - "name": "create-wasm-app", - "version": "0.1.0", - "description": "create an app to consume rust-generated wasm packages", - "main": "./bootstrap.js", - "types": "dist/index.d.ts", - "scripts": { - "develop": "webpack --mode development --watch", - "build": "webpack --config webpack.config.js", - "start": "webpack-dev-server --content-base dist/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/rustwasm/create-wasm-app.git" - }, - "keywords": [ - "webassembly", - "wasm", - "rust", - "webpack", - "mozaic" - ], - "dependencies": { - "config-ini-parser": "^1.2.2", - "extract-svg-path": "^2.1.0", - "load-svg": "^1.0.0", - "planetwars": "file:../pkg", - "svg-mesh-3d": "^1.1.0", - "ts-heap": "^1.1.3" - }, - "author": "Arthur Vercruysse ", - "license": "(MIT OR Apache-2.0)", - "bugs": { - "url": "https://github.com/ajuvercr/Planetwars/issues" - }, - "homepage": "https://github.com/ajuvercr/Planetwars#Readme", - "devDependencies": { - "webpack": "^4.29.3", - "ts-loader": "^6.0.2", - "static-eval": ">=2.0.0", - "typescript": "^3.5.2", - "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.5", - "copy-webpack-plugin": "^5.0.0" - } -} + "name": "create-wasm-app", + "version": "0.1.0", + "description": "create an app to consume rust-generated wasm packages", + "main": "./bootstrap.js", + "types": "dist/index.d.ts", + "scripts": { + "develop": "webpack --mode development --watch", + "build": "webpack --config webpack.config.js", + "start": "webpack-dev-server --content-base dist/" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/rustwasm/create-wasm-app.git" + }, + "keywords": [ + "webassembly", + "wasm", + "rust", + "webpack", + "mozaic" + ], + "dependencies": { + "config-ini-parser": "^1.2.2", + "extract-svg-path": "^2.1.0", + "load-svg": "^1.0.0", + "planetwars": "file:../pkg", + "svg-mesh-3d": "^1.1.0", + "ts-heap": "^1.1.3" + }, + "author": "Arthur Vercruysse ", + "license": "(MIT OR Apache-2.0)", + "bugs": { + "url": "https://github.com/ajuvercr/Planetwars/issues" + }, + "homepage": "https://github.com/ajuvercr/Planetwars#Readme", + "devDependencies": { + "webpack": "^4.29.3", + "ts-loader": "^6.0.2", + "static-eval": ">=2.0.0", + "typescript": "^3.5.2", + "webpack-cli": "^3.1.0", + "webpack-dev-server": "^3.1.5", + "copy-webpack-plugin": "^5.0.0" + } +} \ No newline at end of file diff --git a/frontend/www/static/res/assets/font.png b/frontend/www/static/res/assets/font.png new file mode 100644 index 0000000000000000000000000000000000000000..1724e0dd27e96c4c0977e4bf4b4939553d6e79dd GIT binary patch literal 912 zcmV;B18@9^P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00Q_)L_t(&-tAiLj^iK<%%Hsg zBcJa_kHi>%LfT#3Zlp@x7#m}ZXG|Bz{Sf;O1%Rh{%Z~*x^(#W<2a79D=PyACmlISqiU#=_9J~Q^Njg@gDe1Q zl$BYeBS@rW`ibmk=Feua8UVgSD?6B|CDk1+Wk?^B@m~$V#ts31TLUUPoLDU8wT<&v z9xPVjuS5YWI!h>U{PZV^QbtY-_TkhFTJV3F$25Kh`^TyQmYl!y^rQOHot0w~_DT3g zf$ua0pcfva06~#IdVacd3jw@%fItC~D4@yDWU-;_0#*amowK0KgSZ_OYt8~mqnX|U zyd6+%Q-STJxj^KfTLX+#!*2BDS$r6lF8>52PBGOi3RJ7?DvEpp1%9#rsU1d=3GVWU z9eKF^wuqy0484;_ROD42v2!0#>c=_EeW#~YE4a}OMj3)nG$LLrtoT0%z=J3U=gE*i zSEwE!L6(2#Za-b@<--LUM~Rdp1dByheAvEK-9U-z%`+7dsM4+eY##8Ad-=?*O(jFJ zjE<@q+_E?u;JnKt5=|9ld4>}Z(+AK0Gh3vb01ZE?7(9Y9{d|UJ7)6e}R@y19FBmj% zd`dO1!fv#(2o`6_5fHIdC)&Cp=SGO4#V2?~SZx*iurkb}>sEj*9s`Hcn(6C}G5T@q zcLQwLR|}o82JbfuJi!A#h0|3OsPdHG`v4=taCV58z8ps)!dy;;=u17zPGn#AuQ@o& z4)CAhqZyxNd_pNlsqv6{vk&sUw13Zk`vDq?gx$}Bq%*khQP7R|cXhcJkbB3wo~6%& z@{-Ro?nD23`u_kHh<`+8G&E060NON(C69OcQ0##0#b-Otre<$*`u!<|^vQ48ILUY> zyWDe-f3)-#iy$pv-W=L9-3QeBc`F0v0O}bM?LzMwoSXcnazX5n$2D|~_U}OJp{xjge ziA#Ws3k2d4;p5{GkPwlO5EBs-laf(Ukdjf75ff89p`fIup{1oIA%9BugociahL+}k zAXqs6e1N!wxVVHgq{O5&|KIl54WJ|dGy%?VuqXl8lvp^FSdaYxW&q${Wd95I{~8`X z5EmPV0E-atZ&{84fQ^NNgN=&~#KFS_V&ed?u>WO1iAVJuAcRlNCa+CnMIh{fqGb<% zqM&14)F9F`wR!dw#Bna_`9^p89|98=00;2@YyiZ?!^XkKBKRM^JSE_N@IV}VAT~A@ zHV_}{A3h}j_?$`zS64OpeqrLoUfMp>Bwtt!1?kbbok}UbW<8@zys~&dn4@&? z+L=qYO4r88 zH|j>xCM6-vVvV`8qIG1x`JPYT+H~gH6y&EYzgq20pn*sgk`6Bp)1AU^TI&{HF)Jvf zJOUhrutg}yf|$Gy6t6|+0vm?~iepv<^f>BZ$-9pLa>G?hc3;$zJ9fK1QK75-C_wr? zuJM#L9h*k694EFi+C5EeM2;>+<({}F z_~!`U6-HngJ~#dGzwt+aGASvOPTiT_v&8MRl)1be7aZ~0%@sQK*rH{3;&1FX@sQkp z&-gIKaR+~jV7DdE`w5u^N^PY^xMfqDae|uXiBxwF1?#ckuc?>eVpPjM%(%?=672_v zwQQ;EVoC~pZ2d(sZ)7v8Q;@Vq$(9nMQA;d%tparfT5ul91($YL_QDk7ztvyemfo2J z9Wg_U#a`7H(Shk12T}@r-pv}5I$!B8&rS1M9OIrCz2oC5)#S|1rqQ$DrIRCKGsi;3 z+}MFOnCRVHvK`ZZgb^dCpw#sM09e@%%q6=12+-8u%IhHJjfv1gdirn=r?hD&f-G_7 z<4*3SX0){6>INKD#ZAD31^uZ=DSx$S{)gH=#oJid{O@97{4)@4V6z9k z4drk+I*T5Jg$P3Y)pS?gzg4nigbd~^m5Nmzo*Nnn_aJoWz6!NV^s1= z=&a9%3E2y^!$n_O&J=^GA66B`D^y0N$6Cr1E+_pDDdUotiA&;#G`}?0r&D>q?dOd0GY9O$^ESA)`? z8FoM1{5+fZ-`c8u!RqdJkX;3JhM-Ly7NR+**okFmfgy0zr{$P>eB_gXZdTRv503go z!n6>4KUQSGHnW8N)hmqdTPtF1Mij_Kh!fv9`-$JvIHi%36+(OfH;5ZIz@D1Zn#?ki zR${oY(nn2q`SQC_`(ZTNHhadq8)f}3D*?Jn#;5yzj0nIoU+e# zR$pG62@Ts{PIk@T11_aiaN&HU4uaFniU%`p@k3! z;{JQ1$b7Sy{;sm8E?76}tk`K{@^oNG)_{I&Hhdw;i#4{yOGI}4%egPLPTyt0t$o6M zu+1M9fqOcgfKtE%U+Ao7sZ+#RM#f#MYP@1BKIA3dlZ z0c6#R$SIhRM{43QzbBGC>FHZ7)BS>0D1$0*o;^Vvz%?+X9;ZMiZLUpeTVE|oIYLLH zSrL~YNzQg00`QDXF=IB_;*31&?3GNSRlojLRdrNZRFR>XFkh8kgnSai7U{bc7oY#Q(63u9~XaUbOc@CTw%?4M*kd|Y0 zgMLd-=zKn@sw%b(=S?c7}Y^wn5Alj{}L%OVyH9Lt@3rqHhK$`3hb@vOZnLgVcQl-Ch^yYI_e^rJNN z8rXkHlH}XRG4=)eA5`Rfr#q#^wI!;JgoUG5LT)G47cYzQUsRaG2(du>^wQOO#miRj zw`A;GOS(cnbIYt^DmUA+Y~$ICR_G2khY+;CXg#4zGCL_Jf%O3}!{<$VA6@Duk9bqY z@%sX;4!(m)udmFcn!lZhp1}^>k`z&Dy)OF)Md)cEC;&nL~8osg48 zA!rZi^RejmDb?#->CV_9)M!)M|~B z!eDBgL^q2a{ohI;@Cx*szApJpdDOeK0Y2Z9%f`ZWm1)g6lo`AE&04u} zHMyia4rL}@oi$A^N0sqPs0G0DkzQTqMJM?hW5uGSHiN=NI1Y|m{QztA^2V!K_tTNR6xQr-zJ8Yz(w3Uk-tV=(e!`Y7 z>H6(khrcF1F1hgv6v_$Z^ik7Dp%lV#9uh4*f89?X?AUp5_;DlIr8HtyQ{MfF7JbF< zo%h0e%Dr{|8)!oFeP%Dlobp_ckMrEO(BB*JGZShHHk&J;vliDEM#;3jaAcPQ3hvyT715ZTn^Kt5F88gmcO|69nJ$eGCtmSU>1ivV>{xK!ew#9l3HjW6q(ZhFS$Qs+w1Oz4QEmAhq=%Ve1!Htv{=e_cMm zH8Ot?27BfK!F{7h!R34kB8{jzK6y$LOm*{JLuzR*MbbeG?6&!rsjZ zUvbyGI*@Z^;Z>2Xef9OsCG{ezmxg4I0AW+mmq(C|i5AwLDbX95OdOcD^9-=J344{u ze*clW-)OUCZ@AEmNGCbP zcBHgaZjrfylVn~jyEjK?l++oes@ybLTr)XCkZ&B7Y!APO=y%(LxqRKhLpJWG@lQ2J z^jLI|z)ChecS%fAhkeZEOaDW?yhhnxC^2l9gU2+5saLL7{)n2k16{nlucLVNOgROn zu6W^UmndT6G04S~50-xJ$n7(kL0d|l(&cyP)O&ZixWv;RPY4xc)s&Oa=*?vyKgYK_ zOYad&py$1kd~Q_fxLuJDLy#I~c$a+S`(licwEzO%ugmFcdtl;R{DkQMI(9>_LGj>~ zy#40j)nhI~w`6n9tsht^_(mKsoy6|J<0Muld9?gel-SLdLUli^^PRb92m&3GuI#@T z6gkRYpU;qYohHecxPeesQN7J*vZ#Y8m;fSYvzVscU-#eZzO?sOZ^iR@U+JOh0(}JZ z!{D+`DP5Ggf9%t1D;+P7mCkF9Am=NN`T0R13Y~J%alE=O3xtFY1kz;jD_s=PmH-u2 zIqFeK#Fh6K{!uLDAq$wgC6UqnWFr@kt6X{j-i3J*5T-?T}1zHOl3ZTTC`6b zkn5`@Clwh+1~?MMw7jw1&`I%s*(~k8IU5>!-qM#bop9w-t!VPDjz+?qk5*wu_f&PU zeMlYl+aiXQo{*g$xG6W_wNQoS#3SE-AxRXtY%1NNw3I5Qa;$ODw$D{`Dn?}{f~&Mt zpjO(Hno$JTulsdF;TT-onfAM#Zb{>p?KPcl{^w3ey?bF6JTDRP;~8w($)zpDPQ4mt zscdEO5JinMFC(f{3-v&%`ZUaj)Ms+}66+;*q-tL{$a9;Sp4C=^yxz5Y@gythZ=WC%BVSo}B;%^b5-}h0w)JZGC}fvT`D`pIXLQlUTRn>|ko|81w*)44kvfX%MEzOm?5qTEav>`KTRDY)O)WsOEDmC^9SoCCx zd3$AQ*6L)m`R%z^#~$Dqus=(wFnz>Z`y@_4l<$psq7&w*>Y3L7l%;K|^O&CgS5F)( zq9ce&NuPM(Ex<50xBqu!m|TBSkBFkh*|m7zho^kJ&?qDc3Bj1pdP5xM)PJt!AyuM| zh?;*vZcgaCsM70MP|l9OFJ;V4_x3wVgoL1MkaOwzQ*d5#dwk?a7V5RrrD~-uF&F(BrAD!eE5I>wTSW{93aSMl0;qjWd@L z5pbY%vGCfh?G7Hlo5>|rBJ$-&?F#Rl6qxNtJ)Y=}ZPd06C*VfO%4hy5@y?~lf~KCH zG}WCJL)L5Nu$@YvXP4K-ylPjwPT$9uI$_uv8n3QyW-O*mmRgJ)pYw1+G(<$$UQJx- zH#NOJr8^bXA2HDb-PkbJc)nOvt=e7v;7+&?`a8&Uxmqyqu|PGg#Yw^hvH_)x?aMUF zdjF%$*P(OE8M8WB)VzFb?nt~1Pg;k~)#78fxu?uv_xqiQu(rywh$_eu{soEzMazm! zMrgGvFST8I0uFk+AGI{rQW91|Er^L6e@$bY{V8! zf(>|m+toqD+Dy%#RQGVp_`-3h=(DgJRxDn88@>!He7)^A;Q`{9r<2H0!;GY>I3_Q# z-|&e@RXTifW{<^feCUp)-BIJHhy3KaVXS*v`V5*{#bLBZ${BMk?~GN$ zq;buWs*A2F!~F=%Ph`gd^pNi{do_9#^4B$gad&y<$Y}MQ$F9Ud?(fC-X1sgrUNdkB zYna5L({h@fP3AMrHxS;n7Ei*Yt=?S#k+3Q+28br-`YCIBdh0TQZx%_tg|rjmfUy&6 zinDCwdpsm_Uzg=`xD2@5Ru2<3Lp9PObc+CYIfh(NUSVaj&+T%hlcDsQ)o#sc9cy*P zKWIm?In?=-^!OdMsa~d6i)rGS7Jn>$-9yJEt#!7Hy)}zq-BgX1@ZN!73;SB|V>{%r zZ+B_;H)hh3Z7wYiJFTG`llRd&ZnBJ2drg`~%pzViEJ(9|krHu^!KXj3@#)*%Z5 zqJsMgm1cEIS|j1<*IiCTChGsr6Qt=^Q{%{fSRwLP`-!JG%O9UXc=SX}V)jX$slb5F z44Xldn&0}@`;vDKu^IHo2_LjwW3ma2b0!e_UgGxP469#qSV7@~ns6FM7b1uTif$im zdo!jf$A?GJX^?8AVnie?PZ)-WYR#a=_EgubIlHeKt+-N>7FL_Px-4ceT*0J`j+7fD z`Qd~!P=pTN``krh7&5n&aylQBwKNmN230swq+g2fs@W>$Et%V zG?d17J#0X@bk_?37}Pkf%WWZWafBhGapeKSS8L=#LTANwHPk-a665779=!5eAUdgS zN5<0ra6vB>86Kyv$L3|FwJrK8s+Y%UY9Dhn&b!GgZAe~=NQDW40{wBSKEBO=dSl!j z0kVydBTQlS0mY|CSLZ(PIPa5vQg4lCzoF%XaHvjaf|Af!Bl0*ka~z~JOJ5j*NM1K5 zYb?)$TUQP1zSuismI^YgCIsfAjEb+Po;XX0azRt#bjs!EdDqkND{Eee zV>bS%pFH;*`GCM>lb_>pyB;Y7b~D|e=xA+9_-7>y`c*^9&*jzKodR~sYqQvlCu^Gu zb~))p6Y%Cu1@UF>j@Bq$?q%4FLa?-8zA5n|%3depEe;+IJ=TVPj4Uzn~uiI)%j_S49 zT7DOar~{GAeaewE_hjp*{l*@nCv5t!uxcE&hA)`t^1>!Wj4!`5nORi5{drWA4I4{= zAZc1mo~&K5renXX{P8@_HnARn8rYnU_E?ThW6%o!!3xQ%00k9S(qA*IO%<03XmCT* z#J?cjvGC~u7smzzKV0ffxUXo;pDgq1kLp>*pS%X=H#*v;%N@F(i_i#RAQMtJad$C`rmJ6-tPL&;CQ}K>-ch ztanQupJhiGhe3g|tHtu6yH}$L8yj0YU9OsBZyn$yn(yT$ z`|sJdUn5?7_p-1$^(H91|4m3;k0&)U?ZHY7aav_{bXCq~7-`@4Gxpzpflfst!B%0O zq2l|PD=FlGogAdf;a2kb)dv+CZMs?DhHYN#mD*PlS>o)-{$-iUs*=iZ(gboDW8=z_ z2$h{g-i{r#UMdq-uGbJg2v@+E5rR6oCG*Z2Fm4IqU!{d@001JAws8|PqSWNk1MAu< zs-Cde&N^JxfxBfRqP^M?WWTXf zz2g~5`?r+f)9?5aC44i~pzli3jd`cMV#;%P7+?*kWC6pEyn`HcIQhx}2c+vyVa|_$ zZBsi?q;&bKsLVZ2g3GKbMfY{GV^izzS5!^umz#T4U=%f?l2Do`UOmJO{_2KGf?vVyD_G*_tPM)KpGE;4Ue#aS|c7i zZe)xpmC*7R2`sY8ua5xK8<8&Z*mABw=Rq1D0vgVZu2mc_eL`p%oNKlpxrsn!BEby8MV z-8@I3HL9dPHOT8p;0<)$P7eJ$*e0|tt7*^dyVOBtke{VE_$WM~ zqqb9n^uHEPrB51g72Rj}69&NW0qyWXj19tF=I(TMs8XvFv);cpzDW0F=*r3x(u=V1Or9&AB_QMA*5d~x63Mq9X+V_uuitK4) z!WTk%CbhcH(g@;@D|fustH>B0{>U7>FcH@(n*L<+>NSsE(sC%f6Sj_G z=>>BSK5JrRGl>OXGhXdfi>jW_IJmmBI=`;&SfFQ~fc=9fPXfUL6{@+O#=%!;9WgyY z_bzeigE`wK8B}68iY!x8e9;D+JF7R*m=TP9cCLZznkGy^VWJ@KkG4}W$&K$3wvxRP zt*>fWab?JF>6p`lHw#J*(k{UTzl)kyy8e=rJ|6<_r_46*Q~0j^Z2J(+&DyK4;n6@Z z^Yf~@Q`X^_sZsNge{vxq%h)5Lh(iQWT|~)ToGjpL+=r_^a%w<3a;h`nxTn>CGsGyT zePE5xMJLT0c-NVs=e=iY4h{d>S-1aLax?wdeU{i)jzBX_h?ANwD-ZE{=~d+o196%( zNhJwNu`~dwh(>}e7ni;#Cc2AEj7yR=KkeVgY5Nj;GK}uKrVj;@!_g5kO{<_x8_RS{c@g)g$8F9tA6b2JSWq;E9_-Jr5+^uq1T8t|{(zCqMAt=zu z`|Qa42sl#en6s80#`nl8R@^{EVD-e6)Pm)UT;@cN%hPxNxT=<-d%P>cbA}OwIHlff zYB3IO0sycz;p#d*W>KuSPgNC^2NSHb1q~7Gkac@;t(v||k{v7vX*T8He&bT6B=i6; zB049R8Xf?9azSh~M91~&~;;Z!td0_F3gX!EqtdH}4<+^&}$sLHF=d`hT zW1O`0&j8XwQl(z|`cd_N69h(lqEk6|Sj-eL&U*DYtjyhuTI!*tE%QrmjjL}T0Uw-H zlXS+!aMg7ykR>c}o4@cGl{JaO0E0TXm;SsiE{7h90mH&nV626%i;a29vrAnLGbp5Abq!C6qR9?&`qB|?$5e2L!74{UG!G&5i{0;M|E zf8iTp2>3i)v{_Iby595En}g?X+XeJzV933J_;t)t{3q|~e5T%15IzYqA~j409?4Yx zI)9r+d+TZR+{cj4$wSLysC`MomrZGPrxJb$Hb2cloANYOw~M6-PGp!!YrOyFZ1I)6 z0@iFw9-Ae0sBMHMzy1l1OJALfu8%blY_{~komnD2jR&fxU`ZE2-Lx~e9e zd|i{EiLs3w-|)(B_vw53>Lj}TIgDE7Q$dJ##9Zy7i8|7o8RX5L>N~JlY(ZH1}pAyv=5Gq>Q{d5H+IIurGy$wUx|n@{|tl%}aR7!*h1@8q~{Z)P6> zOlDunTiz^S!DbbdQG#-NwgABXQf7-AiRFx5YpuikLI#ad=T^qq4%aJC+SLuGFKrksO00n`iH{baNGnkR@9BW3pIqQ%_uKDEFPEZLWO5|EZxgh`x=8XSq+_$y zhL2&e(eFy*@}iX_UaDxD@?>YB2(M-=P&CVWh1UYDA1qfJk~qg+Uq-3tIo_)!zeqUT z^GlK}{*8>TSo@QWNbNs=c3AF)n7=O6Gy&cgki05tneF}~d=RA3a=ta=&}Cd%*W_jL z&e*O-y^lMrnoJqO)!HI5x8HbuY-(EF57Ck1BvX0b@MpqwTGsK!N<%%_GNw!kN+oU2 z_4&n#-khwiwk|WXsF+0_haD9~$eE#whR}hY9vzdrgz;gQU0RK+17lE3-=)relFgxg$?Tkg{tTtA0jL26Z^Rp6A-@TFwp=uv=viS=a#jo|7!N*_=wltt zV7h3^4RUCeey3C1;%{kNUaGjqqivJe5@eanwEZk}ZPjLrtobC$O&`ASij})J{77HJ zFZ6EX+1>Tgxx4c_ODiHFS}SHa1?1##PtF}kmw56}@H2GvVZWA_6l1H8n}&*(J1wY% zI%^6F4sl4K5pvui^+8NtcMX$H48oBK&;E=JG_be)6AEm7LdToQUw{+}lcF}lZ@|M7 zQZ_|>Xd4^l`X(7M*8Ph#&7!v1X-I(yptdYY(W6g@ktTv3%=(``PUH%-cf*Rec??mA$3*~6s|S$byBB6KlN zAg@~1&%_cOZR29&wDiX^uR$d4vc|g_evl)YKEiNtNJ08hD$F@4u8&SH#$cIi*tpMoo&cnXUBj9wJ!;BA( z`OX!1!12RNI(AKVPA%J~A}TE3yXwu04Y;M{wk&(W!Ti7){Aef8pq6J40y==+{AsC# zr(KwujVTMkqXc;~M|MKByb?Vg{+pmTZ`V;+n$q$hZ`d!UAlb(HpT$t=LAbzrvMo>e@&Kijz$|lZ^EBKsJCk2$UGL`kkIfYfwc>be#Q%f43f^Nrc*U@ zz`HF%I+aZv{}c<_J+?I0AJ#IBstiq+s$hW~w%(l5QErPPZvv;l4x_%Ny6*pRRvWoM zi4>wJ8}@-LiW-lAIp{f~WYF$^m^R}8AP;C)m;lHlL*es(Fb< zCWl7$b~zpH3KYR!u!h-I$uGo#wZr7+fP1)Lo3oE^)?DL#YU-CyT8*FBWfzx4!{fer zwP;$)(QCm0vfRwx__h4Z7ID9+;CVYF8W(?$NBBO74YXZV;rLF7Q?yOXvDvr)hpETO z0={#;JJlaGTi4We%yDV8Fm%>azVc{gIG#B{r9l9=IHQR!<`Dc`j|HAD?_(C*mYgs7 zpD_ z>8$;)LSshNr)_SIgJcHOboo0w69cCOb3nB$UdW9@@lU5SbXtoPheetDuE)Pr$L;d6 zdg27T;oO#dBnqaFStSdfhhjp%8t56!=;<7EaEBJd0)0`b>iio4-qR$mL?f{C@j%Pf zb~~s?w0vIdg;s6ta;kuV+MzQJuQ~Nlctt)56}xzfn#I6pyPivp#1_+Rx~i&^Pbc@< zY>>%sbX@%XyQ-qL=Miug@x$t5b5kqE_9t7zA7^^!MMVP{% zAV*>IWpUqhn%6d5&n9{wgT$hFTN+Kj{5kmsP9331zg;KkXn8z6jtXx-I68t`0+Cy1 zF@q^mGf`#D zh1;UT&(A%}eM#LgiEN}(cMk5yDPfWTWtH`lKzbctJOp_QBs$W(Ji856_@1n~&nuD` z@m}k5+h5`L8ui*jf*wnsUh|hQe6#od7TkxOV2d)O{=!xg-aUMCoLJC0C|`rNCnz)v zNxhq_VX=5WVcaXgXVIc5Trjj)nApKbx*9ip-9z_Zo^@-2+^meypZVCAQ%NE+u@_=) z72c#S!{$^MQR^{_{6c5IW_oIO?qRHkc|WdJQdv zBRtF@W!FpEZnpy2!CA4jMJ;?SF^=aoC5PUBBSUa_o=;D|PWta(MPEj!YjHfO$?sji zFv89uqAfg7N+B8cC7(=_D`f>WAHDoaQ1L$V-bGE<(Y?$++LChe8ERkOw=;xP_YoLNohTBKHy6IJtaPJ#wWuv1)e(v=KhV{)a;&a=o*19r zTt*YOtM$7oykJ&iy+s76X-<=bTMOb)RNLP_(j~Az>UnYq;@BR$TnZ#SI6~1fD5Sd3 zT%J$s>j?4CDiSQ(-{=GH4arZ=R6I}Yx)|l{W2Y5M+xyr zDt#e8jxV*{Doyu+nf;FwSF`^ z#Gj78ZZ_^|(b30wUppqR@{S;&8v0%Q-E7W4iJx@^THPZy6@E_6X`2QBvJmk0v#W!& z_8{#;{Eb2*<1PU@D&>&ShVk0xu%=2wqiy~;#Q5Pd$bP^yOqqOn8Sz&F;;XC_P^L&r z{U0X&h47#OwJ1fGb2qN9tMxBg4RN~T$crf})dCpMgqAg;=CoBJtmBI+Q?pp8I(?-} z9sQ5w^y=>AGxCGM^^_?dA4TB6JBL3`@G^-;Ia%_FB)uPQ5)(S6=HYr9Z@#eeF1 zd}=h5(PV?boqg+zl?}2y!{wE=O70e_kFgUpDe<3rZw=x)=kTVbcP13$?-Hzx#gybL z+JAg}8l;r}#Y*GG*ta~xJz7;zM5@!qBTP;k`R0bbg2c#!^Yl{UmO+;{%aBkiUfRKN z2_p6^u#VZQ5BJ*#A{}@!;|OJ?H6b^)CQk~L98lf!SM!X&8|a1+_db7H*W<&uaRN}G z)9eP*nl*;!E=JB7W+Vo-;HNsEWTkFpci0s=lPqKINZe9!($ zCS#V6&RZXjK^-T+6`^}g`@Hg6h+gmE?795U!TdEtnYd-3%D@(2XhPZe{Mmx^?(hmG zH&|9u`}&VM`Kufal1b&v3pp8cYcN^}G-Y4Df>>lL3i~Zvf!2O&{h&N0#G&1;NsRDQPl*D9AVV$9wf~pn_CXC#p zWKj=KLFtIzYdaA-cks@$8IgTxay?+E~ypPJw9l#JBM{PqRW6h zj1+2^JIG9UI~@`@OKNxPJS%Z(X2^4uUtWt|b))VP(N$w8hk`t(rGk14*+`y8%dhOP z3laeirZ%wumWGz~YULgeS?lv2 zQ~~h3)+cS9TP~(+HJCA9>O`zCzIc{!^9X~qHLwk1Bh9CJ>Euw&7TF3@k}lW$*;85M z<*`{$*MUVMu`U5In2Bh*v#k;3(Qtle%qzBFP zChP^wS_Co(K~{!wlGdE$HTTD+e`S71&NdN(fiJ_))^F!&mgQup^i6H9Ve~v$s z>1TmtEV*WWng+HFcTcqMr#x(jPevDl^{wg)c=oln;>~)Kg++!=|E}z`u8Yfld?Szw zAgndO<%Vhv?<)HKHNPH!s9EYd3%Q0JQao^?^$ zVpi`>JD$J#U8%LvRKdUR3+3VhpS)l6L9Y|2kyZ~FyUu1XjZA33WuYqU3w`ZUH<@Wk z!ar;fK_1g{UA2<>?>{|W657;X7xFq0hIxxDvy#tN+yhlSD${@;T`lseTr}Hai8bgDk^6e^uLepw zjWgXfQ%p^o5zMMg9|wC5l0MI)X*u)7+@s5BYFOR}N=ee){?l?O<%i}5VpGF$jBD?gIp{67>`YK5Vd3A#+6N3|0 z>&50aDtTODyx*7dyzgultTjMb@L{$Y6{(p3_u_WBX^JlB}Fr@Dvx{^X5kA4|5{-5h+;mwFL`BAk)qQ={5EmhZ#{Ez z+$xBmMV{ZP?G{w4hvO z%3iLPL9A>X@2af4T}5%z8_zPB_;mAjc9Gf)N%gCFNt`q|rxKm>)pqC)uIQHZ$q{H6 z2^A-}yE||E?z_hMguSzysgK`l1idy7fHgI8RQrOm4e*Qmx^*x_(tf7dQ?1(qo5M*jyb-6ysQ0 zv#Y6SP%)#qqRHT`a~wn2wS2;e??S|Z`)+jLpXbia-26CYx_E-t9e_}(ufQW6Rt#tu z5T9@QX|zDdtm4*wD)3c`x>lc8laA)rch!Y4HJ!g|;l->kv#1Mr4A}!!hMXzWnc+q) zIB!^7nZT007~eL7$;bu*GC$o(DHv%8jYo@^x`o`8EpYH=`~8lezA zBZNUV*2E2i0T*ipzK6yM9g!Zj!nKOGsXd~0EbNid!MW&8gr@bkWGNpfW)>=))B)L7 z--1uhAnsB8{635!vX8{wcboO9ik?&94#b1_xlI3;RW`taS z&-A-Ip?MlDpD7i8z>XJyxSH)PUZ{Bc?!*Vubm?SV*MOw(1dy!Q2t+@tlW@9jL}Lxn zioB(De{exz3b<)6_~3_}^%f2>5keja3N{Slvyx)a0DVh~9}hnSJTBd^T*p$4a>XX( zFx_|a10PvvE?TTV&vON{dw_?je+3q+Hwfth(U?-NZ- z`1ZwE(}`i#enfYu4tYb*fXMNr)lT~!G`qfK>xthRvySirekNO2*1aaiM8}=3&9rzj zy<)B_uH1ry^XnrHI{M=Dt-|iRxh9GkGw#Db-|qeUO;Y&FRf5k5u9+Te6PX^osf!dL zwcKO^5S2ap?_T;)9op| zF8*wF@DX5U4H^6U)BBK`TlVE%e7d2_s|zO2xD#5BusZTT+<(WtklzP^zOtcV)0&xk z>4m%p8)czA7Z@#EvOaVQ6$C8hAdu{Ocbe?F>SBz5eiJq?y$cK_Por||57@0s5SV&W zlxEG2Ymj>P2zWv!yI!VIBM+L^VD;tz?t@SNtHL!es4IUjwkZRzM0+HatS7t59W4jN z%<=5;d@iZ&c+SN-RhQe5R-S;cMLUVb2u>+0?>c~DHU29k#9@=RAQ6X8A(4om_pVD>vr;r{pJTnfoEd7vMDa_9DM4G0)6`#Vn!Jds7D57^s)wm!s zp_RX%7RC*RUX$03>UW7!#|r{(_CNcoxU9PaNCpmg=N)NI%Ds>KFIZ+TGHKZ@aoX%fdlwK}7J}6dm#P8p?6t?N82rJ2K zrabwTY5;Ik+eCP+?5)ipEQDNL^b2*xa|fj-LURZY-wrqn z>D@MV#bdHlAcnQQ`36~4Z+Uqgk3bGw{`XPNVLdrgEZf^vG!KwC7%}1WYiz}beQNd0 z)()5%tS-HAS&`D|@HX`sQBSFEB9XV7M z*ZXnr#%7&vdV%^AkA71n?<1g+NL;ETc=&cs>4tjiDMw61&RGhUBR9Tb9IGqvl(LJo zT(hZl`$dkD+~wH>=7s2l-c{O5zY?RhH@mz9DeusQEf;-3JW{znUlx+g8cWl2=VrN6 z*bCf)X4k1Uq}V^CRbGo)Q*V|Nh(snbd9s0Zt+#K}%k#sW(k5@J7@LIB3K4(u6=ntu z$Q~$1nRn~`u(~Y}IjccB(^AJx@GnLTDqjoQg1*6;yPvf*JOUD3e%g7OMunAS{XWq^ zB8A4GM-bliZ_naVjwnM+Rz+#6H5@<`VYwu50YpBYuD%>>yN1qU~jw%+xw~A4sXSuwZQ=4QTjseq>JuX z7CMk*LC@?&|5zGlcC{K)o9fb^Y$4@d_}3395v)fzH!@^>8&6lm)MK_}j2s zyg(i_si5hyi(EcFci;aqi+rdb1^l=k{zs*{;1->ch~mzRT;0A$0O_>7>@SHxm$NdT zbkv`YEZRhk_xW|Pj>3eDt9|c+i1gVJ_`EP(llzC7*2~$ zm`PQ$H$@0DKwmqE4+KoWZX27KVH6Jk+85DS-%>W0T1I@KJM8tzKG2#%3jI#V7^QFV zDWk{ow)FUWi=!U3iZ_lx&$_I>rN3e+{80z2gw&iG7i*!6o;6M{(DlDd43@$ucd_4C z{s|wD`EheJ_rjg~tQ5qoT*grre zY0J&bl&CZQ#(pITJzh=@E&tV)^}^rv4stiBIbpnD*PoD4G{M6-ozMbqy1JyM9-$K2 z-vWQ#Ac%mbk%3?Q76)@pmlb_AE)T#NO6W%q#7| zW{*io^Tv0g@jp?Y37rmx;=AUVthGe%P>?$f>S_Rxm9nU#J25MVP8E=FEZ0z8R|7*z zL?-xnoeuw*Y%9Fdr!!_Y!o5)bCc(hZq2|f>q{Dim_sU#af!@I>iQ`dq-Q_?>qxg-M zjCjs0+{XQKCwU@rz+*XYZDh@T!hy7}_4~UHD9bZ2lzO+H>k;7Dii0CA6&oKU)ZdmJ zT}>7*s z0bL(Newp@Qkvj5K7cCI#1{{ACwqZoEY$lX%ewlE01u}i{nd{XhGS0vTLja(x?ksS% zw92jFTp;hTrhy8s*fsYCsk9h^xly-8aFeZD_~l)y?ZpAWTZQiWWn2@^*fW}f72uF` z=}7GJiAR77%p}O9@1Ne)Ct?^6uUNFY2o?U*V`bX67*GDwJ9Iwkpyw#pmOI*Vj<_c6 zrPf->j&ASibKbrdapwohv(W!mU7!*@>XtDO!vo!Bmg=r|%t{u1v$Ut4UHY_v36*;C zsEBC%X}|_I^Vjv_XFh&>=lZtUb{ntKdvyQ_v1(`knnv6T=B^=FUdu%^G`T4WY^yB1 z5z4%{CjI{hu|Q70v&f9C6QL@>{W$w$+oFO<0y9qk08I(Osak(cT=i_mn$Kx!>2O;o zXhj2(OE(;M<0s%Ajb*i|QCnMJ43Q(RDWm->r>*)L>qB&%rLVhEREmLJ1czcTqYE4k z00d$|zd1Ym3=WtL3U9&!K1QAY0L3`!7N*?uC_qUN0qlC8g>@bKTj>o~Pi(x{kzUgc zAf}p{;R#hNNixMFj#!h<@60kjIsxaBr?fT*N;POz&vTTwkVZj^!03#d{JB~VFu-%#)aSOt} zAdk#bZWDGCR@KPT5@3_D2iLa!D#rEriPzQi!ji90R#L?*Ner`6ZiT89Jt2+`0pkQm zv=TWP>s$WOaVLC%(vkB%{l~f~BDhBkE{_R>fFy|n@cZVgwAB^2THh!sZnwv`K|G>4 zZnIMwk0m|ZY2=@8ft;S*EVXf~i=-m;(hzqhOnc14?L4}Sl|b);J}`Z{4Qdfbw$f3U&(5UK zQLTL9B2^<^Wn!Zte@-&Q-~sQE_vw3)IDtXjz2T1_$*Wh1B5n>fqhkOp(d!2bZxZcGU(PHBUlU`&qH zeMJ+BniP=4|D1XhhYL(X0)( zknl>R@O{W!57hC;P*(yNdpIa2ebO=X`_xYo2lVbb-pP#Scl%Sux`FL7RLKE(5#SPt z!Q6)gclqN#>(Fg5-Ro#70B3j~`k>fB@%? zows!^q{ayQnjxT{5!S?JKA+sx=8M+!Nl!&!)N(8~%Bd!j8pkD#1ckpb@0gjguA@6} z90GHk_>H&fSsohntV|wT%qy|xcNjfEoxQVlw^em&B}srsC1Y<;G7mkf#`{Lr3z>-R zib7Pzt0qY$axlY&QoMtck%P!RPg}m=1rCT%IUk4N$nQnlxQn)=k6({X!5bQ+`uM3~ zjvazEjuk}OfesI{;9z@yzkaCLxG4mtDA38BK+KceW35nM?DQI*(@RHC40A@rQdRSq zv$PaqP6~JL#&Abt)8)7>nbLoEFXDk!s)E#{oOC$@xb^2oUIIRh@bgL4YMT3s(qB@t zdts-lf##7ygkm5>A%^93-2AREIOjM1%)P?+J(<6}SWAi}CUSAfAZKDE`b<|9_P)!F z@s1!k9jj8xQkBX`N@UJfql^%Ar+#_lXLtNt*=@RR<6BK>xr>xY(a@yp6Gb&i4dtsa zaLT@&0@lyvcFe)fE_#P@&$L4l%|^=WzKx-EC}!{{U$D{$iY=#NrIodqis5d*+?B$8d=n zY9&ev!4zwiAnk&o0Uw!{(%2o-J;=*)w5)1RE$WiYn}jGfuR3$;I)5IqwHj!z4J~@N z#40;YeSLH^m61#IZc4-^soR+1Esx6hBRCm64*fjgwvFFB8F9@rFk_$k&v=Rpj&ZxL z6~fhdY=EMIom=4(3RHDYBy`_uzq8t{b$Vp!r;1%WR|M}Qme^uxW|Bs7uM_0q3aXvX za@()~z{r~72^LPN7Zc(PM*jdz0U4TydG{Y$Z*}Vn%+D^rz@ab1Bo(?}LQ_-IP(edP zRRYBfp_ULB5K8-#j&q;BdJ5Xf=N$b-!egMGwBLr?q5l98=dydy%ofph(^Pi~npK6W zp-Q%<*mkQo<{)q23~KT(gFINNv{eTMhYvA^}gBZ6zcOF25SO2-@7zo^^#FCF^V zY|*!6EuHztTI^gd#k)32QX)t>GEO}?ZS$e_#F8*_2?Hb#QO5(E_WkqMq#AG(r#z@0 z`rQm_W<*tNvDy{fV4vHe7a(=5FBPQr1L$E9OrppL%h38 z{+&&66QmF+tE*mslti3(TiH6WYIA zHhd+>{nPz^f2t={LsAk=|W65RPj(dHEIO%~v(j%1^w!%^gN$; zDP=28Vzsd_ZiwW1}JJuBsyf6olPjT(mNf8zJB(!9VR4Qwf_G^SS zNYWy!0V1h5WXL3A_9N~FI%L6V{r;fhd#ze55U#spBBq6h^a`*eZ(MQ9rKv_0mUdjBr3P;&}-qGuxBI=Sq#*~d-vndnGa!)uMrfZ9}4TM^32~s6k zrSk+bN9JZiN|FXg*b;i@4g(s2ucy*A{_Utv3506EjopHkUVotey#}L1htk?fnwv(2 zTq)bQf>@Ee9DM%(zd|TUAoKkH0CXcwo0a&AIOHmIRV=iLDl}!>6k-4kf=6P1Z_}+R zIv z7$9Rm_x9^{`_}&e1cc+3X^ly97W#^Gj%gIA^G?!35_wz`@G<+IdQWzpTkTj|Gps3U z9lt$jh8;|4?XuQ1iyY`yctm8XuGdy41LtYS>ygy?XonKGm_MJLDRt$#u0oQXfR3b} z>zz0&b;*L`REDG-le5JnLj&`YJ01wf$4G505(kH-21ll8HI=F#Wg|}F4ElWjHJ+o_ z@M;+6BkhyZYetmBvgSuGVK=74k4v=!;t$x%PS9l4;}mG9aAA5S&gcpYi)!ix~Mbjoc8DNiiJ^9rUm|3blR;P zigHHm@!uIgwn4|h(q&tk{1hHIKi}W)6`e)E>IxOCw@4*fatwYkS!ye_GEqlaZKs;DrfE_%qzQ$Ka3uMF zA5K6#_G63_?6nrpFk$26jsDTs{fVQjRHZ9J#v|kI{KW(Kg;N%v`dj}1khL}RTSCTE z$cJ&sR~f+o5(qqFr(py$q&YRUYYfsgsd7!M8ooZ2o2^2Bel0CxUAGkd)zqdub(~UR# zK`|7;fT!)yw-QoR0U(GXMD6*CgDjYk?jE7{@6xo17@`?(%Z;nG{0RIX=*@ljcA?Z- z+Pv7V_sXMFL2xA%;DByOLS%w>g?HPYLk>9V&d-E4t594~NmWd{#_n0YU9F>DB2%~% zGtA?V+i0$B@uR_-{{RT}3emxJq}5hRx-__3uGZ2D)YGwGTe;;~7#tJWeB-2-yg1se zAqP-A{{X7Av`X(;Q=z3Kp+zc59%PXRBhc>@uZ~2oq$nQ>`9qcU0q?>6efnHdT3$xg zq$5JL56|Mhi)hL}5By&IHR$a_uIi|S_Ss&A82h$|`TU>;k6H|AzI4Y7gF@Nv&w zt)p+m?AvwGh#5GJM9(bGJom0MZ+phQpW^RY@RF}GiP0L8pmHTUY+^(a*11l3X{|S_ zg=UqWORbWMbZ8#&PZWXOfyJx>1spKZl`L2GdB<)Tr< z+6Z)sR)Sz2#N;22de^=!=xRj#J?TwPX}5^$#rmSvcbY7gs*#7EPC|q2mW%)a>^bSL z=$m(jA;ysk1`K9ok^D!jS2%l!yskNOWb(;DLRN`^qyY*aa((ng{hYXIjTJpD{@YV! zzuRjmWSWv(EkqH>o|Ax2vxENt*IcgqhPz_&nt3pH9mmu7=Sompy3p;qwIIw9v5#7R zXm0@L@r%KWWpsM-raNWMHfgNwN+hC=piQ;hsY7i$kykbTYuPKPcQ^z^CmG!ZD&}u7ZXcL*`}ppsm?)f?;p^9T=&BC ziEAXsAy$_PCkY!>WDwM{qsFBBkO|8CcK81PZiG~1bEV-bdKDf~y-bF0r;NxOYbgV7 z!0S$&MK($jh^nidz|T~QVv!XAb(tFq55ec2w`lUtJt$Wm)a(#&H{?4Su(n#MXm51% z`m|gvG;$5wKAfbo0tVb}0}6TNa(>+PhM+vi6G)xnXl>%#(mg@>ed&p>6HHXoMJ|+R z;`$n8^O7QCMvb2fo!DlRIs5JJ-A7v1>PbpQNtlkpO!TH!cdaR54y+wtQ?U2_Vuz!; z8mS$OrI&Xq!#{98`sbkB8i*A}MuY?>1l7d1SE&yXz%s9>oNzIY4{itSG1B)LDU({? zY%NfyZ( zB`nW8X}l6pmLv?Y2PEVl**&`4R;Q8)n4Xgur1p`lxYEfxo|wmP_Z3&C+L9}D5K=Q^ z%xC&}A2Nb_G7?7P{!`9Ac>0%PC9UDvx6>Jpjn}8%a&!>_Pwg@C~p0i73 z)Rdj~uh&h5ps8Pjr{~g`mTwR(-V*9|ylL?@?y9bWS{sBnNW?KAb|OOSLV0**; z>Cr0Xg%wJ`#L2XP09GMYm=1X-Es^aS);uHu0zf06uh!nk*qeiPqp3tp5@0CqWDZAZ z#L^#7Tq~96t!(Pn$Y+|Ol&r}hjizAE71Rx=SZ8415>L6#DY{eR&Gf6bN|vW~*<98v~G0#I(!)KbP!FJ1l;1JF|C2d)>MS_%zVnPX>JiSLxe9uu| zHC=IW#ZU$4(&}&)oMZn06{K?RoP)@T6r}u6UhLY2f>ibGtM>&*I^nkl2ow3P9YAAj=#bN1?^gC7pjI)v(<{_K4*>s*i8=S$wu zt%i(BP}%f5{cX;G2c>u9+B0cjZBUR9fq@%GJbTR?X`dKwz6K@NL)YKwY&I*k8NASW5XmKKG=Qv> zF_5AtB`!A^BnN2i>g%H0yKQS`gel^r=mZdDK*myVKr=rdc;I&yHylriOKgCxWEmPn z1BLDir7;`KbFPQ7elI*f)Ot4ab)&AD({;DdOH{U-x(a#Ol@7p->P98DjtM;eQ`@3H z=B(XmYi+?G69)(2clS*pZ`mFt`sQsG-lVNSYD}m^)xe9MH@&${{X{N>I2_AJCBYt&`&HO1!_=8+qpdkMF)R$EI1#j4XGGE5Q#4r&8@z~7xN?^+UzPl}U6U01YD0sbMjNa6tcj7WeM ziATQAn1nl=M;8)7)v|Y$aVOZzg5n zhF$#o_B?_J_UM$Ts0GA!<}0$;+#9A_elmCU=kuv>)Fgy%_BkZypZxFEE}~>nuMI@a zTcvEVLrPRQF9A|U&=r-6;E+k&MjQ0~x&dv3pElH@#YsZkLE2~Y-%xIDwK2PfRFq|# zBw?4i{-XzvanF9Ywvr4}geSp2`+lF-)6%PGE8&Wcre!SVO{7jAF_KSWG444SJ@e5o zwW+uu;Qmb_;?1`5TWv6NBd*-Lb@x@DhxTht`r|!SRac!YJ8u*_cy2}sJ;=|-0LMPv zX>={N_T^gJW)bB(e7$>A%atIuNXb0pu$7V58Fc{uZ|XT6WXpuNDXJD+FS2y=A5r$E z4yDj99W*u8wlg|WL{%67YM2J!DRNF7fdB!4g#hw%(pGQ$BjGG!O!odoL|b0G$k6XD zT{;0(wyimGucW2A&`>p9x_X3$K@an0Y>Ydq1G^(Cef#~orDFETk{rSBx5lx!x^dB^ zvId;z80pffwGDJD9McFViYVpTB|L~xfx#dyNWsYLa7Hui2|vTt1qTPFX_HSUK`J8y zKc9kyE|gH#Tar@gJQ8J+M?5}$J`ZqzPK8qnZBm|8)f!w#akD8S^VXqhn$s_lW~};@ zRdOkAY{8U#LizUg8T$@6>kF2{pu8hjG1tGXDP60UrW!#l^}!v%<|CBV{{V}3tLCet z({=Zn*{deB!qz!epL)coe8`Ft0Re+Q2et>>tXsMl42<%`^y%@buhOR3N+?K3StlIl zW9x{ZYAYGO)*^BA$sm!)ZcYw%ckX}Cb%1ywr&SpS@6|}17qLt z()#4G)>D5Rvq2qwo(VTR#wL1DRDt?Ig(dQn4dcvdhLon$JEi;d-(ix)leG5TYy{{ULbm8hVB$_##Y%|<`% z1NQ`{QA#>*^Er;6TA!x8Lf1MTrU^9ms;;xf3xNvE=9sL2`J>8QDr9!r0RZsJ(e~Va z=Gh(^6m)?QIdtA<*Kl)46y2NZ@f~C>Q#l*^gmv0uv{7w}+v0bLmO7iTT54?@W0ooj zZWQn=P@}jTV1s)WmO;2~BRg^n9P!buTbC^rco6{SCp*OcZ&tiJj6HkA+TqGxl`2() z>;Rs)CLkVEz_a)xyYKxTqOx30RDnhKH@t^mS=zn%0s{{Hg2eY380+IKu!7D8cv~4CB92oI{SVmr|!t zr4f*v0lp`Fhgl}M1?}a!mY)||fHM*RCTBaGcBxgiZ=<=+D=*T^=Tf+;FjB=5BtU*u z+*pEf#t6q9D0hjj_))LP+ioz? znmW{^QH6T8{$brA-qF*ebI&2z`(r$D)7KQGG^X$*fq~Ehqw^6@_&b2wy>ePYB>KQVOj0ZLWn=>joc zA?2-F)MlzGXdc{vZ!V?wmT$jcdSJ_;*~C@vv-gR4E4LUU%sw!F)xLxIl@44ejX+_w zj&e_8Kl42jP9t+xt|3cThu5Fi`m4o%{j)f1%^bX*+1xoK{-FH#>2lMt?@3E2ZtF~W zNcvW)sTQUukw_e5#y9|fh<&q<(EYn+gp{09bOkg+YREeuvHbbfP>Lr?mztVFVOYqP zGlg8f4p$%M7&#oCjdDw5CX#WGu-a7LBl-Q*F%;X&Yg}$OK}(f08a5jlO*RI z=!e6*bQF{~I;sfiEqY=pWZ4OMJjkW;<1#w}N^Qu<_W+LmcIO` zB;`kaz0vm_X+5rWBv;$b#h%=1=pv3Zr)kCtXK+^|<+4XTyLCj%Pd0$-Y9Nf5?@ZC9 zX-O)HnH_r2UBbm`du0n-sNUC77{Vi_c99E`4)TwjcOU?M-8Z(mxOK=`)JzWN;k_|s z)!TyDBSLZ{2p{XMP~Wsg;yB}qs-B+Z2>x7?(M`5sr}DES-~dhrK{!47tNjwwZf^(e zNrTw;`+Vz9{{V;>ySYycx^7maf)p{RLCSNIBRu*|N4H*Xl=ffYmGpHjW_R7Tbi}c` zjtWbR9x=mi`(vd~w%U%oPi#?%w?*1~fTu{Man;99#M?o!=ctylHst%GfBOAw0PlY6Ri6X`C(>$FbCs89woySnF zqCNCq;l94Gcy;21n^M^;;?yRJZ|73fLa`+Ax|NbJ&%kY`Amf4u->fYz7sK8|fD#8T z+fWye+40L2SeH^YZJdHK40QW#nlJok{wMAJHda}zI$2$G&8!NxmD6j@R_U~YxWMv( zWb~2=W+0FO>Gv4oNWBPYui7$a8UCx0HN&0Z?zr2qR19Fq`1h`2NVl*%Ks=nDrU{BG zCWh1ZsnUfgB_K&8$NN11%^7ae zom>GuhhF`MI_*l^^||~ku;?2-jw>CO)#b<|HJ)s;$}-Ma?NCcQGnU3lIl$`1_l>gk zzv);;l;C9Y8wejf_ow_1gq{(6t|05-#|R^68Hqd2!T{tkLbWp7YV_P%P*zmQW2&nX zT&+SxNba$Xpi|EvK>kq!1S9x(jXZoK#$G$tIc)c&YJOl{{W{n?Z?9!87{PtNR+nwPNbHG6^&OJkg5;_U}Zqg zJ79y3pR>oD;%fae?e!k&fIa|zZAcgF)wc+@H_5#%qDD)4i9H9JeuJ%YZ-SmQ(0Dhk z?%HKwWRXzGO#<#Lg+AC^`HUJ$ZVzs9g6%Ux`_ zRm%{TGX#-}ij4VUR~r*(?(7(Y@y0qS8WQ4d+w4diz{&IwJ!*c{?+Ns-Hu`h*RFMO-B;QRVk6!sb}`wKaCv%~k3; zlR}ji`pRdgnmL3rB%=i+UQZqPBmI5)F44JIaVA83zq(_=RFe3ltx|b(_X(M5ce+u*2zin6K=!k%^;ok8rmIXEDkA93HU z7R@}8RL7WfIQLH98c>D3Z~jDi(hukHT3SkN1w}ZlxHX;xEKK((Hj0GXme@v1ZQMB+ z_=um?lJT+x=7vmy6eGrp+CKh6+hyQzREgE}0}VqC|uMq>S=?`*qPC z_K|>xP|IWdpSW_Ji5p=1>za!qmD{@WrAlUWk%C0ZK|7f0B1Hiu&g)AXKyHu;3b8$MBbJHJ6#ItI0LLKgM{N9i>JK7xyqxvx{8fW)=>fs!N%;Q&e^Jh<;JigoUea3bbh6b_ zvjvt`K3YBq?mOe7E$*$Dcz&fao}ZCSpF^r(gn0=V*!+m+OIzld)YLaA^;6!Ky0)E! zzf*6kc_aWH0mjm!?gtsqTh9>ioC{;DjK(T%<)>7QF8PFW{v&Ud18dzCX1;iXKCC)| zNG((!NEB7HU~Kyy3mVtgcZ1$ z9qX)duV}2AaTb=B0#o~fM2|wOj@f$JT3e_UvDZFnYCya1&(mof%mu!_X;DM-uqBj$RJCV-0rI!m;wY!qhQ3XR( zMxn|lFn0KgzWhRI>sExWv0CWv6I`cTYi_BCfUg@${z#1mLZ}Ml9!cH_Cp}m3-V!)# z&9WU1NYybIC!S9G(jFDXU*e3C+eHN=f(#N)Iv< zpOf>{UBF0Mygj7V5ji?Wa;?|7ZWFaltwj&zy8=NYesVH9ocwWuKd@+p6WRn-y*hIt~F`7@Rzn0@y?M?bC!$;V59ZMaK`9Y;~n{{R%a^5W5$ zw`7qU#@_kd_ue_w(M4TNUJX=o(Ou|?1!Y4F@fFTggN9rZco^<}dJL$dQ71{It4nST zX)+GPWPf!30Mfn=*W9l3mYr92Z>yd&U2bLFG*iSuX(KF1FB`_hHqwoNN45b-b5K(+ zoB$x>Bik z$vF2Xpe}CNLiM=juT>h1YWc*NQbAAu08jq_Z}C+AB2oDkl}d$*p@7}z$G3I|e&@LM zAAYe&wYyLZE6vl_zu~4w9gZ3ho+OJO8rmJdowna%NK7u@% zRQ~|Xew`XTL=8m$02KLLI@$KW$k_D)K4b4oifFr1U z0Db81NnNd#^lX$cyH&%my**PtQ$HI>1$aE;_UH?b5wbOaL9T|2@7E1zQ16YtG~0(- z!)&*TTFPoDN1@_|jm($;b|WPF`<{onwF^pAmBIYQ2&)qM!;P$-vLdLdbPYZJYmd=E zH}I;t1N|w)n^YWu$H6BaAjgk*{D@M|o+{wl}3nkdnFg><`_jNM*2<~mc*Y&mba`cBnD zsppdI{64Z;sj4MPRH7*)g}k`q{EE3eFR0)S2R$0w?yg&OlN(P>fcI@c@ed6lE;Efp z$m)8Yr@cti(ubssF~-%~`e5`0$b}fKtjgL#yL~E-mNwKEDWqtbD9^7AyL^w6=uwZ# z0Ledm4tiv`wrl`=-sl6V?skujHxH5iHusI_lB(Yo*4C7<1&U~Z5h&p~_Q3Wy$JmaS zGT~|BrEIO~j|IIpHz&mXj$vP8@jn7;t^{alk~vrzfGXq+Q6Z_})=ya#UQHmbkSsGQy$E8!ST8U`s=9onk)3Zq| zWC;~j05~U(K|b7`$Bv2$Z6aL~p4;}p?@~U}-8*cN;3x$o5+`h!*4$n?!XqY4v~6Q~7|#zx)v9-!Ggbj90n?Sq$ZsWYV@91-O+rg<+p07)AX zE09~>gx@sNdWh7bsgPxO)f~~#&Nd(%({jGP>8VrrJ+cb@Le~i6Ss=Rwl5;SP6ufR( zj6^a9-?(r{U`Jf>x0JU$7S5n~N6dZ-uW9Dm^Qi$kW;C30!RtNdojw`qHit=2TdtHB z2d=VJ?kg2oFGo=s41A{Uu}Gy@rz*jY1`kI&aN7;(d8HWbfl2RfQdXxLSOLKww*(%& zNIM)6Ovov#FAlOs1+thN4TB}WelkDPsbcB9T-&x1lh1#z&ycCNA8n~he9G#1d?+7V zTcFf78_hf}@wAY=BvnN2lX+9lPx-Jh((7xbv3A6?e;kbD?4&oatA#B05rDLSD5G?rjF|@ zMgt*@Rz1bo0nUE;=Z@Ta^r;q#OP}EfQhg(T;-i&dhE|}gK_a}wK?l@i8o0CQE6Jn!hNCp}?fDGtu(t=W@7!as@79Zl(1hwyqO4hU zEooXvJf?pTIAA}S-#GdE{3G!n^6=KVXoZ4)-ZAYusQ4Ey){an`3nT69cKW}tZLOYis#kR#}4AAYsfo(TAl zrfpsuR#80_6`AfOsG=@OGSo;m`EEeV1;}8>fId2xb+;DrWSv~+w_1vSg)hZHWA^T2 zlzi#!qIA8VMOu<5>BRN(U`Z}FODlU0Imh`&{rVy2?YMJVV3Ki(lkm^DQv0VAR7~ke zquOghc?|VYB`PohspB|iBewqlj04-hJx{LP>%|4WoeBQ{#1d!Mr=a)L%a;YuBV_uY zwM5q1o-0kH(Nb|!DB5;7U5Gp5<3H`5vcG-1D^(%95+l%a_nO^nYC$hAVYhSreP|Np zR~@@gz*Y7VRNw+I+q-ogW)$cvAL^M}Ax>&nzvoKLZ6AS;=w0E`j8aaRl#2wHvp#RXGc;Z;L4g(;R65_60KPUGLWz{grHl3X4^=A2VEP+Sm^ z=A3*!ooPAZbp(wlsCiOy7tB{t-A*yj_qg}y+b03Q5!!*`?4s(!#0iBqcI0G>$q_#b|ZQWUf4T+p^=D#F=W4bqxfX;r3$9LBN*{#=B2+DCus)`yx~ zBzTyb5$2TIQnhSO{{Y=s&rh`h!1ZtrJACvSRWvrFs6P%0% zn6k7Xl__8KlkgPN@u%?5sk{xTq7|2VONBjqN-625bXu?%g=qq00k;G&Jm7=RT5qjQ zp&mLyNj!}0&N?1j`qNt*W_YSpt6juyAZMo|++g&i?3UZDB|%@|b+W=EUDB+JFkBJ& zLgk4${{XMgLWQ!l&ae#a1bog1o#c;4)6*sa4Yjd(r zpzy^=ARfen+t~DSTGFJcmy`bh)By%IpQL;#a` z11bt@l`TwV7Z}8H>?{;}2HZWk>3z+M7pFv}Dm>Dp9QP6g`a$QKc+KtGP55slsUxT| zJq%C88e+bwt-ZzMQ#4XYHpLLFF0qc;CuggUc=ta8rwO}5y1YJI{{Y=tHN=~{4t|ds zV<()>!0Qd?$l7ZcmQ>nGvmaWQ ztmsyI6j2aBW|Bo9`zSyLJ&4IY29;qazt~dtmQBR;kC~u*9d%t@B;jfztRdekH>HVQ z;1D~Wdya{4dHpGY2yllHxAz|^O!%isSr)32qM(uME?La?wynPY;n&J8`(gNw>wDw_CMF8_ant6D)-b$NmGg&ICYc#VX>=~R3s^$avw2=g3j=l;DZ`B$hdRo3?U8ItC) z@+n{;B@#vXao{wmzCogrHwHwV9Snp?}7fEJ#YU2>?a(k)`paL zhuvG_-$zpf*xb7aEB-8ZU;O_7mso-n2&|a7DJ5tyq?rfv<|wyO>6)z>d^D1YJeNeC zo>w37VsVu@?VfOX?mEid(Qu7i#{U4Hn69vG`(&A-~Gc!NUf=&3sar#Q%-yYINEmZVtbjpv!92sj9P zeZJkgTI9qe0qa36lG=1OkSCe0)$m)#TNIjFI4ad?Z3PSKP8fb^|*C33Q;6@z>! zwvt>~t@TusyDZhRmXcBkMHo0d`ibl@>^h3#Pc3PYx6IHNN|Zu2+t!!5+fl7{wXxda zVV13pHUsnj0Dh@BWwx7jIIL;|u4vZjosV2;LiG`y#Zy8i$qY_zk}TBAZFl0_iY zZWt<{`wnv7r%sjO+a$_?S=u>AGxin4?rsgTw8W9P+uhVtH&9o^bln{VJZo-Pqs=TZ zF2JX_10BIR2e&6Y^eZJUsHwpsWX^qesXhkM5@2y&ER$MezztWd` zZNOTtRF@QLdcwh0J;27rivpl+bIITgoOI^UxHYMR3XSoDxc5^I9vLp_eL9kN8_4&5 zO)&M|rqs6`UtLS7CasSC?d82y@!xxaz}b$yHZhM)pL{GZPB z@F~KWab*5ng)kIO7iJ3$|RezCqmO7zAnHgyCP=YN%0-E|#YO&%s- zN&LI8aq-Y?FoFn*^7%EWo-NU+F5!0unPNtLr)W{#q8OV&*yqcA*fI0J#622bnJ?j)$IK){Oq!_>8# zhCuSAWDlyUm&SX2H1t;Tgn6*YN)FqhVa|Twf%_hcX?g($i~->e9`WK#sca=j{--{l z?mjdVM@uY;3c^4@0-Tg;(pV=^+}pqgPvXKgQK*4w)d%8?*%)v+l;g->cp(_4WXqU1YoWK$0xT*uK0_# zwqK<<@ieIQ089^m?)M6O^FgN9c0p_aa!$_{z4AMx+_;{)YbYPf+*3ct7&11N@ElvMPslAjGizXKGl?VBRFX)*%p;IRmd57W!26sMf2Uq41U9tLi*&sy7!=>Gxrzt# z<(thh4>8Cdk;gsz`}5S@!%BMLrsL3mbhT`$*V}F)d3sVR&vcUM;+2n4%8d57>SgsH z?^-oa*!WhdH zA;~IO3~o|6Imb$P-v_i+vYl)csUsQ3zn{*UaK1mpSXYGGkPl2BVq>u0I+4?*8*9Z)rMYEFfg(NpyJs}Bs`~vK zb(Y_Dq^qHEI;@me2n*5VjXAxyDa? zXQazzv^ZRBMEV-&j&a2y1HuwuO!e*knyG5an8U0i>Cxo#kCICIz0OBJe!nUftxL(J zYvUKL;3@|}kM5;Wy={A)0YhfIa2#^w^B zk?&FXit4WnY!y{Dx_X+Grm32wd4g!UQiPHiXWsyHjclp+6gr=MS3ab&+E6e61KdbsV&!B=i%}A`cE~lrcPpHLE+Dfs@ zM!@<(9~@vF$DXulAcc{?&a4`FjxTOgFnNxLk?tpIm!+v>p|R9WH93;9x~M|}GHqm4 zjdx^w4cz{nez-8$fe}9b{^3wpiCwg|tVie1dMEw|U3ZFsDE38&;S$ubE3r2QBoW;0 z7z5;wZkIZ?SZ`a~M1j=CKfc}RO{O8jFbw44nl@je)7I616@d|0sRbo^+Ciaz;O=Ok1`}R1S@{ zABaKq&crrFR6g!{)V%x%l4quSzs{#_Q10!ne}o?i}qs+Yw}U41Pj9nuNDk862z zNlwb=2l5<`NFSMSK1V@>G*m%M_CKEb9pDyaymBD=erA_;%fs5PhNa{+1E)LV_H+`{S$PqmTx5tFCcb2l>@)^{{W{#@XHf%dyg@Y2d!_kZKhmu?c?Nc zB=$~E@F`#6gw-_~Zquq}rDJBAoxY-ox!nlbV|h6L01SbKe#ff&mX&96;~>dOx9T?^ z5Jh=uesAs5bdd^j522iocGV&cUu|y?^wyuYJkwn2XL+frW0#LEJ+jRi$8g91?apz* z&sRRpA^VH6`XFh5W^xYk-yG^++e@}k$hLeEqm;w}F}jDpmS&!jR@bu4Je2YzNJ$ZS zB*@AJMli%=_108iU{@3aIi%*WviN~Mg4Z?w0K_Y5XsVK`vXYugi6eNaH!Fx59hl0Z zxW-OF=_e3n%Zp{f#27~`_eTI zr#!m)a}>{r+FG^4Si6|ja^z^A#Y?@`%xY~&AG9#G$^{#iZ z-=r1oZmOsfxZKMka3%E-j1itO?eWy@-Jl5ZkeK90O}{f#nap@s zgoh5pAxLF8n`!L744rz!2Z%=!E1&Z9z)Lq<^o@wYEE?^n^5S zQHSMla6#+O3=>45#GEKsR_GtY4vM~M-9J@b9d$)TT_rry%JYK7om8H7=iiRI0~brT zWHi_SdUfklJY{RUzJ2FZNd$q|CO`wWKBlkyAFh_Z)KXohIGRWm6k$T7j*G|Wqo}6P z5Va&%c)kEO+J@u<@c_`DiSo5xv8`Ef+onKIzvct}eFg@%h&_JsL|UH;(^7xxl1IRr zQfi+FY|?A%ZJ+9H0+pn$gb_F_i4-X67b}61iO#$-cb}YcQ)nk+P_~=`o+4;=Y zJX#j&6(6_)eidtKyG|_>FX<#giZh-)BcIo(+obDtK}TYKR3+00Lxr^93MQkqQ(Sx? z*EjmaHFp|Hni?urm5Ew)mQ1^-{{R^Yv=f{LYyeJrs<7eq>EQ2QY4gZP>^pa0$Yhnc-kVfvBfE0*5 zD|9O$3q1{HtrJ*3ePhR;@=&liMp{molrwcQl7{5<488*ucOX zeY)_vr390`W@NRRNli@+MO+IkNg$DcZOhzr%@YKJT2wGp4!~1a!+UKdf`RGiDb@;g za^ftKpx_R~{{W{$xZ>OeOEPJ#j#+VEn^JS})2S=ythSqEGVG?SsVg=Isg+|67e5)n zKc`i`##=UDQ<0RWN2w$e>lGvITg$fL4KmSy>b%{+^GC;jK~sMP8j`-V!Yl0z^BHOF zmu-~F**g|!x4QfhxE2H0vwZZ!+D4L_^;dQ8?sFsdOaS)H`+@M^-p@_lYpL}DhZUf}8ZAskE{{V5QziXT;<8RL*2mb)% zQ2jrNTAH5=9qPSDz^j-40QJ=$ioUfU0;c}}>ZYvn9e=bJ5T#C6>g2%~>_+b0d*ZC% zn)mkkhT^&&#V^)dDTIooJPPCij!4g3Mb^^Y%FvyrulR`660^DcfNJZ)%RE{SRh#9m zYK5-2TV$%2%#`d``l(^cg~xP79Ot%r>l_IQ{T;i}=F&&HYBKHjNpjy;kd%@;frIM? zDtC%LE$UyVq@#wn=~kkw7m^IC3RY5YJS~%`A@N#lJ`s6HKI^D=j$XS99Is5cAK*yEffwa)| z4Cdjc8c7vY?Tq0v21J2J`gF-?4c+M-{*z61R-w?-R$19$J+YI=&N`7BMJe4R)kD-Y z^AgznzQga)0U;w5w18Rn{i-(-OR; + stages: Stage[]; textures: WebGLTexture[]; @@ -25,30 +27,36 @@ export class Foo implements Renderable { width: number; height: number; + constructor(gl: WebGLRenderingContext, width: number, height: number) { + this.uniforms = {}; this.width = width; this.height = height; for (let ii = 0; ii < 2; ++ii) { const texture = createAndSetupTexture(gl); this.textures.push(texture); - + // make the texture the same size as the image gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - + // Create a framebuffer const fbo = gl.createFramebuffer(); this.framebuffers.push(fbo); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - + // Attach a texture to it. gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); } } + getUniforms(): Dictionary { + 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; + getUniforms(): Dictionary { + return this.uniforms; + } + render(gl: WebGLRenderingContext) { this.program.bind(gl); diff --git a/frontend/www/webgl/renderer.ts b/frontend/www/webgl/renderer.ts index 38fc65d..b9e2f9f 100644 --- a/frontend/www/webgl/renderer.ts +++ b/frontend/www/webgl/renderer.ts @@ -6,18 +6,17 @@ import { Texture } from './texture'; import { Dictionary } from './util'; export interface Renderable { + getUniforms() : Dictionary; render(gl: WebGLRenderingContext): void; } -class RenderShit implements Renderable { +export class RenderShit implements Renderable { ibo: IndexBuffer; va: VertexArray; shader: Shader; textures: Texture[]; uniforms: Dictionary; - enabled: boolean; - constructor( ibo: IndexBuffer, va: VertexArray, @@ -25,7 +24,6 @@ class RenderShit implements Renderable { textures: Texture[], uniforms: Dictionary, ) { - this.enabled = true; this.ibo = ibo; this.va = va; this.shader = shader; @@ -33,10 +31,11 @@ class RenderShit implements Renderable { this.uniforms = uniforms; } + getUniforms(): Dictionary { + return this.uniforms; + } + render(gl: WebGLRenderingContext): void { - if (!this.enabled) { - return; - } const indexBuffer = this.ibo; const vertexArray = this.va; @@ -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) => 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, 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); } diff --git a/frontend/www/webgl/text.ts b/frontend/www/webgl/text.ts new file mode 100644 index 0000000..ce72cb0 --- /dev/null +++ b/frontend/www/webgl/text.ts @@ -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; +} + +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 { + 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); +}