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 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));
|
||||
|
||||
println!("Tokens:");
|
||||
|
|
|
@ -5,6 +5,8 @@ use serde_json;
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
mod pw_config;
|
||||
mod pw_serializer;
|
||||
|
@ -17,25 +19,30 @@ pub use pw_config::Config;
|
|||
pub struct PlanetWarsGame {
|
||||
state: pw_rules::PlanetWars,
|
||||
planet_map: HashMap<String, usize>,
|
||||
log_file: File
|
||||
}
|
||||
|
||||
impl PlanetWarsGame {
|
||||
|
||||
pub fn new(state: pw_rules::PlanetWars) -> Self {
|
||||
let planet_map = state.planets.iter().map(|p| (p.name.clone(), p.id)).collect();
|
||||
let file = File::create("game.json").unwrap();
|
||||
|
||||
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);
|
||||
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)) {
|
||||
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)
|
||||
} else {
|
||||
proto::ServerMessage::FinalState(state)
|
||||
|
@ -45,7 +52,7 @@ impl PlanetWarsGame {
|
|||
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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,3 +7,11 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[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() {
|
||||
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 { memory } from "planetwars/plantwars_bg"
|
||||
import { Shader } from "./webgl/shader"
|
||||
|
||||
const URL = window.location.origin+window.location.pathname;
|
||||
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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
|
@ -4094,8 +4100,7 @@
|
|||
}
|
||||
},
|
||||
"planetwars": {
|
||||
"version": "file:../pkg",
|
||||
"dev": true
|
||||
"version": "file:../pkg"
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.21",
|
||||
|
@ -5192,6 +5197,70 @@
|
|||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||
"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": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||
|
@ -5220,6 +5289,12 @@
|
|||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
"name": "create-wasm-app",
|
||||
"version": "0.1.0",
|
||||
"description": "create an app to consume rust-generated wasm packages",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack-dev-server"
|
||||
|
@ -18,6 +19,9 @@
|
|||
"webpack",
|
||||
"mozaic"
|
||||
],
|
||||
"dependencies": {
|
||||
"planetwars": "file:../pkg"
|
||||
},
|
||||
"author": "Arthur Vercruysse <arthur.vercruysse@outlook.com>",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bugs": {
|
||||
|
@ -25,8 +29,9 @@
|
|||
},
|
||||
"homepage": "https://github.com/ajuvercr/Planetwars#Readme",
|
||||
"devDependencies": {
|
||||
"planetwars": "file:../pkg",
|
||||
"webpack": "^4.29.3",
|
||||
"ts-loader": "^6.0.2",
|
||||
"typescript": "^3.5.2",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.5",
|
||||
"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');
|
||||
|
||||
module.exports = {
|
||||
entry: "./bootstrap.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "bootstrap.js",
|
||||
mode: 'development',
|
||||
entry: './bootstrap.js',
|
||||
module: {
|
||||
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: [
|
||||
new CopyWebpackPlugin(['index.html'])
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue