diff options
author | Ilion Beyst <ilion.beyst@gmail.com> | 2021-12-29 21:24:57 +0100 |
---|---|---|
committer | Ilion Beyst <ilion.beyst@gmail.com> | 2021-12-29 21:25:29 +0100 |
commit | 0c6d978442b244ca3f29c1ffdd44b5007ae7ad93 (patch) | |
tree | baae5fa459a49ecd362e548e0649e2f58c669a70 /web/pw-frontend/src/lib/visualizer/index.ts | |
parent | 3eeaab6cec70e7a06a99a1ac2662974f71064bee (diff) | |
download | planetwars.dev-0c6d978442b244ca3f29c1ffdd44b5007ae7ad93.tar.xz planetwars.dev-0c6d978442b244ca3f29c1ffdd44b5007ae7ad93.zip |
separate out visualizer library
Diffstat (limited to 'web/pw-frontend/src/lib/visualizer/index.ts')
-rw-r--r-- | web/pw-frontend/src/lib/visualizer/index.ts | 666 |
1 files changed, 0 insertions, 666 deletions
diff --git a/web/pw-frontend/src/lib/visualizer/index.ts b/web/pw-frontend/src/lib/visualizer/index.ts deleted file mode 100644 index 363a1c5..0000000 --- a/web/pw-frontend/src/lib/visualizer/index.ts +++ /dev/null @@ -1,666 +0,0 @@ -import { Game } from "planetwars-rs"; -// import { memory } from "planetwars-rs/planetwars_rs_bg"; -// const memory = planetwars_bg.memory; -import type { Dictionary } from './webgl/util'; -import type { BBox } from "./voronoi/voronoi-core"; - -import { - Resizer, - resizeCanvasToDisplaySize, - FPSCounter, - url_to_mesh, - Mesh, -} from "./webgl/util"; -import { - Shader, - Uniform4f, - Uniform3fv, - Uniform1f, - Uniform2f, - ShaderFactory, - Uniform3f, - UniformMatrix3fv, - UniformBool, -} from "./webgl/shader"; -import { Renderer } from "./webgl/renderer"; -import { VertexBuffer, IndexBuffer } from "./webgl/buffer"; -import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout"; -import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text"; -import { VoronoiBuilder } from "./voronoi/voronoi"; - -// svg-mesh requires global to exist -(window as any).global = window; - - - -function to_bbox(box: number[]): BBox { - return { - xl: box[0], - xr: box[0] + box[2], - yt: box[1], - yb: box[1] + box[3], - }; -} - -// function f32v(ptr: number, size: number): Float32Array { -// return new Float32Array(memory.buffer, ptr, size); -// } - -// function i32v(ptr: number, size: number): Int32Array { -// return new Int32Array(memory.buffer, ptr, size); -// } - -export function set_game_name(name: string) { - ELEMENTS["name"].innerHTML = name; -} - -export function set_loading(loading: boolean) { - if (loading) { - if (!ELEMENTS["main"].classList.contains("loading")) { - ELEMENTS["main"].classList.add("loading"); - } - } else { - ELEMENTS["main"].classList.remove("loading"); - } -} - -const ELEMENTS: any = {}; -var CANVAS: any; -var RESOLUTION: any; -var GL: any; -var ms_per_frame: any; - -const LAYERS = { - vor: -1, // Background - planet: 1, - planet_label: 2, - ship: 3, - ship_label: 4, -}; - -const COUNTER = new FPSCounter(); - - - -export function init() { - [ - "name", - "turnCounter", - "main", - "turnSlider", - "fileselect", - "speed", - "canvas", - ].forEach((n) => (ELEMENTS[n] = document.getElementById(n))); - - CANVAS = ELEMENTS["canvas"]; - RESOLUTION = [CANVAS.width, CANVAS.height]; - - ms_per_frame = parseInt(ELEMENTS["speed"].value); - - GL = CANVAS.getContext("webgl"); - - GL.clearColor(0, 0, 0, 1); - GL.clear(GL.COLOR_BUFFER_BIT); - - GL.enable(GL.BLEND); - GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); - - window.addEventListener( - "resize", - function () { - resizeCanvasToDisplaySize(CANVAS); - - if (game_instance) { - game_instance.on_resize(); - } - }, - { capture: false, passive: true } - ); - - ELEMENTS["turnSlider"].oninput = function () { - if (game_instance) { - game_instance.updateTurn(parseInt(ELEMENTS["turnSlider"].value)); - } - }; - - ELEMENTS["speed"].onchange = function () { - ms_per_frame = parseInt(ELEMENTS["speed"].value); - }; -} - -export class GameInstance { - resizer: Resizer; - game: Game; - - shader: Shader; - vor_shader: Shader; - image_shader: Shader; - - text_factory: LabelFactory; - planet_labels: Label[]; - ship_labels: Label[]; - - ship_ibo: IndexBuffer; - ship_vao: VertexArray; - // TODO: find a better way - max_num_ships: number; - - renderer: Renderer; - planet_count: number; - - vor_builder: VoronoiBuilder; - - vor_counter = 3; - use_vor = true; - playing = true; - time_stopped_delta = 0; - last_time = 0; - frame = -1; - - turn_count = 0; - - constructor( - game: Game, - meshes: Mesh[], - ship_mesh: Mesh, - shaders: Dictionary<ShaderFactory> - ) { - this.game = game; - const planets = game.get_planets(); - this.planet_count = planets.length; - - this.shader = shaders["normal"].create_shader(GL, { - MAX_CIRCLES: "" + planets.length, - }); - this.image_shader = shaders["image"].create_shader(GL); - this.vor_shader = shaders["vor"].create_shader(GL, { - PLANETS: "" + planets.length, - }); - - this.text_factory = defaultLabelFactory(GL, this.image_shader); - this.planet_labels = []; - this.ship_labels = []; - - this.resizer = new Resizer(CANVAS, [...game.get_viewbox()], true); - this.renderer = new Renderer(); - this.game.update_turn(0); - - // Setup key handling - document.addEventListener("keydown", this.handleKey.bind(this)); - - // List of [(x, y, r)] for all planets - this._create_voronoi(planets); - this._create_planets(planets, meshes); - - // create_shipes - this.ship_ibo = new IndexBuffer(GL, ship_mesh.cells); - const ship_positions = new VertexBuffer(GL, ship_mesh.positions); - const ship_layout = new VertexBufferLayout(); - ship_layout.push(GL.FLOAT, 3, 4, "a_position"); - this.ship_vao = new VertexArray(); - this.ship_vao.addBuffer(ship_positions, ship_layout); - this.max_num_ships = 0; - - // Set slider correctly - this.turn_count = game.turn_count(); - ELEMENTS["turnSlider"].max = this.turn_count - 1 + ""; - } - - push_state(state: string) { - this.game.push_state(state); - - if (this.frame == this.turn_count - 1) { - this.playing = true; - } - - // Set slider correctly - this.turn_count = this.game.turn_count(); - this.updateTurnCounters(); - } - - _create_voronoi(planets: Float32Array) { - const planet_points = []; - for (let i = 0; i < planets.length; i += 3) { - planet_points.push({ x: -planets[i], y: -planets[i + 1] }); - } - - const bbox = to_bbox(this.resizer.get_viewbox()); - - this.vor_builder = new VoronoiBuilder( - GL, - this.vor_shader, - planet_points, - bbox - ); - this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor); - } - - _create_planets(planets: Float32Array, meshes: Mesh[]) { - for (let i = 0; i < this.planet_count; i++) { - { - const transform = new UniformMatrix3fv([ - 1, - 0, - 0, - 0, - 1, - 0, - -planets[i * 3], - -planets[i * 3 + 1], - 1, - ]); - - const indexBuffer = new IndexBuffer( - GL, - meshes[i % meshes.length].cells - ); - const positionBuffer = new VertexBuffer( - GL, - meshes[i % meshes.length].positions - ); - - const layout = new VertexBufferLayout(); - layout.push(GL.FLOAT, 3, 4, "a_position"); - const vao = new VertexArray(); - vao.addBuffer(positionBuffer, layout); - - this.renderer.addToDraw( - indexBuffer, - vao, - this.shader, - { - u_trans: transform, - u_trans_next: transform, - }, - [], - LAYERS.planet - ); - } - - { - const transform = new UniformMatrix3fv([ - 1, - 0, - 0, - 0, - 1, - 0, - -planets[i * 3], - -planets[i * 3 + 1] - 1.2, - 1, - ]); - - const label = this.text_factory.build(GL, transform); - this.planet_labels.push(label); - this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label); - } - } - } - - on_resize() { - this.resizer = new Resizer(CANVAS, [...this.game.get_viewbox()], true); - const bbox = to_bbox(this.resizer.get_viewbox()); - this.vor_builder.resize(GL, bbox); - } - - _update_state() { - this._update_planets(); - this._update_ships(); - } - - _update_planets() { - const colours = this.game.get_planet_colors(); - const planet_ships = this.game.get_planet_ships(); - - this.vor_shader.uniform(GL, "u_planet_colours", new Uniform3fv(colours)); - - for (let i = 0; i < this.planet_count; i++) { - const u = new Uniform3f( - colours[i * 6], - colours[i * 6 + 1], - colours[i * 6 + 2] - ); - this.renderer.updateUniform( - i, - (us) => (us["u_color"] = u), - LAYERS.planet - ); - const u2 = new Uniform3f( - colours[i * 6 + 3], - colours[i * 6 + 4], - colours[i * 6 + 5] - ); - this.renderer.updateUniform( - i, - (us) => (us["u_color_next"] = u2), - LAYERS.planet - ); - - this.planet_labels[i].setText( - GL, - "*" + planet_ships[i], - Align.Middle, - Align.Begin - ); - } - } - - _update_ships() { - const ships = this.game.get_ship_locations(); - const labels = this.game.get_ship_label_locations(); - const ship_counts = this.game.get_ship_counts(); - const ship_colours = this.game.get_ship_colours(); - - for (let i = this.max_num_ships; i < ship_counts.length; i++) { - this.renderer.addToDraw( - this.ship_ibo, - this.ship_vao, - this.shader, - {}, - [], - LAYERS.ship - ); - - const label = this.text_factory.build(GL); - this.ship_labels.push(label); - this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label); - } - if (ship_counts.length > this.max_num_ships) - this.max_num_ships = ship_counts.length; - - // TODO: actually remove obsolete ships - for (let i = 0; i < this.max_num_ships; i++) { - if (i < ship_counts.length) { - this.ship_labels[i].setText( - GL, - "" + ship_counts[i], - Align.Middle, - Align.Middle - ); - - this.renderer.enableRenderable(i, LAYERS.ship); - this.renderer.enableRenderable(i, LAYERS.ship_label); - - const u = new Uniform3f( - ship_colours[i * 3], - ship_colours[i * 3 + 1], - ship_colours[i * 3 + 2] - ); - - const t1 = new UniformMatrix3fv(ships.slice(i * 18, i * 18 + 9)); - const t2 = new UniformMatrix3fv(ships.slice(i * 18 + 9, i * 18 + 18)); - - const tl1 = new UniformMatrix3fv(labels.slice(i * 18, i * 18 + 9)); - const tl2 = new UniformMatrix3fv(labels.slice(i * 18 + 9, i * 18 + 18)); - - this.renderer.updateUniform( - i, - (us) => { - us["u_color"] = u; - us["u_color_next"] = u; - us["u_trans"] = t1; - us["u_trans_next"] = t2; - }, - LAYERS.ship - ); - - this.renderer.updateUniform( - i, - (us) => { - us["u_trans"] = tl1; - us["u_trans_next"] = tl2; - }, - LAYERS.ship_label - ); - } else { - this.renderer.disableRenderable(i, LAYERS.ship); - this.renderer.disableRenderable(i, LAYERS.ship_label); - } - } - } - - render(time: number) { - COUNTER.frame(time); - - if (COUNTER.delta(time) < 30) { - this.vor_counter = Math.min(3, this.vor_counter + 1); - } else { - this.vor_counter = Math.max(-3, this.vor_counter - 1); - } - - if (this.vor_counter < -2) { - this.use_vor = false; - } - - // If not playing, still reder with different viewbox, so people can still pan etc. - if (!this.playing) { - this.last_time = time; - - this.shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - this.vor_shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - this.image_shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - - this.renderer.render(GL); - return; - } - - // Check if turn is still correct - if (time > this.last_time + ms_per_frame) { - this.last_time = time; - this.updateTurn(this.frame + 1); - if (this.frame == this.turn_count - 1) { - this.playing = false; - } - } - - // Do GL things - GL.bindFramebuffer(GL.FRAMEBUFFER, null); - GL.viewport(0, 0, GL.canvas.width, GL.canvas.height); - GL.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); - - this.vor_shader.uniform( - GL, - "u_time", - new Uniform1f((time - this.last_time) / ms_per_frame) - ); - this.vor_shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - this.vor_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); - this.vor_shader.uniform(GL, "u_vor", new UniformBool(this.use_vor)); - - this.shader.uniform( - GL, - "u_time", - new Uniform1f((time - this.last_time) / ms_per_frame) - ); - this.shader.uniform( - GL, - "u_mouse", - new Uniform2f(this.resizer.get_mouse_pos()) - ); - this.shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - this.shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); - - this.image_shader.uniform( - GL, - "u_time", - new Uniform1f((time - this.last_time) / ms_per_frame) - ); - this.image_shader.uniform( - GL, - "u_mouse", - new Uniform2f(this.resizer.get_mouse_pos()) - ); - this.image_shader.uniform( - GL, - "u_viewbox", - new Uniform4f(this.resizer.get_viewbox()) - ); - this.image_shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); - - // Render - this.renderer.render(GL); - - COUNTER.frame_end(); - } - - updateTurn(turn: number) { - this.frame = Math.max(0, turn); - const new_frame = this.game.update_turn(this.frame); - if (new_frame < this.frame) { - this.frame = new_frame; - this.playing = false; - } else { - this._update_state(); - this.playing = true; - } - - this.updateTurnCounters(); - } - - updateTurnCounters() { - ELEMENTS["turnCounter"].innerHTML = - this.frame + " / " + (this.turn_count - 1); - ELEMENTS["turnSlider"].value = this.frame + ""; - ELEMENTS["turnSlider"].max = this.turn_count - 1 + ""; - } - - handleKey(event: KeyboardEvent) { - // Space - if (event.keyCode == 32) { - if (this.playing) { - this.playing = false; - } else { - this.playing = true; - } - } - - // Arrow left - if (event.keyCode == 37) { - // This feels more natural than -1 what it should be, I think - this.updateTurn(this.frame - 2); - } - - // Arrow right - if (event.keyCode == 39) { - this.updateTurn(this.frame + 1); - } - - // d key - if (event.keyCode == 68) { - ELEMENTS["speed"].value = ms_per_frame + 10 + ""; - ELEMENTS["speed"].onchange(undefined); - } - - // a key - if (event.keyCode == 65) { - ELEMENTS["speed"].value = Math.max(ms_per_frame - 10, 0) + ""; - ELEMENTS["speed"].onchange(undefined); - } - } -} - -var game_instance: GameInstance; -var meshes: Mesh[]; -var shaders: Dictionary<ShaderFactory>; - -export async function set_instance(source: string): Promise<GameInstance> { - if (!meshes || !shaders) { - const mesh_promises = ["ship.svg", "earth.svg", "mars.svg", "venus.svg"] - .map((name) => "/static/res/assets/" + name) - .map(url_to_mesh); - - const shader_promies = [ - (async () => - <[string, ShaderFactory]>[ - "normal", - await ShaderFactory.create_factory( - "/static/shaders/frag/simple.glsl", - "/static/shaders/vert/simple.glsl" - ), - ])(), - (async () => - <[string, ShaderFactory]>[ - "vor", - await ShaderFactory.create_factory( - "/static/shaders/frag/vor.glsl", - "/static/shaders/vert/vor.glsl" - ), - ])(), - (async () => - <[string, ShaderFactory]>[ - "image", - await ShaderFactory.create_factory( - "/static/shaders/frag/image.glsl", - "/static/shaders/vert/simple.glsl" - ), - ])(), - ]; - let shaders_array: [string, ShaderFactory][]; - [meshes, shaders_array] = await Promise.all([ - Promise.all(mesh_promises), - Promise.all(shader_promies), - ]); - - shaders = {}; - shaders_array.forEach(([name, fac]) => (shaders[name] = fac)); - } - - resizeCanvasToDisplaySize(CANVAS); - - game_instance = new GameInstance( - Game.new(source), - meshes.slice(1), - meshes[0], - shaders - ); - - set_loading(false); - start(); - return game_instance; -} - -var _animating = false; - -export function start() { - if (_animating) { - // already running - return; - } - _animating = true; - requestAnimationFrame(step); -} - -export function stop() { - _animating = false; -} - -function step(time: number) { - if (game_instance) { - game_instance.render(time); - } - - if (_animating) { - requestAnimationFrame(step); - } -} |