wtf
This commit is contained in:
parent
2ad7354de5
commit
c846042597
17 changed files with 1239 additions and 16 deletions
|
@ -43,7 +43,7 @@ pub fn run(args : Vec<String>) {
|
||||||
|
|
||||||
let ids: HashMap<util::Identifier, util::PlayerId> = (0..number_of_clients).map(|x| (rand::thread_rng().gen::<u64>().into(), x.into())).collect();
|
let ids: HashMap<util::Identifier, util::PlayerId> = (0..number_of_clients).map(|x| (rand::thread_rng().gen::<u64>().into(), x.into())).collect();
|
||||||
|
|
||||||
let config = planetwars::Config { map_file: String::from("map.json"), max_turns: 500 };
|
let config = planetwars::Config { map_file: String::from("hex.json"), max_turns: 500 };
|
||||||
let game = planetwars::PlanetWarsGame::new(config.create_game(number_of_clients as usize));
|
let game = planetwars::PlanetWarsGame::new(config.create_game(number_of_clients as usize));
|
||||||
|
|
||||||
println!("Tokens:");
|
println!("Tokens:");
|
||||||
|
|
|
@ -5,6 +5,8 @@ use serde_json;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
mod pw_config;
|
mod pw_config;
|
||||||
mod pw_serializer;
|
mod pw_serializer;
|
||||||
|
@ -17,25 +19,30 @@ pub use pw_config::Config;
|
||||||
pub struct PlanetWarsGame {
|
pub struct PlanetWarsGame {
|
||||||
state: pw_rules::PlanetWars,
|
state: pw_rules::PlanetWars,
|
||||||
planet_map: HashMap<String, usize>,
|
planet_map: HashMap<String, usize>,
|
||||||
|
log_file: File
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlanetWarsGame {
|
impl PlanetWarsGame {
|
||||||
|
|
||||||
pub fn new(state: pw_rules::PlanetWars) -> Self {
|
pub fn new(state: pw_rules::PlanetWars) -> Self {
|
||||||
let planet_map = state.planets.iter().map(|p| (p.name.clone(), p.id)).collect();
|
let planet_map = state.planets.iter().map(|p| (p.name.clone(), p.id)).collect();
|
||||||
|
let file = File::create("game.json").unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
state, planet_map
|
state, planet_map,
|
||||||
|
log_file: file,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_state(&self, were_alive: Vec<usize>, updates: &mut Vec<game::Update>, ) {
|
fn dispatch_state(&mut self, were_alive: Vec<usize>, updates: &mut Vec<game::Update>, ) {
|
||||||
let state = pw_serializer::serialize(&self.state);
|
let state = pw_serializer::serialize(&self.state);
|
||||||
println!("{}", serde_json::to_string(&state).unwrap());
|
write!(self.log_file, "{}\n", serde_json::to_string(&state).unwrap()).unwrap();
|
||||||
|
|
||||||
|
// println!("{}", serde_json::to_string(&state).unwrap());
|
||||||
|
|
||||||
for player in self.state.players.iter().filter(|p| were_alive.contains(&p.id)) {
|
for player in self.state.players.iter().filter(|p| were_alive.contains(&p.id)) {
|
||||||
let state = pw_serializer::serialize_rotated(&self.state, player.id);
|
let state = pw_serializer::serialize_rotated(&self.state, player.id);
|
||||||
let state = if player.alive {
|
let state = if player.alive && !self.state.is_finished() {
|
||||||
proto::ServerMessage::GameState(state)
|
proto::ServerMessage::GameState(state)
|
||||||
} else {
|
} else {
|
||||||
proto::ServerMessage::FinalState(state)
|
proto::ServerMessage::FinalState(state)
|
||||||
|
@ -45,7 +52,7 @@ impl PlanetWarsGame {
|
||||||
game::Update::Player((player.id as u64).into(), serde_json::to_vec(&state).unwrap())
|
game::Update::Player((player.id as u64).into(), serde_json::to_vec(&state).unwrap())
|
||||||
);
|
);
|
||||||
|
|
||||||
if !player.alive {
|
if !player.alive || self.state.is_finished() {
|
||||||
updates.push(game::Update::Kick((player.id as u64).into()));
|
updates.push(game::Update::Kick((player.id as u64).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,11 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
mozaic = { git = "https://github.com/ajuvercr/MOZAIC" }
|
||||||
|
tokio = "0.1.22"
|
||||||
|
capnp = "0.10.1"
|
||||||
|
futures = "0.1.28"
|
||||||
|
serde = "1.0.100"
|
||||||
|
serde_derive = "1.0.100"
|
||||||
|
serde_json = "1.0"
|
||||||
|
rand = { version = "0.6.5", default-features = true }
|
||||||
|
|
|
@ -1,3 +1,290 @@
|
||||||
|
extern crate mozaic;
|
||||||
|
extern crate tokio;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate capnp;
|
||||||
|
extern crate rand;
|
||||||
|
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use mozaic::core_capnp::{initialize, terminate_stream, identify, actor_joined};
|
||||||
|
use mozaic::messaging::reactor::*;
|
||||||
|
use mozaic::messaging::types::*;
|
||||||
|
use mozaic::errors::*;
|
||||||
|
use mozaic::client_capnp::{client_message, host_message, client_kicked};
|
||||||
|
use mozaic::mozaic_cmd_capnp::{bot_input, bot_return};
|
||||||
|
use mozaic::server::runtime::{Broker, BrokerHandle};
|
||||||
|
use mozaic::server;
|
||||||
|
use mozaic::modules::{BotReactor};
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
mod types;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let id = args.get(1).unwrap().parse().unwrap();
|
||||||
|
let client_args = args.get(2..).expect("How do you expect me to spawn your bot?").to_vec();
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:9142".parse().unwrap();
|
||||||
|
let self_id: ReactorId = rand::thread_rng().gen();
|
||||||
|
|
||||||
|
tokio::run(futures::lazy(move || {
|
||||||
|
let mut broker = Broker::new().unwrap();
|
||||||
|
let reactor = ClientReactor {
|
||||||
|
server: None,
|
||||||
|
id,
|
||||||
|
broker: broker.clone(),
|
||||||
|
args: client_args,
|
||||||
|
};
|
||||||
|
broker.spawn(self_id.clone(), reactor.params(), "main").display();
|
||||||
|
|
||||||
|
tokio::spawn(server::connect_to_server(broker, self_id, &addr));
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main client logic
|
||||||
|
/// ? greeter_id is the server, the tcp stream that you connected to
|
||||||
|
/// ? runtime_id is your own runtime, to handle visualisation etc
|
||||||
|
/// ? user are you ...
|
||||||
|
struct ClientReactor {
|
||||||
|
server: Option<ReactorId>,
|
||||||
|
broker: BrokerHandle,
|
||||||
|
id: u64,
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientReactor {
|
||||||
|
fn params<C: Ctx>(self) -> CoreParams<Self, C> {
|
||||||
|
let mut params = CoreParams::new(self);
|
||||||
|
params.handler(initialize::Owned, CtxHandler::new(Self::initialize));
|
||||||
|
params.handler(actor_joined::Owned, CtxHandler::new(Self::open_host));
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reactor setup
|
||||||
|
fn initialize<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut ReactorHandle<C>,
|
||||||
|
_: initialize::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
// open link with runtime, for communicating with chat GUI
|
||||||
|
let runtime_link = RuntimeLink::params(handle.id().clone());
|
||||||
|
handle.open_link(runtime_link)?;
|
||||||
|
|
||||||
|
let bot = BotReactor::new(self.broker.clone(), handle.id().clone(), self.args.clone());
|
||||||
|
let bot_id = handle.spawn(bot.params(), "Bot Driver")?;
|
||||||
|
|
||||||
|
handle.open_link(BotLink::params(bot_id))?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_host<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut ReactorHandle<C>,
|
||||||
|
r: actor_joined::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
let id = r.get_id()?;
|
||||||
|
|
||||||
|
if let Some(server) = &self.server {
|
||||||
|
handle.open_link(HostLink::params(ReactorId::from(id)))?;
|
||||||
|
self.broker.register_as(id.into(), server.clone());
|
||||||
|
|
||||||
|
// Fake bot msg
|
||||||
|
let mut chat_message = MsgBuffer::<bot_return::Owned>::new();
|
||||||
|
chat_message.build(|b| {
|
||||||
|
b.set_message(b"");
|
||||||
|
});
|
||||||
|
handle.send_internal(chat_message)?;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
handle.open_link(ServerLink::params(id.into()))?;
|
||||||
|
self.server = Some(id.into());
|
||||||
|
|
||||||
|
let mut identify = MsgBuffer::<identify::Owned>::new();
|
||||||
|
identify.build(|b| {
|
||||||
|
b.set_key(self.id);
|
||||||
|
});
|
||||||
|
handle.send_internal(identify).display();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for the connection with the chat server
|
||||||
|
struct ServerLink;
|
||||||
|
impl ServerLink {
|
||||||
|
fn params<C: Ctx>(foreign_id: ReactorId) -> LinkParams<Self, C> {
|
||||||
|
let mut params = LinkParams::new(foreign_id, Self);
|
||||||
|
|
||||||
|
params.external_handler( terminate_stream::Owned, CtxHandler::new(Self::close_handler) );
|
||||||
|
params.external_handler( actor_joined::Owned, CtxHandler::new(actor_joined::e_to_i) );
|
||||||
|
|
||||||
|
params.internal_handler( identify::Owned, CtxHandler::new(Self::identify) );
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identify<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut LinkHandle<C>,
|
||||||
|
id: identify::Reader,
|
||||||
|
) -> Result<()> {
|
||||||
|
let id = id.get_key();
|
||||||
|
|
||||||
|
let mut chat_message = MsgBuffer::<identify::Owned>::new();
|
||||||
|
chat_message.build(|b| {
|
||||||
|
b.set_key(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.send_message(chat_message).display();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_handler<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut LinkHandle<C>,
|
||||||
|
_: terminate_stream::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
// also close our end of the stream
|
||||||
|
handle.close_link()?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HostLink;
|
||||||
|
impl HostLink {
|
||||||
|
fn params<C: Ctx>(remote_id: ReactorId) -> LinkParams<Self, C> {
|
||||||
|
let mut params = LinkParams::new(remote_id, HostLink);
|
||||||
|
|
||||||
|
params.external_handler(
|
||||||
|
host_message::Owned,
|
||||||
|
CtxHandler::new(Self::receive_host_message),
|
||||||
|
);
|
||||||
|
|
||||||
|
params.internal_handler(
|
||||||
|
bot_return::Owned,
|
||||||
|
CtxHandler::new(Self::send_chat_message),
|
||||||
|
);
|
||||||
|
|
||||||
|
params.external_handler(
|
||||||
|
client_kicked::Owned,
|
||||||
|
CtxHandler::new(Self::client_kicked),
|
||||||
|
);
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick up a 'send_message' event from the reactor, and put it to effect
|
||||||
|
// by constructing the chat message and sending it to the chat server.
|
||||||
|
fn send_chat_message<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut LinkHandle<C>,
|
||||||
|
send_message: bot_return::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
let message = send_message.get_message()?;
|
||||||
|
|
||||||
|
println!("Our bot sent");
|
||||||
|
println!("{}", str::from_utf8(&message).unwrap());
|
||||||
|
|
||||||
|
let mut chat_message = MsgBuffer::<client_message::Owned>::new();
|
||||||
|
chat_message.build(|b| {
|
||||||
|
b.set_data(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.send_message(chat_message)?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick up a 'send_message' event from the reactor, and put it to effect
|
||||||
|
// by constructing the chat message and sending it to the chat server.
|
||||||
|
fn client_kicked<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut LinkHandle<C>,
|
||||||
|
_: client_kicked::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
// Disconnect
|
||||||
|
|
||||||
|
handle.close_link()?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive a chat message from the chat server, and broadcast it on the
|
||||||
|
// reactor.
|
||||||
|
fn receive_host_message<C: Ctx>(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut LinkHandle<C>,
|
||||||
|
host_message: host_message::Reader,
|
||||||
|
) -> Result<()>
|
||||||
|
{
|
||||||
|
let message = host_message.get_data()?;
|
||||||
|
|
||||||
|
let message: types::ServerMessage = serde_json::from_slice(message).unwrap();
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
match message {
|
||||||
|
types::ServerMessage::GameState(state) => {
|
||||||
|
// println!("New game state");
|
||||||
|
let mut bot_msg = MsgBuffer::<bot_input::Owned>::new();
|
||||||
|
|
||||||
|
bot_msg.build(|b| {
|
||||||
|
b.set_input(&serde_json::to_vec(&state).unwrap());
|
||||||
|
});
|
||||||
|
handle.send_internal(bot_msg).display();
|
||||||
|
},
|
||||||
|
types::ServerMessage::FinalState(state) => {
|
||||||
|
println!("Game finished with");
|
||||||
|
println!("{:?}", state);
|
||||||
|
}
|
||||||
|
types::ServerMessage::PlayerAction(action) => {
|
||||||
|
println!("Out bot did");
|
||||||
|
println!("{:?}", action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BotLink;
|
||||||
|
impl BotLink {
|
||||||
|
fn params<C: Ctx>(foreign_id: ReactorId) -> LinkParams<Self, C> {
|
||||||
|
let mut params = LinkParams::new(foreign_id, Self);
|
||||||
|
|
||||||
|
params.external_handler(bot_return::Owned, CtxHandler::new(bot_return::e_to_i));
|
||||||
|
params.internal_handler(bot_input::Owned, CtxHandler::new(bot_input::i_to_e));
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuntimeLink;
|
||||||
|
impl RuntimeLink {
|
||||||
|
fn params<C: Ctx>(foreign_id: ReactorId) -> LinkParams<Self, C> {
|
||||||
|
let mut params = LinkParams::new(foreign_id, Self);
|
||||||
|
|
||||||
|
params.external_handler(
|
||||||
|
actor_joined::Owned,
|
||||||
|
CtxHandler::new(actor_joined::e_to_i),
|
||||||
|
);
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Game } from "planetwars";
|
import { Game } from "planetwars";
|
||||||
import { memory } from "planetwars/plantwars_bg"
|
import { memory } from "planetwars/plantwars_bg"
|
||||||
|
import { Shader } from "./webgl/shader"
|
||||||
|
|
||||||
const URL = window.location.origin+window.location.pathname;
|
const URL = window.location.origin+window.location.pathname;
|
||||||
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
|
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
|
||||||
|
|
79
frontend/www/package-lock.json
generated
79
frontend/www/package-lock.json
generated
|
@ -4063,6 +4063,12 @@
|
||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"picomatch": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||||
|
@ -4094,8 +4100,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"planetwars": {
|
"planetwars": {
|
||||||
"version": "file:../pkg",
|
"version": "file:../pkg"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.21",
|
"version": "1.0.21",
|
||||||
|
@ -5192,6 +5197,70 @@
|
||||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ts-loader": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-7JedeOu2rsYHQDEr2fwmMozABwbQTZXEaEMZPSIWG7gpzRefOLJCqwdazcegHtyaxp04PeEgs/b0m08WMpnIzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^2.3.0",
|
||||||
|
"enhanced-resolve": "^4.0.0",
|
||||||
|
"loader-utils": "^1.0.2",
|
||||||
|
"micromatch": "^4.0.0",
|
||||||
|
"semver": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"braces": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fill-range": "^7.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fill-range": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"to-regex-range": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-number": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"micromatch": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"braces": "^3.0.1",
|
||||||
|
"picomatch": "^2.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"to-regex-range": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-number": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
|
@ -5220,6 +5289,12 @@
|
||||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "3.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
|
||||||
|
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"name": "create-wasm-app",
|
"name": "create-wasm-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "create an app to consume rust-generated wasm packages",
|
"description": "create an app to consume rust-generated wasm packages",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "webpack --config webpack.config.js",
|
||||||
"start": "webpack-dev-server"
|
"start": "webpack-dev-server"
|
||||||
|
@ -18,6 +19,9 @@
|
||||||
"webpack",
|
"webpack",
|
||||||
"mozaic"
|
"mozaic"
|
||||||
],
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"planetwars": "file:../pkg"
|
||||||
|
},
|
||||||
"author": "Arthur Vercruysse <arthur.vercruysse@outlook.com>",
|
"author": "Arthur Vercruysse <arthur.vercruysse@outlook.com>",
|
||||||
"license": "(MIT OR Apache-2.0)",
|
"license": "(MIT OR Apache-2.0)",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -25,8 +29,9 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/ajuvercr/Planetwars#Readme",
|
"homepage": "https://github.com/ajuvercr/Planetwars#Readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"planetwars": "file:../pkg",
|
|
||||||
"webpack": "^4.29.3",
|
"webpack": "^4.29.3",
|
||||||
|
"ts-loader": "^6.0.2",
|
||||||
|
"typescript": "^3.5.2",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.0",
|
||||||
"webpack-dev-server": "^3.1.5",
|
"webpack-dev-server": "^3.1.5",
|
||||||
"copy-webpack-plugin": "^5.0.0"
|
"copy-webpack-plugin": "^5.0.0"
|
||||||
|
|
15
frontend/www/tsconfig.json
Normal file
15
frontend/www/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["es2017", "es7", "es6", "dom"],
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
55
frontend/www/webgl/buffer.ts
Normal file
55
frontend/www/webgl/buffer.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
export class Buffer {
|
||||||
|
buffer: WebGLBuffer;
|
||||||
|
data: any;
|
||||||
|
count: number;
|
||||||
|
type: number;
|
||||||
|
|
||||||
|
constructor(gl: WebGLRenderingContext, data: number[], type: number) {
|
||||||
|
this.buffer = gl.createBuffer();
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
|
if (data)
|
||||||
|
this.updateData(gl, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toArray(data: number[]): any {
|
||||||
|
return new Float32Array(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData(gl: WebGLRenderingContext, data: number[]) {
|
||||||
|
this.data = data;
|
||||||
|
this.count = data.length;
|
||||||
|
gl.bindBuffer(this.type, this.buffer);
|
||||||
|
gl.bufferData(this.type, this._toArray(data), gl.STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(gl: WebGLRenderingContext) {
|
||||||
|
gl.bindBuffer(this.type, this.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCount(): number {
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VertexBuffer extends Buffer {
|
||||||
|
constructor(gl: WebGLRenderingContext, data: any) {
|
||||||
|
super(gl, data, gl.ARRAY_BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toArray(data: number[]): any {
|
||||||
|
return new Float32Array(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class IndexBuffer extends Buffer {
|
||||||
|
constructor(gl: WebGLRenderingContext, data: any) {
|
||||||
|
super(gl, data, gl.ELEMENT_ARRAY_BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toArray(data: number[]): any {
|
||||||
|
return new Uint16Array(data);
|
||||||
|
}
|
||||||
|
}
|
72
frontend/www/webgl/instance.ts
Normal file
72
frontend/www/webgl/instance.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
frontend/www/webgl/renderer.ts
Normal file
72
frontend/www/webgl/renderer.ts
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
import { IndexBuffer } from './buffer';
|
||||||
|
import { Shader, Uniform1i } from './shader';
|
||||||
|
import { VertexArray } from './vertexBufferLayout';
|
||||||
|
import { Texture } from './texture';
|
||||||
|
|
||||||
|
export interface Renderable {
|
||||||
|
render(gl: WebGLRenderingContext): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Renderer {
|
||||||
|
renderables: Renderable[];
|
||||||
|
|
||||||
|
indexBuffers: IndexBuffer[];
|
||||||
|
vertexArrays: VertexArray[];
|
||||||
|
shaders: Shader[];
|
||||||
|
textures: Texture[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.indexBuffers = [];
|
||||||
|
this.vertexArrays = [];
|
||||||
|
this.shaders = [];
|
||||||
|
this.textures = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addRenderable(item: Renderable) {
|
||||||
|
this.renderables.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
addToDraw(indexBuffer: IndexBuffer, vertexArray: VertexArray, shader: Shader, texture?: Texture): number {
|
||||||
|
this.indexBuffers.push(indexBuffer);
|
||||||
|
this.vertexArrays.push(vertexArray);
|
||||||
|
this.shaders.push(shader);
|
||||||
|
this.textures.push(texture);
|
||||||
|
|
||||||
|
return this.indexBuffers.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(gl: WebGLRenderingContext) {
|
||||||
|
const maxTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
|
||||||
|
let texLocation = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < this.indexBuffers.length; i ++) {
|
||||||
|
const indexBuffer = this.indexBuffers[i];
|
||||||
|
const vertexArray = this.vertexArrays[i];
|
||||||
|
const shader = this.shaders[i];
|
||||||
|
const texture = this.textures[i];
|
||||||
|
|
||||||
|
if (texture) {
|
||||||
|
|
||||||
|
shader.uniform(gl, texture.name, new Uniform1i(texLocation));
|
||||||
|
texture.bind(gl, texLocation);
|
||||||
|
|
||||||
|
texLocation ++;
|
||||||
|
if (texLocation > maxTextures) {
|
||||||
|
console.error("Using too many textures, this is not supported yet\nUndefined behaviour!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertexArray && shader) {
|
||||||
|
vertexArray.bind(gl, shader);
|
||||||
|
|
||||||
|
if (indexBuffer) {
|
||||||
|
indexBuffer.bind(gl);
|
||||||
|
gl.drawElements(gl.TRIANGLES, indexBuffer.getCount(), gl.UNSIGNED_SHORT, 0);
|
||||||
|
} else {
|
||||||
|
console.error("IndexBuffer is required to render, for now");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
305
frontend/www/webgl/shader.ts
Normal file
305
frontend/www/webgl/shader.ts
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
import { Dictionary } from './util';
|
||||||
|
|
||||||
|
function error(msg: string) {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultShaderType = [
|
||||||
|
"VERTEX_SHADER",
|
||||||
|
"FRAGMENT_SHADER"
|
||||||
|
];
|
||||||
|
|
||||||
|
function loadShader(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
shaderSource: string,
|
||||||
|
shaderType: number,
|
||||||
|
opt_errorCallback: any,
|
||||||
|
): WebGLShader {
|
||||||
|
var errFn = opt_errorCallback || error;
|
||||||
|
// Create the shader object
|
||||||
|
var shader = gl.createShader(shaderType);
|
||||||
|
|
||||||
|
// Load the shader source
|
||||||
|
gl.shaderSource(shader, shaderSource);
|
||||||
|
|
||||||
|
// Compile the shader
|
||||||
|
gl.compileShader(shader);
|
||||||
|
|
||||||
|
// Check the compile status
|
||||||
|
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
||||||
|
if (!compiled) {
|
||||||
|
// Something went wrong during compilation; get the error
|
||||||
|
var lastError = gl.getShaderInfoLog(shader);
|
||||||
|
errFn("*** Error compiling shader '" + shader + "':" + lastError);
|
||||||
|
gl.deleteShader(shader);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("created shader with source");
|
||||||
|
console.log(shaderSource);
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createProgram(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
shaders: WebGLShader[],
|
||||||
|
opt_attribs: string[],
|
||||||
|
opt_locations: number[],
|
||||||
|
opt_errorCallback: any,
|
||||||
|
): WebGLProgram {
|
||||||
|
var errFn = opt_errorCallback || error;
|
||||||
|
var program = gl.createProgram();
|
||||||
|
shaders.forEach(function (shader) {
|
||||||
|
gl.attachShader(program, shader);
|
||||||
|
});
|
||||||
|
if (opt_attribs) {
|
||||||
|
opt_attribs.forEach(function (attrib, ndx) {
|
||||||
|
gl.bindAttribLocation(
|
||||||
|
program,
|
||||||
|
opt_locations ? opt_locations[ndx] : ndx,
|
||||||
|
attrib);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
// Check the link status
|
||||||
|
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||||
|
if (!linked) {
|
||||||
|
// something went wrong with the link
|
||||||
|
var lastError = gl.getProgramInfoLog(program);
|
||||||
|
errFn("Error in program linking:" + lastError);
|
||||||
|
|
||||||
|
gl.deleteProgram(program);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createShaderFromScript(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
scriptId: string,
|
||||||
|
context: Dictionary<any>,
|
||||||
|
opt_shaderType: number,
|
||||||
|
opt_errorCallback: any,
|
||||||
|
): WebGLShader {
|
||||||
|
var shaderSource = "";
|
||||||
|
var shaderType;
|
||||||
|
var shaderScript = document.getElementById(scriptId) as HTMLScriptElement;
|
||||||
|
if (!shaderScript) {
|
||||||
|
console.log("*** Error: unknown script element" + scriptId);
|
||||||
|
}
|
||||||
|
shaderSource = shaderScript.text;
|
||||||
|
|
||||||
|
for (let key in context) {
|
||||||
|
console.log("substitute " + key);
|
||||||
|
shaderSource = shaderSource.replace(new RegExp("\\$" + key, 'g'), context[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opt_shaderType) {
|
||||||
|
if (shaderScript.type === "x-shader/x-vertex") {
|
||||||
|
shaderType = 35633;
|
||||||
|
} else if (shaderScript.type === "x-shader/x-fragment") {
|
||||||
|
shaderType = 35632;
|
||||||
|
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
|
||||||
|
console.log("*** Error: unknown shader type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadShader(
|
||||||
|
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
|
||||||
|
opt_errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Shader {
|
||||||
|
shader: WebGLProgram;
|
||||||
|
uniformCache: Dictionary<WebGLUniformLocation>;
|
||||||
|
attribCache: Dictionary<number>;
|
||||||
|
|
||||||
|
static createProgramFromScripts(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
shaderScriptIds: string[],
|
||||||
|
context = {},
|
||||||
|
opt_attribs?: string[],
|
||||||
|
opt_locations?: number[],
|
||||||
|
opt_errorCallback?: any,
|
||||||
|
): Shader {
|
||||||
|
var shaders = [];
|
||||||
|
for (var ii = 0; ii < shaderScriptIds.length; ++ii) {
|
||||||
|
shaders.push(createShaderFromScript(
|
||||||
|
gl, shaderScriptIds[ii], context, (gl as any)[defaultShaderType[ii % 2]] as number, opt_errorCallback));
|
||||||
|
}
|
||||||
|
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createProgramFromUrls(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
vert_url: string,
|
||||||
|
frag_url: string,
|
||||||
|
context?: Dictionary<string>,
|
||||||
|
opt_attribs?: string[],
|
||||||
|
opt_locations?: number[],
|
||||||
|
opt_errorCallback?: any,
|
||||||
|
): Promise<Shader> {
|
||||||
|
const sources = (await Promise.all([
|
||||||
|
fetch(vert_url).then((r) => r.text()),
|
||||||
|
fetch(frag_url).then((r) => r.text()),
|
||||||
|
])).map(x => {
|
||||||
|
for (let key in context) {
|
||||||
|
x = x.replace(new RegExp("\\$" + key, 'g'), context[key]);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
|
||||||
|
const shaders = [
|
||||||
|
loadShader(gl, sources[0], 35633, opt_errorCallback),
|
||||||
|
loadShader(gl, sources[1], 35632, opt_errorCallback),
|
||||||
|
];
|
||||||
|
return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(shader: WebGLProgram) {
|
||||||
|
this.shader = shader;
|
||||||
|
this.uniformCache = {};
|
||||||
|
this.attribCache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(gl: WebGLRenderingContext) {
|
||||||
|
gl.useProgram(this.shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different locations have different types :/
|
||||||
|
getUniformLocation(gl: WebGLRenderingContext, name: string): WebGLUniformLocation {
|
||||||
|
if (this.uniformCache[name] === undefined) {
|
||||||
|
this.uniformCache[name] = gl.getUniformLocation(this.shader, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.uniformCache[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttribLocation(gl: WebGLRenderingContext, name: string): number {
|
||||||
|
if (this.attribCache[name] === undefined) {
|
||||||
|
this.attribCache[name] = gl.getAttribLocation(this.shader, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.attribCache[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
uniform<T extends Uniform>(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
name: string,
|
||||||
|
uniform: T,
|
||||||
|
) {
|
||||||
|
this.bind(gl);
|
||||||
|
const location = this.getUniformLocation(gl, name);
|
||||||
|
if (location < 0) {
|
||||||
|
console.log("No location found with name " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
uniform.setUniform(gl, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Uniform {
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform2fv implements Uniform {
|
||||||
|
data: number[];
|
||||||
|
constructor(data: number[]) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform2fv(location, this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform3fv implements Uniform {
|
||||||
|
data: number[];
|
||||||
|
constructor(data: number[]) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform3fv(location, this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform1iv implements Uniform {
|
||||||
|
data: number[];
|
||||||
|
constructor(data: number[]) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform1iv(location, this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform1i implements Uniform {
|
||||||
|
texture: number;
|
||||||
|
|
||||||
|
constructor(texture: number) {
|
||||||
|
this.texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform1i(location, this.texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform1f implements Uniform {
|
||||||
|
texture: number;
|
||||||
|
|
||||||
|
constructor(texture: number) {
|
||||||
|
this.texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform1f(location, this.texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform2f implements Uniform {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
|
||||||
|
constructor(x: number, y: number) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform2f(location, this.x, this.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Uniform4f implements Uniform {
|
||||||
|
v0: number;
|
||||||
|
v1: number;
|
||||||
|
v2: number;
|
||||||
|
v3: number;
|
||||||
|
|
||||||
|
constructor(vec: number[]) {
|
||||||
|
this.v0 = vec[0];
|
||||||
|
this.v1 = vec[1];
|
||||||
|
this.v2 = vec[2];
|
||||||
|
this.v3 = vec[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniform4f(location, this.v0, this.v1, this.v2, this.v3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UniformMatrix3fv implements Uniform {
|
||||||
|
data: number[];
|
||||||
|
constructor(data: number[]) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
|
||||||
|
gl.uniformMatrix3fv(location, false, this.data);
|
||||||
|
}
|
||||||
|
}
|
68
frontend/www/webgl/texture.ts
Normal file
68
frontend/www/webgl/texture.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: fix texture locations, not use only 0
|
||||||
|
export class Texture {
|
||||||
|
texture: WebGLTexture;
|
||||||
|
image: HTMLImageElement;
|
||||||
|
loaded: boolean;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
path: string,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
|
this.loaded = false;
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
this.image = new Image();
|
||||||
|
this.image.onload = () => this.handleImageLoaded(gl);
|
||||||
|
this.image.onerror = error;
|
||||||
|
this.image.src = path;
|
||||||
|
|
||||||
|
this.texture = gl.createTexture();
|
||||||
|
this.bind(gl);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
|
||||||
|
gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleImageLoaded(gl: WebGLRenderingContext) {
|
||||||
|
console.log('handling image loaded');
|
||||||
|
this.bind(gl);
|
||||||
|
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image);
|
||||||
|
|
||||||
|
this.unbind(gl);
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(gl: WebGLRenderingContext, location=0) {
|
||||||
|
gl.activeTexture(gl.TEXTURE0 + location);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind(gl: WebGLRenderingContext) {
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getWidth(): number {
|
||||||
|
return this.image.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeight(): number {
|
||||||
|
return this.image.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(e: any) {
|
||||||
|
console.error("IMAGE LOAD ERROR");
|
||||||
|
console.error(e);
|
||||||
|
}
|
129
frontend/www/webgl/util.ts
Normal file
129
frontend/www/webgl/util.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
|
||||||
|
export interface Dictionary<T> {
|
||||||
|
[Key: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface OnLoadable {
|
||||||
|
onload: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onload2promise<T extends OnLoadable>(obj: T): Promise<T> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
obj.onload = () => resolve(obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resizeCanvasToDisplaySize(
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
multiplier?: number,
|
||||||
|
): boolean {
|
||||||
|
multiplier = multiplier || 1;
|
||||||
|
var width = canvas.clientWidth * multiplier | 0;
|
||||||
|
var height = canvas.clientHeight * multiplier | 0;
|
||||||
|
if (canvas.width !== width || canvas.height !== height) {
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FPSCounter {
|
||||||
|
last: number;
|
||||||
|
count: number;
|
||||||
|
constructor() {
|
||||||
|
this.last = 0;
|
||||||
|
this.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame(now: number) {
|
||||||
|
this.count += 1;
|
||||||
|
if (now - this.last > 1) {
|
||||||
|
this.last = now;
|
||||||
|
console.log(this.count + " fps");
|
||||||
|
this.count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class M3 {
|
||||||
|
_data: any;
|
||||||
|
|
||||||
|
constructor(data: any) {
|
||||||
|
this._data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ident(): M3 {
|
||||||
|
return new M3([
|
||||||
|
1, 0, 0,
|
||||||
|
0, 1, 0,
|
||||||
|
0, 0, 1
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
multiply(other: M3): M3 {
|
||||||
|
const a = this._data;
|
||||||
|
const b = other._data;
|
||||||
|
|
||||||
|
var a00 = a[0 * 3 + 0];
|
||||||
|
var a01 = a[0 * 3 + 1];
|
||||||
|
var a02 = a[0 * 3 + 2];
|
||||||
|
var a10 = a[1 * 3 + 0];
|
||||||
|
var a11 = a[1 * 3 + 1];
|
||||||
|
var a12 = a[1 * 3 + 2];
|
||||||
|
var a20 = a[2 * 3 + 0];
|
||||||
|
var a21 = a[2 * 3 + 1];
|
||||||
|
var a22 = a[2 * 3 + 2];
|
||||||
|
var b00 = b[0 * 3 + 0];
|
||||||
|
var b01 = b[0 * 3 + 1];
|
||||||
|
var b02 = b[0 * 3 + 2];
|
||||||
|
var b10 = b[1 * 3 + 0];
|
||||||
|
var b11 = b[1 * 3 + 1];
|
||||||
|
var b12 = b[1 * 3 + 2];
|
||||||
|
var b20 = b[2 * 3 + 0];
|
||||||
|
var b21 = b[2 * 3 + 1];
|
||||||
|
var b22 = b[2 * 3 + 2];
|
||||||
|
|
||||||
|
return new M3([
|
||||||
|
b00 * a00 + b01 * a10 + b02 * a20,
|
||||||
|
b00 * a01 + b01 * a11 + b02 * a21,
|
||||||
|
b00 * a02 + b01 * a12 + b02 * a22,
|
||||||
|
b10 * a00 + b11 * a10 + b12 * a20,
|
||||||
|
b10 * a01 + b11 * a11 + b12 * a21,
|
||||||
|
b10 * a02 + b11 * a12 + b12 * a22,
|
||||||
|
b20 * a00 + b21 * a10 + b22 * a20,
|
||||||
|
b20 * a01 + b21 * a11 + b22 * a21,
|
||||||
|
b20 * a02 + b21 * a12 + b22 * a22,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
translation(x: number, y: number): M3 {
|
||||||
|
const out = [...this._data];
|
||||||
|
out[6] += x;
|
||||||
|
out[7] += y;
|
||||||
|
return new M3(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate(rad: number): M3 {
|
||||||
|
var c = Math.cos(rad);
|
||||||
|
var s = Math.sin(rad);
|
||||||
|
|
||||||
|
const out = new M3([...this._data]);
|
||||||
|
|
||||||
|
return out.multiply(new M3([
|
||||||
|
c, -s, 0,
|
||||||
|
s, c, 0,
|
||||||
|
0, 0, 1
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
scale(s_x: number, s_y = s_x, s_z = 1): M3 {
|
||||||
|
const out = new M3([...this._data]);
|
||||||
|
return out.multiply(new M3([
|
||||||
|
s_x, 0, 0,
|
||||||
|
0, s_y, 0,
|
||||||
|
0, 0, s_z,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
0
frontend/www/webgl/vertexArray.ts
Normal file
0
frontend/www/webgl/vertexArray.ts
Normal file
112
frontend/www/webgl/vertexBufferLayout.ts
Normal file
112
frontend/www/webgl/vertexBufferLayout.ts
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import { Buffer, VertexBuffer } from './buffer';
|
||||||
|
import { Shader } from './shader';
|
||||||
|
|
||||||
|
export class VertexBufferElement {
|
||||||
|
type: number;
|
||||||
|
amount: number;
|
||||||
|
type_size: number;
|
||||||
|
normalized: boolean;
|
||||||
|
index: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: number,
|
||||||
|
amount: number,
|
||||||
|
type_size: number,
|
||||||
|
index: string,
|
||||||
|
normalized: boolean,
|
||||||
|
) {
|
||||||
|
this.type = type;
|
||||||
|
this.amount = amount;
|
||||||
|
this.type_size = type_size;
|
||||||
|
this.normalized = normalized;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VertexBufferLayout {
|
||||||
|
elements: VertexBufferElement[];
|
||||||
|
stride: number;
|
||||||
|
offset: number;
|
||||||
|
|
||||||
|
constructor(offset = 0) {
|
||||||
|
this.elements = [];
|
||||||
|
this.stride = 0;
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe wrong normalized type
|
||||||
|
push(
|
||||||
|
type: number,
|
||||||
|
amount: number,
|
||||||
|
type_size: number,
|
||||||
|
index: string,
|
||||||
|
normalized = false,
|
||||||
|
) {
|
||||||
|
this.elements.push(new VertexBufferElement(type, amount, type_size, index, normalized));
|
||||||
|
this.stride += amount * type_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
getElements(): VertexBufferElement[] {
|
||||||
|
return this.elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStride(): number {
|
||||||
|
return this.stride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// glEnableVertexAttribArray is to specify what location of the current program the follow data is needed
|
||||||
|
// glVertexAttribPointer tells gl that that data is at which location in the supplied data
|
||||||
|
export class VertexArray {
|
||||||
|
// There is no renderer ID, always at bind buffers and use glVertexAttribPointer
|
||||||
|
buffers: Buffer[];
|
||||||
|
layouts: VertexBufferLayout[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.buffers = [];
|
||||||
|
this.layouts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addBuffer(vb: VertexBuffer, layout: VertexBufferLayout) {
|
||||||
|
this.buffers.push(vb);
|
||||||
|
this.layouts.push(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind buffers providing program data
|
||||||
|
bind(gl: WebGLRenderingContext, shader: Shader) {
|
||||||
|
shader.bind(gl);
|
||||||
|
for(let i = 0; i < this.buffers.length; i ++) {
|
||||||
|
const buffer = this.buffers[i];
|
||||||
|
const layout = this.layouts[i];
|
||||||
|
|
||||||
|
buffer.bind(gl);
|
||||||
|
const elements = layout.getElements();
|
||||||
|
let offset = layout.offset;
|
||||||
|
|
||||||
|
for (let j = 0; j < elements.length; j ++) {
|
||||||
|
const element = elements[j];
|
||||||
|
const location = shader.getAttribLocation(gl, element.index);
|
||||||
|
|
||||||
|
if (location >= 0) {
|
||||||
|
gl.enableVertexAttribArray(location);
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
location, element.amount, element.type,
|
||||||
|
element.normalized, layout.stride, offset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += element.amount * element.type_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Undo bind operation
|
||||||
|
unbind(gl: WebGLRenderingContext) {
|
||||||
|
this.layouts.forEach((layout) => {
|
||||||
|
layout.getElements().forEach((_, index) => {
|
||||||
|
gl.disableVertexAttribArray(index);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,24 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: "./bootstrap.js",
|
mode: 'development',
|
||||||
output: {
|
entry: './bootstrap.js',
|
||||||
path: path.resolve(__dirname, "dist"),
|
module: {
|
||||||
filename: "bootstrap.js",
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: [ '.tsx', '.ts', '.js', '.wasm' ]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: 'bootstrap.js',
|
||||||
|
path: path.resolve(__dirname, 'dist')
|
||||||
},
|
},
|
||||||
mode: "development",
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin(['index.html'])
|
new CopyWebpackPlugin(['index.html'])
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue