aboutsummaryrefslogtreecommitdiff
path: root/web/pw-visualizer/src
diff options
context:
space:
mode:
authorIlion Beyst <ilion.beyst@gmail.com>2022-11-06 17:43:00 +0100
committerIlion Beyst <ilion.beyst@gmail.com>2022-11-06 18:07:16 +0100
commitfdc2ab9421c6a38c6bbd9b621c4da4f2c147a773 (patch)
tree50466d9b751f1e1eb3ee8a47479a4d28b5050aa6 /web/pw-visualizer/src
parentd3845eb85fc8f75562a6e121c5e437763ffeea63 (diff)
downloadplanetwars.dev-fdc2ab9421c6a38c6bbd9b621c4da4f2c147a773.tar.xz
planetwars.dev-fdc2ab9421c6a38c6bbd9b621c4da4f2c147a773.zip
msdf font renderen
Diffstat (limited to 'web/pw-visualizer/src')
-rw-r--r--web/pw-visualizer/src/assets.ts5
-rw-r--r--web/pw-visualizer/src/index.ts37
-rw-r--r--web/pw-visualizer/src/webgl/msdf_text.ts181
-rw-r--r--web/pw-visualizer/src/webgl/texture.ts10
-rw-r--r--web/pw-visualizer/src/webgl/util.ts1
5 files changed, 223 insertions, 11 deletions
diff --git a/web/pw-visualizer/src/assets.ts b/web/pw-visualizer/src/assets.ts
index 5fa5215..abd17e4 100644
--- a/web/pw-visualizer/src/assets.ts
+++ b/web/pw-visualizer/src/assets.ts
@@ -9,11 +9,16 @@ export {default as shipPng} from "../assets/res/ship.png";
export {default as fontPng} from "../assets/res/font.png";
+export {default as robotoMsdfPng} from "../assets/res/fonts/roboto.png";
+export {default as robotoMsdfJson} from "../assets/res/fonts/roboto.json";
+
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";
+export {default as msdfFragmentShader} from "../assets/shaders/frag/msdf.glsl?url";
+
export {default as imageVertexShader} from "../assets/shaders/vert/image.glsl?url";
export {default as simpleVertexShader} from "../assets/shaders/vert/simple.glsl?url";
diff --git a/web/pw-visualizer/src/index.ts b/web/pw-visualizer/src/index.ts
index 4231707..97bdb3d 100644
--- a/web/pw-visualizer/src/index.ts
+++ b/web/pw-visualizer/src/index.ts
@@ -25,6 +25,7 @@ import { defaultLabelFactory, LabelFactory, Align, Label } from "./webgl/text";
import { VoronoiBuilder } from "./voronoi/voronoi";
import * as assets from "./assets";
import { loadImage, Texture } from "./webgl/texture";
+import { defaultMsdfLabelFactory, MsdfLabelFactory, Label as MsdfLabel } from "./webgl/msdf_text";
function to_bbox(box: number[]): BBox {
@@ -84,7 +85,7 @@ export function init() {
ms_per_frame = parseInt(ELEMENTS["speed"].value);
- GL = CANVAS.getContext("webgl");
+ GL = CANVAS.getContext("webgl", { antialias: true });
GL.clearColor(0, 0, 0, 1);
GL.clear(GL.COLOR_BUFFER_BIT);
@@ -124,9 +125,12 @@ export class GameInstance {
image_shader: Shader;
masked_image_shader: Shader;
+ msdf_shader: Shader;
+
text_factory: LabelFactory;
- planet_labels: Label[];
- ship_labels: Label[];
+ msdf_text_factory: MsdfLabelFactory;
+ planet_labels: MsdfLabel[];
+ ship_labels: MsdfLabel[];
ship_ibo: IndexBuffer;
ship_vao: VertexArray;
@@ -153,6 +157,7 @@ export class GameInstance {
planets_textures: Texture[],
ship_texture: Texture,
font_texture: Texture,
+ robotoMsdfTexture: Texture,
shaders: Dictionary<ShaderFactory>
) {
this.game = game;
@@ -168,7 +173,9 @@ export class GameInstance {
});
this.masked_image_shader = shaders["masked_image"].create_shader(GL);
+ this.msdf_shader = shaders["msdf"].create_shader(GL);
this.text_factory = defaultLabelFactory(GL, font_texture, this.image_shader);
+ this.msdf_text_factory = defaultMsdfLabelFactory(GL, robotoMsdfTexture, this.msdf_shader);
this.planet_labels = [];
this.ship_labels = [];
@@ -278,11 +285,11 @@ export class GameInstance {
1,
0,
-planets[i * 3],
- -planets[i * 3 + 1] - 1.2,
+ -planets[i * 3 + 1] - 1.171875,
1,
]);
- const label = this.text_factory.build(GL, transform);
+ const label = this.msdf_text_factory.build(GL, transform);
this.planet_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.planet_label);
}
@@ -330,7 +337,7 @@ export class GameInstance {
this.planet_labels[i].setText(
GL,
- "*" + planet_ships[i],
+ "" + planet_ships[i],
Align.Middle,
Align.Begin
);
@@ -375,7 +382,7 @@ export class GameInstance {
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);
+ const label = this.msdf_text_factory.build(GL);
this.ship_labels.push(label);
this.renderer.addRenderable(label.getRenderable(), LAYERS.ship_label);
@@ -451,10 +458,11 @@ export class GameInstance {
this.shader,
this.image_shader,
this.masked_image_shader,
+ this.msdf_shader,
];
- // If not playing, still reder with different viewbox, so people can still pan etc.
+ // If not playing, still render with different viewbox, so that panning is still possible
if (!this.playing) {
this.last_time = time;
@@ -595,6 +603,7 @@ export async function set_instance(source: string): Promise<GameInstance> {
loadImage(assets.fontPng),
loadImage(assets.shipPng),
loadImage(assets.earthPng),
+ loadImage(assets.robotoMsdfPng),
];
const shader_promies = [
@@ -630,7 +639,14 @@ export async function set_instance(source: string): Promise<GameInstance> {
assets.simpleVertexShader,
),
])(),
-
+ (async () =>
+ <[string, ShaderFactory]>[
+ "msdf",
+ await ShaderFactory.create_factory(
+ assets.msdfFragmentShader,
+ assets.simpleVertexShader,
+ ),
+ ])(),
];
let shaders_array: [string, ShaderFactory][];
[texture_images, shaders_array] = await Promise.all([
@@ -646,12 +662,15 @@ export async function set_instance(source: string): Promise<GameInstance> {
const fontTexture = Texture.fromImage(GL, texture_images[0], "font");
const shipTexture = Texture.fromImage(GL, texture_images[1], "ship");
const earthTexture = Texture.fromImage(GL, texture_images[2], "earth");
+ const robotoMsdfTexture = Texture.fromImage(GL, texture_images[3], "robotoMsdf");
+
game_instance = new GameInstance(
Game.new(source),
[earthTexture],
shipTexture,
fontTexture,
+ robotoMsdfTexture,
shaders
);
diff --git a/web/pw-visualizer/src/webgl/msdf_text.ts b/web/pw-visualizer/src/webgl/msdf_text.ts
new file mode 100644
index 0000000..5bbac20
--- /dev/null
+++ b/web/pw-visualizer/src/webgl/msdf_text.ts
@@ -0,0 +1,181 @@
+import { Shader, Uniform1f, Uniform4f, UniformMatrix3fv } from "./shader";
+import { Texture } from "./texture";
+import { DefaultRenderable } from "./renderer";
+import { IndexBuffer, VertexBuffer } from "./buffer";
+import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout";
+import { robotoMsdfJson } from "../assets";
+import { GlypInfo } from "./text";
+
+
+export enum Align {
+ Begin,
+ End,
+ Middle,
+}
+
+export type FontAtlas = {
+ atlas: AtlasMeta,
+ metrics: Metrics,
+ glyphs: Glyph[],
+}
+
+export type AtlasMeta = {
+ type: string,
+ distanceRange: number,
+ size: number,
+ width: number,
+ height: number,
+ yOrigin: string,
+}
+
+export type Metrics = {
+ emSize: number,
+ lineHeight: number,
+ ascender: number,
+ descender: number,
+ underlineY: number,
+ underlineThickness: number,
+}
+
+
+export type Glyph = {
+ unicode: number,
+ advance: number,
+ planeBounds?: Bounds,
+ atlasBounds?: Bounds,
+}
+
+export type Bounds = {
+ left: number,
+ bottom: number,
+ right: number,
+ top: number,
+}
+
+
+export class MsdfLabelFactory {
+ texture: Texture;
+ font: FontAtlas;
+ shader: Shader;
+
+ constructor(gl: WebGLRenderingContext, fontTexture: Texture, font: FontAtlas, shader: Shader) {
+ this.texture = fontTexture;
+ this.font = font;
+ this.shader = shader;
+ }
+
+ build(gl: WebGLRenderingContext, transform?: UniformMatrix3fv): Label {
+ return new Label(gl, this.shader, this.texture, this.font, transform);
+ }
+}
+
+export class Label {
+ inner: DefaultRenderable;
+
+ font: FontAtlas;
+ charAtlas: {[unicodeNumber: number]: Glyph};
+
+ constructor(gl: WebGLRenderingContext, shader: Shader, tex: Texture, font: FontAtlas, transform: UniformMatrix3fv) {
+ this.font = font;
+ this.charAtlas = {}
+ this.font.glyphs.forEach((glyph) => {
+ this.charAtlas[glyph.unicode] = glyph;
+ });
+
+ const uniforms = {
+ "u_trans": transform,
+ "u_trans_next": transform,
+ "u_fgColor": new Uniform4f([1.0, 1.0, 1.0, 1.0]),
+ "u_bgColor": new Uniform4f([0.0, 0.0, 0.0, 1.0]),
+ "u_distanceRange": new Uniform1f(font.atlas.distanceRange),
+ "u_glyphSize": new Uniform1f(font.atlas.size),
+ };
+ const ib = new IndexBuffer(gl, []);
+ const vb_pos = new VertexBuffer(gl, []);
+ const vb_tex = new VertexBuffer(gl, []);
+
+ 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);
+
+ this.inner = new DefaultRenderable(ib, vao, shader, [tex], uniforms);
+ }
+
+ getRenderable(): DefaultRenderable {
+ return this.inner;
+ }
+
+ setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) {
+ const idxs = [];
+ const verts_pos = [];
+ const verts_tex = [];
+
+ let xPos = 0;
+ let yPos = 0;
+ switch (v_align) {
+ case Align.Begin:
+ yPos = -1;
+ break;
+ case Align.End:
+ yPos = 0;
+ break;
+ case Align.Middle:
+ yPos = -0.5;
+ break;
+ }
+
+ // track position in the index buffer
+ let bufPos = 0;
+ for (let charIndex = 0; charIndex < text.length; charIndex++) {
+ let char = this.charAtlas[text.charCodeAt(charIndex)]
+ if (char.atlasBounds && char.planeBounds) {
+ verts_pos.push(xPos + char.planeBounds.left, yPos-char.planeBounds.top);
+ verts_pos.push(xPos + char.planeBounds.right, yPos-char.planeBounds.top);
+ verts_pos.push(xPos + char.planeBounds.left, yPos-char.planeBounds.bottom);
+ verts_pos.push(xPos + char.planeBounds.right, yPos-char.planeBounds.bottom);
+
+ const atlasWidth = this.font.atlas.width;
+ const atlasHeight = this.font.atlas.height;
+
+ verts_tex.push(char.atlasBounds.left / atlasWidth, char.atlasBounds.top / atlasHeight);
+ verts_tex.push(char.atlasBounds.right / atlasWidth, char.atlasBounds.top / atlasHeight);
+ verts_tex.push(char.atlasBounds.left / atlasWidth, char.atlasBounds.bottom / atlasHeight);
+ verts_tex.push(char.atlasBounds.right / atlasWidth, char.atlasBounds.bottom / atlasHeight);
+
+ idxs.push(bufPos+0, bufPos+1, bufPos+2);
+ idxs.push(bufPos+1, bufPos+2, bufPos+3);
+ bufPos += 4;
+ }
+ xPos += char.advance;
+ }
+
+ let shift = 0;
+ switch (h_align) {
+ case Align.End:
+ shift = xPos;
+ break;
+ case Align.Middle:
+ shift = xPos / 2;
+ break;
+ }
+
+ for (let i = 0; i < verts_pos.length; i += 2) {
+ verts_pos[i] -= shift;
+ }
+
+
+ this.inner.updateIndexBuffer(gl, idxs);
+ this.inner.updateVAOBuffer(gl, 0, verts_pos);
+ this.inner.updateVAOBuffer(gl, 1, verts_tex);
+ }
+}
+
+export function defaultMsdfLabelFactory(gl: WebGLRenderingContext, fontTexture: Texture, shader: Shader): MsdfLabelFactory {
+ return new MsdfLabelFactory(gl, fontTexture, robotoMsdfJson, shader);
+}
diff --git a/web/pw-visualizer/src/webgl/texture.ts b/web/pw-visualizer/src/webgl/texture.ts
index 9624489..9ebd5c0 100644
--- a/web/pw-visualizer/src/webgl/texture.ts
+++ b/web/pw-visualizer/src/webgl/texture.ts
@@ -67,8 +67,14 @@ export class Texture {
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);
+ if (name == "font") {
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ } else {
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+
+ }
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
diff --git a/web/pw-visualizer/src/webgl/util.ts b/web/pw-visualizer/src/webgl/util.ts
index 7b55d19..922b016 100644
--- a/web/pw-visualizer/src/webgl/util.ts
+++ b/web/pw-visualizer/src/webgl/util.ts
@@ -74,6 +74,7 @@ export class Resizer {
mouse_pos = [0, 0];
last_drag = [0, 0];
+ // x, y, w, h
viewbox: number[];
orig_viewbox: number[];