* change wasm to real vectors

* Add volume bind command to README

* add changable host name variable

Co-authored-by: Francis <francisklinck@gmail.com>
This commit is contained in:
ajuvercr 2020-06-24 14:16:14 +02:00 committed by GitHub
parent 442f969da0
commit dcf1173cb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 755 additions and 470 deletions

38
Dockerfile Normal file
View file

@ -0,0 +1,38 @@
FROM rust:1.44 AS build-env
WORKDIR /sources
RUN git clone -b wasm32-target-fix https://github.com/drager/wasm-pack.git
WORKDIR wasm-pack
RUN rustup default nightly
RUN cargo install --path .
WORKDIR /planetwars
COPY . .
WORKDIR backend
RUN cargo build --release
WORKDIR ../frontend
RUN cargo update
RUN wasm-pack build
FROM node:10
COPY --from=build-env /planetwars /planetwars
WORKDIR /planetwars/frontend/www
RUN npm install
RUN npm run build
WORKDIR /planetwars/backend
EXPOSE 9142
EXPOSE 8123
EXPOSE 3012
CMD ["target/release/planetwars"]

9
README.md Normal file
View file

@ -0,0 +1,9 @@
# Planetwars
## Docker
Build: `docker build --tag pw:1.0 .`
Run: `docker run --rm -p 8123:8123 -p 9142:9142 -p 3012:3012 -v $(pwd)/backend/games:/planetwars/backend/games --name planetwars pw:1.0`
Add parameter `-e ROCKET_HOST_NAME=<domain>`, for example `-e ROCKET_HOST_NAME=mozaic.zeus.gent`, to set the domain used in the application.

View file

@ -1,3 +1,5 @@
# Planetwars backend # Planetwars backend
Change hostname in info slides with ROCKET_HOST_NAME env variable, or in `Rocket.toml`.
The main planetwars server that instanciates planetwars matches etc... The main planetwars server that instanciates planetwars matches etc...

View file

@ -1,6 +1,7 @@
[development] [development]
address = "localhost" address = "localhost"
port = 8000 port = 8000
host_name = "localhost"
[staging] [staging]
address = "0.0.0.0" address = "0.0.0.0"
@ -9,3 +10,4 @@ port = 80
[production] [production]
address = "0.0.0.0" address = "0.0.0.0"
port = 8123 port = 8123
host_name = "mozaic.zeus.gent"

0
backend/games/.keep Normal file
View file

View file

@ -1,4 +1,4 @@
#![feature(proc_macro_hygiene)] #![feature(proc_macro_hygiene, async_closure)]
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
@ -40,6 +40,7 @@ mod util;
use util::Games; use util::Games;
use util::COLOURS; use util::COLOURS;
use rocket::fairing::AdHoc;
use rocket_contrib::templates::tera::{self, Value}; use rocket_contrib::templates::tera::{self, Value};
use rocket_contrib::templates::{Engines, Template}; use rocket_contrib::templates::{Engines, Template};
@ -85,6 +86,14 @@ fn get_colour(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value>
)); ));
} }
struct HostNameFilter(String);
impl tera::Filter for HostNameFilter {
fn filter(&self, _: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
Ok(Value::String(self.0.clone()))
}
}
/// Async main function, starting logger, graph and rocket /// Async main function, starting logger, graph and rocket
#[launch] #[launch]
async fn rocket() -> rocket::Rocket { async fn rocket() -> rocket::Rocket {
@ -103,16 +112,25 @@ async fn rocket() -> rocket::Rocket {
let mut routes = Vec::new(); let mut routes = Vec::new();
routes::fuel(&mut routes); routes::fuel(&mut routes);
let tera = Template::custom(|engines: &mut Engines| {
engines.tera.register_filter("calc_viewbox", calc_viewbox);
engines.tera.register_filter("get_colour", get_colour);
});
rocket::ignite() rocket::ignite()
.manage(gm) .manage(gm)
.manage(pool) .manage(pool)
.manage(Games::new()) .manage(Games::new())
.attach(tera) .attach(AdHoc::on_attach("Assets Config", async move |mut rocket| {
let host_name = rocket.config().await
.get_str("host_name")
.unwrap_or("mozaic.zeus.gent")
.to_string();
let tera = Template::custom(move |engines: &mut Engines| {
let filter = HostNameFilter(host_name.clone());
engines.tera.register_filter("calc_viewbox", calc_viewbox);
engines.tera.register_filter("get_colour", get_colour);
engines.tera.register_filter("get_host_name", filter);
});
Ok(rocket.attach(tera))
}))
.mount("/", routes) .mount("/", routes)
} }
@ -125,5 +143,5 @@ async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
let ep = TcpEndpoint::new(addr, pool.clone()); let ep = TcpEndpoint::new(addr, pool.clone());
let gmb = gmb.add_endpoint(ep, "TCP endpoint"); let gmb = gmb.add_endpoint(ep, "TCP endpoint");
gmb.build("games.ini", pool).await.unwrap() gmb.build("games/games.json", pool).await.unwrap()
} }

View file

