Skip to content

Commit

Permalink
Higher res font and better HUD layout for SVGA
Browse files Browse the repository at this point in the history
  • Loading branch information
ruben3d committed Apr 5, 2023
1 parent 250e94c commit b298d5b
Show file tree
Hide file tree
Showing 11 changed files with 457 additions and 302 deletions.
Binary file added assets/font-hud-large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file added dist/assets/font-hud-large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
329 changes: 195 additions & 134 deletions dist/bundle.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/script/render/screen/canvasPainter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TextAlignment, TextEffect, TextRenderer } from "./text";
import { Font, TextAlignment, TextEffect, TextRenderer } from "./text";


function XOR(p: boolean, q: boolean): boolean {
Expand Down Expand Up @@ -218,8 +218,8 @@ export class CanvasPainter {
}
}

text(x: number, y: number, text: string, color?: string, alignment: TextAlignment = TextAlignment.LEFT) {
this.textRenderer.text(x, y, text, color, alignment, this.textEffect, this.textEffectColor);
text(font: Font, x: number, y: number, text: string, color?: string, alignment: TextAlignment = TextAlignment.LEFT) {
this.textRenderer.text(font,x, y, text, color, alignment, this.textEffect, this.textEffectColor);
}

batch(): BatchCanvasPainter {
Expand Down
40 changes: 25 additions & 15 deletions src/script/render/screen/mathDebug.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,63 @@
import * as THREE from 'three';
import { CanvasPainter } from './canvasPainter';
import { CHAR_HEIGHT, CHAR_MARGIN, CHAR_WIDTH, TextAlignment } from './text';
import { Font, FontDefs, TextAlignment } from './text';

const tmpArray4: number[] = new Array(4);

export function debugDisplayMatrix4(painter: CanvasPainter, x: number, y: number, m: THREE.Matrix4, precision: number): void {
export function debugDisplayMatrix4(font: Font, painter: CanvasPainter, x: number, y: number, m: THREE.Matrix4, precision: number): void {
const fontDef = FontDefs[font];
const charWidth = fontDef.charWidth;
const charHeight = fontDef.charHeight;
const charSpacing = fontDef.charSpacing;

const longest = getLongest(m.elements);
const baseX = getBaseX(x, longest);
const stride = (longest + 1 + precision + 1) * (CHAR_WIDTH + CHAR_MARGIN);
const baseX = getBaseX(x, longest, charWidth, charSpacing);
const stride = (longest + 1 + precision + 1) * (charWidth + charSpacing);

const ROWS = 4;
const COLS = 4;
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const n = m.elements[row + col * ROWS];
const dx = col * stride;
const dy = row * (CHAR_HEIGHT + CHAR_MARGIN);
debugDisplayFloat(painter, baseX + dx, y + dy, n, precision);
const dy = row * (charHeight + charSpacing);
debugDisplayFloat(font, painter, baseX + dx, y + dy, n, precision);
}
}
}

export function debugDisplayVectorCol(painter: CanvasPainter, x: number, y: number, v: THREE.Vector3 | THREE.Vector4, precision: number): void {
export function debugDisplayVectorCol(font: Font, painter: CanvasPainter, x: number, y: number, v: THREE.Vector3 | THREE.Vector4, precision: number): void {
const fontDef = FontDefs[font];
const charWidth = fontDef.charWidth;
const charHeight = fontDef.charHeight;
const charSpacing = fontDef.charSpacing;

const elements = v.toArray(tmpArray4);
const longest = getLongest(elements);
const baseX = getBaseX(x, longest);
const baseX = getBaseX(x, longest, charWidth, charSpacing);

const size = 'w' in v ? 4 : 3;
for (let i = 0; i < size; i++) {
const n = elements[i];
const dy = i * (CHAR_HEIGHT + CHAR_MARGIN);
debugDisplayFloat(painter, baseX, y + dy, n, precision);
const dy = i * (charHeight + charSpacing);
debugDisplayFloat(font, painter, baseX, y + dy, n, precision);
}
}

// Position relative to decimal point
export function debugDisplayFloat(painter: CanvasPainter, x: number, y: number, n: number, precision: number): void {
export function debugDisplayFloat(font: Font, painter: CanvasPainter, x: number, y: number, n: number, precision: number): void {
let int = Math.abs(Math.trunc(n));
const fract = Math.abs(n) - int;
const fractStr = fract.toFixed(precision);
if (!fractStr.includes('0.')) {
int = int + 1;
}
painter.text(x, y, (n < 0 ? '-' : '') + int.toString() + '.', undefined, TextAlignment.RIGHT);
painter.text(x, y, fractStr.substring(2) || '0', undefined, TextAlignment.LEFT);
painter.text(font, x, y, (n < 0 ? '-' : '') + int.toString() + '.', undefined, TextAlignment.RIGHT);
painter.text(font, x, y, fractStr.substring(2) || '0', undefined, TextAlignment.LEFT);
}

function getBaseX(x: number, longest: number): number {
return x + longest * (CHAR_WIDTH + CHAR_MARGIN);
function getBaseX(x: number, longest: number, charWidth: number, charSpacing: number): number {
return x + longest * (charWidth + charSpacing);
}

function getLongest(ns: number[]): number {
Expand Down
145 changes: 102 additions & 43 deletions src/script/render/screen/text.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,69 @@
export const CHAR_WIDTH = 3;
export const CHAR_HEIGHT = 5;
export const CHAR_MARGIN = 1;
import { assertIsDefined } from "../../utils/asserts";

export enum TextAlignment {
CENTER,
LEFT,
RIGHT
}

export enum TextEffect {
NONE,
SHADOW,
BACKGROUND
}

export enum Font {
HUD_SMALL = '1',
HUD_LARGE = '2'
}

enum FontCategory {
HUD
}

interface FontDefinition {
readonly category: FontCategory;
readonly atlas: string;
readonly charWidth: number;
readonly charHeight: number;
readonly charSpacing: number;
}

const HUDSmallFont: FontDefinition = {
category: FontCategory.HUD,
atlas: 'assets/font-hud-small.png',
charWidth: 3,
charHeight: 5,
charSpacing: 1
};

const HUDLargeFont: FontDefinition = {
category: FontCategory.HUD,
atlas: 'assets/font-hud-large.png',
charWidth: 7,
charHeight: 11,
charSpacing: 1
};

export const FontDefs = {
[Font.HUD_SMALL]: HUDSmallFont,
[Font.HUD_LARGE]: HUDLargeFont,
};

interface FontHandle {
def: FontDefinition;
source: HTMLImageElement;
colors: Map<string, HTMLCanvasElement>;
}

const ASCII_0 = 48;
const ASCII_BIG_A = 65;
const ASCII_SMALL_A = 97;
const ASCII_LETTERS = 26;
const ASCII_NUMBERS = 10;
const FONT_CHAR_PADDING = 1;

const NumberMap = new Map<number, { col: number, row: number }>([
const HUDFontNumberMap = new Map<number, { col: number, row: number }>([
[0, { col: 2, row: 3 }],
[1, { col: 3, row: 3 }],
[2, { col: 4, row: 3 }],
Expand All @@ -21,7 +76,7 @@ const NumberMap = new Map<number, { col: number, row: number }>([
[9, { col: 3, row: 4 }]
]);

const LetterMap = new Map<number, { col: number, row: number }>([
const HUDFontLetterMap = new Map<number, { col: number, row: number }>([
[0, { col: 0, row: 0 }],
[1, { col: 1, row: 0 }],
[2, { col: 2, row: 0 }],
Expand Down Expand Up @@ -50,69 +105,73 @@ const LetterMap = new Map<number, { col: number, row: number }>([
[25, { col: 1, row: 3 }]
]);

export enum TextAlignment {
CENTER,
LEFT,
RIGHT
}

export enum TextEffect {
NONE,
SHADOW,
BACKGROUND
}

export class TextRenderer {
private smallFont: HTMLImageElement;
private smallFontColors: Map<string, HTMLCanvasElement> = new Map();
private handles: Map<Font, FontHandle>;

constructor(private ctx: CanvasRenderingContext2D, colors: string[] = []) {
const allColors = [...colors];
this.smallFont = new Image();
this.smallFont.src = 'assets/smallFont.png';
if (this.smallFont.complete) {
this.onImageLoaded(allColors);
this.handles = new Map([
[Font.HUD_SMALL, this.buildFontHandle(HUDSmallFont, allColors)],
[Font.HUD_LARGE, this.buildFontHandle(HUDLargeFont, allColors)]
]);
}

private buildFontHandle(def: FontDefinition, colors: string[]): FontHandle {
const handle: FontHandle = {
def: def,
source: new Image(),
colors: new Map()
};
handle.source.src = def.atlas;
if (handle.source.complete) {
this.onImageLoaded(handle, colors);
} else {
this.smallFont.addEventListener('load', this.onImageLoaded.bind(this, allColors));
this.smallFont.addEventListener('error', () => { throw Error(`Unable to load "${this.smallFont.src}"`) });
handle.source.addEventListener('load', this.onImageLoaded.bind(this, handle, colors));
handle.source.addEventListener('error', () => { throw Error(`Unable to load "${handle.def.atlas}"`) });
}
return handle;
}

text(x: number, y: number, text: string, color: string | undefined, alignment: TextAlignment, effect: TextEffect = TextEffect.NONE, effectColor: string = '') {
const srcCanvas = this.smallFontColors.get(color?.toLowerCase() || '') || this.smallFont;
text(font: Font, x: number, y: number, text: string, color: string | undefined, alignment: TextAlignment, effect: TextEffect = TextEffect.NONE, effectColor: string = '') {
const h = this.handles.get(font);
assertIsDefined(h);
const charWidth = h.def.charWidth;
const charHeight = h.def.charHeight;
const charSpacing = h.def.charSpacing;
const srcCanvas = h.colors.get(color?.toLowerCase() || '') || h.source;
let x0 = x;
if (alignment === TextAlignment.RIGHT) {
x0 = x - text.length * CHAR_WIDTH - (text.length - 1) * CHAR_MARGIN;
x0 = x - text.length * charWidth - (text.length - 1) * charSpacing;
} else if (alignment === TextAlignment.CENTER) {
x0 = x - Math.floor((text.length * CHAR_WIDTH + (text.length - 1) * CHAR_MARGIN) / 2);
x0 = x - Math.floor((text.length * charWidth + (text.length - 1) * charSpacing) / 2);
}

if (effect === TextEffect.BACKGROUND) {
this.ctx.fillStyle = effectColor;
this.ctx.fillRect(x0 - 1, y - 1, text.length * (CHAR_WIDTH + CHAR_MARGIN) + 1, CHAR_HEIGHT + 2);
this.ctx.fillRect(x0 - 1, y - 1, text.length * (charWidth + charSpacing) + 1, charHeight + 2);
}

for (let i = 0; i < text.length; i++) {
const c = text.charCodeAt(i);
const { srcX, srcY } = this.codeToCoords(c);
const dstX = x0 + i * (CHAR_WIDTH + CHAR_MARGIN);
const { srcX, srcY } = this.codeToCoords(c, charWidth, charHeight);
const dstX = x0 + i * (charWidth + charSpacing);

if (effect === TextEffect.SHADOW) {
const shadowCanvas = this.smallFontColors.get(effectColor) || this.smallFont;
const shadowCanvas = h.colors.get(effectColor) || h.source;
this.ctx.drawImage(shadowCanvas,
srcX, srcY, CHAR_WIDTH, CHAR_HEIGHT,
dstX + 1, y + 1, CHAR_WIDTH, CHAR_HEIGHT);
srcX, srcY, charWidth, charHeight,
dstX + 1, y + 1, charWidth, charHeight);
}

this.ctx.drawImage(srcCanvas,
srcX, srcY, CHAR_WIDTH, CHAR_HEIGHT,
dstX, y, CHAR_WIDTH, CHAR_HEIGHT);
srcX, srcY, charWidth, charHeight,
dstX, y, charWidth, charHeight);
}
}

private onImageLoaded(colors: string[]) {
private onImageLoaded(handle: FontHandle, colors: string[]) {
colors.forEach(c => {
this.smallFontColors.set(c.toLowerCase(), this.createColor(this.smallFont, c));
handle.colors.set(c.toLowerCase(), this.createColor(handle.source, c));
});
}

Expand All @@ -133,7 +192,7 @@ export class TextRenderer {
return canvas;
}

private codeToCoords(code: number): { srcX: number, srcY: number } {
private codeToCoords(code: number, charWidth: number, charHeight: number): { srcX: number, srcY: number } {
let c = code;
if (c >= ASCII_SMALL_A && c <= ASCII_SMALL_A + ASCII_LETTERS) {
c -= ASCII_SMALL_A - ASCII_BIG_A; // Convert to upper case
Expand All @@ -143,10 +202,10 @@ export class TextRenderer {

if (c >= ASCII_0 && c <= ASCII_0 + ASCII_NUMBERS) {
c -= ASCII_0;
({ col, row } = NumberMap.get(c) || { col: 7, row: 4 });
({ col, row } = HUDFontNumberMap.get(c) || { col: 7, row: 4 });
} else if (c >= ASCII_BIG_A && c <= ASCII_BIG_A + ASCII_LETTERS) {
c -= ASCII_BIG_A;
({ col, row } = LetterMap.get(c) || { col: 7, row: 4 });
({ col, row } = HUDFontLetterMap.get(c) || { col: 7, row: 4 });
} else if (c === 46) {
col = 4;
row = 4;
Expand All @@ -158,6 +217,6 @@ export class TextRenderer {
row = 4;
}

return { srcX: col * (CHAR_WIDTH + CHAR_MARGIN), srcY: row * (CHAR_HEIGHT + CHAR_MARGIN) };
return { srcX: col * (charWidth + FONT_CHAR_PADDING), srcY: row * (charHeight + FONT_CHAR_PADDING) };
}
}
38 changes: 23 additions & 15 deletions src/script/scene/entities/overlay/cockpit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as THREE from 'three';
import { Palette, PaletteCategory, PaletteColor } from "../../../config/palettes/palette";
import { H_RES, LO_H_RES } from "../../../defs";
import { CanvasPainter } from "../../../render/screen/canvasPainter";
import { CHAR_HEIGHT, CHAR_MARGIN, TextAlignment } from "../../../render/screen/text";
import { FORWARD, Scene, SceneLayers, UP } from "../../scene";
import { Font, FontDefs, TextAlignment } from "../../../render/screen/text";
import { vectorHeading } from '../../../utils/math';
import { Entity } from "../../entity";
import { Palette, PaletteCategory, PaletteColor } from "../../../config/palettes/palette";
import { LO_H_RES } from "../../../defs";
import { PlayerEntity } from "../player";
import { FORWARD, Scene, SceneLayers, UP } from "../../scene";
import { updateTargetCamera } from '../../utils';
import { GroundTargetEntity } from '../groundTarget';
import { vectorHeading } from '../../../utils/math';
import { PlayerEntity } from "../player";
import { calculatePitchRoll, formatHeading } from './overlayUtils';
import { updateTargetCamera } from '../../utils';


// Pixels
Expand Down Expand Up @@ -100,6 +100,13 @@ export class CockpitEntity implements Entity {
render2D(targetWidth: number, targetHeight: number, camera: THREE.Camera, lists: Set<string>, painter: CanvasPainter, palette: Palette): void {
if (!lists.has(SceneLayers.Overlay)) return;

//! This should always be an integer!
const scale = Math.max(1, Math.round(targetWidth / H_RES));

const font = scale > 1 ? Font.HUD_LARGE : Font.HUD_SMALL;
const fontDef = FontDefs[font];
const charHeight = fontDef.charHeight;
const charSpacing = fontDef.charSpacing;
const hudColor = PaletteColor(palette, PaletteCategory.HUD_TEXT);

this.renderAttitudeIndicator(targetWidth, targetHeight, painter, palette);
Expand All @@ -112,7 +119,8 @@ export class CockpitEntity implements Entity {
this.renderMFD2(
CockpitMFD2X(targetWidth, targetHeight, MFDSize),
CockpitMFD2Y(targetWidth, targetHeight, MFDSize),
MFDSize, painter, hudColor, palette);
MFDSize, painter, hudColor, palette,
font, charHeight, charSpacing);
}

private renderAttitudeIndicator(targetWidth: number, targetHeight: number, painter: CanvasPainter, palette: Palette) {
Expand Down Expand Up @@ -287,25 +295,25 @@ export class CockpitEntity implements Entity {
.commit();
}

private renderMFD2(x: number, y: number, size: number, painter: CanvasPainter, hudColor: string, palette: Palette) {
private renderMFD2(x: number, y: number, size: number, painter: CanvasPainter, hudColor: string, palette: Palette, font: Font, charHeight: number, charSpacing: number) {
painter.setColor(hudColor);
painter.rectangle(x - 1, y - 1, size + 2, size + 2);

if (this.weaponsTarget === undefined) {
painter.setBackground(PaletteColor(palette, PaletteCategory.COCKPIT_MFD_BACKGROUND));
painter.rectangle(x, y, size, size, true);
painter.text(x + CHAR_MARGIN, y + size - CHAR_HEIGHT - CHAR_MARGIN, 'No target', hudColor);
painter.text(font, x + charSpacing, y + size - charHeight - charSpacing, 'No target', hudColor);
} else {
painter.clear(x, y, size, size);
painter.text(x + CHAR_MARGIN, y + CHAR_MARGIN,
painter.text(font, x + charSpacing, y + charSpacing,
this.weaponsTarget.targetType, hudColor);
painter.text(x + CHAR_MARGIN, y + CHAR_MARGIN * 2 + CHAR_HEIGHT,
painter.text(font, x + charSpacing, y + charSpacing * 2 + charHeight,
`at ${this.weaponsTarget.targetLocation}`, hudColor);
painter.text(x + CHAR_MARGIN, y + size - 2 * (CHAR_HEIGHT + CHAR_MARGIN),
painter.text(font, x + charSpacing, y + size - 2 * (charHeight + charSpacing),
`BRG ${formatHeading(this.weaponsTargetBearing)}`, hudColor);
painter.text(x + size - CHAR_MARGIN, y + size - 2 * (CHAR_HEIGHT + CHAR_MARGIN),
painter.text(font, x + size - charSpacing, y + size - 2 * (charHeight + charSpacing),
`${this.weaponsTargetZoomFactor.toFixed(0)}x`, hudColor, TextAlignment.RIGHT);
painter.text(x + CHAR_MARGIN, y + size - CHAR_HEIGHT - CHAR_MARGIN,
painter.text(font, x + charSpacing, y + size - charHeight - charSpacing,
`Range ${this.weaponsTargetRange.toFixed(1)} KM`, hudColor);
}
}
Expand Down
Loading

0 comments on commit b298d5b

Please sign in to comment.