Frontend/visualiser (#24)

* refactor to src folder

* add working voronoi js file with types

* some spring cleaning

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

* make voronoi go really fast

* do the voronoi dance

* better handle outliers in voronoi

* make renderer use multiple layers

* resize voronoi with resize + squash all in one buffer

* actually wait for shader factories to be created + cleanup

* show more info with FPS Counter

* POWER UP
This commit is contained in:
ajuvercr 2020-04-20 15:38:11 +02:00 committed by GitHub
parent 998cb3d535
commit 129c904967
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 2259 additions and 2362 deletions

View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -1,13 +1,20 @@
import { Game } from "planetwars"; import { Game } from "planetwars";
import { memory } from "planetwars/planetwars_bg"; import { memory } from "planetwars/planetwars_bg";
import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh } from "./webgl/util"; import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh, Dictionary } from "./webgl/util";
import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader'; import { Shader, Uniform4f, Uniform3fv, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader';
import { Renderer } from "./webgl/renderer"; import { Renderer } from "./webgl/renderer";
import { VertexBuffer, IndexBuffer } from "./webgl/buffer"; import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout"; import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
import { Texture } from "./webgl/texture";
import { callbackify } from "util";
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text"; import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
import { VoronoiBuilder } from "./voronoi/voronoi";
import { BBox } from "./voronoi/voronoi-core";
function to_bbox(box: number[]): BBox {
return {
'xl': box[0], 'xr': box[0] + box[2],
'yt': box[1], 'yb': box[1] + box[3]
};
}
function f32v(ptr: number, size: number): Float32Array { function f32v(ptr: number, size: number): Float32Array {
return new Float32Array(memory.buffer, ptr, size); return new Float32Array(memory.buffer, ptr, size);
@ -18,62 +25,45 @@ function i32v(ptr: number, size: number): Int32Array {
} }
export function set_game_name(name: string) { export function set_game_name(name: string) {
GAMENAME.innerHTML = name; ELEMENTS["name"].innerHTML = name;
} }
const GAMENAME = document.getElementById("name");
const TURNCOUNTER = document.getElementById("turnCounter");
const COUNTER = new FPSCounter();
const LOADER = document.getElementById("main");
const SLIDER = <HTMLInputElement>document.getElementById("turnSlider");
const FILESELECTOR = <HTMLInputElement>document.getElementById("fileselect");
const SPEED = <HTMLInputElement>document.getElementById("speed");
export function set_loading(loading: boolean) { export function set_loading(loading: boolean) {
if (loading) { if (loading) {
if (!LOADER.classList.contains("loading")) { if (!ELEMENTS["main"].classList.contains("loading")) {
LOADER.classList.add("loading"); ELEMENTS["main"].classList.add("loading");
} }
} else { } else {
LOADER.classList.remove("loading"); ELEMENTS["main"].classList.remove("loading");
} }
} }
const URL = window.location.origin + window.location.pathname; const ELEMENTS = {};
export const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1); ["name", "turnCounter", "main", "turnSlider", "fileselect", "speed", "canvas"].forEach(n => ELEMENTS[n] = document.getElementById(n));
const CANVAS = <HTMLCanvasElement>document.getElementById("c");
const CANVAS = ELEMENTS["canvas"];
const RESOLUTION = [CANVAS.width, CANVAS.height]; const RESOLUTION = [CANVAS.width, CANVAS.height];
const LAYERS = {
'vor': -1, // Background
'planet': 1,
'planet_label': 2,
'ship': 3,
'ship_label': 4
}
const COUNTER = new FPSCounter();
var ms_per_frame = parseInt(ELEMENTS["speed"].value);
const GL = CANVAS.getContext("webgl"); const GL = CANVAS.getContext("webgl");
var ms_per_frame = parseInt(SPEED.value); GL.clearColor(0, 0, 0, 1);
resizeCanvasToDisplaySize(CANVAS);
GL.clearColor(0, 0, 0, 0);
GL.clear(GL.COLOR_BUFFER_BIT); GL.clear(GL.COLOR_BUFFER_BIT);
GL.enable(GL.BLEND); GL.enable(GL.BLEND);
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
var SHADERFACOTRY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl"
).then((e) => SHADERFACOTRY = e);
var VOR_SHADER_FACTORY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/vor.glsl", LOCATION + "static/shaders/vert/vor.glsl"
).then((e) => VOR_SHADER_FACTORY = e);
var IMAGE_SHADER_FACTORY: ShaderFactory;
ShaderFactory.create_factory(
LOCATION + "static/shaders/frag/image.glsl", LOCATION + "static/shaders/vert/simple.glsl"
).then((e) => IMAGE_SHADER_FACTORY = e);
class GameInstance { class GameInstance {
resizer: Resizer; resizer: Resizer;
game: Game; game: Game;
@ -89,24 +79,24 @@ class GameInstance {
renderer: Renderer; renderer: Renderer;
planet_count: number; planet_count: number;
vor_builder: VoronoiBuilder;
vor_counter = 3; vor_counter = 3;
use_vor = true; use_vor = true;
playing = true; // 0 is paused, 1 is playing but not rerendered, 2 is playing and rerendered playing = true;
time_stopped_delta = 0; time_stopped_delta = 0;
last_time = 0; last_time = 0;
frame = -1; frame = -1;
ship_indices: number[];
turn_count = 0; turn_count = 0;
constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh) { constructor(game: Game, meshes: Mesh[], ship_mesh: Mesh, shaders: Dictionary<ShaderFactory>) {
this.game = game; this.game = game;
this.planet_count = this.game.get_planet_count(); this.planet_count = this.game.get_planet_count();
this.shader = SHADERFACOTRY.create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count }); this.shader = shaders["normal"].create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count });
this.image_shader = IMAGE_SHADER_FACTORY.create_shader(GL); this.image_shader = shaders["image"].create_shader(GL);
this.vor_shader = VOR_SHADER_FACTORY.create_shader(GL, { "PLANETS": '' + this.planet_count }); this.vor_shader = shaders["vor"].create_shader(GL, { "PLANETS": '' + this.planet_count });
this.text_factory = defaultLabelFactory(GL, this.image_shader); this.text_factory = defaultLabelFactory(GL, this.image_shader);
this.planet_labels = []; this.planet_labels = [];
@ -116,33 +106,33 @@ class GameInstance {
this.renderer = new Renderer(); this.renderer = new Renderer();
this.game.update_turn(0); this.game.update_turn(0);
const indexBuffer = new IndexBuffer(GL, [
0, 1, 2,
1, 2, 3,
]);
const positionBuffer = new VertexBuffer(GL, [
-1, -1,
-1, 1,
1, -1,
1, 1,
]);
const layout = new VertexBufferLayout();
layout.push(GL.FLOAT, 2, 4, "a_pos");
const vao = new VertexArray();
vao.addBuffer(positionBuffer, layout);
this.renderer.addToDraw(indexBuffer, vao, this.vor_shader);
// Setup key handling // Setup key handling
document.addEventListener('keydown', this.handleKey.bind(this)); document.addEventListener('keydown', this.handleKey.bind(this));
// List of [(x, y, r)] for all planets
const planets = f32v(game.get_planets(), this.planet_count * 3); const planets = f32v(game.get_planets(), this.planet_count * 3);
this._create_voronoi(planets);
this._create_planets(planets, meshes);
this._create_shipes(ship_mesh);
// Set slider correctly
this.turn_count = game.turn_count();
ELEMENTS["turnSlider"].max = this.turn_count - 1 + '';
}
_create_voronoi(planets: Float32Array) {
const planet_points = [];
for (let i = 0; i < planets.length; i += 3) {
planet_points.push({ 'x': -planets[i], 'y': -planets[i + 1] });
}
const bbox = to_bbox(this.resizer.get_viewbox());
this.vor_builder = new VoronoiBuilder(GL, this.vor_shader, planet_points, bbox);
this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor);
}
_create_planets(planets: Float32Array, meshes: Mesh[]) {
for (let i = 0; i < this.planet_count; i++) { for (let i = 0; i < this.planet_count; i++) {
{ {
const transform = new UniformMatrix3fv([ const transform = new UniformMatrix3fv([
@ -166,7 +156,9 @@ class GameInstance {
{ {
"u_trans": transform, "u_trans": transform,
"u_trans_next": transform, "u_trans_next": transform,
} },
[],
LAYERS.planet
); );
} }
@ -174,18 +166,17 @@ class GameInstance {
const transform = new UniformMatrix3fv([ const transform = new UniformMatrix3fv([
1., 0, 0, 1., 0, 0,
0, 1., 0, 0, 1., 0,
-planets[i * 3], -planets[i * 3 + 1] -1.2, 1., -planets[i * 3], -planets[i * 3 + 1] - 1.2, 1.,
]); ]);
const label = this.text_factory.build(GL, transform); const label = this.text_factory.build(GL, transform);
this.renderer.addRenderable(label);
this.planet_labels.push(label); this.planet_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
} }
} }
}
this.turn_count = game.turn_count(); _create_shipes(ship_mesh: Mesh) {
this.ship_indices = [];
const ship_ibo = new IndexBuffer(GL, ship_mesh.cells); const ship_ibo = new IndexBuffer(GL, ship_mesh.cells);
const ship_positions = new VertexBuffer(GL, ship_mesh.positions); const ship_positions = new VertexBuffer(GL, ship_mesh.positions);
const ship_layout = new VertexBufferLayout(); const ship_layout = new VertexBufferLayout();
@ -194,31 +185,33 @@ class GameInstance {
ship_vao.addBuffer(ship_positions, ship_layout); ship_vao.addBuffer(ship_positions, ship_layout);
for (let i = 0; i < this.game.get_max_ships(); i++) { for (let i = 0; i < this.game.get_max_ships(); i++) {
this.ship_indices.push( this.renderer.addToDraw(
this.renderer.addToDraw( ship_ibo,
ship_ibo, ship_vao,
ship_vao, this.shader,
this.shader, {},
{} [],
) LAYERS.ship
); );
const label = this.text_factory.build(GL); const label = this.text_factory.build(GL);
this.ship_labels.push(label); this.ship_labels.push(label);
this.renderer.addRenderable(label) this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label)
} }
this.vor_shader.uniform(GL, "u_planets", new Uniform3fv(planets));
// Set slider correctly
SLIDER.max = this.turn_count - 1 + '';
} }
on_resize() { on_resize() {
this.resizer = new Resizer(CANVAS, [...f32v(this.game.get_viewbox(), 4)], true); this.resizer = new Resizer(CANVAS, [...f32v(this.game.get_viewbox(), 4)], true);
const bbox = to_bbox(this.resizer.get_viewbox());
this.vor_builder.resize(GL, bbox);
} }
_update_state() { _update_state() {
this._update_planets();
this._update_ships();
}
_update_planets() {
const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6); const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6);
const planet_ships = i32v(this.game.get_planet_ships(), this.planet_count); const planet_ships = i32v(this.game.get_planet_ships(), this.planet_count);
@ -226,13 +219,15 @@ class GameInstance {
for (let i = 0; i < this.planet_count; i++) { for (let i = 0; i < this.planet_count; i++) {
const u = new Uniform3f(colours[i * 6], colours[i * 6 + 1], colours[i * 6 + 2]); const u = new Uniform3f(colours[i * 6], colours[i * 6 + 1], colours[i * 6 + 2]);
this.renderer.updateUniform(2 * i + 1, (us) => us["u_color"] = u); this.renderer.updateUniform(i, (us) => us["u_color"] = u, LAYERS.planet);
const u2 = new Uniform3f(colours[i * 6 + 3], colours[i * 6 + 4], colours[i * 6 + 5]); const u2 = new Uniform3f(colours[i * 6 + 3], colours[i * 6 + 4], colours[i * 6 + 5]);
this.renderer.updateUniform(2 * i + 1, (us) => us["u_color_next"] = u2); this.renderer.updateUniform(i, (us) => us["u_color_next"] = u2, LAYERS.planet);
this.planet_labels[i].setText(GL, "*"+planet_ships[i], Align.Middle, Align.Begin); this.planet_labels[i].setText(GL, "*" + planet_ships[i], Align.Middle, Align.Begin);
} }
}
_update_ships() {
const ship_count = this.game.get_ship_count(); const ship_count = this.game.get_ship_count();
const ships = f32v(this.game.get_ship_locations(), ship_count * 9 * 2); const ships = f32v(this.game.get_ship_locations(), ship_count * 9 * 2);
const labels = f32v(this.game.get_ship_label_locations(), ship_count * 9 * 2); const labels = f32v(this.game.get_ship_label_locations(), ship_count * 9 * 2);
@ -240,17 +235,13 @@ class GameInstance {
const ship_colours = f32v(this.game.get_ship_colours(), ship_count * 3); const ship_colours = f32v(this.game.get_ship_colours(), ship_count * 3);
for (let i = 0; i < this.game.get_max_ships(); i++) { for (let i = 0; i < this.game.get_max_ships(); i++) {
const index = this.ship_indices[i];
if (i < ship_count) { if (i < ship_count) {
this.ship_labels[i].setText(GL, "" + ship_counts[i], Align.Middle, Align.Middle);
this.ship_labels[i].setText(GL, ""+ship_counts[i], Align.Middle, Align.Middle); this.renderer.enableRenderable(i, LAYERS.ship);
this.renderer.enableRenderable(i, LAYERS.ship_label);
this.renderer.enableRendershit(index);
this.renderer.enableRendershit(index+1);
const u = new Uniform3f(ship_colours[i * 3], ship_colours[i * 3 + 1], ship_colours[i * 3 + 2]); const u = new Uniform3f(ship_colours[i * 3], ship_colours[i * 3 + 1], ship_colours[i * 3 + 2]);
// const t1 = new UniformMatrix3fv(new Float32Array(ships, i * 18, 9));
// const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9));
const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9)); const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9));
const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18)); const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18));
@ -258,21 +249,21 @@ class GameInstance {
const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9)); const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9));
const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18)); const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18));
this.renderer.updateUniform(index, (us) => { this.renderer.updateUniform(i, (us) => {
us["u_color"] = u; us["u_color"] = u;
us["u_color_next"] = u; us["u_color_next"] = u;
us["u_trans"] = t1; us["u_trans"] = t1;
us["u_trans_next"] = t2; us["u_trans_next"] = t2;
}); }, LAYERS.ship);
this.renderer.updateUniform(index+1, (us) => { this.renderer.updateUniform(i, (us) => {
us["u_trans"] = tl1; us["u_trans"] = tl1;
us["u_trans_next"] = tl2; us["u_trans_next"] = tl2;
}); }, LAYERS.ship_label);
} else { } else {
this.renderer.disableRenderShift(index); this.renderer.disableRenderable(i, LAYERS.ship);
this.renderer.disableRenderShift(index+1); this.renderer.disableRenderable(i, LAYERS.ship_label);
} }
} }
} }
@ -290,6 +281,7 @@ class GameInstance {
this.use_vor = false; this.use_vor = false;
} }
// If not playing, still reder with different viewbox, so people can still pan etc.
if (!this.playing) { if (!this.playing) {
this.last_time = time; this.last_time = time;
@ -300,16 +292,19 @@ class GameInstance {
this.renderer.render(GL); this.renderer.render(GL);
return; return;
} }
if (time > this.last_time + ms_per_frame) {
// Check if turn is still correct
if (time > this.last_time + ms_per_frame) {
this.last_time = time; this.last_time = time;
this.updateTurn(this.frame + 1); this.updateTurn(this.frame + 1);
} }
// Do GL things
GL.bindFramebuffer(GL.FRAMEBUFFER, null); GL.bindFramebuffer(GL.FRAMEBUFFER, null);
GL.viewport(0, 0, GL.canvas.width, GL.canvas.height); GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
this.vor_shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / ms_per_frame));
this.vor_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox())); this.vor_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor)); this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor));
@ -324,7 +319,10 @@ class GameInstance {
this.image_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox())); this.image_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
this.image_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); this.image_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
// Render
this.renderer.render(GL); this.renderer.render(GL);
COUNTER.frame_end();
} }
updateTurn(turn: number) { updateTurn(turn: number) {
@ -338,8 +336,8 @@ class GameInstance {
this.playing = true; this.playing = true;
} }
TURNCOUNTER.innerHTML = this.frame + " / " + this.turn_count; ELEMENTS["turnCounter"].innerHTML = this.frame + " / " + (this.turn_count - 1);
SLIDER.value = this.frame + ''; ELEMENTS["turnSlider"].value = this.frame + '';
} }
handleKey(event: KeyboardEvent) { handleKey(event: KeyboardEvent) {
@ -365,33 +363,48 @@ class GameInstance {
// d key // d key
if (event.keyCode == 68) { if (event.keyCode == 68) {
SPEED.value = ms_per_frame + 10 + ''; ELEMENTS["speed"].value = ms_per_frame + 10 + '';
SPEED.onchange(undefined); ELEMENTS["speed"].onchange(undefined);
} }
// a key // a key
if (event.keyCode == 65) { if (event.keyCode == 65) {
SPEED.value = Math.max(ms_per_frame - 10, 0) + ''; ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + '';
SPEED.onchange(undefined); ELEMENTS["speed"].onchange(undefined);
} }
} }
} }
var game_instance: GameInstance; var game_instance: GameInstance;
var meshes; var meshes: Mesh[];
var shaders: Dictionary<ShaderFactory>;
export async function set_instance(source: string) { export async function set_instance(source: string) {
if (!meshes) { if (!meshes || !shaders) {
meshes = await Promise.all( const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map(
["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map( (name) => "static/res/assets/" + name
(name) => "static/res/assets/" + name ).map(url_to_mesh);
).map(url_to_mesh)
const shader_promies = [
(async () => <[string, ShaderFactory]>["normal", await ShaderFactory.create_factory("static/shaders/frag/simple.glsl", "static/shaders/vert/simple.glsl")])(),
(async () => <[string, ShaderFactory]>["vor", await ShaderFactory.create_factory("static/shaders/frag/vor.glsl", "static/shaders/vert/vor.glsl")])(),
(async () => <[string, ShaderFactory]>["image", await ShaderFactory.create_factory("static/shaders/frag/image.glsl", "static/shaders/vert/simple.glsl")])(),
];
let shaders_array: [string, ShaderFactory][];
[meshes, shaders_array] = await Promise.all(
[
Promise.all(mesh_promises),
Promise.all(shader_promies),
]
); );
shaders = {};
shaders_array.forEach(([name, fac]) => shaders[name] = fac);
} }
resizeCanvasToDisplaySize(CANVAS); resizeCanvasToDisplaySize(CANVAS);
game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0]); game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0], shaders);
set_loading(false); set_loading(false);
} }
@ -404,26 +417,14 @@ window.addEventListener('resize', function () {
} }
}, { capture: false, passive: true }) }, { capture: false, passive: true })
SLIDER.oninput = function () { ELEMENTS["turnSlider"].oninput = function () {
if (game_instance) { if (game_instance) {
game_instance.updateTurn(parseInt(SLIDER.value)); game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value));
} }
} }
FILESELECTOR.onchange = function () { ELEMENTS["speed"].onchange = function () {
const file = FILESELECTOR.files[0]; ms_per_frame = parseInt(ELEMENTS["speed"].value);
if (!file) { return; }
var reader = new FileReader();
reader.onload = function () {
set_instance(<string>reader.result);
}
reader.readAsText(file);
}
SPEED.onchange = function () {
ms_per_frame = parseInt(SPEED.value);
} }
function step(time: number) { function step(time: number) {
@ -433,6 +434,5 @@ function step(time: number) {
requestAnimationFrame(step); requestAnimationFrame(step);
} }
// set_loading(false);
requestAnimationFrame(step); requestAnimationFrame(step);

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff