Compare commits

...

12 commits

Author SHA1 Message Date
ajuvercr
4de17f78b3 add build script 2021-01-17 15:26:09 +01:00
ajuvercr
4dc946ef92 bump versions 2021-01-17 15:26:00 +01:00
ajuvercr
ef5399217d https://stackoverflow.com/questions/64507718/why-does-wasm-opt-fail-in-wasm-pack-builds-when-generating-a-function-returning 2021-01-17 15:19:02 +01:00
mcbloch
51712bf07e Add javascript bot 2020-12-05 19:35:21 +01:00
ajuvercr
9a33ad42fc fix hostname issue + config 2020-11-14 18:21:27 +01:00
ajuvercr
2ba043862f build and runnable, still no host_name support, but hardcodable 2020-11-13 10:53:09 +01:00
ajuvercr
ba0b2c7655 make it compile, but can't run and break hostname config 2020-11-11 22:21:56 +01:00
ajuvercr
dcf1173cb8
Docker (#25)
* change wasm to real vectors

* Add volume bind command to README

* add changable host name variable

Co-authored-by: Francis <francisklinck@gmail.com>
2020-06-24 14:16:14 +02:00
ajuvercr
442f969da0 rocket is launching 2020-06-20 11:21:41 +02:00
ajuvercr
7684c24db0 rocket is a future now :o 2020-06-20 10:21:40 +02:00
ajuvercr
cedcc09f0a fix dep bugs 2020-06-14 22:20:08 +02:00
ajuvercr
129c904967
Frontend/visualiser (#24)
* refactor to src folder

* add working voronoi js file with types

* some spring cleaning

* update voronoi-core.d.ts to include inner class exports

* make voronoi go really fast

* do the voronoi dance

* better handle outliers in voronoi

* make renderer use multiple layers

* resize voronoi with resize + squash all in one buffer

* actually wait for shader factories to be created + cleanup

* show more info with FPS Counter

* POWER UP
2020-04-20 15:38:11 +02:00
43 changed files with 3149 additions and 2840 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 PW_HOST_NAME=<domain>`, for example `-e PW_HOST_NAME=mozaic.zeus.gent`, to set the domain used in the application.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "backend" name = "planetwars"
version = "0.1.1" version = "0.1.2"
authors = ["ajuvercr <arthur.vercruysse@ugent.be>"] authors = ["ajuvercr <arthur.vercruysse@ugent.be>"]
edition = "2018" edition = "2018"
@ -8,18 +8,20 @@ edition = "2018"
[dependencies] [dependencies]
mozaic = { git = "https://github.com/ZeusWPI/MOZAICP" } mozaic = { git = "https://github.com/ZeusWPI/MOZAICP" }
rand = { version = "0.6.5", default-features = true } rand = { version = "0.8.2", default-features = true }
async-std = { version = "1.5.0", features = ["attributes"] } async-std = { version = "1.9.0", features = ["attributes"] }
futures = { version = "0.3.1", features = ["executor", "thread-pool"] } futures = { version = "0.3.12", features = ["executor", "thread-pool"] }
serde = "1.0.100" figment = "0.10.2"
serde_derive = "1.0.100"
serde = "1.0.119"
serde_derive = "1.0.119"
serde_json = "1.0" serde_json = "1.0"
tracing = "0.1.9" tracing = "0.1.22"
tracing-futures = "0.1.0" tracing-futures = "0.2.4"
tracing-subscriber = "0.1.5" tracing-subscriber = "0.2.15"
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master" }
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", branch = "async", features = ["handlebars_templates", "tera_templates"] } rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["handlebars_templates", "tera_templates"] }
educe = { version = "0.4.2", features = ["Debug", "Default", "Hash", "Clone", "Copy"] } educe = { version = "0.4.13", features = ["Debug", "Default", "Hash", "Clone", "Copy"] }

View file

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

View file

@ -1,11 +0,0 @@
[development]
address = "localhost"
port = 8000
[staging]
address = "0.0.0.0"
port = 80
[production]
address = "0.0.0.0"
port = 8123

View file

@ -1,4 +1,4 @@
#![feature(proc_macro_hygiene)] #![feature(proc_macro_hygiene, async_closure, decl_macro)]
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
@ -11,6 +11,8 @@ extern crate futures;
extern crate mozaic; extern crate mozaic;
extern crate rand; extern crate rand;
extern crate figment;
extern crate tracing; extern crate tracing;
extern crate tracing_futures; extern crate tracing_futures;
extern crate tracing_subscriber; extern crate tracing_subscriber;
@ -22,6 +24,7 @@ extern crate rocket_contrib;
#[macro_use] #[macro_use]
extern crate educe; extern crate educe;
use figment::{providers::{Serialized, Env}};
use tracing_subscriber::{EnvFilter, FmtSubscriber}; use tracing_subscriber::{EnvFilter, FmtSubscriber};
use std::net::SocketAddr; use std::net::SocketAddr;
@ -40,14 +43,33 @@ 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};
use std::collections::HashMap; use std::collections::HashMap;
/// Config for the planetwars server
#[derive(Deserialize, Serialize, Debug)]
pub struct PWConfig {
host_name: String,
address: String,
port: u16,
}
impl Default for PWConfig {
fn default() -> Self {
Self {
host_name: String::from("localhost"),
address: String::from("0.0.0.0"),
port: 8000,
}
}
}
/// Calculate viewbox from array of points (used in map preview), added to Tera engine. /// Calculate viewbox from array of points (used in map preview), added to Tera engine.
/// So this function can be called in template. /// So this function can be called in template.
fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> { fn calc_viewbox(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
let mut min_x = std::f64::MAX; let mut min_x = std::f64::MAX;
let mut min_y = std::f64::MAX; let mut min_y = std::f64::MAX;
let mut max_x = std::f64::MIN; let mut max_x = std::f64::MIN;
@ -79,15 +101,21 @@ fn calc_viewbox(value: Value, _: HashMap<String, Value>) -> tera::Result<Value>
} }
/// Get's the right colour for planets /// Get's the right colour for planets
fn get_colour(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> { fn get_colour(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
return Ok(Value::String( return Ok(Value::String(
COLOURS[value.as_u64().unwrap_or(0) as usize].to_string(), COLOURS[value.as_u64().unwrap_or(0) as usize].to_string(),
)); ));
} }
fn get_host_name(host_name: &str) -> impl Fn(&HashMap<String, Value>) -> tera::Result<Value> + Sync + Send {
let host_name = host_name.to_string();
move |_| Ok(Value::String(host_name.clone()))
}
/// Async main function, starting logger, graph and rocket /// Async main function, starting logger, graph and rocket
#[async_std::main] #[rocket::launch]
async fn main() { async fn rocket() -> rocket::Rocket {
let fut = graph::set_default(); let fut = graph::set_default();
let sub = FmtSubscriber::builder() let sub = FmtSubscriber::builder()
@ -95,26 +123,38 @@ async fn main() {
.finish(); .finish();
tracing::subscriber::set_global_default(sub).unwrap(); tracing::subscriber::set_global_default(sub).unwrap();
let pool = ThreadPool::new().unwrap(); let pool = ThreadPool::builder().create().unwrap();
pool.spawn_ok(fut.map(|_| ())); pool.spawn_ok(fut.map(|_| ()));
let gm = create_game_manager("0.0.0.0:9142", pool.clone()).await; let gm = create_game_manager("0.0.0.0:9142", pool.clone()).await;
async_std::task::sleep(std::time::Duration::from_millis(200)).await;
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| { let figment = rocket::Config::figment()
engines.tera.register_filter("calc_viewbox", calc_viewbox); .merge(Serialized::defaults(PWConfig::default())) // Extend but not overwrite
engines.tera.register_filter("get_colour", get_colour); .merge(Env::prefixed("PW_")); // Overwrite
});
rocket::ignite() rocket::custom(figment)
.manage(gm) .manage(gm)
.manage(pool) .manage(pool)
.manage(Games::new()) .manage(Games::new())
.attach(tera) .attach(AdHoc::config::<PWConfig>()) // Manage the config
.mount("/", routes) .mount("/", routes)
.launch() .attach(AdHoc::on_attach("Assets Config", async move |rocket| {
.unwrap(); let pw_config = rocket.figment().extract::<PWConfig>().unwrap_or_default();
println!("PW Config {:?}", pw_config);
let host_name = pw_config.host_name.clone();
let tera = Template::custom(move |engines: &mut Engines| {
engines.tera.register_filter("calc_viewbox", calc_viewbox);
engines.tera.register_filter("get_colour", get_colour);
engines.tera.register_function("get_host_name", get_host_name(&host_name));
});
Ok(rocket.attach(tera))
}))
} }
/// Creates the actual game_manager /// Creates the actual game_manager
@ -122,9 +162,9 @@ async fn main() {
async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager { async fn create_game_manager(tcp: &str, pool: ThreadPool) -> game::Manager {
let addr = tcp.parse::<SocketAddr>().unwrap(); let addr = tcp.parse::<SocketAddr>().unwrap();
let (gmb, handle) = game::Manager::builder(pool.clone()); let (gmb, handle) = game::Manager::builder(pool.clone());
pool.spawn_ok(handle.map(|_| ())); pool.spawn_ok(handle.map(|_| {println!("I'm done")}));
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

@ -77,7 +77,7 @@ async fn post_game(
state: State<'_, Games>, state: State<'_, Games>,
) -> Result<Json<GameRes>, String> { ) -> Result<Json<GameRes>, String> {
let game = build_builder( let game = build_builder(
tp.clone(), tp.inner().clone(),
game_req.nop, game_req.nop,
game_req.max_turns, game_req.max_turns,
&game_req.map, &game_req.map,
@ -108,6 +108,7 @@ fn generate_string_id() -> String {
rand::thread_rng() rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric) .sample_iter(&rand::distributions::Alphanumeric)
.take(15) .take(15)
.map(|x| x as char)
.collect::<String>() .collect::<String>()
+ ".json" + ".json"
} }

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| {
async {
maybe maybe
.ok() .ok()
.and_then(|line| serde_json::from_str::<FinishedState>(&line).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

@ -9,7 +9,7 @@
<div id=wrapper> <div id=wrapper>
<div id="main" class="loading"> <div id="main" class="loading">
<canvas id="c"></canvas> <canvas id="canvas"></canvas>
<div id="name"></div> <div id="name"></div>
<div id="addbutton" class="button"></div> <div id="addbutton" class="button"></div>

31
build.sh Executable file
View file

@ -0,0 +1,31 @@
#!/bin/bash
info() {
echo
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>> $1"
echo
}
source /home/$USER/.cargo/env
cd "$(dirname "$0")"
info "Pulling git"
git pull
cd frontend
info "Building WASM package"
cargo update
wasm-pack build
cd www
info "Building frontend with npm"
npm run build
cd ../..
cd backend
info "Building backend with cargo --release"
cargo update
cargo build --release

81
client/simple.js Normal file
View file

@ -0,0 +1,81 @@
const fs = require('fs')
const readline = require('readline');
const rl = readline.createInterface({input: process.stdin, output: process.stdout});
const MY_PLAYER_NUMBER = 1
function move(moves) {
let record = {'moves': moves}
console.error(JSON.stringify(record))
console.log(JSON.stringify(record))
}
function find_max_with_key(array, key) {
return array.reduce((item1, item2) => {
let val1 = item1[key];
let val2 = item2[key];
return (val1 > val2 ? item1 : item2);
});
}
function find_min_with_key(array, key) {
return array.reduce((item1, item2) => {
let val1 = item1[key];
let val2 = item2[key];
return (val1 < val2 ? item1 : item2);
});
}
/**
* De main code van de bot
*/
function run_bot(state) {
// Maak een lijst van mijn eigen planeten
let my_planets = state["planets"].filter((planet) => {
return planet['owner'] === MY_PLAYER_NUMBER
})
// Maak een lijst van de planeten van de tegenstander
let other_planets = state["planets"].filter((planet) => {
return planet['owner'] !== MY_PLAYER_NUMBER
})
if (my_planets.length === 0 || other_planets.length === 0) {
// Als er al een speler dood is moet ik niets meer doen
move([])
} else {
// Stuur alle schepen behalve 1 van mijn planeet met meeste schepen
// naar die van de andere speler met zijn minste schepen
let planet = find_max_with_key(my_planets, 'ship_count');
let dest = find_min_with_key(other_planets, 'ship_count');
if (planet !== undefined && dest !== undefined) {
move([{
'origin': planet['name'],
'destination': dest['name'],
'ship_count': planet['ship_count'] - 1
}])
}
}
}
function main() {
// Je kunt als 2de argument een file meegeven. De bot gaat deze dan gebruiken als input voor de ronde
if (process.argv.length > 2) {
try {
const data = fs.readFileSync(process.argv[2], 'utf8')
run_bot(JSON.parse(data))
} catch (err) {
console.error(err)
}
} else {
// Anders gaan we stdin lezen
rl.on('line', (data) => {
run_bot(JSON.parse(data))
});
}
}
let _ = main();

View file

@ -4,6 +4,9 @@ version = "0.1.0"
authors = ["ajuvercr <arthur.vercruysse@ugent.be>"] authors = ["ajuvercr <arthur.vercruysse@ugent.be>"]
edition = "2018" edition = "2018"
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Oz", "--enable-mutable-globals"]
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]

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) =
planets
.iter() .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))); .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

@ -14,7 +14,7 @@ if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, {
} }
}); });
import ("./index.js") import ("./src/index.js")
.then(e => { .then(e => {
h = e.handle; h = e.handle;
}) })

View file

@ -1,149 +0,0 @@
import { create } from "domain";
export class Vertex {
coords: [number, number];
incident_edge: HalfEdge;
constructor(x: number, y: number) {
this.coords = [x, y];
}
get_all(): HalfEdge[] {
const out = [];
let current = this.incident_edge;
do {
out.push(current);
current = current.twin.next;
} while (current != this.incident_edge);
return out;
}
}
export class Face {
attributes: any;
outer_component: HalfEdge;
inner_components: HalfEdge[];
constructor(attributes?: any) {
this.attributes = attributes;
}
loop(): [number, number][] {
const out = [];
let iter = 0;
let current = this.outer_component;
do {
if (iter > 100) {
throw new Error("Fuck off");
}
iter += 1;
console.log(current.id, current.face.attributes, current.origin.coords);
out.push(current.origin.coords);
current = current.next;
} while (current != this.outer_component);
return out;
}
}
var id = 0;
function next_id(): number {
id += 1;
return id;
}
export class HalfEdge {
origin: Vertex;
// destination = twin.origin
next: HalfEdge;
prev: HalfEdge;
twin: HalfEdge;
face: Face;
id: number;
constructor(origin: Vertex, f1: Face, f2?: Face) {
this.id = next_id();
this.origin = origin;
this.next = this;
this.prev = this;
if (f2) {
this.twin = new HalfEdge(origin, f2);
} else {
this.twin = this;
}
this.face = f1;
}
insert(at: Vertex, update_twin = true): HalfEdge {
const new_edge = new HalfEdge(at, this.face);
new_edge.next = this.next;
new_edge.prev = this;
new_edge.twin = this.twin;
this.next.prev = new_edge;
this.next = new_edge;
if (update_twin) {
this.twin = this.twin.insert(at, false);
}
return new_edge;
}
split(to: Vertex) {
const e_to = new HalfEdge(this.origin, this.face);
const e_from = new HalfEdge(to, this.face);
e_to.twin = e_from;
e_from.twin = e_to;
e_to.prev = this.prev;
e_to.next = e_from;
e_from.next = this;
e_from.prev = e_to;
this.prev.next = e_to;
this.prev = e_from;
}
add_face() {
}
to_string(): string {
return `Halfedge from ${this.origin ? this.origin.coords : undefined} face1 ${this.face ? this.face.attributes : undefined}`;
}
}
export function test() {
const f1 = new Face("Face 1");
const f2 = new Face("Face 2");
const v1 = new Vertex(0, 0);
const v2 = new Vertex(1, 1);
const v3 = new Vertex(-1, 0);
const v4 = new Vertex(2, 0);
const e1 = new HalfEdge(v1, f1, f2);
f1.outer_component = e1;
const e2 = e1.insert(v2);
const e3 = e2.split(v3);
const e4 = e2.insert(v4);
e1.twin.next = e4.twin;
f2.outer_component = e4.twin;
// const e3 = e1.insert(v3);
console.log(f1.loop());
console.log(f2.loop());
}

View file

@ -1,106 +0,0 @@
import { set_game_name, set_loading, LOCATION, set_instance } from './index'
import { ConfigIniParser } from 'config-ini-parser'
const OPTIONS = document.getElementById("options");
const game_location = LOCATION + "static/games/mod.ini";
var game_name, game_file;
document.getElementById("addbutton").onclick = function() {
const loc = window.location;
const query = `?game=${game_file}&name=${game_name}`;
navigator.clipboard.writeText(loc.origin+loc.pathname+encodeURI(query)).then(() => {
console.log("Success");
}, () => {
console.log("Failed");
});
}
async function on_load() {
console.log("ON LOAD");
if (OPTIONS) {
const r = await fetch(game_location);
const response = await r.text();
parse_ini(response);
} else {
const options = document.getElementsByClassName("options");
const urlVars = new URLSearchParams(window.location.search);
if (urlVars.get("game") && urlVars.get("name")) {
console.log(urlVars.get("game")+' '+urlVars.get("name"))
handle(urlVars.get("game"),urlVars.get("name"))
} else if (options[0]) {
const options_div = <HTMLDivElement> options[0];
if (options_div.children[0]) {
options_div.children[0].dispatchEvent(new Event('click'));
}
}
}
}
window.addEventListener("load", on_load, false);
export function handle(location, name: string) {
game_file = location;
game_name = name;
set_loading(true);
fetch(location)
.then((r) => r.text())
.then((response) => {
set_instance(response);
set_game_name(name);
}).catch(console.error);
}
function create_option(location: string, name: string, turns: string, players: string): HTMLElement {
const div = document.createElement("div");
div.className = "option";
div.onclick = (_) => handle(location, name);
console.log("hello there");
console.log(`"${location}, "${name}"`);
let ps = "";
if (players) {
ps += "<p>Players</p>";
for (let [index, player] of players.split('"').entries()) {
if (index % 2 == 0) {
continue;
}
ps += `<p>${player}</p>`;
}
}
const html = `
<p>${name}</p>
<p>Turns: ${turns}</p>
` + ps;
div.innerHTML = html;
return div;
}
function parse_ini(inifile: string) {
const parser = new ConfigIniParser();
parser.parse(inifile);
const loc = parser.get(undefined, "path");
OPTIONS.innerHTML = '';
for (let name of parser.sections()) {
const game = parser.get(name, "name");
const turns = parser.get(name, "turns");
const players = parser.get(name, "players")
OPTIONS.appendChild(
create_option(loc+name, game , turns, players)
);
}
}
on_load();

View file

@ -1,438 +0,0 @@
import { Game } from "planetwars";
import { memory } from "planetwars/planetwars_bg";
import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh } from "./webgl/util";
import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader';
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);
}
function i32v(ptr: number, size: number): Int32Array {
return new Int32Array(memory.buffer, ptr, size);
}
export function set_game_name(name: string) {
GAMENAME.innerHTML = name;
}
const GAMENAME = document.getElementById("name");
const TURNCOUNTER = document.getElementById("turnCounter");
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");
export function set_loading(loading: boolean) {
if (loading) {
if (!LOADER.classList.contains("loading")) {
LOADER.classList.add("loading");
}
} else {
LOADER.classList.remove("loading");
}
}
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];
const GL = CANVAS.getContext("webgl");
var ms_per_frame = parseInt(SPEED.value);
resizeCanvasToDisplaySize(CANVAS);
GL.clearColor(0, 0, 0, 0);
GL.clear(GL.COLOR_BUFFER_BIT);
GL.enable(GL.BLEND);
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
var SHADERFACOTRY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl"
).then((e) => SHADERFACOTRY = e);
var VOR_SHADER_FACTORY: ShaderFactory;
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;
vor_counter = 3;
use_vor = true;
playing = true; // 0 is paused, 1 is playing but not rerendered, 2 is playing and rerendered
time_stopped_delta = 0;
last_time = 0;
frame = -1;
ship_indices: number[];
turn_count = 0;
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.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,
]);
const positionBuffer = new VertexBuffer(GL, [
-1, -1,
-1, 1,
1, -1,
1, 1,
]);
const layout = new VertexBufferLayout();
layout.push(GL.FLOAT, 2, 4, "a_pos");
const vao = new VertexArray();
vao.addBuffer(positionBuffer, layout);
this.renderer.addToDraw(indexBuffer, vao, this.vor_shader);
// Setup key handling
document.addEventListener('keydown', this.handleKey.bind(this));
const planets = f32v(game.get_planets(), this.planet_count * 3);
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,
}
);
}
{
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 = [];
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.ship_indices.push(
this.renderer.addToDraw(
ship_ibo,
ship_vao,
this.shader,
{}
)
);
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));
// Set slider correctly
SLIDER.max = this.turn_count - 1 + '';
}
on_resize() {
this.resizer = new Resizer(CANVAS, [...f32v(this.game.get_viewbox(), 4)], true);
}
_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(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 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++) {
const index = this.ship_indices[i];
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 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);
}
}
}
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);
}
if (this.vor_counter < -2) {
this.use_vor = false;
}
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;
}
if (time > this.last_time + ms_per_frame) {
this.last_time = time;
this.updateTurn(this.frame + 1);
}
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_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));
this.renderer.render(GL);
}
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;
}
TURNCOUNTER.innerHTML = this.frame + " / " + this.turn_count;
SLIDER.value = this.frame + '';
}
handleKey(event: KeyboardEvent) {
// Space
if (event.keyCode == 32) {
if (this.playing) {
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) {
SPEED.value = ms_per_frame + 10 + '';
SPEED.onchange(undefined);
}
// a key
if (event.keyCode == 65) {
SPEED.value = Math.max(ms_per_frame - 10, 0) + '';
SPEED.onchange(undefined);
}
}
}
var game_instance: GameInstance;
var meshes;
export async function set_instance(source: string) {
if (!meshes) {
meshes = await Promise.all(
["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map(
(name) => "static/res/assets/" + name
).map(url_to_mesh)
);
}
resizeCanvasToDisplaySize(CANVAS);
game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0]);
set_loading(false);
}
window.addEventListener('resize', function () {
resizeCanvasToDisplaySize(CANVAS);
if (game_instance) {
game_instance.on_resize();
}
}, { capture: false, passive: true })
SLIDER.oninput = function () {
if (game_instance) {
game_instance.updateTurn(parseInt(SLIDER.value));
}
}
FILESELECTOR.onchange = function () {
const file = FILESELECTOR.files[0];
if (!file) { return; }
var reader = new FileReader();
reader.onload = function () {
set_instance(<string>reader.result);
}
reader.readAsText(file);
}
SPEED.onchange = function () {
ms_per_frame = parseInt(SPEED.value);
}
function step(time: number) {
if (game_instance) {
game_instance.render(time);
}
requestAnimationFrame(step);
}
// set_loading(false);
requestAnimationFrame(step);

