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:
parent
998cb3d535
commit
129c904967
26 changed files with 2259 additions and 2362 deletions
|
@ -9,7 +9,7 @@
|
|||
<div id=wrapper>
|
||||
|
||||
<div id="main" class="loading">
|
||||
<canvas id="c"></canvas>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="name"></div>
|
||||
<div id="addbutton" class="button"></div>
|
||||
|
||||
|
|
2
frontend/www/bootstrap.js
vendored
2
frontend/www/bootstrap.js
vendored
|
@ -14,7 +14,7 @@ if (typeof mergeInto !== 'undefined') mergeInto(LibraryManager.library, {
|
|||
}
|
||||
});
|
||||
|
||||
import ("./index.js")
|
||||
import ("./src/index.js")
|
||||
.then(e => {
|
||||
h = e.handle;
|
||||
})
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
import { create } from "domain";
|
||||
|
||||
|
||||
export class Vertex {
|
||||
coords: [number, number];
|
||||
incident_edge: HalfEdge;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.coords = [x, y];
|
||||
}
|
||||
|
||||
get_all(): HalfEdge[] {
|
||||
const out = [];
|
||||
|
||||
let current = this.incident_edge;
|
||||
do {
|
||||
out.push(current);
|
||||
current = current.twin.next;
|
||||
} while (current != this.incident_edge);
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
export class Face {
|
||||
attributes: any;
|
||||
|
||||
outer_component: HalfEdge;
|
||||
inner_components: HalfEdge[];
|
||||
|
||||
constructor(attributes?: any) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
loop(): [number, number][] {
|
||||
const out = [];
|
||||
let iter = 0;
|
||||
|
||||
let current = this.outer_component;
|
||||
do {
|
||||
if (iter > 100) {
|
||||
throw new Error("Fuck off");
|
||||
}
|
||||
iter += 1;
|
||||
console.log(current.id, current.face.attributes, current.origin.coords);
|
||||
out.push(current.origin.coords);
|
||||
current = current.next;
|
||||
} while (current != this.outer_component);
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
var id = 0;
|
||||
function next_id(): number {
|
||||
id += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
export class HalfEdge {
|
||||
origin: Vertex;
|
||||
// destination = twin.origin
|
||||
|
||||
next: HalfEdge;
|
||||
prev: HalfEdge;
|
||||
twin: HalfEdge;
|
||||
|
||||
face: Face;
|
||||
|
||||
id: number;
|
||||
|
||||
constructor(origin: Vertex, f1: Face, f2?: Face) {
|
||||
this.id = next_id();
|
||||
|
||||
this.origin = origin;
|
||||
this.next = this;
|
||||
this.prev = this;
|
||||
|
||||
if (f2) {
|
||||
this.twin = new HalfEdge(origin, f2);
|
||||
} else {
|
||||
this.twin = this;
|
||||
}
|
||||
|
||||
this.face = f1;
|
||||
}
|
||||
|
||||
insert(at: Vertex, update_twin = true): HalfEdge {
|
||||
const new_edge = new HalfEdge(at, this.face);
|
||||
|
||||
new_edge.next = this.next;
|
||||
new_edge.prev = this;
|
||||
new_edge.twin = this.twin;
|
||||
this.next.prev = new_edge;
|
||||
this.next = new_edge;
|
||||
|
||||
if (update_twin) {
|
||||
this.twin = this.twin.insert(at, false);
|
||||
}
|
||||
|
||||
return new_edge;
|
||||
}
|
||||
|
||||
split(to: Vertex) {
|
||||
const e_to = new HalfEdge(this.origin, this.face);
|
||||
const e_from = new HalfEdge(to, this.face);
|
||||
e_to.twin = e_from;
|
||||
e_from.twin = e_to;
|
||||
|
||||
e_to.prev = this.prev;
|
||||
e_to.next = e_from;
|
||||
e_from.next = this;
|
||||
e_from.prev = e_to;
|
||||
|
||||
this.prev.next = e_to;
|
||||
this.prev = e_from;
|
||||
}
|
||||
|
||||
add_face() {
|
||||
|
||||
}
|
||||
|
||||
to_string(): string {
|
||||
return `Halfedge from ${this.origin ? this.origin.coords : undefined} face1 ${this.face ? this.face.attributes : undefined}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function test() {
|
||||
const f1 = new Face("Face 1");
|
||||
const f2 = new Face("Face 2");
|
||||
|
||||
const v1 = new Vertex(0, 0);
|
||||
const v2 = new Vertex(1, 1);
|
||||
const v3 = new Vertex(-1, 0);
|
||||
const v4 = new Vertex(2, 0);
|
||||
|
||||
const e1 = new HalfEdge(v1, f1, f2);
|
||||
f1.outer_component = e1;
|
||||
const e2 = e1.insert(v2);
|
||||
const e3 = e2.split(v3);
|
||||
const e4 = e2.insert(v4);
|
||||
|
||||
e1.twin.next = e4.twin;
|
||||
f2.outer_component = e4.twin;
|
||||
// const e3 = e1.insert(v3);
|
||||
|
||||
console.log(f1.loop());
|
||||
console.log(f2.loop());
|
||||
}
|
|
@ -1,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
47
frontend/www/src/games.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
|
||||
import { set_game_name, set_loading, set_instance } from './index'
|
||||
|
||||
var game_name, game_file;
|
||||
|
||||
document.getElementById("addbutton").onclick = function () {
|
||||
const loc = window.location;
|
||||
const query = `?game=${game_file}&name=${game_name}`;
|
||||
navigator.clipboard.writeText(loc.origin + loc.pathname + encodeURI(query)).then(() => {
|
||||
console.log("Success");
|
||||
}, () => {
|
||||
console.log("Failed");
|
||||
});
|
||||
}
|
||||
|
||||
async function on_load() {
|
||||
const options = document.getElementsByClassName("options");
|
||||
const urlVars = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlVars.get("game") && urlVars.get("name")) {
|
||||
console.log(urlVars.get("game") + ' ' + urlVars.get("name"))
|
||||
handle(urlVars.get("game"), urlVars.get("name"))
|
||||
} else if (options[0]) {
|
||||
const options_div = <HTMLDivElement>options[0];
|
||||
if (options_div.children[0]) {
|
||||
options_div.children[0].dispatchEvent(new Event('click'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", on_load, false);
|
||||
|
||||
export function handle(location: string, name: string) {
|
||||
game_file = location;
|
||||
game_name = name;
|
||||
|
||||
set_loading(true);
|
||||
|
||||
fetch(location)
|
||||
.then((r) => r.text())
|
||||
.then((response) => {
|
||||
set_instance(response);
|
||||
set_game_name(name);
|
||||
}).catch(console.error);
|
||||
}
|
||||
|
||||
on_load();
|
|
@ -1,13 +1,20 @@
|
|||
import { Game } from "planetwars";
|
||||
import { memory } from "planetwars/planetwars_bg";
|
||||
import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh } from "./webgl/util";
|
||||
import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader';
|
||||
import { Resizer, resizeCanvasToDisplaySize, FPSCounter, url_to_mesh, Mesh, Dictionary } from "./webgl/util";
|
||||
import { Shader, Uniform4f, Uniform3fv, Uniform1f, Uniform2f, ShaderFactory, Uniform3f, UniformMatrix3fv, UniformBool } from './webgl/shader';
|
||||
import { Renderer } from "./webgl/renderer";
|
||||
import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
|
||||
import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
|
||||
import { Texture } from "./webgl/texture";
|
||||
import { callbackify } from "util";
|
||||
import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
|
||||
import { VoronoiBuilder } from "./voronoi/voronoi";
|
||||
import { BBox } from "./voronoi/voronoi-core";
|
||||
|
||||
function to_bbox(box: number[]): BBox {
|
||||
return {
|
||||
'xl': box[0], 'xr': box[0] + box[2],
|
||||
'yt': box[1], 'yb': box[1] + box[3]
|
||||
};
|
||||
}
|
||||
|
||||
function f32v(ptr: number, size: number): Float32Array {
|
||||
return new Float32Array(memory.buffer, ptr, size);
|
||||
|
@ -18,62 +25,45 @@ function i32v(ptr: number, size: number): Int32Array {
|
|||
}
|
||||
|
||||
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) {
|
||||
if (loading) {
|
||||
if (!LOADER.classList.contains("loading")) {
|
||||
LOADER.classList.add("loading");
|
||||
if (!ELEMENTS["main"].classList.contains("loading")) {
|
||||
ELEMENTS["main"].classList.add("loading");
|
||||
}
|
||||
} else {
|
||||
LOADER.classList.remove("loading");
|
||||
ELEMENTS["main"].classList.remove("loading");
|
||||
}
|
||||
}
|
||||
|
||||
const URL = window.location.origin + window.location.pathname;
|
||||
export const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
|
||||
const CANVAS = <HTMLCanvasElement>document.getElementById("c");
|
||||
const ELEMENTS = {};
|
||||
["name", "turnCounter", "main", "turnSlider", "fileselect", "speed", "canvas"].forEach(n => ELEMENTS[n] = document.getElementById(n));
|
||||
|
||||
const CANVAS = ELEMENTS["canvas"];
|
||||
const RESOLUTION = [CANVAS.width, CANVAS.height];
|
||||
|
||||
const LAYERS = {
|
||||
'vor': -1, // Background
|
||||
'planet': 1,
|
||||
'planet_label': 2,
|
||||
'ship': 3,
|
||||
'ship_label': 4
|
||||
}
|
||||
|
||||
const COUNTER = new FPSCounter();
|
||||
|
||||
var ms_per_frame = parseInt(ELEMENTS["speed"].value);
|
||||
|
||||
const GL = CANVAS.getContext("webgl");
|
||||
|
||||
var ms_per_frame = parseInt(SPEED.value);
|
||||
|
||||
resizeCanvasToDisplaySize(CANVAS);
|
||||
|
||||
GL.clearColor(0, 0, 0, 0);
|
||||
GL.clearColor(0, 0, 0, 1);
|
||||
GL.clear(GL.COLOR_BUFFER_BIT);
|
||||
|
||||
GL.enable(GL.BLEND);
|
||||
GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
var SHADERFACOTRY: ShaderFactory;
|
||||
ShaderFactory.create_factory(
|
||||
LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl"
|
||||
).then((e) => SHADERFACOTRY = e);
|
||||
|
||||
var VOR_SHADER_FACTORY: ShaderFactory;
|
||||
ShaderFactory.create_factory(
|
||||
LOCATION + "static/shaders/frag/vor.glsl", LOCATION + "static/shaders/vert/vor.glsl"
|
||||
).then((e) => VOR_SHADER_FACTORY = e);
|
||||
|
||||
var IMAGE_SHADER_FACTORY: ShaderFactory;
|
||||
ShaderFactory.create_factory(
|
||||
LOCATION + "static/shaders/frag/image.glsl", LOCATION + "static/shaders/vert/simple.glsl"
|
||||
).then((e) => IMAGE_SHADER_FACTORY = e);
|
||||
|
||||
class GameInstance {
|
||||
resizer: Resizer;
|
||||
game: Game;
|
||||
|
@ -89,24 +79,24 @@ class GameInstance {
|
|||
renderer: Renderer;
|
||||
planet_count: number;
|
||||
|
||||
vor_builder: VoronoiBuilder;
|
||||
|
||||
vor_counter = 3;
|
||||
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;
|
||||
last_time = 0;
|
||||
frame = -1;
|
||||
|
||||
ship_indices: number[];
|
||||
|
||||
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.planet_count = this.game.get_planet_count();
|
||||
|
||||
this.shader = SHADERFACOTRY.create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count });
|
||||
this.image_shader = IMAGE_SHADER_FACTORY.create_shader(GL);
|
||||
this.vor_shader = VOR_SHADER_FACTORY.create_shader(GL, { "PLANETS": '' + this.planet_count });
|
||||
this.shader = shaders["normal"].create_shader(GL, { "MAX_CIRCLES": '' + this.planet_count });
|
||||
this.image_shader = shaders["image"].create_shader(GL);
|
||||
this.vor_shader = shaders["vor"].create_shader(GL, { "PLANETS": '' + this.planet_count });
|
||||
|
||||
this.text_factory = defaultLabelFactory(GL, this.image_shader);
|
||||
this.planet_labels = [];
|
||||
|
@ -116,33 +106,33 @@ class GameInstance {
|
|||
this.renderer = new Renderer();
|
||||
this.game.update_turn(0);
|
||||
|
||||
|
||||
|
||||
const indexBuffer = new IndexBuffer(GL, [
|
||||
0, 1, 2,
|
||||
1, 2, 3,
|
||||
]);
|
||||
|
||||
const positionBuffer = new VertexBuffer(GL, [
|
||||
-1, -1,
|
||||
-1, 1,
|
||||
1, -1,
|
||||
1, 1,
|
||||
]);
|
||||
|
||||
const layout = new VertexBufferLayout();
|
||||
layout.push(GL.FLOAT, 2, 4, "a_pos");
|
||||
|
||||
const vao = new VertexArray();
|
||||
vao.addBuffer(positionBuffer, layout);
|
||||
|
||||
this.renderer.addToDraw(indexBuffer, vao, this.vor_shader);
|
||||
|
||||
// Setup key handling
|
||||
document.addEventListener('keydown', this.handleKey.bind(this));
|
||||
|
||||
// List of [(x, y, r)] for all planets
|
||||
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++) {
|
||||
{
|
||||
const transform = new UniformMatrix3fv([
|
||||
|
@ -166,7 +156,9 @@ class GameInstance {
|
|||
{
|
||||
"u_trans": transform,
|
||||
"u_trans_next": transform,
|
||||
}
|
||||
},
|
||||
[],
|
||||
LAYERS.planet
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -174,18 +166,17 @@ class GameInstance {
|
|||
const transform = new UniformMatrix3fv([
|
||||
1., 0, 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);
|
||||
this.renderer.addRenderable(label);
|
||||
this.planet_labels.push(label);
|
||||
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.turn_count = game.turn_count();
|
||||
|
||||
this.ship_indices = [];
|
||||
_create_shipes(ship_mesh: Mesh) {
|
||||
const ship_ibo = new IndexBuffer(GL, ship_mesh.cells);
|
||||
const ship_positions = new VertexBuffer(GL, ship_mesh.positions);
|
||||
const ship_layout = new VertexBufferLayout();
|
||||
|
@ -194,31 +185,33 @@ class GameInstance {
|
|||
ship_vao.addBuffer(ship_positions, ship_layout);
|
||||
|
||||
for (let i = 0; i < this.game.get_max_ships(); i++) {
|
||||
this.ship_indices.push(
|
||||
this.renderer.addToDraw(
|
||||
ship_ibo,
|
||||
ship_vao,
|
||||
this.shader,
|
||||
{}
|
||||
)
|
||||
this.renderer.addToDraw(
|
||||
ship_ibo,
|
||||
ship_vao,
|
||||
this.shader,
|
||||
{},
|
||||
[],
|
||||
LAYERS.ship
|
||||
);
|
||||
|
||||
const label = this.text_factory.build(GL);
|
||||
this.ship_labels.push(label);
|
||||
this.renderer.addRenderable(label)
|
||||
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() {
|
||||
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() {
|
||||
this._update_planets();
|
||||
this._update_ships();
|
||||
}
|
||||
|
||||
_update_planets() {
|
||||
const colours = f32v(this.game.get_planet_colors(), this.planet_count * 6);
|
||||
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++) {
|
||||
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]);
|
||||
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 ships = f32v(this.game.get_ship_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);
|
||||
|
||||
for (let i = 0; i < this.game.get_max_ships(); i++) {
|
||||
const index = this.ship_indices[i];
|
||||
if (i < ship_count) {
|
||||
this.ship_labels[i].setText(GL, "" + ship_counts[i], Align.Middle, Align.Middle);
|
||||
|
||||
this.ship_labels[i].setText(GL, ""+ship_counts[i], Align.Middle, Align.Middle);
|
||||
|
||||
this.renderer.enableRendershit(index);
|
||||
this.renderer.enableRendershit(index+1);
|
||||
this.renderer.enableRenderable(i, LAYERS.ship);
|
||||
this.renderer.enableRenderable(i, LAYERS.ship_label);
|
||||
|
||||
const u = new Uniform3f(ship_colours[i * 3], ship_colours[i * 3 + 1], ship_colours[i * 3 + 2]);
|
||||
// const t1 = new UniformMatrix3fv(new Float32Array(ships, i * 18, 9));
|
||||
// const t2 = new UniformMatrix3fv(new Float32Array(ships, i * 18 + 9, 9));
|
||||
|
||||
const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9));
|
||||
const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18));
|
||||
|
@ -258,21 +249,21 @@ class GameInstance {
|
|||
const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9));
|
||||
const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18));
|
||||
|
||||
this.renderer.updateUniform(index, (us) => {
|
||||
this.renderer.updateUniform(i, (us) => {
|
||||
us["u_color"] = u;
|
||||
us["u_color_next"] = u;
|
||||
us["u_trans"] = t1;
|
||||
us["u_trans_next"] = t2;
|
||||
});
|
||||
}, LAYERS.ship);
|
||||
|
||||
this.renderer.updateUniform(index+1, (us) => {
|
||||
this.renderer.updateUniform(i, (us) => {
|
||||
us["u_trans"] = tl1;
|
||||
us["u_trans_next"] = tl2;
|
||||
});
|
||||
}, LAYERS.ship_label);
|
||||
|
||||
} else {
|
||||
this.renderer.disableRenderShift(index);
|
||||
this.renderer.disableRenderShift(index+1);
|
||||
this.renderer.disableRenderable(i, LAYERS.ship);
|
||||
this.renderer.disableRenderable(i, LAYERS.ship_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,6 +281,7 @@ class GameInstance {
|
|||
this.use_vor = false;
|
||||
}
|
||||
|
||||
// If not playing, still reder with different viewbox, so people can still pan etc.
|
||||
if (!this.playing) {
|
||||
this.last_time = time;
|
||||
|
||||
|
@ -300,16 +292,19 @@ class GameInstance {
|
|||
this.renderer.render(GL);
|
||||
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.updateTurn(this.frame + 1);
|
||||
}
|
||||
|
||||
// Do GL things
|
||||
GL.bindFramebuffer(GL.FRAMEBUFFER, null);
|
||||
GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
|
||||
GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
|
||||
|
||||
this.vor_shader.uniform(GL, "u_time", new Uniform1f((time - this.last_time) / ms_per_frame));
|
||||
this.vor_shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
|
||||
this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
|
||||
this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor));
|
||||
|
@ -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_resolution", new Uniform2f(RESOLUTION));
|
||||
|
||||
// Render
|
||||
this.renderer.render(GL);
|
||||
|
||||
COUNTER.frame_end();
|
||||
}
|
||||
|
||||
updateTurn(turn: number) {
|
||||
|
@ -338,8 +336,8 @@ class GameInstance {
|
|||
this.playing = true;
|
||||
}
|
||||
|
||||
TURNCOUNTER.innerHTML = this.frame + " / " + this.turn_count;
|
||||
SLIDER.value = this.frame + '';
|
||||
ELEMENTS["turnCounter"].innerHTML = this.frame + " / " + (this.turn_count - 1);
|
||||
ELEMENTS["turnSlider"].value = this.frame + '';
|
||||
}
|
||||
|
||||
handleKey(event: KeyboardEvent) {
|
||||
|
@ -365,33 +363,48 @@ class GameInstance {
|
|||
|
||||
// d key
|
||||
if (event.keyCode == 68) {
|
||||
SPEED.value = ms_per_frame + 10 + '';
|
||||
SPEED.onchange(undefined);
|
||||
ELEMENTS["speed"].value = ms_per_frame + 10 + '';
|
||||
ELEMENTS["speed"].onchange(undefined);
|
||||
}
|
||||
|
||||
// a key
|
||||
if (event.keyCode == 65) {
|
||||
SPEED.value = Math.max(ms_per_frame - 10, 0) + '';
|
||||
SPEED.onchange(undefined);
|
||||
ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + '';
|
||||
ELEMENTS["speed"].onchange(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var game_instance: GameInstance;
|
||||
var meshes;
|
||||
var meshes: Mesh[];
|
||||
var shaders: Dictionary<ShaderFactory>;
|
||||
|
||||
export async function set_instance(source: string) {
|
||||
if (!meshes) {
|
||||
meshes = await Promise.all(
|
||||
["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map(
|
||||
(name) => "static/res/assets/" + name
|
||||
).map(url_to_mesh)
|
||||
if (!meshes || !shaders) {
|
||||
const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"].map(
|
||||
(name) => "static/res/assets/" + name
|
||||
).map(url_to_mesh);
|
||||
|
||||
const shader_promies = [
|
||||
(async () => <[string, ShaderFactory]>["normal", await ShaderFactory.create_factory("static/shaders/frag/simple.glsl", "static/shaders/vert/simple.glsl")])(),
|
||||
(async () => <[string, ShaderFactory]>["vor", await ShaderFactory.create_factory("static/shaders/frag/vor.glsl", "static/shaders/vert/vor.glsl")])(),
|
||||
(async () => <[string, ShaderFactory]>["image", await ShaderFactory.create_factory("static/shaders/frag/image.glsl", "static/shaders/vert/simple.glsl")])(),
|
||||
];
|
||||
let shaders_array: [string, ShaderFactory][];
|
||||
[meshes, shaders_array] = await Promise.all(
|
||||
[
|
||||
Promise.all(mesh_promises),
|
||||
Promise.all(shader_promies),
|
||||
]
|
||||
);
|
||||
|
||||
shaders = {};
|
||||
shaders_array.forEach(([name, fac]) => shaders[name] = fac);
|
||||
}
|
||||
|
||||
resizeCanvasToDisplaySize(CANVAS);
|
||||
|
||||
game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0]);
|
||||
game_instance = new GameInstance(Game.new(source), meshes.slice(1), meshes[0], shaders);
|
||||
|
||||
set_loading(false);
|
||||
}
|
||||
|
@ -404,26 +417,14 @@ window.addEventListener('resize', function () {
|
|||
}
|
||||
}, { capture: false, passive: true })
|
||||
|
||||
SLIDER.oninput = function () {
|
||||
ELEMENTS["turnSlider"].oninput = function () {
|
||||
if (game_instance) {
|
||||
game_instance.updateTurn(parseInt(SLIDER.value));
|
||||
game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value));
|
||||
}
|
||||
}
|
||||
|
||||
FILESELECTOR.onchange = function () {
|
||||
const file = FILESELECTOR.files[0];
|
||||
if (!file) { return; }
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function () {
|
||||
set_instance(<string>reader.result);
|
||||
}
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
SPEED.onchange = function () {
|
||||
ms_per_frame = parseInt(SPEED.value);
|
||||
ELEMENTS["speed"].onchange = function () {
|
||||
ms_per_frame = parseInt(ELEMENTS["speed"].value);
|
||||
}
|
||||
|
||||
function step(time: number) {
|
||||
|
@ -433,6 +434,5 @@ function step(time: number) {
|
|||
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
// set_loading(false);
|
||||
|
||||
requestAnimationFrame(step);
|
56
frontend/www/src/voronoi/voronoi-core.d.ts
vendored
Normal file
56
frontend/www/src/voronoi/voronoi-core.d.ts
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
|
||||
declare namespace Voronoi {
|
||||
class Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
class Site {
|
||||
x: number;
|
||||
y: number;
|
||||
voronoiId: number;
|
||||
}
|
||||
|
||||
class Cell {
|
||||
site: Site;
|
||||
halfedges: HalfEdge[];
|
||||
closeMe: boolean;
|
||||
}
|
||||
|
||||
class Edge {
|
||||
lSite: Site;
|
||||
rSite: Site;
|
||||
vb: Point;
|
||||
va: Point;
|
||||
}
|
||||
|
||||
class HalfEdge {
|
||||
site: Site;
|
||||
edge: Edge;
|
||||
angle: number;
|
||||
getStartpoint(): Point;
|
||||
getEndpoint(): Point;
|
||||
}
|
||||
|
||||
class BBox {
|
||||
xl: number;
|
||||
xr: number;
|
||||
yt: number;
|
||||
yb: number;
|
||||
}
|
||||
|
||||
class VoronoiDiagram {
|
||||
site: any;
|
||||
cells: Cell[];
|
||||
edges: Edge[];
|
||||
vertices: Point[];
|
||||
execTime: number;
|
||||
}
|
||||
}
|
||||
|
||||
declare class Voronoi {
|
||||
constructor();
|
||||
compute(sites: Voronoi.Point[], bbox: Voronoi.BBox): Voronoi.VoronoiDiagram;
|
||||
}
|
||||
|
||||
export = Voronoi;
|
1724
frontend/www/src/voronoi/voronoi-core.js
Normal file
1724
frontend/www/src/voronoi/voronoi-core.js
Normal file
File diff suppressed because it is too large
Load diff
163
frontend/www/src/voronoi/voronoi.ts
Normal file
163
frontend/www/src/voronoi/voronoi.ts
Normal file
|
@ -0,0 +1,163 @@
|
|||
import { Shader } from "../webgl/shader";
|
||||
import { BBox, Point, VoronoiDiagram } from "./voronoi-core";
|
||||
import Voronoi = require("./voronoi-core");
|
||||
import { DefaultRenderable } from "../webgl/renderer";
|
||||
import { IndexBuffer, VertexBuffer } from "../webgl/buffer";
|
||||
import { VertexBufferLayout, VertexArray } from "../webgl/vertexBufferLayout";
|
||||
|
||||
function arcctg(x: number): number { return Math.PI / 2 - Math.atan(x); }
|
||||
|
||||
function to_key(p: Point): string {
|
||||
return [p.x, p.y] + "";
|
||||
}
|
||||
|
||||
function round_point(center: Point, point: Point, amount_fn = (b: number) => 0.7): Point {
|
||||
const d = dist(center, point, true);
|
||||
const x = center.x + amount_fn(d) * (point.x - center.x);
|
||||
const y = center.y + amount_fn(d) * (point.y - center.y);
|
||||
return { 'x': x, 'y': y };
|
||||
}
|
||||
|
||||
function median_point(c: Point, p: Point, n: Point, d = 0.1): number[] {
|
||||
const dd = 1.0 - 2 * d;
|
||||
return [
|
||||
dd * c.x + d * p.x + d * n.x,
|
||||
dd * c.y + d * p.y + d * n.y,
|
||||
]
|
||||
}
|
||||
|
||||
function build_point_map(es: Voronoi.HalfEdge[]): (point: Point) => Point {
|
||||
const mean = es.map(e => dist(e.getStartpoint(), e.getEndpoint())).reduce((a, b) => a + b, 0) / es.length;
|
||||
const map = {};
|
||||
|
||||
for (let edge of es) {
|
||||
const start = edge.getStartpoint();
|
||||
const end = edge.getEndpoint();
|
||||
|
||||
if (dist(start, end) < 0.03 * mean) { // These points have to be merged
|
||||
const middle = { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 };
|
||||
map[to_key(start)] = middle;
|
||||
map[to_key(end)] = middle;
|
||||
}
|
||||
}
|
||||
|
||||
return (p) => map[to_key(p)] || p;
|
||||
}
|
||||
|
||||
function get_round_fn(dist_mean: number, amount = 0.7): (d: number) => number {
|
||||
return (d) => arcctg((d - dist_mean) / dist_mean) / Math.PI + 0.6;
|
||||
}
|
||||
|
||||
function dist(a: Point, b: Point, norm = false): number {
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
if (norm) return Math.sqrt(dx * dx + dy * dy);
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
export class VoronoiBuilder {
|
||||
inner: DefaultRenderable;
|
||||
|
||||
vor: Voronoi;
|
||||
planets: Point[];
|
||||
|
||||
|
||||
constructor(gl: WebGLRenderingContext, shader: Shader, planets: Point[], bbox: BBox) {
|
||||
this.vor = new Voronoi();
|
||||
this.planets = planets;
|
||||
|
||||
const ib = new IndexBuffer(gl, []);
|
||||
const vb = new VertexBuffer(gl, []);
|
||||
|
||||
const layout = new VertexBufferLayout();
|
||||
layout.push(gl.FLOAT, 2, 4, "a_pos");
|
||||
layout.push(gl.FLOAT, 2, 4, "a_center");
|
||||
layout.push(gl.FLOAT, 1, 4, "a_own");
|
||||
layout.push(gl.FLOAT, 1, 4, "a_intensity");
|
||||
|
||||
const vao = new VertexArray();
|
||||
vao.addBuffer(vb, layout);
|
||||
|
||||
this.inner = new DefaultRenderable(ib, vao, shader, [], {});
|
||||
|
||||
this.resize(gl, bbox);
|
||||
}
|
||||
|
||||
getRenderable(): DefaultRenderable {
|
||||
return this.inner;
|
||||
}
|
||||
|
||||
resize(gl: WebGLRenderingContext, bbox: BBox) {
|
||||
const start = new Date().getTime();
|
||||
|
||||
// This voronoi sorts the planets, then owners don't align anymore
|
||||
const own_map = {};
|
||||
this.planets.forEach((p, i) => own_map[to_key(p)] = i);
|
||||
|
||||
const vor = this.vor.compute(this.planets, bbox);
|
||||
|
||||
const attrs = [];
|
||||
const ids = [];
|
||||
|
||||
let vertCount = 0;
|
||||
|
||||
for (let i = 0; i < vor.cells.length; i++) {
|
||||
const cell = vor.cells[i];
|
||||
const planetId = own_map[to_key(cell.site)];
|
||||
const point_map = build_point_map(cell.halfedges);
|
||||
|
||||
const centerId = vertCount++;
|
||||
|
||||
attrs.push(cell.site.x, cell.site.y);
|
||||
attrs.push(cell.site.x, cell.site.y);
|
||||
attrs.push(planetId);
|
||||
attrs.push(1);
|
||||
|
||||
const dist_mean = cell.halfedges.map(e => {
|
||||
const start = e.getStartpoint();
|
||||
const end = e.getEndpoint();
|
||||
return dist(cell.site, start, true) + dist(cell.site, { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 }, true)
|
||||
}).reduce((a, b) => a + b, 0) / cell.halfedges.length / 2;
|
||||
const round_fn = get_round_fn(dist_mean);
|
||||
|
||||
for (let edge of cell.halfedges) {
|
||||
let start = point_map(edge.getStartpoint());
|
||||
let end = point_map(edge.getEndpoint());
|
||||
let center = { 'x': (start.x + end.x) / 2, 'y': (start.y + end.y) / 2 };
|
||||
|
||||
if (to_key(start) == to_key(end)) continue;
|
||||
|
||||
start = round_point(cell.site, start, round_fn);
|
||||
center = round_point(cell.site, center, round_fn);
|
||||
end = round_point(cell.site, end, round_fn);
|
||||
|
||||
ids.push(centerId);
|
||||
ids.push(vertCount++);
|
||||
attrs.push(start.x, start.y);
|
||||
attrs.push(cell.site.x, cell.site.y);
|
||||
attrs.push(planetId);
|
||||
attrs.push(0);
|
||||
|
||||
ids.push(vertCount++);
|
||||
attrs.push(center.x, center.y);
|
||||
attrs.push(cell.site.x, cell.site.y);
|
||||
attrs.push(planetId);
|
||||
attrs.push(0);
|
||||
|
||||
ids.push(centerId);
|
||||
ids.push(vertCount - 1);
|
||||
|
||||
ids.push(vertCount++);
|
||||
attrs.push(end.x, end.y);
|
||||
attrs.push(cell.site.x, cell.site.y);
|
||||
attrs.push(planetId);
|
||||
attrs.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
this.inner.updateIndexBuffer(gl, ids);
|
||||
this.inner.updateVAOBuffer(gl, 0, attrs);
|
||||
|
||||
console.log(`Vor things took ${new Date().getTime() - start} ms!`)
|
||||
}
|
||||
}
|
|
@ -5,12 +5,26 @@ import { VertexArray } from './vertexBufferLayout';
|
|||
import { Texture } from './texture';
|
||||
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 {
|
||||
getUniforms() : Dictionary<Uniform>;
|
||||
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;
|
||||
va: VertexArray;
|
||||
shader: Shader;
|
||||
|
@ -35,6 +49,14 @@ export class RenderShit implements Renderable {
|
|||
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 {
|
||||
|
||||
const indexBuffer = this.ibo;
|
||||
|
@ -75,38 +97,46 @@ export class RenderShit implements Renderable {
|
|||
}
|
||||
|
||||
export class Renderer {
|
||||
renderables: [Renderable, boolean][];
|
||||
renderables: { [id: number] : [Renderable, boolean][]; };
|
||||
renderable_layers: number[];
|
||||
|
||||
constructor() {
|
||||
this.renderables = [];
|
||||
this.renderables = {};
|
||||
this.renderable_layers = [];
|
||||
}
|
||||
|
||||
updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void) {
|
||||
f(this.renderables[i][0].getUniforms());
|
||||
updateUniform(i: number, f: (uniforms: Dictionary<Uniform>) => void, layer=0, ) {
|
||||
f(this.renderables[layer][i][0].getUniforms());
|
||||
}
|
||||
|
||||
disableRenderShift(i: number) {
|
||||
this.renderables[i][1] = false;
|
||||
disableRenderable(i: number, layer=0) {
|
||||
this.renderables[layer][i][1] = false;
|
||||
}
|
||||
|
||||
enableRendershit(i: number) {
|
||||
this.renderables[i][1] = true;
|
||||
enableRenderable(i: number, layer=0) {
|
||||
this.renderables[layer][i][1] = true;
|
||||
}
|
||||
|
||||
addRenderable(item: Renderable): number {
|
||||
this.renderables.push([item, true]);
|
||||
return this.renderables.length - 1;
|
||||
addRenderable(item: Renderable, layer=0): number {
|
||||
if(!this.renderables[layer]) {
|
||||
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(
|
||||
new RenderShit(
|
||||
new DefaultRenderable(
|
||||
indexBuffer,
|
||||
vertexArray,
|
||||
shader,
|
||||
texture || [],
|
||||
uniforms || {},
|
||||
)
|
||||
), layer
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -117,10 +147,11 @@ export class Renderer {
|
|||
|
||||
const maxTextures = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
|
||||
|
||||
for (let [r, e] of this.renderables) {
|
||||
if (!e) continue;
|
||||
r.render(gl);
|
||||
for (let layer of this.renderable_layers) {
|
||||
for (let [r, e] of this.renderables[layer]) {
|
||||
if (!e) continue;
|
||||
r.render(gl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Texture } from "./texture";
|
||||
import { Dictionary } from "./util";
|
||||
import { Renderable, RenderShit } from "./renderer";
|
||||
import { Renderable, DefaultRenderable } from "./renderer";
|
||||
import { Uniform, Shader, UniformMatrix3fv } from "./shader";
|
||||
import { IndexBuffer, VertexBuffer } from "./buffer";
|
||||
import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout";
|
||||
|
@ -43,10 +43,8 @@ export class LabelFactory {
|
|||
}
|
||||
}
|
||||
|
||||
export class Label implements Renderable {
|
||||
inner: Renderable;
|
||||
ib: IndexBuffer;
|
||||
vb: VertexBuffer;
|
||||
export class Label {
|
||||
inner: DefaultRenderable;
|
||||
|
||||
font: FontInfo;
|
||||
|
||||
|
@ -54,30 +52,31 @@ export class Label implements Renderable {
|
|||
this.font = font;
|
||||
|
||||
const uniforms = transform ? { "u_trans": transform, "u_trans_next": transform, } : {};
|
||||
this.ib = new IndexBuffer(gl, []);
|
||||
this.vb = new VertexBuffer(gl, []);
|
||||
const ib = new IndexBuffer(gl, []);
|
||||
const vb_pos = new VertexBuffer(gl, []);
|
||||
const vb_tex = new VertexBuffer(gl, []);
|
||||
|
||||
const layout = new VertexBufferLayout();
|
||||
layout.push(gl.FLOAT, 2, 4, "a_position");
|
||||
layout.push(gl.FLOAT, 2, 4, "a_texCoord");
|
||||
const layout_pos = new VertexBufferLayout();
|
||||
layout_pos.push(gl.FLOAT, 2, 4, "a_position");
|
||||
|
||||
const layout_tex = new VertexBufferLayout();
|
||||
layout_tex.push(gl.FLOAT, 2, 4, "a_texCoord");
|
||||
|
||||
const vao = new VertexArray();
|
||||
vao.addBuffer(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> {
|
||||
return this.inner.getUniforms();
|
||||
}
|
||||
|
||||
render(gl: WebGLRenderingContext): void {
|
||||
return this.inner.render(gl);
|
||||
getRenderable(): DefaultRenderable {
|
||||
return this.inner;
|
||||
}
|
||||
|
||||
setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) {
|
||||
const idxs = [];
|
||||
const verts = [];
|
||||
const verts_pos = [];
|
||||
const verts_tex = [];
|
||||
|
||||
const letterHeight = this.font.letterHeight / this.font.textureHeight;
|
||||
let xPos = 0;
|
||||
|
@ -112,10 +111,16 @@ export class Label implements Renderable {
|
|||
const letterWidth = info.width / this.font.textureWidth;
|
||||
const x0 = info.x / this.font.textureWidth;
|
||||
const y0 = info.y / this.font.textureHeight;
|
||||
verts.push(xPos, yStart, x0, y0);
|
||||
verts.push(xPos + dx, yStart, x0 + letterWidth, y0);
|
||||
verts.push(xPos, yStart-1, x0, y0 + letterHeight);
|
||||
verts.push(xPos + dx, yStart-1, x0 + letterWidth, y0 + letterHeight);
|
||||
verts_pos.push(xPos, yStart);
|
||||
verts_pos.push(xPos + dx, yStart);
|
||||
verts_pos.push(xPos, yStart-1);
|
||||
verts_pos.push(xPos + dx, yStart-1);
|
||||
|
||||
verts_tex.push(x0, y0);
|
||||
verts_tex.push(x0 + letterWidth, y0);
|
||||
verts_tex.push(x0, y0 + letterHeight);
|
||||
verts_tex.push(x0 + letterWidth, y0 + letterHeight);
|
||||
|
||||
xPos += dx;
|
||||
|
||||
idxs.push(j+0, j+1, j+2, j+1, j+2, j+3);
|
||||
|
@ -126,8 +131,9 @@ export class Label implements Renderable {
|
|||
}
|
||||
}
|
||||
|
||||
this.ib.updateData(gl, idxs);
|
||||
this.vb.updateData(gl, verts);
|
||||
this.inner.updateIndexBuffer(gl, idxs);
|
||||
this.inner.updateVAOBuffer(gl, 0, verts_pos);
|
||||
this.inner.updateVAOBuffer(gl, 1, verts_tex);
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,11 @@ export class FPSCounter {
|
|||
last: number;
|
||||
count: number;
|
||||
_delta: number;
|
||||
_prev: number
|
||||
_prev: number;
|
||||
|
||||
_frame_start: number;
|
||||
_total_frametime: number;
|
||||
|
||||
constructor() {
|
||||
this.last = 0;
|
||||
this.count = 0;
|
||||
|
@ -45,17 +49,23 @@ export class FPSCounter {
|
|||
}
|
||||
|
||||
frame(now: number) {
|
||||
this._frame_start = performance.now();
|
||||
this.count += 1;
|
||||
this._delta = now - this._prev;
|
||||
this._prev = now;
|
||||
|
||||
if (now - this.last > 1000) {
|
||||
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._total_frametime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
frame_end() {
|
||||
this._total_frametime += (performance.now() - this._frame_start);
|
||||
}
|
||||
|
||||
delta(now: number): number {
|
||||
return this._delta;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Buffer, VertexBuffer } from './buffer';
|
||||
import { VertexBuffer } from './buffer';
|
||||
import { Shader } from './shader';
|
||||
|
||||
export class VertexBufferElement {
|
||||
|
@ -59,7 +59,7 @@ export class VertexBufferLayout {
|
|||
// 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[];
|
||||
buffers: VertexBuffer[];
|
||||
layouts: VertexBufferLayout[];
|
||||
|
||||
constructor() {
|
||||
|
@ -72,6 +72,10 @@ export class VertexArray {
|
|||
this.layouts.push(layout);
|
||||
}
|
||||
|
||||
updateBuffer(gl: WebGLRenderingContext, index: number, data: number[]) {
|
||||
this.buffers[index].updateData(gl, data);
|
||||
}
|
||||
|
||||
/// Bind buffers providing program data
|
||||
bind(gl: WebGLRenderingContext, shader: Shader) {
|
||||
shader.bind(gl);
|
||||
|
@ -94,7 +98,7 @@ export class VertexArray {
|
|||
element.normalized, layout.stride, offset
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
offset += element.amount * element.type_size;
|
||||
}
|
||||
}
|
||||
|
@ -109,4 +113,3 @@ export class VertexArray {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
width: 100px;
|
||||
}
|
||||
|
||||
#c {
|
||||
#canvas {
|
||||
position: relative;
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
|
|
|
@ -2,29 +2,17 @@
|
|||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform vec3 u_planets[$PLANETS];
|
||||
uniform vec3 u_planet_colours[$PLANETS * 2];
|
||||
#define PI 3.141592
|
||||
|
||||
uniform float u_step_interval;
|
||||
uniform float u_time;
|
||||
uniform bool u_vor;
|
||||
|
||||
varying float v_intensity;
|
||||
varying float v_dist;
|
||||
varying vec3 v_color;
|
||||
varying vec2 v_pos;
|
||||
|
||||
void main() {
|
||||
vec3 color = vec3(0.2);
|
||||
|
||||
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);
|
||||
gl_FragColor = vec4(v_color, (1.0 - pow(1.0 - v_intensity, 1.23)) * 0.7);
|
||||
}
|
||||
|
|
|
@ -3,24 +3,41 @@ precision mediump float;
|
|||
#endif
|
||||
|
||||
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 vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
|
||||
varying float v_intensity;
|
||||
varying float v_dist;
|
||||
varying vec2 v_pos;
|
||||
varying vec3 v_color;
|
||||
|
||||
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;
|
||||
uv = 1.0 - uv;
|
||||
// uv *= -1.0;
|
||||
int own = int(a_own);
|
||||
|
||||
vec2 uv = a_pos;
|
||||
|
||||
// Viewbox's center is top left, a_position's is in the center to the screen
|
||||
// So translate and scale the viewbox**
|
||||
uv *= u_viewbox.zw;
|
||||
uv -= u_viewbox.xy + u_viewbox.zw;
|
||||
|
||||
uv -= u_viewbox.xy + (u_viewbox.zw * 0.5);
|
||||
uv /= u_viewbox.zw * 0.5;
|
||||
v_pos = uv.xy;
|
||||
|
||||
gl_Position = vec4(a_pos, 0.0, 1.0);
|
||||
// v_pos = (uv.xy + 1.0) * 0.5;
|
||||
|
||||
if (own < 0) {
|
||||
v_color = vec3(0., 0., 0.);
|
||||
} else {
|
||||
v_color = mix(u_planet_colours[own * 2], u_planet_colours[own * 2 + 1], u_time);
|
||||
}
|
||||
|
||||
gl_Position = vec4(uv.xy, 0.0, 1.0);
|
||||
}
|
||||
|
|
|
@ -1,496 +0,0 @@
|
|||
import { Heap } from 'ts-heap'
|
||||
import { HalfEdge, Face, Vertex } from './dcel';
|
||||
|
||||
interface WithPriority {
|
||||
get_priority(): number;
|
||||
}
|
||||
|
||||
export class Point {
|
||||
x: number;
|
||||
y: number;
|
||||
face?: Face;
|
||||
|
||||
constructor(x: number, y: number, face? :Face) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.face = face;
|
||||
}
|
||||
|
||||
equals(other: Point): boolean {
|
||||
return Math.abs(this.x - other.x) + Math.abs(this.y - other.y) < 0.00001;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `{x: ${this.x}, y: ${this.y}}`;
|
||||
}
|
||||
}
|
||||
|
||||
class CircleEvent implements WithPriority {
|
||||
y: number;
|
||||
alive: boolean = true;
|
||||
center: Vertex;
|
||||
leaf: Leaf;
|
||||
|
||||
from: Point[];
|
||||
|
||||
static
|
||||
from_sites(s1: Point, s2: Point, s3: Point, leaf: Leaf): CircleEvent {
|
||||
const a = s1.x * (s2.y - s3.y) - s1.y*(s2.x - s3.x) + s2.x*s3.y - s3.x * s2.y;
|
||||
const b = (s1.x ** 2 + s1.y ** 2) * (s3.y - s2.y) + (s2.x ** 2 + s2.y ** 2)*(s1.y - s3.y) + (s3.x ** 2 + s3.y ** 2) * (s2.y - s1.y);
|
||||
const c = (s1.x ** 2 + s1.y ** 2) * (s2.x - s3.x) + (s2.x ** 2 + s2.y ** 2)*(s3.x - s1.x) + (s3.x ** 2 + s3.y ** 2) * (s1.x - s2.x);
|
||||
const d = (s1.x ** 2 + s1.y ** 2) * (s3.x*s2.y - s2.x*s3.y) + (s2.x ** 2 + s2.y ** 2)*(s1.x*s3.y - s3.x*s1.y) + (s3.x ** 2 + s3.y ** 2) * (s2.x*s1.y - s1.x*s2.y);
|
||||
|
||||
const center = new Vertex(-b / (2. * a), -c / (2. * a));
|
||||
const r = Math.sqrt((b ** 2 + c ** 2 - 4. * a * d) / (4. * a ** 2));
|
||||
const y = center.coords[1] - r;
|
||||
|
||||
const out = new CircleEvent();
|
||||
out.y = y;
|
||||
out.center = center;
|
||||
out.leaf = leaf;
|
||||
|
||||
out.from = [s1, s2, s3];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
get_priority(): number {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
print() {
|
||||
console.log(`Circle event at ${this.y} ${JSON.stringify(this.center)}, ${this.leaf.point.toString()} from ${JSON.stringify(this.from.map((e) => e.toString()))}`);
|
||||
}
|
||||
}
|
||||
|
||||
class SiteEvent implements WithPriority{
|
||||
face: Face;
|
||||
point: Point;
|
||||
|
||||
constructor(point: Point) {
|
||||
this.face = new Face(point);
|
||||
this.point = point;
|
||||
this.point.face = this.face;
|
||||
}
|
||||
|
||||
get_priority(): number {
|
||||
return this.point.y;
|
||||
}
|
||||
|
||||
print() {
|
||||
console.log(`Site event ${this.point.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
function calc_x(left: Point, right: Point, y: number): number {
|
||||
const [a1, b1, c1] = from_focus_vertex(left, y);
|
||||
const [a2, b2, c2] = from_focus_vertex(right, y);
|
||||
|
||||
if (Math.abs(a1 - a2) < 0.0001) {
|
||||
return (left.x + right.x) / 2.;
|
||||
}
|
||||
|
||||
const da = a1 - a2;
|
||||
const db = b1 - b2;
|
||||
const dc = c1 - c2;
|
||||
|
||||
const d = db * db - 4. * da * dc;
|
||||
|
||||
if (d <= 0.) {
|
||||
throw new Error(`D is less then 0 ${d}`);
|
||||
}
|
||||
|
||||
const dd = Math.sqrt(d);
|
||||
|
||||
const x = (-db + dd) / (2. * da);
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
function from_focus_vertex(focus: Point, y: number): number[] {
|
||||
const a = (focus.y - y) / 2;
|
||||
const h = focus.x;
|
||||
const k = focus.y - a;
|
||||
|
||||
return [1 / (4. * a), -h / (2 * a), (h ** 2 / (4 * a)) + k]
|
||||
}
|
||||
|
||||
function cmp_event(e1: WithPriority, e2: WithPriority) {
|
||||
return e2.get_priority() - e1.get_priority();
|
||||
}
|
||||
|
||||
type Queue = Heap<WithPriority>;
|
||||
|
||||
type Node = Leaf | Breakpoint;
|
||||
|
||||
type Parent = Breakpoint | State;
|
||||
|
||||
class Leaf {
|
||||
point: Point;
|
||||
event: CircleEvent | undefined;
|
||||
|
||||
left: Leaf | undefined;
|
||||
right: Leaf | undefined;
|
||||
parent: Parent;
|
||||
|
||||
half_edge: HalfEdge;
|
||||
|
||||
constructor(point: Point, parent: Parent) {
|
||||
this.point = point;
|
||||
this.parent = parent;
|
||||
this.half_edge = new HalfEdge(undefined, undefined);
|
||||
}
|
||||
|
||||
false_alarm() {
|
||||
if(this.event) {
|
||||
console.log(`False alarm ${JSON.stringify(this.event.center)} ${this.event.y}`);
|
||||
this.event.alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
update_left(leaf: Leaf) {
|
||||
if (this.left) {
|
||||
this.left.right = leaf;
|
||||
}
|
||||
leaf.left = this.left;
|
||||
}
|
||||
|
||||
update_right(leaf: Leaf) {
|
||||
if (this.right) {
|
||||
this.right.left = leaf;
|
||||
}
|
||||
leaf.right = this.right;
|
||||
}
|
||||
|
||||
split(point: Point, events: Queue) {
|
||||
this.false_alarm();
|
||||
|
||||
if (this.point.y == point.y) {
|
||||
const middle = new Leaf(point, undefined);
|
||||
const parent = this.parent;
|
||||
|
||||
if (this.point.x > point.x) {
|
||||
const br = new Breakpoint([point, middle], [this.point, this], this.parent);
|
||||
|
||||
if (this.left) this.left.right = middle;
|
||||
this.left = middle;
|
||||
middle.right = this;
|
||||
|
||||
if (parent instanceof Breakpoint) {
|
||||
parent.set_me(this, br);
|
||||
} else {
|
||||
parent.root = br;
|
||||
}
|
||||
|
||||
const maybe_left = middle.check_circles(point.y, events);
|
||||
|
||||
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_left.print();
|
||||
middle.event = maybe_left;
|
||||
events.add(maybe_left);
|
||||
}
|
||||
|
||||
const maybe_right = this.check_circles(point.y, events);
|
||||
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_right.print();
|
||||
this.event = maybe_right;
|
||||
events.add(maybe_right);
|
||||
}
|
||||
|
||||
} else {
|
||||
const br = new Breakpoint([this.point, this], [point, middle], this.parent);
|
||||
|
||||
if (this.right) this.right.left = middle;
|
||||
this.right = middle;
|
||||
middle.left = this;
|
||||
|
||||
if (parent instanceof Breakpoint) {
|
||||
parent.set_me(this, br);
|
||||
} else {
|
||||
parent.root = br;
|
||||
}
|
||||
|
||||
const maybe_left = this.check_circles(point.y, events);
|
||||
|
||||
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_left.print();
|
||||
this.event = maybe_left;
|
||||
events.add(maybe_left);
|
||||
}
|
||||
|
||||
const maybe_right = middle.check_circles(point.y, events);
|
||||
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_right.print();
|
||||
middle.event = maybe_right;
|
||||
events.add(maybe_right);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const left = new Leaf(this.point, undefined);
|
||||
left.left = this.left;
|
||||
if (this.left) this.left.right = left;
|
||||
|
||||
const right = new Leaf(this.point, undefined);
|
||||
right.right = this.right;
|
||||
if (this.right) this.right.left = right;
|
||||
|
||||
const middle = new Leaf(point, undefined);
|
||||
middle.left = left;
|
||||
middle.right = right;
|
||||
|
||||
right.left = middle;
|
||||
left.right = middle;
|
||||
|
||||
const br1 = new Breakpoint([this.point, left], [point, middle], undefined);
|
||||
const br2 = new Breakpoint([point, br1], [this.point, right], this.parent);
|
||||
br1.parent = br2;
|
||||
|
||||
if (this.parent instanceof Breakpoint) {
|
||||
this.parent.set_me(this, br2);
|
||||
} else {
|
||||
this.parent.root = br2;
|
||||
}
|
||||
|
||||
const maybe_left = left.check_circles(point.y, events);
|
||||
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_left.print();
|
||||
left.event = maybe_left;
|
||||
events.add(maybe_left);
|
||||
}
|
||||
|
||||
const maybe_right = right.check_circles(point.y, events);
|
||||
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
|
||||
console.log(`Adding circle`);
|
||||
maybe_right.print();
|
||||
right.event = maybe_right;
|
||||
events.add(maybe_right);
|
||||
}
|
||||
}
|
||||
|
||||
check_circles(y: number, events: Queue): CircleEvent | undefined {
|
||||
const left = this.left;
|
||||
const right = this.right;
|
||||
|
||||
if (left && right) {
|
||||
const circle = CircleEvent.from_sites(left.point, this.point, right.point, this);
|
||||
console.log(`${circle.y} < ${y}`);
|
||||
if (circle.y < y ) {
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(vertex: Vertex) {
|
||||
if (this.parent instanceof Breakpoint) {
|
||||
this.parent.remove_me(this.point, vertex);
|
||||
} else {
|
||||
console.error("Shouldnt be here");
|
||||
}
|
||||
}
|
||||
|
||||
print(indent: string) {
|
||||
console.log(`${indent}Leaf from ${this.point.toString()} vertex: ${this.half_edge.to_string()}`);
|
||||
}
|
||||
}
|
||||
|
||||
class Breakpoint {
|
||||
left: [Point, Node];
|
||||
right: [Point, Node];
|
||||
parent: Parent;
|
||||
half_edge: HalfEdge;
|
||||
|
||||
constructor(left: [Point, Node], right: [Point, Node], parent: Parent) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.left[1].parent = this;
|
||||
this.right[1].parent = this;
|
||||
this.parent = parent;
|
||||
|
||||
this.half_edge = new HalfEdge(undefined, left[0].face, right[0].face);
|
||||
}
|
||||
|
||||
remove_me(point: Point, vertex: Vertex) {
|
||||
const edge = this.half_edge.insert(vertex);
|
||||
const other = this.get_other(point);
|
||||
if (this.parent instanceof Breakpoint) {
|
||||
this.parent.set_me(this, other);
|
||||
this.parent.set_edge(edge);
|
||||
} else {
|
||||
this.parent.root = other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
set_edge(edge: HalfEdge) {
|
||||
this.left[1].half_edge = edge;
|
||||
// this.right[1].half_edge = edge.split(edge.origin);
|
||||
}
|
||||
|
||||
set_me(old_me: Node, new_me: Node) {
|
||||
if (this.left[1] == old_me) {
|
||||
this.left[1] = new_me;
|
||||
} else {
|
||||
this.right[1] = new_me;
|
||||
}
|
||||
}
|
||||
|
||||
get(point: Point): Leaf {
|
||||
const { x, y } = point;
|
||||
const test_x = calc_x(this.left[0], this.right[0], y);
|
||||
|
||||
if (test_x >= x) {
|
||||
if (this.left[1] instanceof Leaf) {
|
||||
return this.left[1];
|
||||
} else {
|
||||
return this.left[1].get(point);
|
||||
}
|
||||
} else {
|
||||
if (this.right[1] instanceof Leaf) {
|
||||
return this.right[1];
|
||||
} else {
|
||||
return this.right[1].get(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get_other(point: Point): Node {
|
||||
if (this.left[0].equals(point)) {
|
||||
return this.right[1];
|
||||
} else {
|
||||
return this.left[1];
|
||||
}
|
||||
}
|
||||
|
||||
print(indent: string) {
|
||||
console.log(`${indent}vertex: ${this.half_edge.to_string()}`);
|
||||
console.log(`${indent}left`);
|
||||
this.left[1].print(indent + ' ');
|
||||
console.log(`${indent}right`);
|
||||
this.right[1].print(indent + ' ');
|
||||
}
|
||||
}
|
||||
|
||||
function get_from_node(root: Node, point: Point): Leaf {
|
||||
if (root instanceof Leaf) {
|
||||
return root;
|
||||
} else {
|
||||
return root.get(point);
|
||||
}
|
||||
}
|
||||
|
||||
class State {
|
||||
root: Node | undefined;
|
||||
|
||||
print() {
|
||||
if (this.root) {
|
||||
this.root.print('');
|
||||
} else {
|
||||
console.log("No root no tree");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function voronoi(points: Point[]): Point[] {
|
||||
const out = [];
|
||||
const state = new State;
|
||||
const queue = new Heap<WithPriority>(cmp_event);
|
||||
|
||||
|
||||
for (let point of points) {
|
||||
queue.add(new SiteEvent(point));
|
||||
}
|
||||
|
||||
let event;
|
||||
while (event = queue.pop()){
|
||||
console.log('---------------------------');
|
||||
event.print();
|
||||
|
||||
if (event instanceof SiteEvent) {
|
||||
handle_site_event(event, queue, state, out);
|
||||
} else {
|
||||
if (!event.alive) {
|
||||
console.log("Dead");
|
||||
continue;
|
||||
}
|
||||
handle_circle_event(event, queue, state, out);
|
||||
}
|
||||
state.print();
|
||||
console.log(queue);
|
||||
print_leaves(get_from_node(state.root, new Point(0, 0)));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function handle_site_event(event: SiteEvent, queue: Queue, state: State, out: Point[]) {
|
||||
if (state.root) {
|
||||
const leaf = get_from_node(state.root, event.point);
|
||||
leaf.split(event.point, queue);
|
||||
|
||||
} else {
|
||||
state.root = new Leaf(event.point, state);
|
||||
}
|
||||
}
|
||||
|
||||
function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out: [number, number][]) {
|
||||
if (!event.alive) return;
|
||||
|
||||
event.leaf.delete(event.center);
|
||||
const right = event.leaf.right;
|
||||
const left = event.leaf.left;
|
||||
|
||||
if (right) {
|
||||
right.false_alarm();
|
||||
// if (right.right) right.right.false_alarm();
|
||||
|
||||
right.left = left;
|
||||
const maybe_right = right.check_circles(event.y, queue);
|
||||
if (maybe_right){
|
||||
console.log(`Adding circle event`);
|
||||
maybe_right.print();
|
||||
right.event = maybe_right;
|
||||
queue.add(maybe_right);
|
||||
}
|
||||
}
|
||||
|
||||
if (left) {
|
||||
left.false_alarm();
|
||||
// if (left.left) left.left.false_alarm();
|
||||
left.right = right;
|
||||
const maybe_left = left.check_circles(event.y, queue);
|
||||
|
||||
if (maybe_left){
|
||||
console.log(`Adding circle event`);
|
||||
maybe_left.print();
|
||||
left.event = maybe_left;
|
||||
queue.add(maybe_left);
|
||||
}
|
||||
}
|
||||
|
||||
out.push(event.center.coords);
|
||||
}
|
||||
|
||||
function print_leaves(start: Leaf) {
|
||||
let current = start;
|
||||
|
||||
while (current.left) {
|
||||
current = current.left;
|
||||
}
|
||||
|
||||
const points = [current.point];
|
||||
|
||||
while (current.right) {
|
||||
current = current.right;
|
||||
points.push(current.point);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(points.map((p) => p.toString())));
|
||||
}
|
|
@ -1,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]);
|
||||
}
|
||||
}
|
||||
}
|
20
frontend/www/webgl/webgl-utils.d.ts
vendored
20
frontend/www/webgl/webgl-utils.d.ts
vendored
|
@ -1,20 +0,0 @@
|
|||
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
|
||||
// Project: [~THE PROJECT NAME~]
|
||||
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
|
||||
|
||||
/*~ This is the module template file. You should rename it to index.d.ts
|
||||
*~ and place it in a folder with the same name as the module.
|
||||
*~ For example, if you were writing a file for "super-greeter", this
|
||||
*~ file should be 'super-greeter/index.d.ts'
|
||||
*/
|
||||
|
||||
// /*~ If this module is a UMD module that exposes a global variable 'myLib' when
|
||||
// *~ loaded outside a module loader environment, declare that global here.
|
||||
// *~ Otherwise, delete this declaration.
|
||||
// */
|
||||
// export as namespace myLib;
|
||||
|
||||
/*~ If this module has methods, declare them as functions like so.
|
||||
*/
|
||||
export function createProgramFromScripts(gl: WebGLRenderingContext, shaderScriptIds: string[], opt_attribs?: string[], opt_locations?: number[], opt_errorCallback?: any): any;
|
||||
export function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement, multiplier?: number): boolean;
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue