Compare commits
16 commits
Author | SHA1 | Date | |
---|---|---|---|
|
4de17f78b3 | ||
|
4dc946ef92 | ||
|
ef5399217d | ||
|
51712bf07e | ||
|
9a33ad42fc | ||
|
2ba043862f | ||
|
ba0b2c7655 | ||
|
dcf1173cb8 | ||
|
442f969da0 | ||
|
7684c24db0 | ||
|
cedcc09f0a | ||
|
129c904967 | ||
|
998cb3d535 | ||
|
33abe9515f | ||
|
6b7fd4eff3 | ||
|
fd3178060a |
53 changed files with 3838 additions and 3183 deletions
38
Dockerfile
Normal file
38
Dockerfile
Normal 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
9
README.md
Normal 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.
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "backend"
|
name = "planetwars"
|
||||||
version = "0.1.0"
|
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"] }
|
||||||
|
|
||||||
rust-ini = "0.15.2"
|
educe = { version = "0.4.13", features = ["Debug", "Default", "Hash", "Clone", "Copy"] }
|
||||||
|
|
|
@ -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...
|
||||||
|
|
|
@ -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
|
|
|
@ -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;
|
||||||
|
@ -19,8 +21,10 @@ extern crate tracing_subscriber;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
|
||||||
extern crate ini;
|
#[macro_use]
|
||||||
|
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;
|
||||||
|
@ -39,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;
|
||||||
|
@ -78,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()
|
||||||
|
@ -94,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
|
||||||
|
@ -121,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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::convert::TryInto;
|
||||||
use std::fs::{create_dir, File};
|
use std::fs::{create_dir, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
mod pw_config;
|
mod pw_config;
|
||||||
mod pw_protocol;
|
mod pw_protocol;
|
||||||
|
@ -35,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,
|
||||||
|
@ -213,6 +214,7 @@ impl game::Controller for PlanetWarsGame {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"map": self.map,
|
"map": self.map,
|
||||||
"file": self.log_file_loc,
|
"file": self.log_file_loc,
|
||||||
|
"time": SystemTime::now(),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -220,6 +222,10 @@ impl game::Controller for PlanetWarsGame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_epoch() -> SystemTime {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct FinishedState {
|
pub struct FinishedState {
|
||||||
pub winners: Vec<u64>,
|
pub winners: Vec<u64>,
|
||||||
|
@ -227,5 +233,7 @@ pub struct FinishedState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub file: String,
|
pub file: String,
|
||||||
pub map: String,
|
pub map: String,
|
||||||
|
#[serde(default = "get_epoch")]
|
||||||
|
pub time: SystemTime,
|
||||||
pub players: Vec<(u64, String)>,
|
pub players: Vec<(u64, String)>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::planetwars;
|
use crate::planetwars::{self, FinishedState};
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
use rocket::{Route, State};
|
use rocket::{Route, State};
|
||||||
|
@ -13,10 +13,12 @@ use async_std::fs;
|
||||||
use async_std::prelude::StreamExt;
|
use async_std::prelude::StreamExt;
|
||||||
|
|
||||||
use futures::executor::ThreadPool;
|
use futures::executor::ThreadPool;
|
||||||
|
use futures::future::{join_all, FutureExt};
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
/// The type required to build a game.
|
/// The type required to build a game.
|
||||||
/// (json in POST request).
|
/// (json in POST request).
|
||||||
|
@ -75,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,
|
||||||
|
@ -106,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"
|
||||||
}
|
}
|
||||||
|
@ -174,11 +177,8 @@ async fn get_maps() -> Result<Vec<Map>, String> {
|
||||||
Ok(maps)
|
Ok(maps)
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::planetwars::FinishedState;
|
|
||||||
|
|
||||||
use futures::future::{join_all, FutureExt};
|
|
||||||
pub async fn get_states(
|
pub async fn get_states(
|
||||||
game_ids: &Vec<(String, u64)>,
|
game_ids: &Vec<(String, u64, SystemTime)>,
|
||||||
manager: &game::Manager,
|
manager: &game::Manager,
|
||||||
) -> Result<Vec<GameState>, String> {
|
) -> Result<Vec<GameState>, String> {
|
||||||
let mut states = Vec::new();
|
let mut states = Vec::new();
|
||||||
|
@ -186,17 +186,18 @@ pub async fn get_states(
|
||||||
game_ids
|
game_ids
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|(name, id)| manager.get_state(id).map(move |f| (f, name))),
|
.map(|(name, id, time)| manager.get_state(id).map(move |f| (f, name, time))),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
for (gs, name) in gss {
|
for (gs, name, time) in gss {
|
||||||
if let Some(state) = gs {
|
if let Some(state) = gs {
|
||||||
match state {
|
match state {
|
||||||
Ok((state, conns)) => {
|
Ok((state, conns)) => {
|
||||||
let players: Vec<PlayerStatus> =
|
let players: Vec<PlayerStatus> =
|
||||||
conns.iter().cloned().map(|x| x.into()).collect();
|
conns.iter().cloned().map(|x| x.into()).collect();
|
||||||
let connected = players.iter().filter(|x| x.connected).count();
|
let connected = players.iter().filter(|x| x.connected).count();
|
||||||
|
|
||||||
states.push(GameState::Playing {
|
states.push(GameState::Playing {
|
||||||
name: name,
|
name: name,
|
||||||
total: players.len(),
|
total: players.len(),
|
||||||
|
@ -204,6 +205,7 @@ pub async fn get_states(
|
||||||
connected,
|
connected,
|
||||||
map: String::new(),
|
map: String::new(),
|
||||||
state,
|
state,
|
||||||
|
time,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(value) => {
|
Err(value) => {
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -47,7 +53,9 @@ async fn debug_get() -> Result<Template, String> {
|
||||||
/// Routes the visualizer page, rendering the visualizer Template.
|
/// Routes the visualizer page, rendering the visualizer Template.
|
||||||
#[get("/visualizer")]
|
#[get("/visualizer")]
|
||||||
async fn visualizer_get() -> Template {
|
async fn visualizer_get() -> Template {
|
||||||
let game_options = get_played_games().await;
|
let mut game_options: Vec<GameState> = get_played_games().await;
|
||||||
|
game_options.sort();
|
||||||
|
|
||||||
let context = Context::new_with(
|
let context = Context::new_with(
|
||||||
"Visualizer",
|
"Visualizer",
|
||||||
json!({"games": game_options, "colours": COLOURS}),
|
json!({"games": game_options, "colours": COLOURS}),
|
||||||
|
@ -60,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
|
||||||
|
@ -69,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()
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde_json::Value;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
static NAV: [(&'static str, &'static str); 6] = [
|
static NAV: [(&'static str, &'static str); 6] = [
|
||||||
("/", "Home"),
|
("/", "Home"),
|
||||||
|
@ -20,7 +21,8 @@ pub static COLOURS: [&'static str; 10] = [
|
||||||
|
|
||||||
/// The state of a player, in a running game.
|
/// The state of a player, in a running game.
|
||||||
/// This represents actual players or connection keys.
|
/// This represents actual players or connection keys.
|
||||||
#[derive(Serialize, Eq, PartialEq)]
|
#[derive(Serialize, Educe)]
|
||||||
|
#[educe(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct PlayerStatus {
|
pub struct PlayerStatus {
|
||||||
pub waiting: bool,
|
pub waiting: bool,
|
||||||
pub connected: bool,
|
pub connected: bool,
|
||||||
|
@ -53,48 +55,41 @@ impl From<Connect> for PlayerStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn partial_cmp(a: &SystemTime, b: &SystemTime) -> Option<Ordering> {
|
||||||
|
b.partial_cmp(a)
|
||||||
|
}
|
||||||
|
|
||||||
/// The GameState is the state of a game.
|
/// The GameState is the state of a game.
|
||||||
/// Either Finished, so the game is done, not running, and there is a posible visualization.
|
/// Either Finished, so the game is done, not running, and there is a posible visualization.
|
||||||
/// Or Playing, the game is still being managed by the mozaic framework.
|
/// Or Playing, the game is still being managed by the mozaic framework.
|
||||||
#[derive(Serialize, Eq, PartialEq)]
|
#[derive(Serialize, Educe)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
|
#[educe(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum GameState {
|
pub enum GameState {
|
||||||
Finished {
|
#[educe(PartialOrd(rank = 1))]
|
||||||
name: String,
|
|
||||||
map: String,
|
|
||||||
players: Vec<(String, bool)>,
|
|
||||||
turns: u64,
|
|
||||||
file: String,
|
|
||||||
},
|
|
||||||
Playing {
|
Playing {
|
||||||
|
#[educe(PartialOrd(method = "partial_cmp"))]
|
||||||
|
time: SystemTime,
|
||||||
|
|
||||||
name: String,
|
name: String,
|
||||||
map: String,
|
map: String,
|
||||||
players: Vec<PlayerStatus>,
|
players: Vec<PlayerStatus>,
|
||||||
connected: usize,
|
connected: usize,
|
||||||
total: usize,
|
total: usize,
|
||||||
|
#[educe(Ord(ignore), PartialOrd(ignore))]
|
||||||
state: Value,
|
state: Value,
|
||||||
},
|
},
|
||||||
}
|
#[educe(PartialOrd(rank = 2))]
|
||||||
|
Finished {
|
||||||
|
#[educe(PartialOrd(method = "partial_cmp"))]
|
||||||
|
time: SystemTime,
|
||||||
|
name: String,
|
||||||
|
map: String,
|
||||||
|
|
||||||
impl PartialOrd for GameState {
|
players: Vec<(String, bool)>,
|
||||||
fn partial_cmp(&self, other: &GameState) -> Option<Ordering> {
|
turns: u64,
|
||||||
Some(self.cmp(other))
|
file: String,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for GameState {
|
|
||||||
fn cmp(&self, other: &GameState) -> Ordering {
|
|
||||||
match self {
|
|
||||||
GameState::Finished { name, .. } => match other {
|
|
||||||
GameState::Finished { name: _name, .. } => name.cmp(_name),
|
|
||||||
_ => Ordering::Greater,
|
|
||||||
},
|
},
|
||||||
GameState::Playing { name, .. } => match other {
|
|
||||||
GameState::Playing { name: _name, .. } => name.cmp(_name),
|
|
||||||
_ => Ordering::Less,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FinishedState> for GameState {
|
impl From<FinishedState> for GameState {
|
||||||
|
@ -111,6 +106,7 @@ impl From<FinishedState> for GameState {
|
||||||
name: state.name,
|
name: state.name,
|
||||||
turns: state.turns,
|
turns: state.turns,
|
||||||
file: state.file,
|
file: state.file,
|
||||||
|
time: state.time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,9 +167,9 @@ impl Context<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Games is the game manager wrapper so Rocket can manage it
|
/// State of current live games
|
||||||
pub struct Games {
|
pub struct Games {
|
||||||
inner: Arc<Mutex<Vec<(String, u64)>>>,
|
inner: Arc<Mutex<Vec<(String, u64, SystemTime)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Games {
|
impl Games {
|
||||||
|
@ -184,10 +180,13 @@ impl Games {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_game(&self, name: String, id: u64) {
|
pub fn add_game(&self, name: String, id: u64) {
|
||||||
self.inner.lock().unwrap().push((name, id));
|
self.inner
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push((name, id, SystemTime::now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_games(&self) -> Vec<(String, u64)> {
|
pub fn get_games(&self) -> Vec<(String, u64, SystemTime)> {
|
||||||
self.inner.lock().unwrap().clone()
|
self.inner.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,12 +36,9 @@ async function start_game() {
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open("POST", "/lobby");
|
xhr.open("POST", "/lobby");
|
||||||
xhr.send(JSON.stringify(obj));
|
xhr.addEventListener("loadend", refresh_state);
|
||||||
|
|
||||||
setTimeout(
|
xhr.send(JSON.stringify(obj));
|
||||||
() => refresh_state(),
|
|
||||||
200
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = () => refresh_state();
|
window.onload = () => refresh_state();
|
|
@ -50,3 +50,20 @@
|
||||||
content: "\f091";
|
content: "\f091";
|
||||||
transform: translate(-30px, 0px);
|
transform: translate(-30px, 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
float: left;
|
||||||
|
margin: 0 -20px 0 0;
|
||||||
|
font-family: 'fontawesome';
|
||||||
|
content: "\f14c";
|
||||||
|
transform: translate(-20px, 0px);
|
||||||
|
color: antiquewhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover::before {
|
||||||
|
color: #ff7f00;
|
||||||
|
}
|
|
@ -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 <Your name> -i <Id from the lobby> \
|
-n <Your name> -i <Id from the lobby> \
|
||||||
python3 simple.py
|
python3 simple.py
|
||||||
</code>
|
</code>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<h2 class="handle">
|
<h2 class="handle">
|
||||||
<label for="handle_{{loop.index}}">
|
<label for="handle_{{loop.index}}">
|
||||||
<span>{{state.name}} ({% if state.state %}{{state.state.map}}{% else %}{{state.map}}{% endif %})</span>
|
<span>{{state.name}} ({% if state.state %}{{state.state.map}}{% else %}{{state.map}}{% endif %})</span>
|
||||||
<span style="float: right">{% if state.type == "Playing" %}{{ state.connected }}/{{state.total}}{% endif %}</span>
|
<span style="float: right">{% if state.type == "Playing" %}{{ state.connected }}/{{state.total}}{% else %}<a class="link" href='/visualizer?name={{state.name | safe }}&game=/games/{{state.file | safe}}'></a>{% endif %}</span>
|
||||||
</label>
|
</label>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
|
@ -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
31
build.sh
Executable 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
81
client/simple.js
Normal 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();
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
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::*;
|
||||||
|
|
||||||
|
macro_rules! console_log {
|
||||||
|
// Note that this is using the `log` function imported above during
|
||||||
|
// `bare_bones`
|
||||||
|
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
// allocator.
|
// allocator.
|
||||||
#[cfg(feature = "wee_alloc")]
|
#[cfg(feature = "wee_alloc")]
|
||||||
|
@ -25,11 +31,17 @@ pub struct Circle {
|
||||||
r: f32,
|
r: f32,
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
a1: f32,
|
a0: f32,
|
||||||
a2: f32,
|
ad: f32,
|
||||||
distance: usize,
|
distance: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
fn spr(from: f32) -> f32 {
|
||||||
|
let pi2 = PI * 2.;
|
||||||
|
((from % pi2) + pi2) % pi2
|
||||||
|
}
|
||||||
|
|
||||||
impl Circle {
|
impl Circle {
|
||||||
pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
|
pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
|
||||||
let x1 = p1.x;
|
let x1 = p1.x;
|
||||||
|
@ -37,106 +49,77 @@ impl Circle {
|
||||||
let x2 = p2.x;
|
let x2 = p2.x;
|
||||||
let y2 = p2.y;
|
let y2 = p2.y;
|
||||||
|
|
||||||
let q = ((x2-x1).powi(2) + (y2-y1).powi(2)).sqrt();
|
// Distance between planets
|
||||||
let x3 = (x1+x2)/2.0;
|
let q = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
|
||||||
let y3 = (y1+y2)/2.0;
|
// Center of between planets
|
||||||
|
let x3 = (x1 + x2) / 2.0;
|
||||||
|
let y3 = (y1 + y2) / 2.0;
|
||||||
|
|
||||||
let r = q * 1.1;
|
// Radius of circle
|
||||||
|
let r = q * 1.0;
|
||||||
|
|
||||||
let mut x = x3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
|
// Center of circle
|
||||||
let mut y = y3 + (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
|
let x = x3 + (r.powi(2) - (q / 2.0).powi(2)).sqrt() * (y1 - y2) / q;
|
||||||
|
let y = y3 + (r.powi(2) - (q / 2.0).powi(2)).sqrt() * (x2 - x1) / q;
|
||||||
|
// console_log!("{},{} -> {},{} ({},{} r={})", x1, y1, x2, y2, x, y, r);
|
||||||
|
|
||||||
|
let a0 = spr((y - y1).atan2(x - x1));
|
||||||
|
let a2 = spr((y - y2).atan2(x - x2));
|
||||||
|
|
||||||
let mut a1 = (y - y1).atan2(x - x1);
|
let mut ad = spr(a0 - a2);
|
||||||
let mut a2 = (y - y2).atan2(x - x2);
|
if ad > PI {
|
||||||
|
ad = spr(a2 - a0);
|
||||||
if a2 < a1 {
|
|
||||||
|
|
||||||
x = x3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (y1-y2)/q;
|
|
||||||
y = y3 - (r.powi(2)-(q/2.0).powi(2)).sqrt() * (x2-x1)/q;
|
|
||||||
|
|
||||||
a1 = (y - y1).atan2(x - x1);
|
|
||||||
a2 = (y - y2).atan2(x - x2);
|
|
||||||
}
|
}
|
||||||
|
// console_log!("a1 {} a2 {} ad {}", a0/PI * 180.0, a2/PI * 180.0, ad/PI*180.0);
|
||||||
|
|
||||||
let distance = q.ceil() as usize + 1;
|
let distance = q.ceil() as usize + 1;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
r, x, y, a1, a2, distance
|
r,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
a0,
|
||||||
|
ad,
|
||||||
|
distance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
|
pub fn get_for_remaining(&self, remaining: usize) -> ((Mat3<f32>, f32), (Mat3<f32>, f32)) {
|
||||||
(
|
(
|
||||||
self.get_remaining(remaining),
|
self.get_remaining(remaining),
|
||||||
self.get_remaining((remaining + 1).min(self.distance - 1)),
|
self.get_remaining((remaining + 1).min(self.distance - 1)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
|
fn get_remaining(&self, remaining: usize) -> (Mat3<f32>, f32) {
|
||||||
let alpha = (self.a1 * remaining as f32 + (self.distance - remaining) as f32 * self.a2) / self.distance as f32;
|
let alpha = self.a0 + (1.0 - (remaining as f32 / self.distance as f32)) * self.ad;
|
||||||
|
|
||||||
let cos = alpha.cos();
|
let cos = alpha.cos();
|
||||||
let sin = alpha.sin();
|
let sin = alpha.sin();
|
||||||
|
(
|
||||||
Mat3::new(
|
Mat3::new(
|
||||||
0.3, 0.0, 0.0,
|
0.3,
|
||||||
0.0, 0.4, 0.0,
|
0.0,
|
||||||
-self.x + cos * self.r, -self.y + sin * self.r, 0.3,
|
0.0,
|
||||||
) * Mat3::rotate_z(alpha)
|
0.0,
|
||||||
|
0.3,
|
||||||
|
0.0,
|
||||||
|
-self.x + cos * self.r,
|
||||||
|
-self.y + sin * self.r,
|
||||||
|
0.3,
|
||||||
|
),
|
||||||
|
alpha,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct Line {
|
fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<f32>, Vec<usize>) {
|
||||||
// x1: f32,
|
let mut verts: Vec<[f32; 2]> = planets.iter().map(|p| [p.x, p.y]).collect();
|
||||||
// y1: f32,
|
|
||||||
// x2: f32,
|
|
||||||
// y2: f32,
|
|
||||||
// a: f32,
|
|
||||||
// d: usize,
|
|
||||||
// }
|
|
||||||
// impl Line {
|
|
||||||
// pub fn new(p1: &types::Planet, p2: &types::Planet) -> Self {
|
|
||||||
// let dx = p1.x - p2.x;
|
|
||||||
// let dy = p1.y - p2.y;
|
|
||||||
// let a = dy.atan2(dx);
|
|
||||||
// // let a = (dy / dx).atan();
|
|
||||||
// let d = (dx * dx + dy * dy).sqrt().ceil() as usize + 1;
|
|
||||||
|
|
||||||
// Self {
|
|
||||||
// x1: p1.x,
|
|
||||||
// x2: p2.x,
|
|
||||||
// y1: p1.y,
|
|
||||||
// y2: p2.y,
|
|
||||||
// d, a,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn get_for_remaining(&self, remaining: usize) -> (Mat3<f32>, Mat3<f32>) {
|
|
||||||
// (
|
|
||||||
// self.get_remaining(remaining),
|
|
||||||
// self.get_remaining((remaining + 1).min(self.d - 1)),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn get_remaining(&self, remaining: usize) -> Mat3<f32> {
|
|
||||||
// let x = (self.x1 * remaining as f32 + (self.d - remaining) as f32 * self.x2) / self.d as f32;
|
|
||||||
// // let x = self.x1 + (remaining as f32 / self.d as f32) * (self.x2 - self.x1);
|
|
||||||
// let y = (self.y1 * remaining as f32 + (self.d - remaining) as f32 * self.y2) / self.d as f32;
|
|
||||||
|
|
||||||
// // let y = self.y1 + (remaining as f32 / self.d as f32) * (self.y2 - self.y1);
|
|
||||||
// Mat3::new(
|
|
||||||
// 0.3, 0.0, 0.0,
|
|
||||||
// 0.0, 0.3, 0.0,
|
|
||||||
// x, y, 0.3,
|
|
||||||
// ) * Mat3::rotate_z(self.a)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn create_voronoi(planets: &Vec<types::Planet>, bbox: f32) -> (Vec<Vec2<f32>>, Vec<usize>) {
|
|
||||||
let mut verts: Vec<Vec2<f32>> = planets.iter().map(|p| Vec2::new(p.x, p.y)).collect();
|
|
||||||
let mut ids = Vec::new();
|
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);
|
||||||
|
@ -147,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);
|
||||||
|
@ -158,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>,
|
||||||
|
@ -172,14 +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>,
|
||||||
|
|
||||||
ship_locations: Vec<[f32;9]>,
|
ship_locations: Vec<f32>,
|
||||||
ship_colours: Vec<Vec3<f32>>,
|
ship_label_locations: Vec<f32>,
|
||||||
current_planet_colours: Vec<Vec3<f32>>,
|
ship_colours: Vec<f32>,
|
||||||
|
ship_counts: Vec<usize>,
|
||||||
|
|
||||||
voronoi_vertices: Vec<Vec2<f32>>,
|
current_planet_colours: Vec<f32>,
|
||||||
voronoi_colors: Vec<Vec3<f32>>,
|
|
||||||
|
voronoi_vertices: Vec<f32>,
|
||||||
|
voronoi_colors: Vec<f32>,
|
||||||
voronoi_indices: Vec<usize>,
|
voronoi_indices: Vec<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,10 +173,13 @@ impl Game {
|
||||||
pub fn new(file: &str) -> Self {
|
pub fn new(file: &str) -> Self {
|
||||||
utils::set_panic_hook();
|
utils::set_panic_hook();
|
||||||
|
|
||||||
|
console_log!("Rust is busy being awesome!");
|
||||||
|
|
||||||
// First line is fucked but we just filter out things that cannot parse
|
// First line is fucked but we just filter out things that cannot parse
|
||||||
let states: Vec<types::State> = file.split("\n").filter_map(|line|
|
let states: Vec<types::State> = file
|
||||||
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();
|
||||||
|
|
||||||
|
@ -203,20 +191,27 @@ 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),
|
||||||
|
planet_ships: Vec::new(),
|
||||||
view_box,
|
view_box,
|
||||||
|
|
||||||
planet_map,
|
planet_map,
|
||||||
turn: 0,
|
turn: 0,
|
||||||
states,
|
states,
|
||||||
ship_locations: Vec::new(),
|
ship_locations: Vec::new(),
|
||||||
|
ship_label_locations: Vec::new(),
|
||||||
ship_colours: Vec::new(),
|
ship_colours: Vec::new(),
|
||||||
|
ship_counts: Vec::new(),
|
||||||
current_planet_colours: Vec::new(),
|
current_planet_colours: Vec::new(),
|
||||||
|
|
||||||
voronoi_vertices,
|
voronoi_vertices,
|
||||||
|
@ -225,20 +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_colors(&self) -> *const Vec3<f32> {
|
pub fn get_planet_ships(&self) -> Vec<usize> {
|
||||||
self.current_planet_colours.as_ptr()
|
self.planet_ships.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_planet_count(&self) -> usize {
|
pub fn get_planet_colors(&self) -> Vec<f32> {
|
||||||
self.planets.len()
|
self.current_planet_colours.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn turn_count(&self) -> usize {
|
pub fn turn_count(&self) -> usize {
|
||||||
|
@ -246,91 +241,129 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_turn(&mut self, turn: usize) -> usize {
|
pub fn update_turn(&mut self, turn: usize) -> usize {
|
||||||
self.turn = turn.min(self.states.len() -1);
|
self.turn = turn.min(self.states.len() - 1);
|
||||||
|
|
||||||
|
self.update_planet_ships();
|
||||||
self.update_planet_colours();
|
self.update_planet_colours();
|
||||||
self.update_voronoi_colors();
|
self.update_voronoi_colors();
|
||||||
self.update_ship_locations();
|
self.update_ship_locations();
|
||||||
|
self.update_ship_counts();
|
||||||
|
|
||||||
self.turn
|
self.turn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_planet_ships(&mut self) {
|
||||||
|
self.planet_ships = self.states[self.turn]
|
||||||
|
.planets
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.ship_count as usize)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
fn update_voronoi_colors(&mut self) {
|
fn update_voronoi_colors(&mut self) {
|
||||||
for (i, p) in self.states[self.turn].planets.iter().enumerate() {
|
for (i, p) in self.states[self.turn].planets.iter().enumerate() {
|
||||||
self.voronoi_colors[i] = utils::COLORS[p.owner.unwrap_or(0) as usize % utils::COLORS.len()].into()
|
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) {
|
||||||
let mut new_vec = Vec::new();
|
let mut new_sl = Vec::new();
|
||||||
for ship in self.states[self.turn].expeditions.iter() {
|
let mut new_sll = Vec::new();
|
||||||
let (o1, o2) = self.planet_map.get(&(ship.origin.clone(), ship.destination.clone())).unwrap().get_for_remaining(ship.turns_remaining as usize);
|
|
||||||
new_vec.push(o1.to_array());
|
|
||||||
new_vec.push(o2.to_array());
|
|
||||||
}
|
|
||||||
self.ship_locations = new_vec;
|
|
||||||
|
|
||||||
self.ship_colours = self.states[self.turn].expeditions.iter().map(|s| {
|
let t = Mat3::new(0.2, 0., 0., 0., 0.2, 0.0, 0., -0.5, 0.2);
|
||||||
utils::COLORS[s.owner as usize % utils::COLORS.len()].into()
|
|
||||||
}).collect();
|
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);
|
||||||
|
new_sl.push((o1 * Mat3::rotate_z(a1)).to_array());
|
||||||
|
new_sl.push((o2 * Mat3::rotate_z(a2)).to_array());
|
||||||
|
|
||||||
|
new_sll.push((o1 + t).to_array());
|
||||||
|
new_sll.push((o2 + t).to_array());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ship_locations = new_sl.concat();
|
||||||
|
self.ship_label_locations = new_sll.concat();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
self.ship_counts = self.states[self.turn]
|
||||||
|
.expeditions
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.ship_count as usize)
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_max_ships(&self) -> usize {
|
pub fn get_max_ships(&self) -> usize {
|
||||||
self.states.iter().map(|s| s.expeditions.len()).max().unwrap()
|
self.states
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.expeditions.len())
|
||||||
|
.max()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
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_colours(&self) -> *const Vec3<f32> {
|
pub fn get_ship_colours(&self) -> Vec<f32> {
|
||||||
self.ship_colours.as_ptr()
|
self.ship_colours.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_voronoi_vert_count(&self) -> usize {
|
pub fn get_ship_counts(&self) -> Vec<usize> {
|
||||||
self.voronoi_vertices.len()
|
self.ship_counts.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_voronoi_verts(&self) -> *const Vec2<f32> {
|
pub fn get_voronoi_verts(&self) -> Vec<f32> {
|
||||||
self.voronoi_vertices.as_ptr()
|
self.voronoi_vertices.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_voronoi_colours(&self) -> *const Vec3<f32> {
|
pub fn get_voronoi_colours(&self) -> Vec<f32> {
|
||||||
self.voronoi_colors.as_ptr()
|
self.voronoi_colors.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_voronoi_ind_count(&self) -> usize {
|
pub fn get_voronoi_inds(&self) -> Vec<usize> {
|
||||||
self.voronoi_indices.len()
|
self.voronoi_indices.clone()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_voronoi_inds(&self) -> *const usize {
|
|
||||||
self.voronoi_indices.as_ptr()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern {
|
extern "C" {
|
||||||
fn alert(s: &str);
|
fn alert(s: &str);
|
||||||
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
|
fn log(s: &str);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
12
frontend/www/bootstrap.js
vendored
12
frontend/www/bootstrap.js
vendored
|
@ -2,14 +2,20 @@
|
||||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||||
// that no one else needs to worry about it again.
|
// that no one else needs to worry about it again.
|
||||||
// Import index.js that executes index.ts
|
// Import index.js that executes index.ts
|
||||||
var h = (_a, _b) => { }
|
var h = (_a, _b) => {}
|
||||||
|
|
||||||
export function handle(loc, e) {
|
export function handle(loc, e) {
|
||||||
h(loc, e);
|
h(loc, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
import("./index.js")
|
if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, {
|
||||||
.then(e => {
|
print: function() {
|
||||||
|
console.log("Hello world");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
import ("./src/index.js")
|
||||||
|
.then(e => {
|
||||||
h = e.handle;
|
h = e.handle;
|
||||||
})
|
})
|
||||||
.catch(e => console.error("Error importing `index.js`:", e));
|
.catch(e => console.error("Error importing `index.js`:", e));
|
|
@ -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());
|
|
||||||
}
|
|
|
@ -1,86 +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";
|
|
||||||
|
|
||||||
if (OPTIONS) {
|
|
||||||
fetch(game_location)
|
|
||||||
.then((r) => r.text())
|
|
||||||
.then((response) => {
|
|
||||||
parse_ini(response);
|
|
||||||
}).catch(console.error);
|
|
||||||
} else {
|
|
||||||
const options = document.getElementsByClassName("options");
|
|
||||||
if (options[0]) {
|
|
||||||
const options_div = <HTMLDivElement> options[0];
|
|
||||||
if (options_div.children[0]) {
|
|
||||||
setTimeout(
|
|
||||||
() => options_div.children[0].dispatchEvent(new Event('click')),
|
|
||||||
200,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handle(location, name: string) {
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,378 +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 { callbackify } from "util";
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
document.getElementById("addbutton").onclick = function() {
|
|
||||||
FILESELECTOR.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
class GameInstance {
|
|
||||||
resizer: Resizer;
|
|
||||||
game: Game;
|
|
||||||
shader: Shader;
|
|
||||||
vor_shader: Shader;
|
|
||||||
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.vor_shader = VOR_SHADER_FACTORY.create_shader(GL, {"PLANETS": ''+this.planet_count});
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours));
|
|
||||||
|
|
||||||
for(let i=0; i < this.planet_count; i++){
|
|
||||||
const u = new Uniform3f(colours[i*6], colours[i*6 + 1], colours[i*6 + 2]);
|
|
||||||
this.renderer.updateUniform(i+1, (us) => us["u_color"] = u);
|
|
||||||
const u2 = new Uniform3f(colours[i*6 + 3], colours[i*6 + 4], colours[i*6 + 5]);
|
|
||||||
this.renderer.updateUniform(i+1, (us) => us["u_color_next"] = u2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ships = f32v(this.game.get_ship_locations(), this.game.get_ship_count() * 9 * 2);
|
|
||||||
const ship_colours = f32v(this.game.get_ship_colours(), this.game.get_ship_count() * 3);
|
|
||||||
|
|
||||||
for (let i=0; i < this.game.get_max_ships(); i++) {
|
|
||||||
const index = this.ship_indices[i];
|
|
||||||
if (i < this.game.get_ship_count()) {
|
|
||||||
|
|
||||||
this.renderer.enableRendershit(index);
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
this.renderer.updateUniform(index, (us) => {
|
|
||||||
us["u_color"] = u;
|
|
||||||
us["u_color_next"] = u;
|
|
||||||
us["u_trans"] = t1;
|
|
||||||
us["u_trans_next"] = t2;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.renderer.disableRenderShift(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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.shader.uniform(GL, "u_vor", new UniformBool(true));
|
|
||||||
|
|
||||||
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
47
frontend/www/src/games.ts
Normal 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
606
frontend/www/src/index.ts
Normal 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);
|
56
frontend/www/src/voronoi/voronoi-core.d.ts
vendored
Normal file
56
frontend/www/src/voronoi/voronoi-core.d.ts
vendored
Normal 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;
|
1724
frontend/www/src/voronoi/voronoi-core.js
Normal file
1724
frontend/www/src/voronoi/voronoi-core.js
Normal file
File diff suppressed because it is too large
Load diff
163
frontend/www/src/voronoi/voronoi.ts
Normal file
163
frontend/www/src/voronoi/voronoi.ts
Normal 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!`)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,19 +5,32 @@ import { VertexArray } from './vertexBufferLayout';
|
||||||
import { Texture } from './texture';
|
import { Texture } from './texture';
|
||||||
import { Dictionary } from './util';
|
import { Dictionary } from './util';
|
||||||
|
|
||||||
export interface Renderable {
|
function sortedIndex(array, value) {
|
||||||
render(gl: WebGLRenderingContext): void;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RenderShit implements Renderable {
|
export interface Renderable {
|
||||||
|
getUniforms() : Dictionary<Uniform>;
|
||||||
|
render(gl: WebGLRenderingContext): void;
|
||||||
|
updateVAOBuffer(gl: WebGLRenderingContext, index: number, data: number[]);
|
||||||
|
updateIndexBuffer(gl: WebGLRenderingContext, data: number[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DefaultRenderable implements Renderable {
|
||||||
ibo: IndexBuffer;
|
ibo: IndexBuffer;
|
||||||
va: VertexArray;
|
va: VertexArray;
|
||||||
shader: Shader;
|
shader: Shader;
|
||||||
textures: Texture[];
|
textures: Texture[];
|
||||||
uniforms: Dictionary<Uniform>;
|
uniforms: Dictionary<Uniform>;
|
||||||
|
|
||||||
enabled: boolean;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ibo: IndexBuffer,
|
ibo: IndexBuffer,
|
||||||
va: VertexArray,
|
va: VertexArray,
|
||||||
|
@ -25,7 +38,6 @@ class RenderShit implements Renderable {
|
||||||
textures: Texture[],
|
textures: Texture[],
|
||||||
uniforms: Dictionary<Uniform>,
|
uniforms: Dictionary<Uniform>,
|
||||||
) {
|
) {
|
||||||
this.enabled = true;
|
|
||||||
this.ibo = ibo;
|
this.ibo = ibo;
|
||||||
this.va = va;
|
this.va = va;
|
||||||
this.shader = shader;
|
this.shader = shader;
|
||||||
|
@ -33,11 +45,20 @@ class RenderShit implements Renderable {
|
||||||
this.uniforms = uniforms;
|
this.uniforms = uniforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(gl: WebGLRenderingContext): void {
|
getUniforms(): Dictionary<Uniform> {
|
||||||
if (!this.enabled) {
|
return this.uniforms;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
const indexBuffer = this.ibo;
|
const indexBuffer = this.ibo;
|
||||||
const vertexArray = this.va;
|
const vertexArray = this.va;
|
||||||
const uniforms = this.uniforms;
|
const uniforms = this.uniforms;
|
||||||
|
@ -73,45 +94,50 @@ class RenderShit implements Renderable {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
renderables: RenderShit[];
|
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].uniforms);
|
f(this.renderables[layer][i][0].getUniforms());
|
||||||
}
|
}
|
||||||
|
|
||||||
disableRenderShift(i: number) {
|
disableRenderable(i: number, layer=0) {
|
||||||
this.renderables[i].enabled = false;
|
this.renderables[layer][i][1] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
enableRendershit(i: number) {
|
enableRenderable(i: number, layer=0) {
|
||||||
this.renderables[i].enabled = true;
|
this.renderables[layer][i][1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// addRenderable(item: Renderable) {
|
addRenderable(item: Renderable, layer=0): number {
|
||||||
// this.renderables.push(item);
|
if(!this.renderables[layer]) {
|
||||||
// }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
this.renderables.push(
|
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, uniforms?: Dictionary<Uniform>, texture?: Texture[], layer=0): number {
|
||||||
new RenderShit(
|
return this.addRenderable(
|
||||||
|
new DefaultRenderable(
|
||||||
indexBuffer,
|
indexBuffer,
|
||||||
vertexArray,
|
vertexArray,
|
||||||
shader,
|
shader,
|
||||||
texture || [],
|
texture || [],
|
||||||
uniforms || {},
|
uniforms || {},
|
||||||
)
|
), layer
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.renderables.length - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(gl: WebGLRenderingContext, frameBuffer?: WebGLFramebuffer, width?: number, height?: number) {
|
render(gl: WebGLRenderingContext, frameBuffer?: WebGLFramebuffer, width?: number, height?: number) {
|
||||||
|
@ -121,9 +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 of this.renderables) {
|
for (let layer of this.renderable_layers) {
|
||||||
|
for (let [r, e] of this.renderables[layer]) {
|
||||||
|
if (!e) continue;
|
||||||
r.render(gl);
|
r.render(gl);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
192
frontend/www/src/webgl/text.ts
Normal file
192
frontend/www/src/webgl/text.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import { Texture } from "./texture";
|
||||||
|
import { Dictionary } from "./util";
|
||||||
|
import { Renderable, DefaultRenderable } from "./renderer";
|
||||||
|
import { Uniform, Shader, UniformMatrix3fv } from "./shader";
|
||||||
|
import { IndexBuffer, VertexBuffer } from "./buffer";
|
||||||
|
import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout";
|
||||||
|
|
||||||
|
|
||||||
|
export enum Align {
|
||||||
|
Begin,
|
||||||
|
End,
|
||||||
|
Middle,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GlypInfo {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FontInfo {
|
||||||
|
letterHeight: number;
|
||||||
|
spaceWidth: number;
|
||||||
|
spacing: number;
|
||||||
|
textureWidth: number;
|
||||||
|
textureHeight: number;
|
||||||
|
glyphInfos: Dictionary<GlypInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LabelFactory {
|
||||||
|
texture: Texture;
|
||||||
|
font: FontInfo;
|
||||||
|
shader: Shader;
|
||||||
|
|
||||||
|
constructor(gl: WebGLRenderingContext, loc: string, font: FontInfo, shader: Shader) {
|
||||||
|
this.texture = Texture.fromImage(gl, loc, 'font');
|
||||||
|
this.font = font;
|
||||||
|
this.shader = shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(gl: WebGLRenderingContext, transform?: UniformMatrix3fv): Label {
|
||||||
|
return new Label(gl, this.shader, this.texture, this.font, transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Label {
|
||||||
|
inner: DefaultRenderable;
|
||||||
|
|
||||||
|
font: FontInfo;
|
||||||
|
|
||||||
|
constructor(gl: WebGLRenderingContext, shader: Shader, tex: Texture, font: FontInfo, transform?: UniformMatrix3fv) {
|
||||||
|
this.font = font;
|
||||||
|
|
||||||
|
const uniforms = transform ? { "u_trans": transform, "u_trans_next": transform, } : {};
|
||||||
|
const ib = new IndexBuffer(gl, []);
|
||||||
|
const vb_pos = new VertexBuffer(gl, []);
|
||||||
|
const vb_tex = new VertexBuffer(gl, []);
|
||||||
|
|
||||||
|
const layout_pos = new VertexBufferLayout();
|
||||||
|
layout_pos.push(gl.FLOAT, 2, 4, "a_position");
|
||||||
|
|
||||||
|
const layout_tex = new VertexBufferLayout();
|
||||||
|
layout_tex.push(gl.FLOAT, 2, 4, "a_texCoord");
|
||||||
|
|
||||||
|
const vao = new VertexArray();
|
||||||
|
vao.addBuffer(vb_pos, layout_pos);
|
||||||
|
vao.addBuffer(vb_tex, layout_tex);
|
||||||
|
|
||||||
|
this.inner = new DefaultRenderable(ib, vao, shader, [tex], uniforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderable(): DefaultRenderable {
|
||||||
|
return this.inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) {
|
||||||
|
const idxs = [];
|
||||||
|
const verts_pos = [];
|
||||||
|
const verts_tex = [];
|
||||||
|
|
||||||
|
const letterHeight = this.font.letterHeight / this.font.textureHeight;
|
||||||
|
let xPos = 0;
|
||||||
|
|
||||||
|
switch (h_align) {
|
||||||
|
case Align.Begin:
|
||||||
|
break;
|
||||||
|
case Align.End:
|
||||||
|
xPos = -1 * [...text].map(n => this.font.glyphInfos[n] ? this.font.glyphInfos[n].width : this.font.spaceWidth).reduce((a, b) => a + b, 0) / this.font.letterHeight;
|
||||||
|
break;
|
||||||
|
case Align.Middle:
|
||||||
|
xPos = -1 * [...text].map(n => this.font.glyphInfos[n] ? this.font.glyphInfos[n].width : this.font.spaceWidth).reduce((a, b) => a + b, 0) / this.font.letterHeight / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let yStart = 0;
|
||||||
|
switch (v_align) {
|
||||||
|
case Align.Begin:
|
||||||
|
break;
|
||||||
|
case Align.End:
|
||||||
|
yStart = 1;
|
||||||
|
break;
|
||||||
|
case Align.Middle:
|
||||||
|
yStart = 0.5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let j = 0;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
const info = this.font.glyphInfos[text[i]];
|
||||||
|
if (info) {
|
||||||
|
const dx = info.width / this.font.letterHeight;
|
||||||
|
const letterWidth = info.width / this.font.textureWidth;
|
||||||
|
const x0 = info.x / this.font.textureWidth;
|
||||||
|
const y0 = info.y / this.font.textureHeight;
|
||||||
|
verts_pos.push(xPos, yStart);
|
||||||
|
verts_pos.push(xPos + dx, yStart);
|
||||||
|
verts_pos.push(xPos, yStart-1);
|
||||||
|
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;
|
||||||
|
|
||||||
|
idxs.push(j+0, j+1, j+2, j+1, j+2, j+3);
|
||||||
|
j += 4;
|
||||||
|
} else {
|
||||||
|
// Just move xPos
|
||||||
|
xPos += this.font.spaceWidth / this.font.letterHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inner.updateIndexBuffer(gl, idxs);
|
||||||
|
this.inner.updateVAOBuffer(gl, 0, verts_pos);
|
||||||
|
this.inner.updateVAOBuffer(gl, 1, verts_tex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultLabelFactory(gl: WebGLRenderingContext, shader: Shader): LabelFactory {
|
||||||
|
const fontInfo = {
|
||||||
|
letterHeight: 8,
|
||||||
|
spaceWidth: 8,
|
||||||
|
spacing: -1,
|
||||||
|
textureWidth: 64,
|
||||||
|
textureHeight: 40,
|
||||||
|
glyphInfos: {
|
||||||
|
'a': { x: 0, y: 0, width: 8, },
|
||||||
|
'b': { x: 8, y: 0, width: 8, },
|
||||||
|
'c': { x: 16, y: 0, width: 8, },
|
||||||
|
'd': { x: 24, y: 0, width: 8, },
|
||||||
|
'e': { x: 32, y: 0, width: 8, },
|
||||||
|
'f': { x: 40, y: 0, width: 8, },
|
||||||
|
'g': { x: 48, y: 0, width: 8, },
|
||||||
|
'h': { x: 56, y: 0, width: 8, },
|
||||||
|
'i': { x: 0, y: 8, width: 8, },
|
||||||
|
'j': { x: 8, y: 8, width: 8, },
|
||||||
|
'k': { x: 16, y: 8, width: 8, },
|
||||||
|
'l': { x: 24, y: 8, width: 8, },
|
||||||
|
'm': { x: 32, y: 8, width: 8, },
|
||||||
|
'n': { x: 40, y: 8, width: 8, },
|
||||||
|
'o': { x: 48, y: 8, width: 8, },
|
||||||
|
'p': { x: 56, y: 8, width: 8, },
|
||||||
|
'q': { x: 0, y: 16, width: 8, },
|
||||||
|
'r': { x: 8, y: 16, width: 8, },
|
||||||
|
's': { x: 16, y: 16, width: 8, },
|
||||||
|
't': { x: 24, y: 16, width: 8, },
|
||||||
|
'u': { x: 32, y: 16, width: 8, },
|
||||||
|
'v': { x: 40, y: 16, width: 8, },
|
||||||
|
'w': { x: 48, y: 16, width: 8, },
|
||||||
|
'x': { x: 56, y: 16, width: 8, },
|
||||||
|
'y': { x: 0, y: 24, width: 8, },
|
||||||
|
'z': { x: 8, y: 24, width: 8, },
|
||||||
|
'0': { x: 16, y: 24, width: 8, },
|
||||||
|
'1': { x: 24, y: 24, width: 8, },
|
||||||
|
'2': { x: 32, y: 24, width: 8, },
|
||||||
|
'3': { x: 40, y: 24, width: 8, },
|
||||||
|
'4': { x: 48, y: 24, width: 8, },
|
||||||
|
'5': { x: 56, y: 24, width: 8, },
|
||||||
|
'6': { x: 0, y: 32, width: 8, },
|
||||||
|
'7': { x: 8, y: 32, width: 8, },
|
||||||
|
'8': { x: 16, y: 32, width: 8, },
|
||||||
|
'9': { x: 24, y: 32, width: 8, },
|
||||||
|
'-': { x: 32, y: 32, width: 8, },
|
||||||
|
'*': { x: 40, y: 32, width: 8, },
|
||||||
|
'!': { x: 48, y: 32, width: 8, },
|
||||||
|
'?': { x: 56, y: 32, width: 8, },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return new LabelFactory(gl, 'static/res/assets/font.png', fontInfo, shader);
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
0
frontend/www/src/webgl/vertexArray.ts
Normal file
0
frontend/www/src/webgl/vertexArray.ts
Normal 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 {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
frontend/www/static/res/assets/font.png
Normal file
BIN
frontend/www/static/res/assets/font.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 912 B |
BIN
frontend/www/static/res/assets/leaves.jpg
Normal file
BIN
frontend/www/static/res/assets/leaves.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -1,46 +1,46 @@
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main {
|
#main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#name {
|
#name {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#meta {
|
#meta {
|
||||||
padding: 10px 2%;
|
padding: 10px 2%;
|
||||||
color: white;
|
color: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 96%;
|
width: 96%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@ -49,46 +49,46 @@ p {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
color: white;
|
color: white;
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option:last-child {
|
.option:last-child {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.option:hover {
|
.option:hover {
|
||||||
background-color: #777;
|
background-color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller {
|
.lds-roller {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading>.lds-roller {
|
.loading>.lds-roller {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller {
|
.lds-roller {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div {
|
.lds-roller div {
|
||||||
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
transform-origin: 40px 40px;
|
transform-origin: 40px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:after {
|
.lds-roller div:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -97,152 +97,136 @@ p {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: -4px 0 0 -4px;
|
margin: -4px 0 0 -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(1) {
|
.lds-roller div:nth-child(1) {
|
||||||
animation-delay: -0.036s;
|
animation-delay: -0.036s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(1):after {
|
.lds-roller div:nth-child(1):after {
|
||||||
top: 63px;
|
top: 63px;
|
||||||
left: 63px;
|
left: 63px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(2) {
|
.lds-roller div:nth-child(2) {
|
||||||
animation-delay: -0.072s;
|
animation-delay: -0.072s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(2):after {
|
.lds-roller div:nth-child(2):after {
|
||||||
top: 68px;
|
top: 68px;
|
||||||
left: 56px;
|
left: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(3) {
|
.lds-roller div:nth-child(3) {
|
||||||
animation-delay: -0.108s;
|
animation-delay: -0.108s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(3):after {
|
.lds-roller div:nth-child(3):after {
|
||||||
top: 71px;
|
top: 71px;
|
||||||
left: 48px;
|
left: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(4) {
|
.lds-roller div:nth-child(4) {
|
||||||
animation-delay: -0.144s;
|
animation-delay: -0.144s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(4):after {
|
.lds-roller div:nth-child(4):after {
|
||||||
top: 72px;
|
top: 72px;
|
||||||
left: 40px;
|
left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(5) {
|
.lds-roller div:nth-child(5) {
|
||||||
animation-delay: -0.18s;
|
animation-delay: -0.18s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(5):after {
|
.lds-roller div:nth-child(5):after {
|
||||||
top: 71px;
|
top: 71px;
|
||||||
left: 32px;
|
left: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(6) {
|
.lds-roller div:nth-child(6) {
|
||||||
animation-delay: -0.216s;
|
animation-delay: -0.216s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(6):after {
|
.lds-roller div:nth-child(6):after {
|
||||||
top: 68px;
|
top: 68px;
|
||||||
left: 24px;
|
left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(7) {
|
.lds-roller div:nth-child(7) {
|
||||||
animation-delay: -0.252s;
|
animation-delay: -0.252s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(7):after {
|
.lds-roller div:nth-child(7):after {
|
||||||
top: 63px;
|
top: 63px;
|
||||||
left: 17px;
|
left: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(8) {
|
.lds-roller div:nth-child(8) {
|
||||||
animation-delay: -0.288s;
|
animation-delay: -0.288s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lds-roller div:nth-child(8):after {
|
.lds-roller div:nth-child(8):after {
|
||||||
top: 56px;
|
top: 56px;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes lds-roller {
|
@keyframes lds-roller {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#speed {
|
#speed {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#c {
|
#canvas {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
width: 40px;
|
width: 0;
|
||||||
height: 40px;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:before,
|
.button::before {
|
||||||
.button:after {
|
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
display: block;
|
||||||
background-color: grey;
|
float: left;
|
||||||
}
|
margin: 0 -20px 0 0;
|
||||||
|
font-family: 'fontawesome';
|
||||||
|
content: "\f1e1";
|
||||||
|
transform: translate(-1em, 0px);
|
||||||
|
color: antiquewhite;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.button:hover:before,
|
.button:hover::before {
|
||||||
.button:hover:after {
|
color: #ff7f00;
|
||||||
background-color: white;
|
}
|
||||||
}
|
/* ----------------------------------------------
|
||||||
|
|
||||||
.button:before {
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
width: 6px;
|
|
||||||
height: 100%;
|
|
||||||
margin-left: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:after {
|
|
||||||
top: 50%;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 6px;
|
|
||||||
margin-top: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------
|
|
||||||
* Generated by Animista on 2019-9-17 14:35:13
|
* Generated by Animista on 2019-9-17 14:35:13
|
||||||
* Licensed under FreeBSD License.
|
* Licensed under FreeBSD License.
|
||||||
* See http://animista.net/license for more info.
|
* See http://animista.net/license for more info.
|
||||||
* w: http://animista.net, t: @cssanimista
|
* w: http://animista.net, t: @cssanimista
|
||||||
* ---------------------------------------------- */
|
* ---------------------------------------------- */
|
||||||
|
/**
|
||||||
|
|
||||||
/**
|
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
* animation slide-top
|
* animation slide-top
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@-webkit-keyframes slide-top {
|
@-webkit-keyframes slide-top {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: translate(-50%, 50%);
|
-webkit-transform: translate(-50%, 50%);
|
||||||
transform: translate(-50%, 50%);
|
transform: translate(-50%, 50%);
|
||||||
|
@ -251,9 +235,9 @@ p {
|
||||||
-webkit-transform: translate(-50%, -150%);
|
-webkit-transform: translate(-50%, -150%);
|
||||||
transform: translate(-50%, -150%);
|
transform: translate(-50%, -150%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slide-top {
|
@keyframes slide-top {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: translate(-50%, 50%);
|
-webkit-transform: translate(-50%, 50%);
|
||||||
transform: translate(-50%, 50%);
|
transform: translate(-50%, 50%);
|
||||||
|
@ -262,22 +246,20 @@ p {
|
||||||
-webkit-transform: translate(-50%, -150%);
|
-webkit-transform: translate(-50%, -150%);
|
||||||
transform: translate(-50%, -150%);
|
transform: translate(-50%, -150%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
|
||||||
/**
|
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
* Copy from https://www.w3schools.com/howto/howto_js_rangeslider.asp
|
* Copy from https://www.w3schools.com/howto/howto_js_rangeslider.asp
|
||||||
* ----------------------------------------
|
* ----------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.slidecontainer {
|
.slidecontainer {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* Width of the outside container */
|
/* Width of the outside container */
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
|
@ -287,9 +269,9 @@ p {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
-webkit-transition: .2s;
|
-webkit-transition: .2s;
|
||||||
transition: opacity .2s;
|
transition: opacity .2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-webkit-slider-thumb {
|
.slider::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
|
@ -297,31 +279,31 @@ p {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ff7000;
|
background: #ff7000;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider::-moz-range-thumb {
|
.slider::-moz-range-thumb {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ff7000;
|
background: #ff7000;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: #F90;
|
background-color: #F90;
|
||||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent)
|
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent)
|
||||||
}
|
}
|
14
frontend/www/static/shaders/frag/image.glsl
Normal file
14
frontend/www/static/shaders/frag/image.glsl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifdef GL_ES
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Passed in from the vertex shader.
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
// The texture.
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture2D(u_texture, v_texCoord);
|
||||||
|
// gl_FragColor = vec4(0.7, 0.7, 0.0, 1.0);
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
33
frontend/www/static/shaders/vert/image.glsl
Normal file
33
frontend/www/static/shaders/vert/image.glsl
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#ifdef GL_ES
|
||||||
|
precision mediump float;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute vec2 a_texCoord;
|
||||||
|
|
||||||
|
uniform float u_time;
|
||||||
|
|
||||||
|
uniform vec4 u_viewbox; // [x, y, width, height]
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform mat3 u_trans;
|
||||||
|
|
||||||
|
varying vec2 v_pos;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 pos = vec3(a_position, 1.0);
|
||||||
|
|
||||||
|
pos = u_trans * pos;
|
||||||
|
|
||||||
|
vec2 uv = pos.xy;
|
||||||
|
|
||||||
|
// Viewbox's center is top left, a_position's is in the center to the screen
|
||||||
|
// So translate and scale the viewbox**
|
||||||
|
uv -= u_viewbox.xy + (u_viewbox.zw * 0.5);
|
||||||
|
uv /= u_viewbox.zw * 0.5;
|
||||||
|
|
||||||
|
v_pos = (uv.xy + 1.0) * 0.5;
|
||||||
|
|
||||||
|
gl_Position = vec4(uv.xy, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ precision mediump float;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
attribute vec2 a_position;
|
attribute vec2 a_position;
|
||||||
|
attribute vec2 a_texCoord;
|
||||||
|
|
||||||
uniform float u_time;
|
uniform float u_time;
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ uniform mat3 u_trans;
|
||||||
uniform mat3 u_trans_next;
|
uniform mat3 u_trans_next;
|
||||||
|
|
||||||
varying vec2 v_pos;
|
varying vec2 v_pos;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 pos = vec3(a_position, 1.0);
|
vec3 pos = vec3(a_position, 1.0);
|
||||||
|
@ -30,4 +32,5 @@ void main() {
|
||||||
v_pos = (uv.xy + 1.0) * 0.5;
|
v_pos = (uv.xy + 1.0) * 0.5;
|
||||||
|
|
||||||
gl_Position = vec4(uv.xy, 0.0, 1.0);
|
gl_Position = vec4(uv.xy, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())));
|
|
||||||
}
|
|
|
@ -1,72 +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 {
|
|
||||||
stages: Stage[];
|
|
||||||
|
|
||||||
textures: WebGLTexture[];
|
|
||||||
framebuffers: WebGLFramebuffer[];
|
|
||||||
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
|
|
||||||
constructor(gl: WebGLRenderingContext, width: number, height: number) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>;
|
|
||||||
|
|
||||||
render(gl: WebGLRenderingContext) {
|
|
||||||
this.program.bind(gl);
|
|
||||||
|
|
||||||
for (let name in this.uniforms) {
|
|
||||||
this.program.uniform(gl, name, this.uniforms[name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
frontend/www/webgl/webgl-utils.d.ts
vendored
20
frontend/www/webgl/webgl-utils.d.ts
vendored
|
@ -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
Loading…
Reference in a new issue