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

* make all fleets fly forward

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

View file

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

View file

@ -2,14 +2,20 @@
// asynchronously. This `bootstrap.js` file does the single async import, so // asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again. // that no one else needs to worry about it again.
// Import index.js that executes index.ts // Import index.js that executes index.ts
var h = (_a, _b) => { } var h = (_a, _b) => {}
export function handle(loc, e) { export function handle(loc, e) {
h(loc, e); h(loc, e);
} }
import("./index.js") if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, {
.then(e => { print: function() {
console.log("Hello world");
}
});
import ("./index.js")
.then(e => {
h = e.handle; h = e.handle;
}) })
.catch(e => console.error("Error importing `index.js`:", e)); .catch(e => console.error("Error importing `index.js`:", e));

View file

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

View file

@ -5,7 +5,9 @@ import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Unifor
import { Renderer } from "./webgl/renderer"; import { Renderer } from "./webgl/renderer";
import { VertexBuffer, IndexBuffer } from "./webgl/buffer"; import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout"; import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
import { Texture } from "./webgl/texture";
import { callbackify } from "util"; import { callbackify } from "util";
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
function f32v(ptr: number, size: number): Float32Array { function f32v(ptr: number, size: number): Float32Array {
return new Float32Array(memory.buffer, ptr, size); return new Float32Array(memory.buffer, ptr, size);
@ -27,8 +29,8 @@ const COUNTER = new FPSCounter();
const LOADER = document.getElementById("main"); const LOADER = document.getElementById("main");
const SLIDER = <HTMLInputElement>document.getElementById("turnSlider"); const SLIDER = <HTMLInputElement>document.getElementById("turnSlider");
const FILESELECTOR = <HTMLInputElement> document.getElementById("fileselect"); const FILESELECTOR = <HTMLInputElement>document.getElementById("fileselect");
const SPEED = <HTMLInputElement> document.getElementById("speed"); const SPEED = <HTMLInputElement>document.getElementById("speed");
export function set_loading(loading: boolean) { export function set_loading(loading: boolean) {
if (loading) { 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); export const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
const CANVAS = <HTMLCanvasElement>document.getElementById("c"); const CANVAS = <HTMLCanvasElement>document.getElementById("c");
const RESOLUTION = [CANVAS.width, CANVAS.height]; 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" LOCATION + "static/shaders/frag/vor.glsl", LOCATION + "static/shaders/vert/vor.glsl"
).then((e) => VOR_SHADER_FACTORY = e); ).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 { class GameInstance {
resizer: Resizer; resizer: Resizer;
game: Game; game: Game;
shader: Shader; shader: Shader;
vor_shader: Shader; vor_shader: Shader;
image_shader: Shader;
text_factory: LabelFactory;
planet_labels: Label[];
ship_labels: Label[];
renderer: Renderer; renderer: Renderer;
planet_count: number; planet_count: number;
@ -86,15 +100,24 @@ class GameInstance {
turn_count = 0; turn_count = 0;
constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) { constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) {
this.game = game; this.game = game;
this.planet_count = this.game.get_planet_count(); 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.resizer = new Resizer(CANVAS, [...f32v(game.get_viewbox(), 4)], true);
this.renderer = new Renderer(); this.renderer = new Renderer();
this.game.update_turn(0); this.game.update_turn(0);
const indexBuffer = new IndexBuffer(GL, [ const indexBuffer = new IndexBuffer(GL, [
0, 1, 2, 0, 1, 2,
1, 2, 3, 1, 2, 3,
@ -120,31 +143,44 @@ class GameInstance {
const planets = f32v(game.get_planets(), this.planet_count * 3); 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([ const indexBuffer = new IndexBuffer(GL, meshes[i % meshes.length].cells);
1, 0, 0, const positionBuffer = new VertexBuffer(GL, meshes[i % meshes.length].positions);
0, 1, 0,
-planets[i*3], -planets[i*3+1], 1,
]);
const indexBuffer = new IndexBuffer(GL, meshes[i % meshes.length].cells); const layout = new VertexBufferLayout();
const positionBuffer = new VertexBuffer(GL, meshes[i % meshes.length].positions); layout.push(GL.FLOAT, 3, 4, "a_position");
const vao = new VertexArray();
vao.addBuffer(positionBuffer, layout);
const layout = new VertexBufferLayout(); this.renderer.addToDraw(
layout.push(GL.FLOAT, 3, 4, "a_position"); indexBuffer,
const vao = new VertexArray(); vao,
vao.addBuffer(positionBuffer, layout); this.shader,
{
"u_trans": transform,
"u_trans_next": transform,
}
);
}
this.renderer.addToDraw( {
indexBuffer, const transform = new UniformMatrix3fv([
vao, 1., 0, 0,
this.shader, 0, 1., 0,
{ -planets[i * 3], -planets[i * 3 + 1] -1.2, 1.,
"u_trans": transform, ]);
"u_trans_next": transform,
} const label = this.text_factory.build(GL, transform);
); this.renderer.addRenderable(label);
this.planet_labels.push(label);
}
} }
this.turn_count = game.turn_count(); 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)); this.vor_shader.uniform(GL, "u_planets", new Uniform3fv(planets));
@ -180,40 +220,59 @@ class GameInstance {
_update_state() { _update_state() {
const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6); 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)); this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours));
for(let i=0; i < this.planet_count; i++){ for (let i = 0; i < this.planet_count; i++) {
const u = new Uniform3f(colours[i*6], colours[i*6 + 1], colours[i*6 + 2]); const u = new Uniform3f(colours[i * 6], colours[i * 6 + 1], colours[i * 6 + 2]);
this.renderer.updateUniform(i+1, (us) => us["u_color"] = u); this.renderer.updateUniform(2 * i + 1, (us) => us["u_color"] = u);
const u2 = new Uniform3f(colours[i*6 + 3], colours[i*6 + 4], colours[i*6 + 5]); const u2 = new Uniform3f(colours[i * 6 + 3], colours[i * 6 + 4], colours[i * 6 + 5]);
this.renderer.updateUniform(i+1, (us) => us["u_color_next"] = u2); this.renderer.updateUniform(2 * i + 1, (us) => us["u_color_next"] = u2);
this.planet_labels[i].setText(GL, "*"+planet_ships[i], Align.Middle, Align.Begin);
} }
const ships = f32v(this.game.get_ship_locations(), this.game.get_ship_count() * 9 * 2); const ship_count = this.game.get_ship_count();
const ship_colours = f32v(this.game.get_ship_colours(), this.game.get_ship_count() * 3); 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]; 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);
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 t1 = new UniformMatrix3fv(new Float32Array(ships, i * 18, 9));
// const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9)); // const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9));
const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 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 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) => { this.renderer.updateUniform(index, (us) => {
us["u_color"] = u; us["u_color"] = u;
us["u_color_next"] = u; us["u_color_next"] = u;
us["u_trans"] = t1; us["u_trans"] = t1;
us["u_trans_next"] = t2; us["u_trans_next"] = t2;
}); });
this.renderer.updateUniform(index+1, (us) => {
us["u_trans"] = tl1;
us["u_trans_next"] = tl2;
});
} else { } else {
this.renderer.disableRenderShift(index); 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.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.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); this.renderer.render(GL);
return; 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_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_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); 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); this.renderer.render(GL);
} }
@ -332,33 +396,33 @@ export async function set_instance(source: string) {
set_loading(false); set_loading(false);
} }
window.addEventListener('resize', function() { window.addEventListener('resize', function () {
resizeCanvasToDisplaySize(CANVAS); resizeCanvasToDisplaySize(CANVAS);
if (game_instance) { if (game_instance) {
game_instance.on_resize(); game_instance.on_resize();
} }
}, { capture: false, passive: true}) }, { capture: false, passive: true })
SLIDER.oninput = function() { SLIDER.oninput = function () {
if (game_instance) { if (game_instance) {
game_instance.updateTurn(parseInt(SLIDER.value)); game_instance.updateTurn(parseInt(SLIDER.value));
} }
} }
FILESELECTOR.onchange = function(){ FILESELECTOR.onchange = function () {
const file = FILESELECTOR.files[0]; const file = FILESELECTOR.files[0];
if(!file) { return; } if (!file) { return; }
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function() { reader.onload = function () {
set_instance(<string> reader.result); set_instance(<string>reader.result);
} }
reader.readAsText(file); reader.readAsText(file);
} }
SPEED.onchange = function() { SPEED.onchange = function () {
ms_per_frame = parseInt(SPEED.value); ms_per_frame = parseInt(SPEED.value);
} }

