diff options
Diffstat (limited to 'web/pw-visualizer/src')
-rw-r--r-- | web/pw-visualizer/src/assets.ts | 5 | ||||
-rw-r--r-- | web/pw-visualizer/src/index.ts | 260 | ||||
-rw-r--r-- | web/pw-visualizer/src/webgl/index.ts | 2 | ||||
-rw-r--r-- | web/pw-visualizer/src/webgl/text.ts | 22 | ||||
-rw-r--r-- | web/pw-visualizer/src/webgl/texture.ts | 26 |
5 files changed, 170 insertions, 145 deletions
diff --git a/web/pw-visualizer/src/assets.ts b/web/pw-visualizer/src/assets.ts index e04f2c1..5fa5215 100644 --- a/web/pw-visualizer/src/assets.ts +++ b/web/pw-visualizer/src/assets.ts @@ -4,9 +4,14 @@ export {default as earthSvg} from "../assets/res/earth.svg"; export {default as marsSvg} from "../assets/res/mars.svg"; export {default as venusSvg} from "../assets/res/venus.svg"; +export {default as earthPng} from "../assets/res/earth.png"; +export {default as shipPng} from "../assets/res/ship.png"; + export {default as fontPng} from "../assets/res/font.png"; export {default as imageFragmentShader} from "../assets/shaders/frag/image.glsl?url"; +export {default as maskedImageFragmentShader} from "../assets/shaders/frag/masked_image.glsl?url"; + export {default as simpleFragmentShader} from "../assets/shaders/frag/simple.glsl?url"; export {default as vorFragmentShader} from "../assets/shaders/frag/vor.glsl?url"; diff --git a/web/pw-visualizer/src/index.ts b/web/pw-visualizer/src/index.ts index cd58aa7..bee1bab 100644 --- a/web/pw-visualizer/src/index.ts +++ b/web/pw-visualizer/src/index.ts @@ -1,6 +1,4 @@ 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"; @@ -8,8 +6,6 @@ import { Resizer, resizeCanvasToDisplaySize, FPSCounter, - url_to_mesh, - Mesh, } from "./webgl/util"; import { Shader, @@ -22,12 +18,13 @@ import { UniformMatrix3fv, UniformBool, } from "./webgl/shader"; -import { Renderer } from "./webgl/renderer"; +import { DefaultRenderable, 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"; import * as assets from "./assets"; +import { Texture } from "./webgl/texture"; function to_bbox(box: number[]): BBox { @@ -39,14 +36,6 @@ function to_bbox(box: number[]): BBox { }; } -// 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; } @@ -133,6 +122,7 @@ export class GameInstance { shader: Shader; vor_shader: Shader; image_shader: Shader; + masked_image_shader: Shader; text_factory: LabelFactory; planet_labels: Label[]; @@ -140,6 +130,7 @@ export class GameInstance { ship_ibo: IndexBuffer; ship_vao: VertexArray; + ship_texture: Texture; // TODO: find a better way max_num_ships: number; @@ -159,8 +150,9 @@ export class GameInstance { constructor( game: Game, - meshes: Mesh[], - ship_mesh: Mesh, + planets_textures: Texture[], + ship_texture: Texture, + font_texture: Texture, shaders: Dictionary<ShaderFactory> ) { this.game = game; @@ -174,11 +166,14 @@ export class GameInstance { this.vor_shader = shaders["vor"].create_shader(GL, { PLANETS: "" + planets.length, }); + this.masked_image_shader = shaders["masked_image"].create_shader(GL); - this.text_factory = defaultLabelFactory(GL, this.image_shader); + this.text_factory = defaultLabelFactory(GL, font_texture, this.image_shader); this.planet_labels = []; this.ship_labels = []; + this.ship_texture = ship_texture + this.resizer = new Resizer(CANVAS, [...game.get_viewbox()], true); this.renderer = new Renderer(); this.game.update_turn(0); @@ -188,15 +183,8 @@ export class GameInstance { // 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._create_planets(planets, planets_textures); + this.max_num_ships = 0; // Set slider correctly @@ -233,46 +221,52 @@ export class GameInstance { this.renderer.addRenderable(this.vor_builder.getRenderable(), LAYERS.vor); } - _create_planets(planets: Float32Array, meshes: Mesh[]) { + _create_planets(planets: Float32Array, planets_textures: Texture[]) { 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, + 1, 0, 0, + 0, 1, 0, + -planets[i * 3], -planets[i * 3 + 1], 1, // TODO: why are negations needed? ]); - 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 gl = GL; + const ib = new IndexBuffer(gl, [ + 0, 1, 2, + 1, 2, 3 + ]); + const vb_pos = new VertexBuffer(gl, [ + -1, 1, + 1, 1, + -1, -1, + 1, -1 + ]); + const vb_tex = new VertexBuffer(gl, [ + 0, 0, + 1, 0, + 0, 1, + 1, 1]); + + const layout_pos = new VertexBufferLayout(); + // 2? + 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(positionBuffer, layout); - - this.renderer.addToDraw( - indexBuffer, - vao, - this.shader, - { - u_trans: transform, - u_trans_next: transform, - }, - [], - LAYERS.planet - ); + vao.addBuffer(vb_pos, layout_pos); + vao.addBuffer(vb_tex, layout_tex); + + const uniforms = { + u_trans: transform, + u_trans_next: transform, + }; + + const renderable = new DefaultRenderable(ib, vao, this.masked_image_shader, [planets_textures[0]], uniforms); + + this.renderer.addRenderable(renderable, LAYERS.planet); + } { @@ -350,16 +344,39 @@ export class GameInstance { 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 gl = GL; + const ib = new IndexBuffer(gl, [ + 0, 1, 2, + 1, 2, 3 + ]); + const ratio = this.ship_texture.getWidth() / this.ship_texture.getHeight(); + const vb_pos = new VertexBuffer(gl, [ + -ratio, 1, + ratio, 1, + -ratio, -1, + ratio, -1 + ]); + const vb_tex = new VertexBuffer(gl, [ + 0, 0, + 1, 0, + 0, 1, + 1, 1, + ]); + + 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(vb_pos, layout_pos); + vao.addBuffer(vb_tex, layout_tex); + const renderable = new DefaultRenderable(ib, vao, this.masked_image_shader, [this.ship_texture], {}); + this.renderer.addRenderable(renderable, LAYERS.ship); const label = this.text_factory.build(GL); + this.ship_labels.push(label); this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label); } @@ -430,25 +447,30 @@ export class GameInstance { this.use_vor = false; } + const shaders_to_update = [ + this.shader, + this.image_shader, + this.masked_image_shader, + ]; + + // 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()) - ); + shaders_to_update.forEach((shader) => { + 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; @@ -481,39 +503,24 @@ export class GameInstance { 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)); + shaders_to_update.forEach((shader) => { + shader.uniform( + GL, + "u_time", + new Uniform1f((time - this.last_time) / ms_per_frame) + ); + shader.uniform( + GL, + "u_mouse", + new Uniform2f(this.resizer.get_mouse_pos()) + ); + shader.uniform( + GL, + "u_viewbox", + new Uniform4f(this.resizer.get_viewbox()) + ); + shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION)); + }); // Render this.renderer.render(GL); @@ -578,18 +585,17 @@ export class GameInstance { } var game_instance: GameInstance; -var meshes: Mesh[]; +var textures: Texture[]; var shaders: Dictionary<ShaderFactory>; export async function set_instance(source: string): Promise<GameInstance> { // TODO: embed shader programs - if (!meshes || !shaders) { - const mesh_promises = [ - assets.shipSvg, - assets.earthSvg, - assets.marsSvg, - assets.venusSvg, - ].map(url_to_mesh); + if (!textures || !shaders) { + const texture_promises = [ + Texture.fromImage(GL, assets.fontPng, "font"), + Texture.fromImage(GL, assets.shipPng, "ship"), + Texture.fromImage(GL, assets.earthPng, "earth") + ]; const shader_promies = [ (async () => @@ -616,10 +622,19 @@ export async function set_instance(source: string): Promise<GameInstance> { assets.simpleVertexShader, ), ])(), + (async () => + <[string, ShaderFactory]>[ + "masked_image", + await ShaderFactory.create_factory( + assets.maskedImageFragmentShader, + assets.simpleVertexShader, + ), + ])(), + ]; let shaders_array: [string, ShaderFactory][]; - [meshes, shaders_array] = await Promise.all([ - Promise.all(mesh_promises), + [textures, shaders_array] = await Promise.all([ + Promise.all(texture_promises), Promise.all(shader_promies), ]); @@ -631,8 +646,9 @@ export async function set_instance(source: string): Promise<GameInstance> { game_instance = new GameInstance( Game.new(source), - meshes.slice(1), - meshes[0], + textures.slice(2), + textures[1], + textures[0], shaders ); diff --git a/web/pw-visualizer/src/webgl/index.ts b/web/pw-visualizer/src/webgl/index.ts index 1742713..8d785ef 100644 --- a/web/pw-visualizer/src/webgl/index.ts +++ b/web/pw-visualizer/src/webgl/index.ts @@ -57,8 +57,8 @@ async function main() { return; } + // TODO: do we still need this? const mesh = await url_to_mesh("static/res/images/earth.svg"); - console.log(Math.max(...mesh.positions), Math.min(...mesh.positions)); const renderer = new Renderer(); const factory = await ShaderFactory.create_factory(assets.simpleFragmentShader, assets.simpleVertexShader); diff --git a/web/pw-visualizer/src/webgl/text.ts b/web/pw-visualizer/src/webgl/text.ts index fdfbc55..1ae6f37 100644 --- a/web/pw-visualizer/src/webgl/text.ts +++ b/web/pw-visualizer/src/webgl/text.ts @@ -33,8 +33,8 @@ export class LabelFactory { font: FontInfo; shader: Shader; - constructor(gl: WebGLRenderingContext, loc: string, font: FontInfo, shader: Shader) { - this.texture = Texture.fromImage(gl, loc, 'font'); + constructor(gl: WebGLRenderingContext, fontTexture: Texture, font: FontInfo, shader: Shader) { + this.texture = fontTexture; this.font = font; this.shader = shader; } @@ -79,7 +79,6 @@ export class Label { const verts_pos = []; const verts_tex = []; - const letterHeight = this.font.letterHeight / this.font.textureHeight; let xPos = 0; switch (h_align) { @@ -108,10 +107,17 @@ export class Label { for (let i = 0; i < text.length; i++) { const info = this.font.glyphInfos[text[i]]; if (info) { + const dx = info.width / this.font.letterHeight; - const letterWidth = info.width / this.font.textureWidth; - const x0 = info.x / this.font.textureWidth; - const y0 = info.y / this.font.textureHeight; + + // apply half-pixel correction to prevent texture bleeding + // we should address the center of each texel, not the border + // https://gamedev.stackexchange.com/questions/46963/how-to-avoid-texture-bleeding-in-a-texture-atlas + const x0 = (info.x + 0.5) / this.font.textureWidth; + const y0 = (info.y + 0.5) / this.font.textureHeight; + const letterWidth = (info.width - 1) / this.font.textureWidth; + const letterHeight = (this.font.letterHeight - 1) / this.font.textureHeight; + verts_pos.push(xPos, yStart); verts_pos.push(xPos + dx, yStart); verts_pos.push(xPos, yStart-1); @@ -138,7 +144,7 @@ export class Label { } } -export function defaultLabelFactory(gl: WebGLRenderingContext, shader: Shader): LabelFactory { +export function defaultLabelFactory(gl: WebGLRenderingContext, fontTexture: Texture, shader: Shader): LabelFactory { const fontInfo = { letterHeight: 8, spaceWidth: 8, @@ -189,5 +195,5 @@ export function defaultLabelFactory(gl: WebGLRenderingContext, shader: Shader): }, }; - return new LabelFactory(gl, fontPng, fontInfo, shader); + return new LabelFactory(gl, fontTexture, fontInfo, shader); } diff --git a/web/pw-visualizer/src/webgl/texture.ts b/web/pw-visualizer/src/webgl/texture.ts index 9d6adcf..faafe76 100644 --- a/web/pw-visualizer/src/webgl/texture.ts +++ b/web/pw-visualizer/src/webgl/texture.ts @@ -11,15 +11,18 @@ export class Texture { gl: WebGLRenderingContext, path: string, name: string, - ): Texture { - const out = new Texture(gl, name); - - const image = new Image(); - image.onload = out.setImage.bind(out, gl, image); - image.onerror = error; - image.src = path; - - return out; + ): Promise<Texture> { + return new Promise((resolve, reject) => { + const out = new Texture(gl, name); + + const image = new Image(); + image.onload = () => { + out.setImage(gl, image); + resolve(out); + } + image.onerror = reject; + image.src = path; + }) } static fromRenderer( @@ -99,8 +102,3 @@ export class Texture { return this.height; } } - -function error(e: any) { - console.error("IMAGE LOAD ERROR"); - console.error(e); -} |