aboutsummaryrefslogtreecommitdiff
path: root/web/pw-visualizer
diff options
context:
space:
mode:
authorIlion Beyst <ilion.beyst@gmail.com>2022-07-18 21:03:34 +0200
committerIlion Beyst <ilion.beyst@gmail.com>2022-07-18 21:03:34 +0200
commit7daf8f643798ce76733006f8469890bf1a3fd05e (patch)
treed755c4f0979f56792684b109d263624aef4baaad /web/pw-visualizer
parent608d05bc167c57d190d3c06f250b5e4a5662e77e (diff)
parentd092f5d89c0fda5cc67349d5489b4ef1b294e053 (diff)
downloadplanetwars.dev-7daf8f643798ce76733006f8469890bf1a3fd05e.tar.xz
planetwars.dev-7daf8f643798ce76733006f8469890bf1a3fd05e.zip
Merge branch 'next'
Diffstat (limited to 'web/pw-visualizer')
-rw-r--r--web/pw-visualizer/assets/res/earth.pngbin0 -> 12873 bytes
-rw-r--r--web/pw-visualizer/assets/res/ship.pngbin0 -> 4963 bytes
-rw-r--r--web/pw-visualizer/assets/res/ship.svg51
-rw-r--r--web/pw-visualizer/assets/shaders/frag/image.glsl1
-rw-r--r--web/pw-visualizer/assets/shaders/frag/masked_image.glsl21
-rw-r--r--web/pw-visualizer/src/assets.ts5
-rw-r--r--web/pw-visualizer/src/index.ts260
-rw-r--r--web/pw-visualizer/src/webgl/index.ts2
-rw-r--r--web/pw-visualizer/src/webgl/text.ts22
-rw-r--r--web/pw-visualizer/src/webgl/texture.ts26
10 files changed, 218 insertions, 170 deletions
diff --git a/web/pw-visualizer/assets/res/earth.png b/web/pw-visualizer/assets/res/earth.png
new file mode 100644
index 0000000..7897e32
--- /dev/null
+++ b/web/pw-visualizer/assets/res/earth.png
Binary files differ
diff --git a/web/pw-visualizer/assets/res/ship.png b/web/pw-visualizer/assets/res/ship.png
new file mode 100644
index 0000000..fe289aa
--- /dev/null
+++ b/web/pw-visualizer/assets/res/ship.png
Binary files differ
diff --git a/web/pw-visualizer/assets/res/ship.svg b/web/pw-visualizer/assets/res/ship.svg
index ee202ff..41af3ea 100644
--- a/web/pw-visualizer/assets/res/ship.svg
+++ b/web/pw-visualizer/assets/res/ship.svg
@@ -2,20 +2,20 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="100mm"
- height="100mm"
- viewBox="0 0 100 99.999999"
+ width="10.231839cm"
+ height="19.597593cm"
+ viewBox="0 0 102.31839 195.97593"
version="1.1"
id="svg8"
sodipodi:docname="ship.svg"
- inkscape:version="0.92.4 (f8dce91, 2019-08-02)">
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
@@ -26,8 +26,8 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.0993438"
- inkscape:cx="676.08563"
- inkscape:cy="474.10966"
+ inkscape:cx="424.34405"
+ inkscape:cy="384.32017"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
@@ -35,15 +35,19 @@
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
- inkscape:window-width="2560"
- inkscape:window-height="1417"
+ inkscape:window-width="1920"
+ inkscape:window-height="1048"
inkscape:window-x="0"
- inkscape:window-y="0"
- inkscape:window-maximized="0"
- gridtolerance="10">
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ gridtolerance="10"
+ inkscape:pagecheckerboard="0"
+ units="cm">
<inkscape:grid
type="xygrid"
- id="grid894" />
+ id="grid894"
+ originx="-160.50747"
+ originy="118.75037" />
</sodipodi:namedview>
<metadata
id="metadata5">
@@ -53,7 +57,6 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
@@ -61,17 +64,17 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
- transform="translate(229.05372,-117.27915)">
+ transform="translate(68.546255,1.4712222)">
<ellipse
ry="79.47506"
rx="48.672089"
cy="39.779182"
cx="439.0813"
id="ellipse888"
- style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:6.61458302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ style="opacity:1;fill:#00ffff;fill-opacity:1;stroke:none;stroke-width:6.61458;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
- style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:24.99999809;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d="M 800 -448.82031 C 800 -448.82031 640 -342.04689 640 -92.046875 C 640 -16.101728 661.67774 51.924976 695.88672 97.775391 C 640.96482 152.90966 593.39426 234.74166 610 267.95312 C 620.00003 287.95314 720.00001 297.95311 730 287.95312 C 751.8315 266.12161 757.39662 198.03742 758.92773 149.62305 C 772.03579 155.04778 785.80002 157.95312 800 157.95312 C 814.19998 157.95312 827.96422 155.04778 841.07227 149.62305 C 842.60338 198.03742 848.1685 266.12161 870 287.95312 C 879.99999 297.95311 979.99997 287.95314 990 267.95312 C 1006.6057 234.74166 959.03518 152.90966 904.11328 97.775391 C 938.32226 51.924976 959.99999 -16.101728 960 -92.046875 C 960.00002 -342.04689 800 -448.82031 800 -448.82031 z M 800 -352.04688 C 800 -352.04688 908.96486 -279.33252 908.96484 -109.07617 C 908.96484 -15.046189 860.17918 61.179688 800 61.179688 C 739.82082 61.179688 691.03515 -15.046189 691.03516 -109.07617 C 691.03516 -279.33252 800 -352.04687 800 -352.04688 z "
+ style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 800,-448.82031 c 0,0 -160,106.77342 -160,356.773435 0,75.945147 21.67774,143.971851 55.88672,189.822266 C 640.96482,152.90966 593.39426,234.74166 610,267.95312 c 10.00003,20.00002 110.00001,29.99999 120,20 21.8315,-21.83151 27.39662,-89.9157 28.92773,-138.33007 13.10806,5.42473 26.87229,8.33007 41.07227,8.33007 14.19998,0 27.96422,-2.90534 41.07227,-8.33007 1.53111,48.41437 7.09623,116.49856 28.92773,138.33007 9.99999,9.99999 109.99997,2e-5 120,-20 C 1006.6057,234.74166 959.03518,152.90966 904.11328,97.775391 938.32226,51.924976 959.99999,-16.101728 960,-92.046875 960.00002,-342.04689 800,-448.82031 800,-448.82031 Z m 0,96.77343 c 0,0 108.96486,72.71436 108.96484,242.97071 0,94.029981 -48.78566,170.255858 -108.96484,170.255858 -60.17918,0 -108.96485,-76.225877 -108.96484,-170.255858 C 691.03516,-279.33252 800,-352.04687 800,-352.04688 Z"
transform="matrix(0.26458333,0,0,0.26458333,-229.05372,117.27915)"
id="path4600" />
</g>
diff --git a/web/pw-visualizer/assets/shaders/frag/image.glsl b/web/pw-visualizer/assets/shaders/frag/image.glsl
index 69c8b91..f8d62c9 100644
--- a/web/pw-visualizer/assets/shaders/frag/image.glsl
+++ b/web/pw-visualizer/assets/shaders/frag/image.glsl
@@ -10,5 +10,4 @@ uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
-// gl_FragColor = vec4(0.7, 0.7, 0.0, 1.0);
}
diff --git a/web/pw-visualizer/assets/shaders/frag/masked_image.glsl b/web/pw-visualizer/assets/shaders/frag/masked_image.glsl
new file mode 100644
index 0000000..da0c787
--- /dev/null
+++ b/web/pw-visualizer/assets/shaders/frag/masked_image.glsl
@@ -0,0 +1,21 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+// Passed in from the vertex shader.
+varying vec2 v_texCoord;
+
+uniform float u_step_interval;
+uniform float u_time;
+uniform vec3 u_color;
+uniform vec3 u_color_next;
+
+
+// The texture.
+uniform sampler2D u_texture;
+
+void main() {
+ float alpha = texture2D(u_texture, v_texCoord).a;
+ vec3 color = mix(u_color, u_color_next, u_time);
+ gl_FragColor = vec4(color, alpha);
+}
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);
-}