View file

@ -1,46 +1,46 @@
{ {
"name": "create-wasm-app", "name": "create-wasm-app",
"version": "0.1.0", "version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages", "description": "create an app to consume rust-generated wasm packages",
"main": "./bootstrap.js", "main": "./bootstrap.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"scripts": { "scripts": {
"develop": "webpack --mode development --watch", "develop": "webpack --mode development --watch",
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"start": "webpack-dev-server --content-base dist/" "start": "webpack-dev-server --content-base dist/"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git" "url": "git+https://github.com/rustwasm/create-wasm-app.git"
}, },
"keywords": [ "keywords": [
"webassembly", "webassembly",
"wasm", "wasm",
"rust", "rust",
"webpack", "webpack",
"mozaic" "mozaic"
], ],
"dependencies": { "dependencies": {
"config-ini-parser": "^1.2.2", "config-ini-parser": "^1.2.2",
"extract-svg-path": "^2.1.0", "extract-svg-path": "^2.1.0",
"load-svg": "^1.0.0", "load-svg": "^1.0.0",
"planetwars": "file:../pkg", "planetwars": "file:../pkg",
"svg-mesh-3d": "^1.1.0", "svg-mesh-3d": "^1.1.0",
"ts-heap": "^1.1.3" "ts-heap": "^1.1.3"
}, },
"author": "Arthur Vercruysse <arthur.vercruysse@outlook.com>", "author": "Arthur Vercruysse <arthur.vercruysse@outlook.com>",
"license": "(MIT OR Apache-2.0)", "license": "(MIT OR Apache-2.0)",
"bugs": { "bugs": {
"url": "https://github.com/ajuvercr/Planetwars/issues" "url": "https://github.com/ajuvercr/Planetwars/issues"
}, },
"homepage": "https://github.com/ajuvercr/Planetwars#Readme", "homepage": "https://github.com/ajuvercr/Planetwars#Readme",
"devDependencies": { "devDependencies": {
"webpack": "^4.29.3", "webpack": "^4.29.3",
"ts-loader": "^6.0.2", "ts-loader": "^6.0.2",
"static-eval": ">=2.0.0", "static-eval": ">=2.0.0",
"typescript": "^3.5.2", "typescript": "^3.5.2",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5", "webpack-dev-server": "^3.1.5",
"copy-webpack-plugin": "^5.0.0" "copy-webpack-plugin": "^5.0.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

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

View file

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

View file

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

View file

@ -17,6 +17,8 @@ function createAndSetupTexture(gl: WebGLRenderingContext): WebGLTexture {
} }
export class Foo implements Renderable { export class Foo implements Renderable {
uniforms: Dictionary<Uniform>;
stages: Stage[]; stages: Stage[];
textures: WebGLTexture[]; textures: WebGLTexture[];
@ -25,30 +27,36 @@ export class Foo implements Renderable {
width: number; width: number;
height: number; height: number;
constructor(gl: WebGLRenderingContext, width: number, height: number) { constructor(gl: WebGLRenderingContext, width: number, height: number) {
this.uniforms = {};
this.width = width; this.width = width;
this.height = height; this.height = height;
for (let ii = 0; ii < 2; ++ii) { for (let ii = 0; ii < 2; ++ii) {
const texture = createAndSetupTexture(gl); const texture = createAndSetupTexture(gl);
this.textures.push(texture); this.textures.push(texture);
// make the texture the same size as the image // make the texture the same size as the image
gl.texImage2D( gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null); gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer // Create a framebuffer
const fbo = gl.createFramebuffer(); const fbo = gl.createFramebuffer();
this.framebuffers.push(fbo); this.framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it. // Attach a texture to it.
gl.framebufferTexture2D( gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
} }
} }
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) { render(gl: WebGLRenderingContext) {
this.stages.forEach( (item, i) => { this.stages.forEach( (item, i) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i%2]); gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i%2]);
@ -62,6 +70,10 @@ class Stage implements Renderable {
program: Shader; program: Shader;
uniforms: Dictionary<Uniform>; uniforms: Dictionary<Uniform>;
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) { render(gl: WebGLRenderingContext) {
this.program.bind(gl); this.program.bind(gl);

View file

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

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

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