47
frontend/www/src/games.ts Normal file
View file

@ -0,0 +1,47 @@
import { set_game_name, set_loading, set_instance } from './index'
var game_name, game_file;
document.getElementById("addbutton").onclick = function () {
const loc = window.location;
const query = `?game=${game_file}&name=${game_name}`;
navigator.clipboard.writeText(loc.origin + loc.pathname + encodeURI(query)).then(() => {
console.log("Success");
}, () => {
console.log("Failed");
});
}
async function on_load() {
const options = document.getElementsByClassName("options");
const urlVars = new URLSearchParams(window.location.search);
if (urlVars.get("game") && urlVars.get("name")) {
console.log(urlVars.get("game") + ' ' + urlVars.get("name"))
handle(urlVars.get("game"), urlVars.get("name"))
} else if (options[0]) {
const options_div = <HTMLDivElement>options[0];
if (options_div.children[0]) {
options_div.children[0].dispatchEvent(new Event('click'));
}
}
}
window.addEventListener("load", on_load, false);
export function handle(location: string, name: string) {
game_file = location;
game_name = name;
set_loading(true);
fetch(location)
.then((r) => r.text())
.then((response) => {
set_instance(response);
set_game_name(name);
}).catch(console.error);
}
on_load();

606
frontend/www/src/index.ts Normal file
View file

@ -0,0 +1,606 @@
import { Game } from "planetwars";
import { memory } from "planetwars/planetwars_bg";
import {
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 { VertexBuffer, IndexBuffer } from "./webgl/buffer";
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
import { VoronoiBuilder } from "./voronoi/voronoi";
import { BBox } from "./voronoi/voronoi-core";
function to_bbox(box: number[]): BBox {
return {
xl: box[0],
xr: box[0] + box[2],
yt: box[1],
yb: box[1] + box[3],
};
}
function f32v(ptr: number, size: number): Float32Array {
return new Float32Array(memory.buffer, ptr, size);
}
function i32v(ptr: number, size: number): Int32Array {
return new Int32Array(memory.buffer, ptr, size);
}
export function set_game_name(name: string) {
ELEMENTS["name"].innerHTML = name;
}
export function set_loading(loading: boolean) {
if (loading) {
if (!ELEMENTS["main"].classList.contains("loading")) {
ELEMENTS["main"].classList.add("loading");
}
} else {
ELEMENTS["main"].classList.remove("loading");
}
}
const ELEMENTS = {};
[
"name",
"turnCounter",
"main",
"turnSlider",
"fileselect",
"speed",
"canvas",
].forEach((n) => (ELEMENTS[n] = document.getElementById(n)));
const CANVAS = ELEMENTS["canvas"];
const RESOLUTION = [CANVAS.width, CANVAS.height];
const LAYERS = {
vor: -1, // Background
planet: 1,
planet_label: 2,
ship: 3,
ship_label: 4,
};
const COUNTER = new FPSCounter();
var ms_per_frame = parseInt(ELEMENTS["speed"].value);
const GL = CANVAS.getContext("webgl");
GL.clearColor(0, 0, 0, 1);
GL.clear(GL.COLOR_BUFFER_BIT);
GL.enable(GL.BLEND);
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
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;
vor_builder: VoronoiBuilder;
vor_counter = 3;
use_vor = true;
playing = true;
time_stopped_delta = 0;
last_time = 0;
frame = -1;
turn_count = 0;
constructor(
game: Game,
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: "" + planets.length,
});
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.planet_labels = [];
this.ship_labels = [];
this.resizer = new Resizer(CANVAS, [...game.get_viewbox()], true);
this.renderer = new Renderer();
this.game.update_turn(0);
// Setup key handling
document.addEventListener("keydown", this.handleKey.bind(this));
// List of [(x, y, r)] for all planets
this._create_voronoi(planets);
this._create_planets(planets, meshes);
this._create_shipes(ship_mesh);
// Set slider correctly
this.turn_count = game.turn_count();
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] });
}
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);
}
_create_planets(planets: Float32Array, meshes: Mesh[]) {
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);
}
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) {
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;
}
ELEMENTS["turnCounter"].innerHTML =
this.frame + " / " + (this.turn_count - 1);
ELEMENTS["turnSlider"].value = this.frame + "";
}
handleKey(event: KeyboardEvent) {
// Space
if (event.keyCode == 32) {
if (this.playing) {
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 meshes: Mesh[];
var shaders: Dictionary<ShaderFactory>;
export async function set_instance(source: string) {
if (!meshes || !shaders) {
const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"]
.map((name) => "static/res/assets/" + name)
.map(url_to_mesh);
const shader_promies = [
(async () =>
<[string, ShaderFactory]>[
"normal",
await ShaderFactory.create_factory(
"static/shaders/frag/simple.glsl",
"static/shaders/vert/simple.glsl"
),
])(),
(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_array.forEach(([name, fac]) => (shaders[name] = fac));
}
resizeCanvasToDisplaySize(CANVAS);
game_instance = new GameInstance(
Game.new(source),
meshes.slice(1),
meshes[0],
shaders
);
set_loading(false);
}
window.addEventListener(
"resize",
function () {
resizeCanvasToDisplaySize(CANVAS);
if (game_instance) {
game_instance.on_resize();
}
},
{ capture: false, passive: true }
);
ELEMENTS["turnSlider"].oninput = function () {
if (game_instance) {
game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value));
}
};
ELEMENTS["speed"].onchange = function () {
ms_per_frame = parseInt(ELEMENTS["speed"].value);
};
function step(time: number) {
if (game_instance) {
game_instance.render(time);
}
requestAnimationFrame(step);
}
requestAnimationFrame(step);

View file

@ -0,0 +1,56 @@
declare namespace Voronoi {
class Point {
x: number;
y: number;
}
class Site {
x: number;
y: number;
voronoiId: number;
}
class Cell {
site: Site;
halfedges: HalfEdge[];
closeMe: boolean;
}
class Edge {
lSite: Site;
rSite: Site;
vb: Point;
va: Point;
}
class HalfEdge {
site: Site;
edge: Edge;
angle: number;
getStartpoint(): Point;
getEndpoint(): Point;
}
class BBox {
xl: number;
xr: number;
yt: number;
yb: number;
}
class VoronoiDiagram {
site: any;
cells: Cell[];
edges: Edge[];
vertices: Point[];
execTime: number;
}
}
declare class Voronoi {
constructor();
compute(sites: Voronoi.Point[], bbox: Voronoi.BBox): Voronoi.VoronoiDiagram;
}
export = Voronoi;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
import { Shader } from "../webgl/shader";
import { BBox, Point, VoronoiDiagram } from "./voronoi-core";
import Voronoi = require("./voronoi-core");
import { DefaultRenderable } from "../webgl/renderer";
import { IndexBuffer, VertexBuffer } from "../webgl/buffer";
import { VertexBufferLayout, VertexArray } from "../webgl/vertexBufferLayout";
function arcctg(x: number): number { return Math.PI / 2 - Math.atan(x); }
function to_key(p: Point): string {
return [p.x, p.y] + "";
}
function round_point(center: Point, point: Point, amount_fn = (b: number) => 0.7): Point {
const d = dist(center, point, true);
const x = center.x + amount_fn(d) * (point.x - center.x);
const y = center.y + amount_fn(d) * (point.y - center.y);
return { 'x': x, 'y': y };
}
function median_point(c: Point, p: Point, n: Point, d = 0.1): number[] {
const dd = 1.0 - 2 * d;
return [
dd * c.x + d * p.x + d * n.x,
dd * c.y + d * p.y + d * n.y,
]
}
function build_point_map(es: Voronoi.HalfEdge[]): (point: Point) => Point {
const mean = es.map(e => dist(e.getStartpoint(), e.getEndpoint())).reduce((a, b) => a + b, 0) / es.length;
const map = {};
for (let edge of es) {
const start = edge.getStartpoint();
const end = edge.getEndpoint();
if (dist(start, end) < 0.03 * mean) { // These points have to be merged
const middle = { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 };
map[to_key(start)] = middle;
map[to_key(end)] = middle;
}
}
return (p) => map[to_key(p)] || p;
}
function get_round_fn(dist_mean: number, amount = 0.7): (d: number) => number {
return (d) => arcctg((d - dist_mean) / dist_mean) / Math.PI + 0.6;
}
function dist(a: Point, b: Point, norm = false): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
if (norm) return Math.sqrt(dx * dx + dy * dy);
return dx * dx + dy * dy;
}
export class VoronoiBuilder {
inner: DefaultRenderable;
vor: Voronoi;
planets: Point[];
constructor(gl: WebGLRenderingContext, shader: Shader, planets: Point[], bbox: BBox) {
this.vor = new Voronoi();
this.planets = planets;
const ib = new IndexBuffer(gl, []);
const vb = new VertexBuffer(gl, []);
const layout = new VertexBufferLayout();
layout.push(gl.FLOAT, 2, 4, "a_pos");
layout.push(gl.FLOAT, 2, 4, "a_center");
layout.push(gl.FLOAT, 1, 4, "a_own");
layout.push(gl.FLOAT, 1, 4, "a_intensity");
const vao = new VertexArray();
vao.addBuffer(vb, layout);
this.inner = new DefaultRenderable(ib, vao, shader, [], {});
this.resize(gl, bbox);
}
getRenderable(): DefaultRenderable {
return this.inner;
}
resize(gl: WebGLRenderingContext, bbox: BBox) {
const start = new Date().getTime();
// This voronoi sorts the planets, then owners don't align anymore
const own_map = {};
this.planets.forEach((p, i) => own_map[to_key(p)] = i);
const vor = this.vor.compute(this.planets, bbox);
const attrs = [];
const ids = [];
let vertCount = 0;
for (let i = 0; i < vor.cells.length; i++) {
const cell = vor.cells[i];
const planetId = own_map[to_key(cell.site)];
const point_map = build_point_map(cell.halfedges);
const centerId = vertCount++;
attrs.push(cell.site.x, cell.site.y);
attrs.push(cell.site.x, cell.site.y);
attrs.push(planetId);
attrs.push(1);
const dist_mean = cell.halfedges.map(e => {
const start = e.getStartpoint();
const end = e.getEndpoint();
return dist(cell.site, start, true) + dist(cell.site, { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 }, true)
}).reduce((a, b) => a + b, 0) / cell.halfedges.length / 2;
const round_fn = get_round_fn(dist_mean);
for (let edge of cell.halfedges) {
let start = point_map(edge.getStartpoint());
let end = point_map(edge.getEndpoint());
let center = { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 };
if (to_key(start) == to_key(end)) continue;
start = round_point(cell.site, start, round_fn);
center = round_point(cell.site, center, round_fn);
end = round_point(cell.site, end, round_fn);
ids.push(centerId);
ids.push(vertCount++);
attrs.push(start.x, start.y);
attrs.push(cell.site.x, cell.site.y);
attrs.push(planetId);
attrs.push(0);
ids.push(vertCount++);
attrs.push(center.x, center.y);
attrs.push(cell.site.x, cell.site.y);
attrs.push(planetId);
attrs.push(0);
ids.push(centerId);
ids.push(vertCount - 1);
ids.push(vertCount++);
attrs.push(end.x, end.y);
attrs.push(cell.site.x, cell.site.y);
attrs.push(planetId);
attrs.push(0);
}
}
this.inner.updateIndexBuffer(gl, ids);
this.inner.updateVAOBuffer(gl, 0, attrs);
console.log(`Vor things took ${new Date().getTime() - start} ms!`)
}
}

View file

@ -5,12 +5,26 @@ import { VertexArray } from './vertexBufferLayout';
import { Texture } from './texture'; import { Texture } from './texture';
import { Dictionary } from './util'; import { Dictionary } from './util';
function sortedIndex(array, value) {
var low = 0,
high = array.length;
while (low < high) {
var mid = (low + high) >>> 1;
if (array[mid] < value) low = mid + 1;
else high = mid;
}
return low;
}
export interface Renderable { export interface Renderable {
getUniforms() : Dictionary<Uniform>; getUniforms() : Dictionary<Uniform>;
render(gl: WebGLRenderingContext): void; render(gl: WebGLRenderingContext): void;
updateVAOBuffer(gl: WebGLRenderingContext, index: number, data: number[]);
updateIndexBuffer(gl: WebGLRenderingContext, data: number[]);
} }
export class RenderShit implements Renderable { export class DefaultRenderable implements Renderable {
ibo: IndexBuffer; ibo: IndexBuffer;
va: VertexArray; va: VertexArray;
shader: Shader; shader: Shader;
@ -35,6 +49,14 @@ export class RenderShit implements Renderable {
return this.uniforms; return this.uniforms;
} }
updateVAOBuffer(gl: WebGLRenderingContext, index: number, data: number[]) {
this.va.updateBuffer(gl, index, data);
}
updateIndexBuffer(gl: WebGLRenderingContext, data: number[]) {
this.ibo.updateData(gl, data);
}
render(gl: WebGLRenderingContext): void { render(gl: WebGLRenderingContext): void {
const indexBuffer = this.ibo; const indexBuffer = this.ibo;
@ -75,38 +97,46 @@ export class RenderShit implements Renderable {
} }
export class Renderer { export class Renderer {
renderables: [Renderable, boolean][]; renderables: { [id: number] : [Renderable, boolean][]; };
renderable_layers: number[];
constructor() { constructor() {
this.renderables = []; this.renderables = {};
this.renderable_layers = [];
} }
updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void) { updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void, layer=0, ) {
f(this.renderables[i][0].getUniforms()); f(this.renderables[layer][i][0].getUniforms());
} }
disableRenderShift(i: number) { disableRenderable(i: number, layer=0) {
this.renderables[i][1] = false; this.renderables[layer][i][1] = false;
} }
enableRendershit(i: number) { enableRenderable(i: number, layer=0) {
this.renderables[i][1] = true; this.renderables[layer][i][1] = true;
} }
addRenderable(item: Renderable): number { addRenderable(item: Renderable, layer=0): number {
this.renderables.push([item, true]); if(!this.renderables[layer]) {
return this.renderables.length - 1; const idx = sortedIndex(this.renderable_layers, layer);
this.renderable_layers.splice(idx, 0, layer);
this.renderables[layer] = [];
} }
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary<Uniform>, texture?: Texture[]): number { this.renderables[layer].push([item, true]);
return this.renderables[layer].length - 1;
}
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary<Uniform>, texture?: Texture[], layer=0): number {
return this.addRenderable( return this.addRenderable(
new RenderShit( new DefaultRenderable(
indexBuffer, indexBuffer,
vertexArray, vertexArray,
shader, shader,
texture || [], texture || [],
uniforms || {}, uniforms || {},
) ), layer
); );
} }
@ -117,10 +147,11 @@ 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, e] of this.renderables) { for (let layer of this.renderable_layers) {
for (let [r, e] of this.renderables[layer]) {
if (!e) continue; if (!e) continue;
r.render(gl); r.render(gl);
} }
}
} }
} }