@ -36,11 +36,11 @@ impl PlanetWarsGame {
.map(|p| (p.name.clone(), p.id)) .map(|p| (p.name.clone(), p.id))
.collect(); .collect();
if let Err(_) = create_dir("static/games") { if let Err(_) = create_dir("games") {
println!("'static/games' already exists"); println!("'games' already exists");
} }
let file = File::create(format!("static/games/{}", location)).unwrap(); let file = File::create(format!("games/{}", location)).unwrap();
Self { Self {
state, state,

View file

@ -20,7 +20,13 @@ mod maps;
/// Handles all files located in the static folder /// Handles all files located in the static folder
#[get("/<file..>", rank = 6)] #[get("/<file..>", rank = 6)]
async fn files(file: PathBuf) -> Option<NamedFile> { async fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok() NamedFile::open(Path::new("static/").join(file)).await.ok()
}
/// Handles all games files to be served
#[get("/games/<loc>")]
async fn game_get(loc: String) -> Option<NamedFile> {
NamedFile::open(Path::new("games/").join(loc)).await.ok()
} }
/// Routes the index page, rendering the index Template. /// Routes the index page, rendering the index Template.
@ -62,6 +68,7 @@ pub fn fuel(routes: &mut Vec<Route>) {
routes.extend(routes![ routes.extend(routes![
files, files,
index, index,
game_get,
builder_get, builder_get,
visualizer_get, visualizer_get,
debug_get debug_get
@ -71,18 +78,20 @@ pub fn fuel(routes: &mut Vec<Route>) {
info::fuel(routes); info::fuel(routes);
} }
/// Reads games.ini /// Reads games.json
/// File that represents all played games /// File that represents all played games
/// Ready to be visualized /// Ready to be visualized
async fn get_played_games() -> Vec<GameState> { async fn get_played_games() -> Vec<GameState> {
match fs::File::open("games.ini").await { match fs::File::open("games/games.json").await {
Ok(file) => { Ok(file) => {
let file = BufReader::new(file); let file = BufReader::new(file);
file.lines() file.lines()
.filter_map(move |maybe| async { .filter_map(move |maybe| {
maybe async {
.ok() maybe
.and_then(|line| serde_json::from_str::<FinishedState>(&line).ok()) .ok()
.and_then(|line| serde_json::from_str::<FinishedState>(&line).ok())
}
}) })
.map(|state| state.into()) .map(|state| state.into())
.collect() .collect()

View file

@ -16,9 +16,9 @@ How to connect
<pre class="commands"> <pre class="commands">
<code> <code>
$ wget mozaic.zeus.gent/bot/runner.py $ wget {{ "" | get_host_name}}/bot/runner.py
$ wget mozaic.zeus.gent/bot/simple.py $ wget {{ "" | get_host_name}}/bot/simple.py
$ python3 runner.py -p 9142 --host mozaic.zeus.gent \ $ python3 runner.py -p 9142 --host {{ "" | get_host_name}}\
-n &lt;Your name&gt; -i &lt;Id from the lobby&gt; \ -n &lt;Your name&gt; -i &lt;Id from the lobby&gt; \
python3 simple.py python3 simple.py
</code> </code>

View file

@ -1,15 +1,15 @@
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate serde_json;
extern crate octoon_math; extern crate octoon_math;
extern crate serde_json;
extern crate voronoi; extern crate voronoi;
use octoon_math::{Mat3, Vec3, Vec2}; use octoon_math::Mat3;
use voronoi::{Point, voronoi, make_polygons}; use voronoi::{make_polygons, voronoi, Point};
mod utils;
mod types; mod types;
mod utils;
use std::collections::HashMap; use std::collections::HashMap;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -38,7 +38,7 @@ pub struct Circle {
use std::f32::consts::PI; use std::f32::consts::PI;
fn spr(from: f32) -> f32 { fn spr(from: f32) -> f32 {
let pi2 = PI*2.; let pi2 = PI * 2.;
((from % pi2) + pi2) % pi2 ((from % pi2) + pi2) % pi2
} }
@ -50,17 +50,17 @@ impl Circle {
let y2 = p2.y; let y2 = p2.y;
// Distance between planets // 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 // 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;
// Radius of circle // Radius of circle
let r = q * 1.0; let r = q * 1.0;
// Center of circle // Center of circle
let x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/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; 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); // console_log!("{},{} -> {},{} ({},{} r={})", x1, y1, x2, y2, x, y, r);
let a0 = spr((y - y1).atan2(x - x1)); let a0 = spr((y - y1).atan2(x - x1));
@ -74,7 +74,12 @@ impl Circle {
let distance = q.ceil() as usize + 1; let distance = q.ceil() as usize + 1;
Self { Self {
r, x, y, a0, ad, distance r,
x,
y,
a0,
ad,
distance,
} }
} }
@ -90,19 +95,31 @@ impl Circle {
let cos = alpha.cos(); let cos = alpha.cos();
let sin = alpha.sin(); let sin = alpha.sin();
(Mat3::new( (
0.3, 0.0, 0.0, Mat3::new(
0.0, 0.3, 0.0, 0.3,
-self.x + cos * self.r, -self.y + sin * self.r, 0.3, 0.0,
), alpha) 0.0,
0.0,
0.3,
0.0,
-self.x + cos * self.r,
-self.y + sin * self.r,
0.3,
),
alpha,
)
} }
} }
fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, Vec<usize>) { fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<f32>, Vec<usize>) {
let mut verts: Vec<Vec2<f32>> = planets.iter().map(|p| Vec2::new(p.x, p.y)).collect(); let mut verts: Vec<[f32; 2]> = planets.iter().map(|p| [p.x, p.y]).collect();
let mut ids = Vec::new(); let mut ids = Vec::new();
let vor_points = planets.iter().map(|p| Point::new(p.x as f64, p.y as f64)).collect(); let vor_points = planets
.iter()
.map(|p| Point::new(p.x as f64, p.y as f64))
.collect();
let vor = voronoi(vor_points, bbox as f64); let vor = voronoi(vor_points, bbox as f64);
let vor = make_polygons(&vor); let vor = make_polygons(&vor);
@ -113,9 +130,8 @@ fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, V
let mut prev = ids.len() + poly.len() - 1; let mut prev = ids.len() + poly.len() - 1;
for p in poly.iter() { for p in poly.iter() {
let now = verts.len(); let now = verts.len();
verts.push(Vec2::new(p.x.0 as f32, p.y.0 as f32)); verts.push([p.x.0 as f32, p.y.0 as f32]);
ids.push(idx); ids.push(idx);
ids.push(now); ids.push(now);
@ -124,10 +140,9 @@ fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, V
} }
} }
(verts, ids) (verts.concat(), ids)
} }
#[wasm_bindgen] #[wasm_bindgen]
pub struct Game { pub struct Game {
states: Vec<types::State>, states: Vec<types::State>,
@ -138,18 +153,18 @@ pub struct Game {
/* put extra shit here */ /* put extra shit here */
view_box: Vec<f32>, view_box: Vec<f32>,
planets: Vec<Vec3<f32>>, planets: Vec<f32>,
planet_ships: Vec<usize>, planet_ships: Vec<usize>,
ship_locations: Vec<[f32;9]>, ship_locations: Vec<f32>,
ship_label_locations: Vec<[f32;9]>, ship_label_locations: Vec<f32>,
ship_colours: Vec<Vec3<f32>>, ship_colours: Vec<f32>,
ship_counts: Vec<usize>, ship_counts: Vec<usize>,
current_planet_colours: Vec<Vec3<f32>>, current_planet_colours: Vec<f32>,
voronoi_vertices: Vec<Vec2<f32>>, voronoi_vertices: Vec<f32>,
voronoi_colors: Vec<Vec3<f32>>, voronoi_colors: Vec<f32>,
voronoi_indices: Vec<usize>, voronoi_indices: Vec<usize>,
} }
@ -161,9 +176,10 @@ impl Game {
console_log!("Rust is busy being awesome!"); 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
serde_json::from_str(line).ok() .split("\n")
).collect(); .filter_map(|line| serde_json::from_str(line).ok())
.collect();
let mut planet_map = HashMap::new(); let mut planet_map = HashMap::new();
@ -175,10 +191,14 @@ impl Game {
} }
let view_box = utils::caclulate_viewbox(&states[0].planets); let view_box = utils::caclulate_viewbox(&states[0].planets);
let (voronoi_vertices, voronoi_indices) =
create_voronoi(&states[0].planets, view_box[2].max(view_box[3]));
let (voronoi_vertices, voronoi_indices) = create_voronoi(&states[0].planets, view_box[2].max(view_box[3])); let voronoi_colors: Vec<f32> = voronoi_indices
.iter()
let voronoi_colors = voronoi_indices.iter().map(|_| Vec3::new(0.0, 0.0, 0.0)).collect(); // Init these colours on black .map(|_| [0.0, 0.0, 0.0])
.collect::<Vec<[f32; 3]>>()
.concat(); // Init these colours on black
Self { Self {
planets: utils::get_planets(&states[0].planets, 2.0), planets: utils::get_planets(&states[0].planets, 2.0),
@ -200,24 +220,20 @@ impl Game {
} }
} }
pub fn get_viewbox(&self) -> *const f32 { pub fn get_viewbox(&self) -> Vec<f32> {
self.view_box.as_ptr() self.view_box.clone()
} }
pub fn get_planets(&self) -> *const Vec3<f32> { pub fn get_planets(&self) -> Vec<f32> {
self.planets.as_ptr() self.planets.clone()
} }
pub fn get_planet_ships(&self) -> *const usize { pub fn get_planet_ships(&self) -> Vec<usize> {
self.planet_ships.as_ptr() self.planet_ships.clone()
} }
pub fn get_planet_colors(&self) -> *const Vec3<f32> { pub fn get_planet_colors(&self) -> Vec<f32> {
self.current_planet_colours.as_ptr() self.current_planet_colours.clone()
}
pub fn get_planet_count(&self) -> usize {
self.planets.len()
} }
pub fn turn_count(&self) -> usize { pub fn turn_count(&self) -> usize {
@ -225,7 +241,7 @@ 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_ships();
self.update_planet_colours(); self.update_planet_colours();
@ -237,105 +253,114 @@ impl Game {
} }
fn update_planet_ships(&mut self) { fn update_planet_ships(&mut self) {
self.planet_ships = self.states[self.turn].planets.iter().map(|p| p.ship_count as usize).collect(); 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() let color = utils::COLORS[p.owner.unwrap_or(0) as usize % utils::COLORS.len()];
self.voronoi_colors[i * 3 + 0] = color[0];
self.voronoi_colors[i * 3 + 1] = color[1];
self.voronoi_colors[i * 3 + 2] = color[2];
} }
} }
fn update_planet_colours(&mut self) { fn update_planet_colours(&mut self) {
let mut new_vec = Vec::new(); let mut new_vec: Vec<[f32; 3]> = Vec::new();
let planets_now = self.states[self.turn].planets.iter(); let planets_now = self.states[self.turn].planets.iter();
let planets_later = self.states[(self.turn + 1).min(self.states.len() - 1)].planets.iter(); let planets_later = self.states[(self.turn + 1).min(self.states.len() - 1)]
.planets
.iter();
for (p1, p2) in planets_now.zip(planets_later) { for (p1, p2) in planets_now.zip(planets_later) {
new_vec.push( new_vec
utils::COLORS[p1.owner.unwrap_or(0) as usize % utils::COLORS.len()].into() .push(utils::COLORS[p1.owner.unwrap_or(0) as usize % utils::COLORS.len()].into());
); new_vec
new_vec.push( .push(utils::COLORS[p2.owner.unwrap_or(0) as usize % utils::COLORS.len()].into());
utils::COLORS[p2.owner.unwrap_or(0) as usize % utils::COLORS.len()].into()
);
} }
self.current_planet_colours = new_vec; self.current_planet_colours = new_vec.concat::<f32>();
} }
fn update_ship_locations(&mut self) { fn update_ship_locations(&mut self) {
self.ship_locations = Vec::new(); let mut new_sl = Vec::new();
self.ship_label_locations = Vec::new(); let mut new_sll = Vec::new();
let t = Mat3::new(0.2, 0., 0.,
0., 0.2, 0.0, let t = Mat3::new(0.2, 0., 0., 0., 0.2, 0.0, 0., -0.5, 0.2);
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, a1), (o2, a2)) = 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
self.ship_locations.push((o1 * Mat3::rotate_z(a1)).to_array()); .planet_map
self.ship_locations.push((o2 * Mat3::rotate_z(a2)).to_array()); .get(&(ship.origin.clone(), ship.destination.clone()))
.unwrap()
.get_for_remaining(ship.turns_remaining as usize);
new_sl.push((o1 * Mat3::rotate_z(a1)).to_array());
new_sl.push((o2 * Mat3::rotate_z(a2)).to_array());
self.ship_label_locations.push((o1 + t).to_array()); new_sll.push((o1 + t).to_array());
self.ship_label_locations.push((o2 + t).to_array()); new_sll.push((o2 + t).to_array());
} }
self.ship_colours = self.states[self.turn].expeditions.iter().map(|s| { self.ship_locations = new_sl.concat();
utils::COLORS[s.owner as usize % utils::COLORS.len()].into() self.ship_label_locations = new_sll.concat();
}).collect();
self.ship_colours = self.states[self.turn]
.expeditions
.iter()
.map(|s| utils::COLORS[s.owner as usize % utils::COLORS.len()])
.collect::<Vec<[f32; 3]>>()
.concat();
} }
fn update_ship_counts(&mut self) { fn update_ship_counts(&mut self) {
self.ship_counts = self.states[self.turn].expeditions.iter().map(|s| { self.ship_counts = self.states[self.turn]
s.ship_count as usize .expeditions
}).collect(); .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()
} }
pub fn get_ship_count(&self) -> usize { pub fn get_ship_locations(&self) -> Vec<f32> {
self.states[self.turn].expeditions.len() self.ship_locations.clone()
} }
pub fn get_ship_locations(&self) -> *const [f32;9] { pub fn get_ship_label_locations(&self) -> Vec<f32> {
self.ship_locations.as_ptr() self.ship_label_locations.clone()
} }
pub fn get_ship_label_locations(&self) -> *const [f32;9] { pub fn get_ship_colours(&self) -> Vec<f32> {
self.ship_label_locations.as_ptr() self.ship_colours.clone()
} }
pub fn get_ship_colours(&self) -> *const Vec3<f32> { pub fn get_ship_counts(&self) -> Vec<usize> {
self.ship_colours.as_ptr() self.ship_counts.clone()
} }
pub fn get_ship_counts(&self) -> *const usize { pub fn get_voronoi_verts(&self) -> Vec<f32> {
self.ship_counts.as_ptr() self.voronoi_vertices.clone()
} }
pub fn get_voronoi_vert_count(&self) -> usize { pub fn get_voronoi_colours(&self) -> Vec<f32> {
self.voronoi_vertices.len() self.voronoi_colors.clone()
} }
pub fn get_voronoi_verts(&self) -> *const Vec2<f32> { pub fn get_voronoi_inds(&self) -> Vec<usize> {
self.voronoi_vertices.as_ptr() self.voronoi_indices.clone()
}
pub fn get_voronoi_colours(&self) -> *const Vec3<f32> {
self.voronoi_colors.as_ptr()
}
pub fn get_voronoi_ind_count(&self) -> usize {
self.voronoi_indices.len()
}
pub fn get_voronoi_inds(&self) -> *const usize {
self.voronoi_indices.as_ptr()
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
fn alert(s: &str); fn alert(s: &str);

View file

@ -9,22 +9,20 @@ pub fn set_panic_hook() {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
} }
use octoon_math::{Vec3};
/// this is total extra, so it the planet viewbox is like 100px wide, it will now be in total 110 pixels wide /// this is total extra, so it the planet viewbox is like 100px wide, it will now be in total 110 pixels wide
static VIEWBOX_SCALE: f32 = 0.1; static VIEWBOX_SCALE: f32 = 0.1;
pub static COLORS: [[f32; 3]; 10] = [ pub static COLORS: [[f32; 3]; 10] = [
[0.5 , 0.5 , 0.5 ], [0.5, 0.5, 0.5],
[1.0 , 0.50, 0.0 ], // #FF8000 [1.0, 0.50, 0.0], // #FF8000
[0.0 , 0.50, 1.0 ], // #0080ff [0.0, 0.50, 1.0], // #0080ff
[1.0 , 0.4 , 0.58 ], // #FF6693 [1.0, 0.4, 0.58], // #FF6693
[0.24, 0.79, 0.33 ], // #3fcb55 [0.24, 0.79, 0.33], // #3fcb55
[0.79, 0.76, 0.24 ], // #cbc33f [0.79, 0.76, 0.24], // #cbc33f
[0.81, 0.25, 0.91 ], // #cf40e9 [0.81, 0.25, 0.91], // #cf40e9
[0.94, 0.32, 0.32 ], // #FF3F0D [0.94, 0.32, 0.32], // #FF3F0D
[0.11, 0.93, 0.94 ], // #1beef0 [0.11, 0.93, 0.94], // #1beef0
[0.05, 0.77, 1.0 ], // #0DC5FF [0.05, 0.77, 1.0], // #0DC5FF
]; ];
use super::types; use super::types;
@ -36,16 +34,32 @@ pub fn caclulate_viewbox(planets: &Vec<types::Planet>) -> Vec<f32> {
Some(p) => (p.x, p.y, p.x, p.y), Some(p) => (p.x, p.y, p.x, p.y),
None => return vec![0.0, 0.0, 0.0, 0.0], None => return vec![0.0, 0.0, 0.0, 0.0],
}; };
let (min_x, min_y, max_x, max_y) = planets let (min_x, min_y, max_x, max_y) =
.iter() planets
.fold(init, |(min_x, min_y, max_x, max_y), p| (min_x.min(p.x), min_y.min(p.y), max_x.max(p.x), max_y.max(p.y))); .iter()
.fold(init, |(min_x, min_y, max_x, max_y), p| {
(
min_x.min(p.x),
min_y.min(p.y),
max_x.max(p.x),
max_y.max(p.y),
)
});
let (width, height) = (max_x - min_x, max_y - min_y); let (width, height) = (max_x - min_x, max_y - min_y);
let (dx, dy) = ((VIEWBOX_SCALE * width).max(6.0), (VIEWBOX_SCALE * height).max(6.0)); let (dx, dy) = (
(VIEWBOX_SCALE * width).max(6.0),
(VIEWBOX_SCALE * height).max(6.0),
);
vec![min_x - dx/2.0, min_y - dy/2.0, width + dx, height + dy] vec![min_x - dx / 2.0, min_y - dy / 2.0, width + dx, height + dy]
} }
pub fn get_planets(planets: &Vec<types::Planet>, r: f32) -> Vec<Vec3<f32>> { pub fn get_planets(planets: &Vec<types::Planet>, r: f32) -> Vec<f32> {
planets.iter().map(|p| Vec3::new(p.x, p.y, r)).collect() planets.iter().fold(Vec::new(), |mut cum, p| {
cum.push(p.x);
cum.push(p.y);
cum.push(r);
cum
})
} }

View file

@ -1,7 +1,24 @@
import { Game } from "planetwars"; import { Game } from "planetwars";
import { memory } from "planetwars/planetwars_bg"; import { memory } from "planetwars/planetwars_bg";
import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh, Dictionary } from "./webgl/util"; import {
import { Shader, Uniform4f, Uniform3fv, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader'; Resizer,
resizeCanvasToDisplaySize,
FPSCounter,
url_to_mesh,
Mesh,
Dictionary,
} from "./webgl/util";
import {
Shader,
Uniform4f,
Uniform3fv,
Uniform1f,
Uniform2f,
ShaderFactory,
Uniform3f,
UniformMatrix3fv,
UniformBool,
} from "./webgl/shader";
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";
@ -10,47 +27,57 @@ import { VoronoiBuilder } from "./voronoi/voronoi";
import { BBox } from "./voronoi/voronoi-core"; import { BBox } from "./voronoi/voronoi-core";
function to_bbox(box: number[]): BBox { function to_bbox(box: number[]): BBox {
return { return {
'xl': box[0], 'xr': box[0] + box[2], xl: box[0],
'yt': box[1], 'yb': box[1] + box[3] xr: box[0] + box[2],
}; yt: box[1],
yb: box[1] + box[3],
};
} }
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);
} }
function i32v(ptr: number, size: number): Int32Array { function i32v(ptr: number, size: number): Int32Array {
return new Int32Array(memory.buffer, ptr, size); return new Int32Array(memory.buffer, ptr, size);
} }
export function set_game_name(name: string) { export function set_game_name(name: string) {
ELEMENTS["name"].innerHTML = name; ELEMENTS["name"].innerHTML = name;
} }
export function set_loading(loading: boolean) { export function set_loading(loading: boolean) {
if (loading) { if (loading) {
if (!ELEMENTS["main"].classList.contains("loading")) { if (!ELEMENTS["main"].classList.contains("loading")) {
ELEMENTS["main"].classList.add("loading"); ELEMENTS["main"].classList.add("loading");
}
} else {
ELEMENTS["main"].classList.remove("loading");
} }
} else {
ELEMENTS["main"].classList.remove("loading");
}
} }
const ELEMENTS = {}; const ELEMENTS = {};
["name", "turnCounter", "main", "turnSlider", "fileselect", "speed", "canvas"].forEach(n => ELEMENTS[n] = document.getElementById(n)); [
"name",
"turnCounter",
"main",
"turnSlider",
"fileselect",
"speed",
"canvas",
].forEach((n) => (ELEMENTS[n] = document.getElementById(n)));
const CANVAS = ELEMENTS["canvas"]; const CANVAS = ELEMENTS["canvas"];
const RESOLUTION = [CANVAS.width, CANVAS.height]; const RESOLUTION = [CANVAS.width, CANVAS.height];
const LAYERS = { const LAYERS = {
'vor': -1, // Background vor: -1, // Background
'planet': 1, planet: 1,
'planet_label': 2, planet_label: 2,
'ship': 3, ship: 3,
'ship_label': 4 ship_label: 4,
} };
const COUNTER = new FPSCounter(); const COUNTER = new FPSCounter();
@ -65,314 +92,427 @@ GL.enable(GL.BLEND);
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
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; image_shader: Shader;
text_factory: LabelFactory; text_factory: LabelFactory;
planet_labels: Label[]; planet_labels: Label[];
ship_labels: Label[]; ship_labels: Label[];
renderer: Renderer; renderer: Renderer;
planet_count: number; planet_count: number;
vor_builder: VoronoiBuilder; vor_builder: VoronoiBuilder;
vor_counter = 3; vor_counter = 3;
use_vor = true; use_vor = true;
playing = true; playing = true;
time_stopped_delta = 0; time_stopped_delta = 0;
last_time = 0; last_time = 0;
frame = -1; frame = -1;
turn_count = 0; turn_count = 0;
constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh, shaders: Dictionary<ShaderFactory>) { constructor(
this.game = game; game: Game,
this.planet_count = this.game.get_planet_count(); meshes: Mesh[],
ship_mesh: Mesh,
shaders: Dictionary<ShaderFactory>
) {
this.game = game;
const planets = game.get_planets();
this.planet_count = planets.length;
this.shader = shaders["normal"].create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count }); this.shader = shaders["normal"].create_shader(GL, {
this.image_shader = shaders["image"].create_shader(GL); MAX_CIRCLES: "" + planets.length,
this.vor_shader = shaders["vor"].create_shader(GL, { "PLANETS": '' + this.planet_count }); });
this.image_shader = shaders["image"].create_shader(GL);
this.vor_shader = shaders["vor"].create_shader(GL, {
PLANETS: "" + planets.length,
});
this.text_factory = defaultLabelFactory(GL, this.image_shader); this.text_factory = defaultLabelFactory(GL, this.image_shader);
this.planet_labels = []; this.planet_labels = [];
this.ship_labels = []; this.ship_labels = [];
this.resizer = new Resizer(CANVAS, [...f32v(game.get_viewbox(), 4)], true); this.resizer = new Resizer(CANVAS, [...game.get_viewbox()], true);
this.renderer = new Renderer(); this.renderer = new Renderer();
this.game.update_turn(0); this.game.update_turn(0);
// Setup key handling // Setup key handling
document.addEventListener('keydown', this.handleKey.bind(this)); document.addEventListener("keydown", this.handleKey.bind(this));
// List of [(x, y, r)] for all planets // List of [(x, y, r)] for all planets
const planets = f32v(game.get_planets(), this.planet_count * 3); this._create_voronoi(planets);
this._create_voronoi(planets); this._create_planets(planets, meshes);
this._create_planets(planets, meshes); this._create_shipes(ship_mesh);
this._create_shipes(ship_mesh);
// Set slider correctly // Set slider correctly
this.turn_count = game.turn_count(); this.turn_count = game.turn_count();
ELEMENTS["turnSlider"].max = this.turn_count - 1 + ''; ELEMENTS["turnSlider"].max = this.turn_count - 1 + "";
}
_create_voronoi(planets: Float32Array) {
const planet_points = [];
for (let i = 0; i < planets.length; i += 3) {
planet_points.push({ x: -planets[i], y: -planets[i + 1] });
} }
_create_voronoi(planets: Float32Array) { const bbox = to_bbox(this.resizer.get_viewbox());
const planet_points = [];
for (let i = 0; i < planets.length; i += 3) {
planet_points.push({ 'x': -planets[i], 'y': -planets[i + 1] });
}
const bbox = to_bbox(this.resizer.get_viewbox()); this.vor_builder = new VoronoiBuilder(
GL,
this.vor_shader,
planet_points,
bbox
);
this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor);
}
this.vor_builder = new VoronoiBuilder(GL, this.vor_shader, planet_points, bbox); _create_planets(planets: Float32Array, meshes: Mesh[]) {
this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor); 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 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);
this.renderer.addToDraw(
indexBuffer,
vao,
this.shader,
{
u_trans: transform,
u_trans_next: transform,
},
[],
LAYERS.planet
);
}
{
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.planet_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
}
}
}
_create_shipes(ship_mesh: Mesh) {
const ship_ibo = new IndexBuffer(GL, ship_mesh.cells);
const ship_positions = new VertexBuffer(GL, ship_mesh.positions);
const ship_layout = new VertexBufferLayout();
ship_layout.push(GL.FLOAT, 3, 4, "a_position");
const ship_vao = new VertexArray();
ship_vao.addBuffer(ship_positions, ship_layout);
for (let i = 0; i < this.game.get_max_ships(); i++) {
this.renderer.addToDraw(
ship_ibo,
ship_vao,
this.shader,
{},
[],
LAYERS.ship
);
const label = this.text_factory.build(GL);
this.ship_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label);
}
}
on_resize() {
this.resizer = new Resizer(CANVAS, [...this.game.get_viewbox()], true);
const bbox = to_bbox(this.resizer.get_viewbox());
this.vor_builder.resize(GL, bbox);
}
_update_state() {
this._update_planets();
this._update_ships();
}
_update_planets() {
const colours = this.game.get_planet_colors();
const planet_ships = this.game.get_planet_ships();
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,
(us) => (us["u_color"] = u),
LAYERS.planet
);
const u2 = new Uniform3f(
colours[i * 6 + 3],
colours[i * 6 + 4],
colours[i * 6 + 5]
);
this.renderer.updateUniform(
i,
(us) => (us["u_color_next"] = u2),
LAYERS.planet
);
this.planet_labels[i].setText(
GL,
"*" + planet_ships[i],
Align.Middle,
Align.Begin
);
}
}
_update_ships() {
const ships = this.game.get_ship_locations();
const labels = this.game.get_ship_label_locations();
const ship_counts = this.game.get_ship_counts();
const ship_colours = this.game.get_ship_colours();
for (let i = 0; i < this.game.get_max_ships(); i++) {
if (i < ship_counts.length) {
this.ship_labels[i].setText(
GL,
"" + ship_counts[i],
Align.Middle,
Align.Middle
);
this.renderer.enableRenderable(i, LAYERS.ship);
this.renderer.enableRenderable(i, LAYERS.ship_label);
const u = new Uniform3f(
ship_colours[i * 3],
ship_colours[i * 3 + 1],
ship_colours[i * 3 + 2]
);
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(
i,
(us) => {
us["u_color"] = u;
us["u_color_next"] = u;
us["u_trans"] = t1;
us["u_trans_next"] = t2;
},
LAYERS.ship
);
this.renderer.updateUniform(
i,
(us) => {
us["u_trans"] = tl1;
us["u_trans_next"] = tl2;
},
LAYERS.ship_label
);
} else {
this.renderer.disableRenderable(i, LAYERS.ship);
this.renderer.disableRenderable(i, LAYERS.ship_label);
}
}
}
render(time: number) {
COUNTER.frame(time);
if (COUNTER.delta(time) < 30) {
this.vor_counter = Math.min(3, this.vor_counter + 1);
} else {
this.vor_counter = Math.max(-3, this.vor_counter - 1);
} }
_create_planets(planets: Float32Array, meshes: Mesh[]) { if (this.vor_counter < -2) {
for (let i = 0; i < this.planet_count; i++) { this.use_vor = false;
{
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 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,
},
[],
LAYERS.planet
);
}
{
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.planet_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
}
}
} }
_create_shipes(ship_mesh: Mesh) { // If not playing, still reder with different viewbox, so people can still pan etc.
const ship_ibo = new IndexBuffer(GL, ship_mesh.cells); if (!this.playing) {
const ship_positions = new VertexBuffer(GL, ship_mesh.positions); this.last_time = time;
const ship_layout = new VertexBufferLayout();
ship_layout.push(GL.FLOAT, 3, 4, "a_position");
const ship_vao = new VertexArray();
ship_vao.addBuffer(ship_positions, ship_layout);
for (let i = 0; i < this.game.get_max_ships(); i++) { this.shader.uniform(
this.renderer.addToDraw( GL,
ship_ibo, "u_viewbox",
ship_vao, new Uniform4f(this.resizer.get_viewbox())
this.shader, );
{}, this.vor_shader.uniform(
[], GL,
LAYERS.ship "u_viewbox",
); new Uniform4f(this.resizer.get_viewbox())
);
this.image_shader.uniform(
GL,
"u_viewbox",
new Uniform4f(this.resizer.get_viewbox())
);
const label = this.text_factory.build(GL); this.renderer.render(GL);
this.ship_labels.push(label); return;
this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label)
}
} }
on_resize() { // Check if turn is still correct
this.resizer = new Resizer(CANVAS, [...f32v(this.game.get_viewbox(), 4)], true); if (time > this.last_time + ms_per_frame) {
const bbox = to_bbox(this.resizer.get_viewbox()); this.last_time = time;
this.vor_builder.resize(GL, bbox); this.updateTurn(this.frame + 1);
} }
_update_state() { // Do GL things
this._update_planets(); GL.bindFramebuffer(GL.FRAMEBUFFER, null);
this._update_ships(); GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
this.vor_shader.uniform(
GL,
"u_time",
new Uniform1f((time - this.last_time) / ms_per_frame)
);
this.vor_shader.uniform(
GL,
"u_viewbox",
new Uniform4f(this.resizer.get_viewbox())
);
this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor));
this.shader.uniform(
GL,
"u_time",
new Uniform1f((time - this.last_time) / ms_per_frame)
);
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.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));
// Render
this.renderer.render(GL);
COUNTER.frame_end();
}
updateTurn(turn: number) {
this.frame = Math.max(0, turn);
const new_frame = this.game.update_turn(this.frame);
if (new_frame < this.frame) {
this.frame = new_frame;
this.playing = false;
} else {
this._update_state();
this.playing = true;
} }
_update_planets() { ELEMENTS["turnCounter"].innerHTML =
const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6); this.frame + " / " + (this.turn_count - 1);
const planet_ships = i32v(this.game.get_planet_ships(), this.planet_count); ELEMENTS["turnSlider"].value = this.frame + "";
}
this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours)); handleKey(event: KeyboardEvent) {
// Space
for (let i = 0; i < this.planet_count; i++) { if (event.keyCode == 32) {
const u = new Uniform3f(colours[i * 6], colours[i * 6 + 1], colours[i * 6 + 2]); if (this.playing) {
this.renderer.updateUniform(i, (us) => us["u_color"] = u, LAYERS.planet); this.playing = false;
const u2 = new Uniform3f(colours[i * 6 + 3], colours[i * 6 + 4], colours[i * 6 + 5]); } else {
this.renderer.updateUniform(i, (us) => us["u_color_next"] = u2, LAYERS.planet); this.playing = true;
}
this.planet_labels[i].setText(GL, "*" + planet_ships[i], Align.Middle, Align.Begin);
}
} }
_update_ships() { // Arrow left
const ship_count = this.game.get_ship_count(); if (event.keyCode == 37) {
const ships = f32v(this.game.get_ship_locations(), ship_count * 9 * 2); // This feels more natural than -1 what it should be, I think
const labels = f32v(this.game.get_ship_label_locations(), ship_count * 9 * 2); this.updateTurn(this.frame - 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++) {
if (i < ship_count) {
this.ship_labels[i].setText(GL, "" + ship_counts[i], Align.Middle, Align.Middle);
this.renderer.enableRenderable(i, LAYERS.ship);
this.renderer.enableRenderable(i, LAYERS.ship_label);
const u = new Uniform3f(ship_colours[i * 3], ship_colours[i * 3 + 1], ship_colours[i * 3 + 2]);
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(i, (us) => {
us["u_color"] = u;
us["u_color_next"] = u;
us["u_trans"] = t1;
us["u_trans_next"] = t2;
}, LAYERS.ship);
this.renderer.updateUniform(i, (us) => {
us["u_trans"] = tl1;
us["u_trans_next"] = tl2;
}, LAYERS.ship_label);
} else {
this.renderer.disableRenderable(i, LAYERS.ship);
this.renderer.disableRenderable(i, LAYERS.ship_label);
}
}
} }
render(time: number) { // Arrow right
COUNTER.frame(time); if (event.keyCode == 39) {
this.updateTurn(this.frame + 1);
if (COUNTER.delta(time) < 30) {
this.vor_counter = Math.min(3, this.vor_counter + 1);
} else {
this.vor_counter = Math.max(-3, this.vor_counter - 1);
}
if (this.vor_counter < -2) {
this.use_vor = false;
}
// If not playing, still reder with different viewbox, so people can still pan etc.
if (!this.playing) {
this.last_time = time;
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;
}
// Check if turn is still correct
if (time > this.last_time + ms_per_frame) {
this.last_time = time;
this.updateTurn(this.frame + 1);
}
// Do GL things
GL.bindFramebuffer(GL.FRAMEBUFFER, null);
GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
this.vor_shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / ms_per_frame));
this.vor_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor));
this.shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / ms_per_frame));
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.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));
// Render
this.renderer.render(GL);
COUNTER.frame_end();
} }
updateTurn(turn: number) { // d key
this.frame = Math.max(0, turn); if (event.keyCode == 68) {
const new_frame = this.game.update_turn(this.frame); ELEMENTS["speed"].value = ms_per_frame + 10 + "";
if (new_frame < this.frame) { ELEMENTS["speed"].onchange(undefined);
this.frame = new_frame;
this.playing = false;
} else {
this._update_state();
this.playing = true;
}
ELEMENTS["turnCounter"].innerHTML = this.frame + " / " + (this.turn_count - 1);
ELEMENTS["turnSlider"].value = this.frame + '';
} }
handleKey(event: KeyboardEvent) { // a key
// Space if (event.keyCode == 65) {
if (event.keyCode == 32) { ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + "";
if (this.playing) { ELEMENTS["speed"].onchange(undefined);
this.playing = false;
} else {
this.playing = true;
}
}
// Arrow left
if (event.keyCode == 37) {
// This feels more natural than -1 what it should be, I think
this.updateTurn(this.frame - 2);
}
// Arrow right
if (event.keyCode == 39) {
this.updateTurn(this.frame + 1);
}
// d key
if (event.keyCode == 68) {
ELEMENTS["speed"].value = ms_per_frame + 10 + '';
ELEMENTS["speed"].onchange(undefined);
}
// a key
if (event.keyCode == 65) {
ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + '';
ELEMENTS["speed"].onchange(undefined);
}
} }
}
} }
var game_instance: GameInstance; var game_instance: GameInstance;
@ -380,59 +520,87 @@ var meshes: Mesh[];
var shaders: Dictionary<ShaderFactory>; var shaders: Dictionary<ShaderFactory>;
export async function set_instance(source: string) { export async function set_instance(source: string) {
if (!meshes || !shaders) { if (!meshes || !shaders) {
const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map( const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"]
(name) => "static/res/assets/" + name .map((name) => "static/res/assets/" + name)
).map(url_to_mesh); .map(url_to_mesh);
const shader_promies = [ const shader_promies = [
(async () => <[string, ShaderFactory]>["normal", await ShaderFactory.create_factory("static/shaders/frag/simple.glsl", "static/shaders/vert/simple.glsl")])(), (async () =>
(async () => <[string, ShaderFactory]>["vor", await ShaderFactory.create_factory("static/shaders/frag/vor.glsl", "static/shaders/vert/vor.glsl")])(), <[string, ShaderFactory]>[
(async () => <[string, ShaderFactory]>["image", await ShaderFactory.create_factory("static/shaders/frag/image.glsl", "static/shaders/vert/simple.glsl")])(), "normal",
]; await ShaderFactory.create_factory(
let shaders_array: [string, ShaderFactory][]; "static/shaders/frag/simple.glsl",
[meshes, shaders_array] = await Promise.all( "static/shaders/vert/simple.glsl"
[ ),
Promise.all(mesh_promises), ])(),
Promise.all(shader_promies), (async () =>
] <[string, ShaderFactory]>[
); "vor",
await ShaderFactory.create_factory(
"static/shaders/frag/vor.glsl",
"static/shaders/vert/vor.glsl"
),
])(),
(async () =>
<[string, ShaderFactory]>[
"image",
await ShaderFactory.create_factory(
"static/shaders/frag/image.glsl",
"static/shaders/vert/simple.glsl"
),
])(),
];
let shaders_array: [string, ShaderFactory][];
[meshes, shaders_array] = await Promise.all([
Promise.all(mesh_promises),
Promise.all(shader_promies),
]);
shaders = {}; shaders = {};
shaders_array.forEach(([name, fac]) => shaders[name] = fac); shaders_array.forEach(([name, fac]) => (shaders[name] = fac));
} }
resizeCanvasToDisplaySize(CANVAS); resizeCanvasToDisplaySize(CANVAS);
game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0], shaders); game_instance = new GameInstance(
Game.new(source),
meshes.slice(1),
meshes[0],
shaders
);
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 }
);
ELEMENTS["turnSlider"].oninput = function () { ELEMENTS["turnSlider"].oninput = function () {
if (game_instance) { if (game_instance) {
game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value)); game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value));
} }
} };
ELEMENTS["speed"].onchange = function () { ELEMENTS["speed"].onchange = function () {
ms_per_frame = parseInt(ELEMENTS["speed"].value); ms_per_frame = parseInt(ELEMENTS["speed"].value);
} };
function step(time: number) { function step(time: number) {
if (game_instance) { if (game_instance) {
game_instance.render(time); game_instance.render(time);
} }
requestAnimationFrame(step); requestAnimationFrame(step);
} }
requestAnimationFrame(step); requestAnimationFrame(step);