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 0000000..1724e0d Binary files /dev/null and b/frontend/www/static/res/assets/font.png differ diff --git a/frontend/www/static/res/assets/leaves.jpg b/frontend/www/static/res/assets/leaves.jpg new file mode 100644 index 0000000..529b94e Binary files /dev/null and b/frontend/www/static/res/assets/leaves.jpg differ diff --git a/frontend/www/static/shaders/frag/image.glsl b/frontend/www/static/shaders/frag/image.glsl new file mode 100644 index 0000000..69c8b91 --- /dev/null +++ b/frontend/www/static/shaders/frag/image.glsl @@ -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); +} diff --git a/frontend/www/static/shaders/vert/image.glsl b/frontend/www/static/shaders/vert/image.glsl new file mode 100644 index 0000000..5dd3f56 --- /dev/null +++ b/frontend/www/static/shaders/vert/image.glsl @@ -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; +} diff --git a/frontend/www/static/shaders/vert/simple.glsl b/frontend/www/static/shaders/vert/simple.glsl index 49026b1..935decf 100644 --- a/frontend/www/static/shaders/vert/simple.glsl +++ b/frontend/www/static/shaders/vert/simple.glsl @@ -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; } diff --git a/frontend/www/webgl/instance.ts b/frontend/www/webgl/instance.ts index ec85144..6020940 100644 --- a/frontend/www/webgl/instance.ts +++ b/frontend/www/webgl/instance.ts @@ -17,6 +17,8 @@ function createAndSetupTexture(gl: WebGLRenderingContext): WebGLTexture { } export class Foo implements Renderable { + uniforms: Dictionary; + 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); +}