View file

@ -1,6 +1,6 @@
import { Texture } from "./texture"; import { Texture } from "./texture";
import { Dictionary } from "./util"; import { Dictionary } from "./util";
import { Renderable, RenderShit } from "./renderer"; import { Renderable, DefaultRenderable } from "./renderer";
import { Uniform, Shader, UniformMatrix3fv } from "./shader"; import { Uniform, Shader, UniformMatrix3fv } from "./shader";
import { IndexBuffer, VertexBuffer } from "./buffer"; import { IndexBuffer, VertexBuffer } from "./buffer";
import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout"; import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout";
@ -43,10 +43,8 @@ export class LabelFactory {
} }
} }
export class Label implements Renderable { export class Label {
inner: Renderable; inner: DefaultRenderable;
ib: IndexBuffer;
vb: VertexBuffer;
font: FontInfo; font: FontInfo;
@ -54,30 +52,31 @@ export class Label implements Renderable {
this.font = font; this.font = font;
const uniforms = transform ? { "u_trans": transform, "u_trans_next": transform, } : {}; const uniforms = transform ? { "u_trans": transform, "u_trans_next": transform, } : {};
this.ib = new IndexBuffer(gl, []); const ib = new IndexBuffer(gl, []);
this.vb = new VertexBuffer(gl, []); const vb_pos = new VertexBuffer(gl, []);
const vb_tex = new VertexBuffer(gl, []);
const layout = new VertexBufferLayout(); const layout_pos = new VertexBufferLayout();
layout.push(gl.FLOAT, 2, 4, "a_position"); layout_pos.push(gl.FLOAT, 2, 4, "a_position");
layout.push(gl.FLOAT, 2, 4, "a_texCoord");
const layout_tex = new VertexBufferLayout();
layout_tex.push(gl.FLOAT, 2, 4, "a_texCoord");
const vao = new VertexArray(); const vao = new VertexArray();
vao.addBuffer(this.vb, layout); vao.addBuffer(vb_pos, layout_pos);
vao.addBuffer(vb_tex, layout_tex);
this.inner = new RenderShit(this.ib, vao, shader, [tex], uniforms); this.inner = new DefaultRenderable(ib, vao, shader, [tex], uniforms);
} }
getUniforms(): Dictionary<Uniform> { getRenderable(): DefaultRenderable {
return this.inner.getUniforms(); return this.inner;
}
render(gl: WebGLRenderingContext): void {
return this.inner.render(gl);
} }
setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) { setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) {
const idxs = []; const idxs = [];
const verts = []; const verts_pos = [];
const verts_tex = [];
const letterHeight = this.font.letterHeight / this.font.textureHeight; const letterHeight = this.font.letterHeight / this.font.textureHeight;
let xPos = 0; let xPos = 0;
@ -112,10 +111,16 @@ export class Label implements Renderable {
const letterWidth = info.width / this.font.textureWidth; const letterWidth = info.width / this.font.textureWidth;
const x0 = info.x / this.font.textureWidth; const x0 = info.x / this.font.textureWidth;
const y0 = info.y / this.font.textureHeight; const y0 = info.y / this.font.textureHeight;
verts.push(xPos, yStart, x0, y0); verts_pos.push(xPos, yStart);
verts.push(xPos + dx, yStart, x0 + letterWidth, y0); verts_pos.push(xPos + dx, yStart);
verts.push(xPos, yStart-1, x0, y0 + letterHeight); verts_pos.push(xPos, yStart-1);
verts.push(xPos + dx, yStart-1, x0 + letterWidth, y0 + letterHeight); verts_pos.push(xPos + dx, yStart-1);
verts_tex.push(x0, y0);
verts_tex.push(x0 + letterWidth, y0);
verts_tex.push(x0, y0 + letterHeight);
verts_tex.push(x0 + letterWidth, y0 + letterHeight);
xPos += dx; xPos += dx;
idxs.push(j+0, j+1, j+2, j+1, j+2, j+3); idxs.push(j+0, j+1, j+2, j+1, j+2, j+3);
@ -126,8 +131,9 @@ export class Label implements Renderable {
} }
} }
this.ib.updateData(gl, idxs); this.inner.updateIndexBuffer(gl, idxs);
this.vb.updateData(gl, verts); this.inner.updateVAOBuffer(gl, 0, verts_pos);
this.inner.updateVAOBuffer(gl, 1, verts_tex);
} }
} }

View file

@ -36,7 +36,11 @@ export class FPSCounter {
last: number; last: number;
count: number; count: number;
_delta: number; _delta: number;
_prev: number _prev: number;
_frame_start: number;
_total_frametime: number;
constructor() { constructor() {
this.last = 0; this.last = 0;
this.count = 0; this.count = 0;
@ -45,17 +49,23 @@ export class FPSCounter {
} }
frame(now: number) { frame(now: number) {
this._frame_start = performance.now();
this.count += 1; this.count += 1;
this._delta = now - this._prev; this._delta = now - this._prev;
this._prev = now; this._prev = now;
if (now - this.last > 1000) { if (now - this.last > 1000) {
this.last = now; this.last = now;
console.log(this.count + " fps"); console.log(`${this.count} fps, ${(this._total_frametime / this.count).toFixed(2)}ms avg per frame`);
this.count = 0; this.count = 0;
this._total_frametime = 0;
} }
} }
frame_end() {
this._total_frametime += (performance.now() - this._frame_start);
}
delta(now: number): number { delta(now: number): number {
return this._delta; return this._delta;
} }

View file

View file

@ -1,4 +1,4 @@
import { Buffer, VertexBuffer } from './buffer'; import { VertexBuffer } from './buffer';
import { Shader } from './shader'; import { Shader } from './shader';
export class VertexBufferElement { export class VertexBufferElement {
@ -59,7 +59,7 @@ export class VertexBufferLayout {
// glVertexAttribPointer tells gl that that data is at which location in the supplied data // glVertexAttribPointer tells gl that that data is at which location in the supplied data
export class VertexArray { export class VertexArray {
// There is no renderer ID, always at bind buffers and use glVertexAttribPointer // There is no renderer ID, always at bind buffers and use glVertexAttribPointer
buffers: Buffer[]; buffers: VertexBuffer[];
layouts: VertexBufferLayout[]; layouts: VertexBufferLayout[];
constructor() { constructor() {
@ -72,6 +72,10 @@ export class VertexArray {
this.layouts.push(layout); this.layouts.push(layout);
} }
updateBuffer(gl: WebGLRenderingContext, index: number, data: number[]) {
this.buffers[index].updateData(gl, data);
}
/// Bind buffers providing program data /// Bind buffers providing program data
bind(gl: WebGLRenderingContext, shader: Shader) { bind(gl: WebGLRenderingContext, shader: Shader) {
shader.bind(gl); shader.bind(gl);
@ -109,4 +113,3 @@ export class VertexArray {
}) })
} }
} }

View file

@ -184,7 +184,7 @@
width: 100px; width: 100px;
} }
#c { #canvas {
position: relative; position: relative;
background-color: black; background-color: black;
width: 100%; width: 100%;

View file

@ -2,29 +2,17 @@
precision mediump float; precision mediump float;
#endif #endif
uniform vec3 u_planets[$PLANETS]; #define PI 3.141592
uniform vec3 u_planet_colours[$PLANETS * 2];
uniform float u_step_interval; uniform float u_step_interval;
uniform float u_time; uniform float u_time;
uniform bool u_vor; uniform bool u_vor;
varying float v_intensity;
varying float v_dist;
varying vec3 v_color;
varying vec2 v_pos; varying vec2 v_pos;
void main() { void main() {
vec3 color = vec3(0.2); gl_FragColor = vec4(v_color, (1.0 - pow(1.0 - v_intensity, 1.23)) * 0.7);
if (u_vor) {
float dis = 1000000.0;
for(int i = 0; i < $PLANETS; i++) {
float d = distance(v_pos, u_planets[i].xy);
if (d < dis) {
dis = d;
color = u_planet_colours[2 * i];
}
}
}
gl_FragColor = vec4(color, 0.2);
} }

View file

@ -3,24 +3,41 @@ precision mediump float;
#endif #endif
attribute vec2 a_pos; attribute vec2 a_pos;
attribute vec2 a_center;
attribute float a_own;
attribute float a_intensity;
uniform vec3 u_planet_colours[$PLANETS * 2];
uniform vec4 u_viewbox; // [x, y, width, height] uniform vec4 u_viewbox; // [x, y, width, height]
uniform vec2 u_resolution; uniform vec2 u_resolution;
uniform float u_time;
varying float v_intensity;
varying float v_dist;
varying vec2 v_pos; varying vec2 v_pos;
varying vec3 v_color;
void main() { void main() {
v_intensity = a_intensity;
v_dist = distance(a_pos * u_resolution , a_center * u_resolution);
vec2 uv = (a_pos.xy + 1.0) * 0.5; int own = int(a_own);
uv = 1.0 - uv;
// uv *= -1.0; vec2 uv = a_pos;
// Viewbox's center is top left, a_position's is in the center to the screen // Viewbox's center is top left, a_position's is in the center to the screen
// So translate and scale the viewbox** // So translate and scale the viewbox**
uv *= u_viewbox.zw; uv -= u_viewbox.xy + (u_viewbox.zw * 0.5);
uv -= u_viewbox.xy + u_viewbox.zw; uv /= u_viewbox.zw * 0.5;
v_pos = uv.xy; v_pos = uv.xy;
gl_Position = vec4(a_pos, 0.0, 1.0); // v_pos = (uv.xy + 1.0) * 0.5;
if (own < 0) {
v_color = vec3(0., 0., 0.);
} else {
v_color = mix(u_planet_colours[own * 2], u_planet_colours[own * 2 + 1], u_time);
}
gl_Position = vec4(uv.xy, 0.0, 1.0);
} }

View file

@ -1,496 +0,0 @@
import { Heap } from 'ts-heap'
import { HalfEdge, Face, Vertex } from './dcel';
interface WithPriority {
get_priority(): number;
}
export class Point {
x: number;
y: number;
face?: Face;
constructor(x: number, y: number, face? :Face) {
this.x = x;
this.y = y;
this.face = face;
}
equals(other: Point): boolean {
return Math.abs(this.x - other.x) + Math.abs(this.y - other.y) < 0.00001;
}
toString(): string {
return `{x: ${this.x}, y: ${this.y}}`;
}
}
class CircleEvent implements WithPriority {
y: number;
alive: boolean = true;
center: Vertex;
leaf: Leaf;
from: Point[];
static
from_sites(s1: Point, s2: Point, s3: Point, leaf: Leaf): CircleEvent {
const a = s1.x * (s2.y - s3.y) - s1.y*(s2.x - s3.x) + s2.x*s3.y - s3.x * s2.y;
const b = (s1.x ** 2 + s1.y ** 2) * (s3.y - s2.y) + (s2.x ** 2 + s2.y ** 2)*(s1.y - s3.y) + (s3.x ** 2 + s3.y ** 2) * (s2.y - s1.y);
const c = (s1.x ** 2 + s1.y ** 2) * (s2.x - s3.x) + (s2.x ** 2 + s2.y ** 2)*(s3.x - s1.x) + (s3.x ** 2 + s3.y ** 2) * (s1.x - s2.x);
const d = (s1.x ** 2 + s1.y ** 2) * (s3.x*s2.y - s2.x*s3.y) + (s2.x ** 2 + s2.y ** 2)*(s1.x*s3.y - s3.x*s1.y) + (s3.x ** 2 + s3.y ** 2) * (s2.x*s1.y - s1.x*s2.y);
const center = new Vertex(-b / (2. * a), -c / (2. * a));
const r = Math.sqrt((b ** 2 + c ** 2 - 4. * a * d) / (4. * a ** 2));
const y = center.coords[1] - r;
const out = new CircleEvent();
out.y = y;
out.center = center;
out.leaf = leaf;
out.from = [s1, s2, s3];
return out;
}
get_priority(): number {
return this.y;
}
print() {
console.log(`Circle event at ${this.y} ${JSON.stringify(this.center)}, ${this.leaf.point.toString()} from ${JSON.stringify(this.from.map((e) => e.toString()))}`);
}
}
class SiteEvent implements WithPriority{
face: Face;
point: Point;
constructor(point: Point) {
this.face = new Face(point);
this.point = point;
this.point.face = this.face;
}
get_priority(): number {
return this.point.y;
}
print() {
console.log(`Site event ${this.point.toString()}`);
}
}
function calc_x(left: Point, right: Point, y: number): number {
const [a1, b1, c1] = from_focus_vertex(left, y);
const [a2, b2, c2] = from_focus_vertex(right, y);
if (Math.abs(a1 - a2) < 0.0001) {
return (left.x + right.x) / 2.;
}
const da = a1 - a2;
const db = b1 - b2;
const dc = c1 - c2;
const d = db * db - 4. * da * dc;
if (d <= 0.) {
throw new Error(`D is less then 0 ${d}`);
}
const dd = Math.sqrt(d);
const x = (-db + dd) / (2. * da);
return x;
}
function from_focus_vertex(focus: Point, y: number): number[] {
const a = (focus.y - y) / 2;
const h = focus.x;
const k = focus.y - a;
return [1 / (4. * a), -h / (2 * a), (h ** 2 / (4 * a)) + k]
}
function cmp_event(e1: WithPriority, e2: WithPriority) {
return e2.get_priority() - e1.get_priority();
}
type Queue = Heap<WithPriority>;
type Node = Leaf | Breakpoint;
type Parent = Breakpoint | State;
class Leaf {
point: Point;
event: CircleEvent | undefined;
left: Leaf | undefined;
right: Leaf | undefined;
parent: Parent;
half_edge: HalfEdge;
constructor(point: Point, parent: Parent) {
this.point = point;
this.parent = parent;
this.half_edge = new HalfEdge(undefined, undefined);
}
false_alarm() {
if(this.event) {
console.log(`False alarm ${JSON.stringify(this.event.center)} ${this.event.y}`);
this.event.alive = false;
}
}
update_left(leaf: Leaf) {
if (this.left) {
this.left.right = leaf;
}
leaf.left = this.left;
}
update_right(leaf: Leaf) {
if (this.right) {
this.right.left = leaf;
}
leaf.right = this.right;
}
split(point: Point, events: Queue) {
this.false_alarm();
if (this.point.y == point.y) {
const middle = new Leaf(point, undefined);
const parent = this.parent;
if (this.point.x > point.x) {
const br = new Breakpoint([point, middle], [this.point, this], this.parent);
if (this.left) this.left.right = middle;
this.left = middle;
middle.right = this;
if (parent instanceof Breakpoint) {
parent.set_me(this, br);
} else {
parent.root = br;
}
const maybe_left = middle.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
console.log(`Adding circle`);
maybe_left.print();
middle.event = maybe_left;
events.add(maybe_left);
}
const maybe_right = this.check_circles(point.y, events);
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle`);
maybe_right.print();
this.event = maybe_right;
events.add(maybe_right);
}
} else {
const br = new Breakpoint([this.point, this], [point, middle], this.parent);
if (this.right) this.right.left = middle;
this.right = middle;
middle.left = this;
if (parent instanceof Breakpoint) {
parent.set_me(this, br);
} else {
parent.root = br;
}
const maybe_left = this.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
console.log(`Adding circle`);
maybe_left.print();
this.event = maybe_left;
events.add(maybe_left);
}
const maybe_right = middle.check_circles(point.y, events);
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle`);
maybe_right.print();
middle.event = maybe_right;
events.add(maybe_right);
}
}
return;
}
const left = new Leaf(this.point, undefined);
left.left = this.left;
if (this.left) this.left.right = left;
const right = new Leaf(this.point, undefined);
right.right = this.right;
if (this.right) this.right.left = right;
const middle = new Leaf(point, undefined);
middle.left = left;
middle.right = right;
right.left = middle;
left.right = middle;
const br1 = new Breakpoint([this.point, left], [point, middle], undefined);
const br2 = new Breakpoint([point, br1], [this.point, right], this.parent);
br1.parent = br2;
if (this.parent instanceof Breakpoint) {
this.parent.set_me(this, br2);
} else {
this.parent.root = br2;
}
const maybe_left = left.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
console.log(`Adding circle`);
maybe_left.print();
left.event = maybe_left;
events.add(maybe_left);
}
const maybe_right = right.check_circles(point.y, events);
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle`);
maybe_right.print();
right.event = maybe_right;
events.add(maybe_right);
}
}
check_circles(y: number, events: Queue): CircleEvent | undefined {
const left = this.left;
const right = this.right;
if (left && right) {
const circle = CircleEvent.from_sites(left.point, this.point, right.point, this);
console.log(`${circle.y} < ${y}`);
if (circle.y < y ) {
return circle;
}
}
}
delete(vertex: Vertex) {
if (this.parent instanceof Breakpoint) {
this.parent.remove_me(this.point, vertex);
} else {
console.error("Shouldnt be here");
}
}
print(indent: string) {
console.log(`${indent}Leaf from ${this.point.toString()} vertex: ${this.half_edge.to_string()}`);
}
}
class Breakpoint {
left: [Point, Node];
right: [Point, Node];
parent: Parent;
half_edge: HalfEdge;
constructor(left: [Point, Node], right: [Point, Node], parent: Parent) {
this.left = left;
this.right = right;
this.left[1].parent = this;
this.right[1].parent = this;
this.parent = parent;
this.half_edge = new HalfEdge(undefined, left[0].face, right[0].face);
}
remove_me(point: Point, vertex: Vertex) {
const edge = this.half_edge.insert(vertex);
const other = this.get_other(point);
if (this.parent instanceof Breakpoint) {
this.parent.set_me(this, other);
this.parent.set_edge(edge);
} else {
this.parent.root = other;
}
}
set_edge(edge: HalfEdge) {
this.left[1].half_edge = edge;
// this.right[1].half_edge = edge.split(edge.origin);
}
set_me(old_me: Node, new_me: Node) {
if (this.left[1] == old_me) {
this.left[1] = new_me;
} else {
this.right[1] = new_me;
}
}
get(point: Point): Leaf {
const { x, y } = point;
const test_x = calc_x(this.left[0], this.right[0], y);
if (test_x >= x) {
if (this.left[1] instanceof Leaf) {
return this.left[1];
} else {
return this.left[1].get(point);
}
} else {
if (this.right[1] instanceof Leaf) {
return this.right[1];
} else {
return this.right[1].get(point);
}
}
}
get_other(point: Point): Node {
if (this.left[0].equals(point)) {
return this.right[1];
} else {
return this.left[1];
}
}
print(indent: string) {
console.log(`${indent}vertex: ${this.half_edge.to_string()}`);
console.log(`${indent}left`);
this.left[1].print(indent + ' ');
console.log(`${indent}right`);
this.right[1].print(indent + ' ');
}
}
function get_from_node(root: Node, point: Point): Leaf {
if (root instanceof Leaf) {
return root;
} else {
return root.get(point);
}
}
class State {
root: Node | undefined;
print() {
if (this.root) {
this.root.print('');
} else {
console.log("No root no tree");
}
}
}
export function voronoi(points: Point[]): Point[] {
const out = [];
const state = new State;
const queue = new Heap<WithPriority>(cmp_event);
for (let point of points) {
queue.add(new SiteEvent(point));
}
let event;
while (event = queue.pop()){
console.log('---------------------------');
event.print();
if (event instanceof SiteEvent) {
handle_site_event(event, queue, state, out);
} else {
if (!event.alive) {
console.log("Dead");
continue;
}
handle_circle_event(event, queue, state, out);
}
state.print();
console.log(queue);
print_leaves(get_from_node(state.root, new Point(0, 0)));
}
return out;
}
function handle_site_event(event: SiteEvent, queue: Queue, state: State, out: Point[]) {
if (state.root) {
const leaf = get_from_node(state.root, event.point);
leaf.split(event.point, queue);
} else {
state.root = new Leaf(event.point, state);
}
}
function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out: [number, number][]) {
if (!event.alive) return;
event.leaf.delete(event.center);
const right = event.leaf.right;
const left = event.leaf.left;
if (right) {
right.false_alarm();
// if (right.right) right.right.false_alarm();
right.left = left;
const maybe_right = right.check_circles(event.y, queue);
if (maybe_right){
console.log(`Adding circle event`);
maybe_right.print();
right.event = maybe_right;
queue.add(maybe_right);
}
}
if (left) {
left.false_alarm();
// if (left.left) left.left.false_alarm();
left.right = right;
const maybe_left = left.check_circles(event.y, queue);
if (maybe_left){
console.log(`Adding circle event`);
maybe_left.print();
left.event = maybe_left;
queue.add(maybe_left);
}
}
out.push(event.center.coords);
}
function print_leaves(start: Leaf) {
let current = start;
while (current.left) {
current = current.left;
}
const points = [current.point];
while (current.right) {
current = current.right;
points.push(current.point);
}
console.log(JSON.stringify(points.map((p) => p.toString())));
}

View file

@ -1,84 +0,0 @@
import { Renderable } from './renderer';
import { Shader, Uniform } from './shader';
import { Dictionary } from './util';
function createAndSetupTexture(gl: WebGLRenderingContext): WebGLTexture {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture so we can render any size image and so we are
// working with pixels.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return texture;
}
export class Foo implements Renderable {
uniforms: Dictionary<Uniform>;
stages: Stage[];
textures: WebGLTexture[];
framebuffers: WebGLFramebuffer[];
width: number;
height: number;
constructor(gl: WebGLRenderingContext, width: number, height: number) {
this.uniforms = {};
this.width = width;
this.height = height;
for (let ii = 0; ii < 2; ++ii) {
const texture = createAndSetupTexture(gl);
this.textures.push(texture);
// make the texture the same size as the image
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer
const fbo = gl.createFramebuffer();
this.framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it.
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}
}
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) {
this.stages.forEach( (item, i) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i%2]);
item.render(gl);
gl.bindTexture(gl.TEXTURE_2D, this.textures[i % 2]);
});
}
}
class Stage implements Renderable {
program: Shader;
uniforms: Dictionary<Uniform>;
getUniforms(): Dictionary<Uniform> {
return this.uniforms;
}
render(gl: WebGLRenderingContext) {
this.program.bind(gl);
for (let name in this.uniforms) {
this.program.uniform(gl, name, this.uniforms[name]);
}
}
}

View file

@ -1,20 +0,0 @@
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
// /*~ If this module is a UMD module that exposes a global variable 'myLib' when
// *~ loaded outside a module loader environment, declare that global here.
// *~ Otherwise, delete this declaration.
// */
// export as namespace myLib;
/*~ If this module has methods, declare them as functions like so.
*/
export function createProgramFromScripts(gl: WebGLRenderingContext, shaderScriptIds: string[], opt_attribs?: string[], opt_locations?: number[], opt_errorCallback?: any): any;
export function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement, multiplier?: number): boolean;

File diff suppressed because it is too large Load diff