Frontend/visualiser: enhancement show ship_count in visualiser (#23)
* make all fleets fly forward * make things beautiful
This commit is contained in:
parent
33abe9515f
commit
998cb3d535
13 changed files with 518 additions and 193 deletions
|
@ -14,6 +14,12 @@ mod types;
|
|||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||
// allocator.
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
|
@ -25,11 +31,17 @@ pub struct Circle {
|
|||
r: f32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
a1: f32,
|
||||
a2: f32,
|
||||
a0: f32,
|
||||
ad: f32,
|
||||
distance: usize,
|
||||
}
|
||||
|
||||
use std::f32::consts::PI;
|
||||
fn spr(from: f32) -> f32 {
|
||||
let pi2 = PI*2.;
|
||||
((from % pi2) + pi2) % pi2
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
|
||||
let x1 = p1.x;
|
||||
|
@ -37,101 +49,55 @@ impl Circle {
|
|||
let x2 = p2.x;
|
||||
let y2 = p2.y;
|
||||
|
||||
// Distance between planets
|
||||
let q = ((x2-x1).powi(2) + (y2-y1).powi(2)).sqrt();
|
||||
// Center of between planets
|
||||
let x3 = (x1+x2)/2.0;
|
||||
let y3 = (y1+y2)/2.0;
|
||||
|
||||
let r = q * 1.1;
|
||||
// Radius of circle
|
||||
let r = q * 1.0;
|
||||
|
||||
let mut x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
|
||||
let mut y = y3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
|
||||
// Center of circle
|
||||
let x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
|
||||
let y = y3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
|
||||
// console_log!("{},{} -> {},{} ({},{} r={})", x1, y1, x2, y2, x, y, r);
|
||||
|
||||
let a0 = spr((y - y1).atan2(x - x1));
|
||||
let a2 = spr((y - y2).atan2(x - x2));
|
||||
|
||||
let mut a1 = (y - y1).atan2(x - x1);
|
||||
let mut a2 = (y - y2).atan2(x - x2);
|
||||
|
||||
if a2 < a1 {
|
||||
|
||||
x = x3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
|
||||
y = y3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
|
||||
|
||||
a1 = (y - y1).atan2(x - x1);
|
||||
a2 = (y - y2).atan2(x - x2);
|
||||
let mut ad = spr(a0 - a2);
|
||||
if ad > PI {
|
||||
ad = spr(a2 - a0);
|
||||
}
|
||||
// console_log!("a1 {} a2 {} ad {}", a0/PI * 180.0, a2/PI * 180.0, ad/PI*180.0);
|
||||
|
||||
let distance = q.ceil() as usize + 1;
|
||||
|
||||
Self {
|
||||
r, x, y, a1, a2, distance
|
||||
r, x, y, a0, ad, distance
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
|
||||
pub fn get_for_remaining(&self, remaining: usize) -> ((Mat3<f32>, f32), (Mat3<f32>, f32)) {
|
||||
(
|
||||
self.get_remaining(remaining),
|
||||
self.get_remaining((remaining + 1).min(self.distance - 1)),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
|
||||
let alpha = (self.a1 * remaining as f32 + (self.distance - remaining) as f32 * self.a2) / self.distance as f32;
|
||||
fn get_remaining(&self, remaining: usize) -> (Mat3<f32>, f32) {
|
||||
let alpha = self.a0 + (1.0 - (remaining as f32 / self.distance as f32)) * self.ad;
|
||||
|
||||
let cos = alpha.cos();
|
||||
let sin = alpha.sin();
|
||||
Mat3::new(
|
||||
(Mat3::new(
|
||||
0.3, 0.0, 0.0,
|
||||
0.0, 0.4, 0.0,
|
||||
0.0, 0.3, 0.0,
|
||||
-self.x + cos * self.r, -self.y + sin * self.r, 0.3,
|
||||
) * Mat3::rotate_z(alpha)
|
||||
), alpha)
|
||||
}
|
||||
}
|
||||
|
||||
// struct Line {
|
||||
// x1: f32,
|
||||
// y1: f32,
|
||||
// x2: f32,
|
||||
// y2: f32,
|
||||
// a: f32,
|
||||
// d: usize,
|
||||
// }
|
||||
// impl Line {
|
||||
// pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
|
||||
// let dx = p1.x - p2.x;
|
||||
// let dy = p1.y - p2.y;
|
||||
// let a = dy.atan2(dx);
|
||||
// // let a = (dy / dx).atan();
|
||||
// let d = (dx * dx + dy * dy).sqrt().ceil() as usize + 1;
|
||||
|
||||
// Self {
|
||||
// x1: p1.x,
|
||||
// x2: p2.x,
|
||||
// y1: p1.y,
|
||||
// y2: p2.y,
|
||||
// d, a,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
|
||||
// (
|
||||
// self.get_remaining(remaining),
|
||||
// self.get_remaining((remaining + 1).min(self.d - 1)),
|
||||
// )
|
||||
// }
|
||||
|
||||
// fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
|
||||
// let x = (self.x1 * remaining as f32 + (self.d - remaining) as f32 * self.x2) / self.d as f32;
|
||||
// // let x = self.x1 + (remaining as f32 / self.d as f32) * (self.x2 - self.x1);
|
||||
// let y = (self.y1 * remaining as f32 + (self.d - remaining) as f32 * self.y2) / self.d as f32;
|
||||
|
||||
// // let y = self.y1 + (remaining as f32 / self.d as f32) * (self.y2 - self.y1);
|
||||
// Mat3::new(
|
||||
// 0.3, 0.0, 0.0,
|
||||
// 0.0, 0.3, 0.0,
|
||||
// x, y, 0.3,
|
||||
// ) * Mat3::rotate_z(self.a)
|
||||
// }
|
||||
// }
|
||||
|
||||
fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, Vec<usize>) {
|
||||
let mut verts: Vec<Vec2<f32>> = planets.iter().map(|p| Vec2::new(p.x, p.y)).collect();
|
||||
let mut ids = Vec::new();
|
||||
|
@ -173,9 +139,13 @@ pub struct Game {
|
|||
view_box: Vec<f32>,
|
||||
|
||||
planets: Vec<Vec3<f32>>,
|
||||
planet_ships: Vec<usize>,
|
||||
|
||||
ship_locations: Vec<[f32;9]>,
|
||||
ship_label_locations: Vec<[f32;9]>,
|
||||
ship_colours: Vec<Vec3<f32>>,
|
||||
ship_counts: Vec<usize>,
|
||||
|
||||
current_planet_colours: Vec<Vec3<f32>>,
|
||||
|
||||
voronoi_vertices: Vec<Vec2<f32>>,
|
||||
|
@ -188,6 +158,8 @@ impl Game {
|
|||
pub fn new(file: &str) -> Self {
|
||||
utils::set_panic_hook();
|
||||
|
||||
console_log!("Rust is busy being awesome!");
|
||||
|
||||
// First line is fucked but we just filter out things that cannot parse
|
||||
let states: Vec<types::State> = file.split("\n").filter_map(|line|
|
||||
serde_json::from_str(line).ok()
|
||||
|
@ -210,13 +182,16 @@ impl Game {
|
|||
|
||||
Self {
|
||||
planets: utils::get_planets(&states[0].planets, 2.0),
|
||||
planet_ships: Vec::new(),
|
||||
view_box,
|
||||
|
||||
planet_map,
|
||||
turn: 0,
|
||||
states,
|
||||
ship_locations: Vec::new(),
|
||||
ship_label_locations: Vec::new(),
|
||||
ship_colours: Vec::new(),
|
||||
ship_counts: Vec::new(),
|
||||
current_planet_colours: Vec::new(),
|
||||
|
||||
voronoi_vertices,
|
||||
|
@ -233,6 +208,10 @@ impl Game {
|
|||
self.planets.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_planet_ships(&self) -> *const usize {
|
||||
self.planet_ships.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_planet_colors(&self) -> *const Vec3<f32> {
|
||||
self.current_planet_colours.as_ptr()
|
||||
}
|
||||
|
@ -248,13 +227,19 @@ impl Game {
|
|||
pub fn update_turn(&mut self, turn: usize) -> usize {
|
||||
self.turn = turn.min(self.states.len() -1);
|
||||
|
||||
self.update_planet_ships();
|
||||
self.update_planet_colours();
|
||||
self.update_voronoi_colors();
|
||||
self.update_ship_locations();
|
||||
self.update_ship_counts();
|
||||
|
||||
self.turn
|
||||
}
|
||||
|
||||
fn update_planet_ships(&mut self) {
|
||||
self.planet_ships = self.states[self.turn].planets.iter().map(|p| p.ship_count as usize).collect();
|
||||
}
|
||||
|
||||
fn update_voronoi_colors(&mut self) {
|
||||
for (i, p) in self.states[self.turn].planets.iter().enumerate() {
|
||||
self.voronoi_colors[i] = utils::COLORS[p.owner.unwrap_or(0) as usize % utils::COLORS.len()].into()
|
||||
|
@ -279,19 +264,32 @@ impl Game {
|
|||
}
|
||||
|
||||
fn update_ship_locations(&mut self) {
|
||||
let mut new_vec = Vec::new();
|
||||
self.ship_locations = Vec::new();
|
||||
self.ship_label_locations = Vec::new();
|
||||
let t = Mat3::new(0.2, 0., 0.,
|
||||
0., 0.2, 0.0,
|
||||
0., -0.5, 0.2);
|
||||
|
||||
for ship in self.states[self.turn].expeditions.iter() {
|
||||
let (o1, o2) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize);
|
||||
new_vec.push(o1.to_array());
|
||||
new_vec.push(o2.to_array());
|
||||
let ((o1, a1), (o2, a2)) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize);
|
||||
self.ship_locations.push((o1 * Mat3::rotate_z(a1)).to_array());
|
||||
self.ship_locations.push((o2 * Mat3::rotate_z(a2)).to_array());
|
||||
|
||||
self.ship_label_locations.push((o1 + t).to_array());
|
||||
self.ship_label_locations.push((o2 + t).to_array());
|
||||
}
|
||||
self.ship_locations = new_vec;
|
||||
|
||||
self.ship_colours = self.states[self.turn].expeditions.iter().map(|s| {
|
||||
utils::COLORS[s.owner as usize % utils::COLORS.len()].into()
|
||||
}).collect();
|
||||
}
|
||||
|
||||
fn update_ship_counts(&mut self) {
|
||||
self.ship_counts = self.states[self.turn].expeditions.iter().map(|s| {
|
||||
s.ship_count as usize
|
||||
}).collect();
|
||||
}
|
||||
|
||||
pub fn get_max_ships(&self) -> usize {
|
||||
self.states.iter().map(|s| s.expeditions.len()).max().unwrap()
|
||||
}
|
||||
|
@ -304,10 +302,18 @@ impl Game {
|
|||
self.ship_locations.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_ship_label_locations(&self) -> *const [f32;9] {
|
||||
self.ship_label_locations.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_ship_colours(&self) -> *const Vec3<f32> {
|
||||
self.ship_colours.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_ship_counts(&self) -> *const usize {
|
||||
self.ship_counts.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_voronoi_vert_count(&self) -> usize {
|
||||
self.voronoi_vertices.len()
|
||||
}
|
||||
|
@ -331,6 +337,8 @@ impl Game {
|
|||
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
extern "C" {
|
||||
fn alert(s: &str);
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
}
|
||||
|
|
12
frontend/www/bootstrap.js
vendored
12
frontend/www/bootstrap.js
vendored
|
@ -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));
|
|
@ -102,3 +102,5 @@ function parse_ini(inifile: string) {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
on_load();
|
||||
|
|
|
@ -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 = <HTMLInputElement>document.getElementById("turnSlider");
|
||||
const FILESELECTOR = <HTMLInputElement> document.getElementById("fileselect");
|
||||
const SPEED = <HTMLInputElement> document.getElementById("speed");
|
||||
const FILESELECTOR = <HTMLInputElement>document.getElementById("fileselect");
|
||||
const SPEED = <HTMLInputElement>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 = <HTMLCanvasElement>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;
|
||||
|
||||
|
@ -89,12 +103,21 @@ class GameInstance {
|
|||
constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) {
|
||||
this.game = game;
|
||||
this.planet_count = this.game.get_planet_count();
|
||||
this.shader = SHADERFACOTRY.create_shader(GL, {"MAX_CIRCLES": ''+this.planet_count});
|
||||
this.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,12 +143,12 @@ 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,
|
||||
-planets[i * 3], -planets[i * 3 + 1], 1,
|
||||
]);
|
||||
|
||||
const indexBuffer = new IndexBuffer(GL, meshes[i % meshes.length].cells);
|
||||
|
@ -147,6 +170,19 @@ class GameInstance {
|
|||
);
|
||||
}
|
||||
|
||||
{
|
||||
const transform = new UniformMatrix3fv([
|
||||
1., 0, 0,
|
||||
0, 1., 0,
|
||||
-planets[i * 3], -planets[i * 3 + 1] -1.2, 1.,
|
||||
]);
|
||||
|
||||
const label = this.text_factory.build(GL, transform);
|
||||
this.renderer.addRenderable(label);
|
||||
this.planet_labels.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
this.turn_count = game.turn_count();
|
||||
|
||||
this.ship_indices = [];
|
||||
|
@ -166,6 +202,10 @@ class GameInstance {
|
|||
{}
|
||||
)
|
||||
);
|
||||
|
||||
const label = this.text_factory.build(GL);
|
||||
this.ship_labels.push(label);
|
||||
this.renderer.addRenderable(label)
|
||||
}
|
||||
|
||||
this.vor_shader.uniform(GL, "u_planets", new Uniform3fv(planets));
|
||||
|
@ -180,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(<string> reader.result);
|
||||
reader.onload = function () {
|
||||
set_instance(<string>reader.result);
|
||||
}
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
SPEED.onchange = function() {
|
||||
SPEED.onchange = function () {
|
||||
ms_per_frame = parseInt(SPEED.value);
|
||||
}
|
||||
|
||||
|
|
BIN
frontend/www/static/res/assets/font.png
Normal file
BIN
frontend/www/static/res/assets/font.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 912 B |
BIN
frontend/www/static/res/assets/leaves.jpg
Normal file
BIN
frontend/www/static/res/assets/leaves.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
14
frontend/www/static/shaders/frag/image.glsl
Normal file
14
frontend/www/static/shaders/frag/image.glsl
Normal 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);
|
||||
}
|
33
frontend/www/static/shaders/vert/image.glsl
Normal file
33
frontend/www/static/shaders/vert/image.glsl
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ function createAndSetupTexture(gl: WebGLRenderingContext): WebGLTexture {
|
|||
}
|
||||
|
||||
export class Foo implements Renderable {
|
||||
uniforms: Dictionary<Uniform>;
|
||||
|
||||
stages: Stage[];
|
||||
|
||||
textures: WebGLTexture[];
|
||||
|
@ -25,7 +27,9 @@ export class Foo implements Renderable {
|
|||
width: number;
|
||||
height: number;
|
||||
|
||||
|
||||
constructor(gl: WebGLRenderingContext, width: number, height: number) {
|
||||
this.uniforms = {};
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
|
@ -49,6 +53,10 @@ export class Foo implements Renderable {
|
|||
}
|
||||
}
|
||||
|
||||
getUniforms(): Dictionary<Uniform> {
|
||||
return this.uniforms;
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext) {
|
||||
this.stages.forEach( (item, i) => {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i%2]);
|
||||
|
@ -62,6 +70,10 @@ class Stage implements Renderable {
|
|||
program: Shader;
|
||||
uniforms: Dictionary<Uniform>;
|
||||
|
||||
getUniforms(): Dictionary<Uniform> {
|
||||
return this.uniforms;
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext) {
|
||||
this.program.bind(gl);
|
||||
|
||||
|
|
|
@ -6,18 +6,17 @@ import { Texture } from './texture';
|
|||
import { Dictionary } from './util';
|
||||
|
||||
export interface Renderable {
|
||||
getUniforms() : Dictionary<Uniform>;
|
||||
render(gl: WebGLRenderingContext): void;
|
||||
}
|
||||
|
||||
class RenderShit implements Renderable {
|
||||
export class RenderShit implements Renderable {
|
||||
ibo: IndexBuffer;
|
||||
va: VertexArray;
|
||||
shader: Shader;
|
||||
textures: Texture[];
|
||||
uniforms: Dictionary<Uniform>;
|
||||
|
||||
enabled: boolean;
|
||||
|
||||
constructor(
|
||||
ibo: IndexBuffer,
|
||||
va: VertexArray,
|
||||
|
@ -25,7 +24,6 @@ class RenderShit implements Renderable {
|
|||
textures: Texture[],
|
||||
uniforms: Dictionary<Uniform>,
|
||||
) {
|
||||
this.enabled = true;
|
||||
this.ibo = ibo;
|
||||
this.va = va;
|
||||
this.shader = shader;
|
||||
|
@ -33,11 +31,12 @@ class RenderShit implements Renderable {
|
|||
this.uniforms = uniforms;
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext): void {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
getUniforms(): Dictionary<Uniform> {
|
||||
return this.uniforms;
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext): void {
|
||||
|
||||
const indexBuffer = this.ibo;
|
||||
const vertexArray = this.va;
|
||||
const uniforms = this.uniforms;
|
||||
|
@ -73,35 +72,34 @@ class RenderShit implements Renderable {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Renderer {
|
||||
renderables: RenderShit[];
|
||||
renderables: [Renderable, boolean][];
|
||||
|
||||
constructor() {
|
||||
this.renderables = [];
|
||||
}
|
||||
|
||||
updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void) {
|
||||
f(this.renderables[i].uniforms);
|
||||
f(this.renderables[i][0].getUniforms());
|
||||
}
|
||||
|
||||
disableRenderShift(i: number) {
|
||||
this.renderables[i].enabled = false;
|
||||
this.renderables[i][1] = false;
|
||||
}
|
||||
|
||||
enableRendershit(i: number) {
|
||||
this.renderables[i].enabled = true;
|
||||
this.renderables[i][1] = true;
|
||||
}
|
||||
|
||||
// addRenderable(item: Renderable) {
|
||||
// this.renderables.push(item);
|
||||
// }
|
||||
addRenderable(item: Renderable): number {
|
||||
this.renderables.push([item, true]);
|
||||
return this.renderables.length - 1;
|
||||
}
|
||||
|
||||
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary<Uniform>, texture?: Texture[]): number {
|
||||
|
||||
this.renderables.push(
|
||||
return this.addRenderable(
|
||||
new RenderShit(
|
||||
indexBuffer,
|
||||
vertexArray,
|
||||
|
@ -110,8 +108,6 @@ export class Renderer {
|
|||
uniforms || {},
|
||||
)
|
||||
);
|
||||
|
||||
return this.renderables.length - 1;
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext, frameBuffer?: WebGLFramebuffer, width?: number, height?: number) {
|
||||
|
@ -121,7 +117,8 @@ export class Renderer {
|
|||
|
||||
const maxTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
|
||||
|
||||
for (let r of this.renderables) {
|
||||
for (let [r, e] of this.renderables) {
|
||||
if (!e) continue;
|
||||
r.render(gl);
|
||||
}
|
||||
|
||||
|
|
186
frontend/www/webgl/text.ts
Normal file
186
frontend/www/webgl/text.ts
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue