UNPKG

oxygen-core

Version:

Oxygen game engine (Xenon Core for browsers)

339 lines (280 loc) 8.5 kB
import VerticesRenderer from './VerticesRenderer'; import System from '../systems/System'; import { vec4 } from '../utils/gl-matrix'; import { stringToRGBA } from '../utils'; export default class TextRenderer extends VerticesRenderer { static factory() { return new TextRenderer(); } static get propsTypes() { return { visible: VerticesRenderer.propsTypes.visible, shader: VerticesRenderer.propsTypes.shader, overrideUniforms: VerticesRenderer.propsTypes.overrideUniforms, overrideSamplers: VerticesRenderer.propsTypes.overrideSamplers, layers: VerticesRenderer.propsTypes.layers, text: 'string', font: 'asset(font)', color: 'rgba', colorOutline: 'rgba', halign: 'enum(left, center, right)', valign: 'enum(top, middle, bottom)', filtering: 'enum(nearest, linear)' }; } get text() { return this._text; } set text(value) { if (typeof value !== 'string') { throw new Error('`value` is not type of String!'); } this._text = value; this._rebuild = true; } get font() { return this._font; } set font(value) { if (typeof value !== 'string') { throw new Error('`value` is not type of String!'); } const assets = System.get('AssetSystem'); if (!assets) { throw new Error('There is no registered AssetSystem!'); } const font = assets.get(`font://${value}`); if (!font) { throw new Error(`There is no font asset loaded: ${value}`); } this._font = value; this._fontData = font.data.descriptor; this._rebuild = true; } get color() { return this._color; } set color(value) { if (typeof value === 'string') { value = stringToRGBA(value); } if (!(value instanceof Array) && !(value instanceof Float32Array)) { throw new Error('`value` is not type of either Array or Float32Array!'); } if (value.length < 4) { throw new Error('`value` array must have at least 4 items!'); } vec4.copy(this._color, value); const { overrideUniforms } = this; overrideUniforms.uColor = this._color; this.overrideUniforms = overrideUniforms; } get colorOutline() { return this._colorOutline; } set colorOutline(value) { if (typeof value === 'string') { value = stringToRGBA(value); } if (!(value instanceof Array) && !(value instanceof Float32Array)) { throw new Error('`value` is not type of either Array or Float32Array!'); } if (value.length < 4) { throw new Error('`value` array must have at least 4 items!'); } vec4.copy(this._colorOutline, value); const { overrideUniforms } = this; overrideUniforms.uColorOutline = this._colorOutline; this.overrideUniforms = overrideUniforms; } get halign() { return this._halign; } set halign(value) { if (typeof value !== 'string') { throw new Error('`value` is not type of String!'); } this._halign = value; this._rebuild = true; } get valign() { return this._valign; } set valign(value) { if (typeof value !== 'string') { throw new Error('`value` is not type of String!'); } this._valign = value; this._rebuild = true; } get filtering() { return this._filtering; } set filtering(value) { if (typeof value !== 'string') { throw new Error('`value` is not type of String!'); } this._filtering = value; const { _fontData } = this; if (!_fontData) { return; } const { pages } = _fontData; const samplers = {}; for (let i = 0, c = pages.size; i < c; ++i) { samplers[`sPage${i}`] = { texture: pages.get(i).file, filtering: value }; } this.overrideSamplers = samplers; } constructor() { super(); this._text = null; this._font = null; this._color = vec4.fromValues(1, 1, 1, 1); this._colorOutline = vec4.fromValues(0, 0, 0, 1); this._halign = 'left'; this._valign = 'top'; this._filtering = 'linear'; this._fontData = null; this._rebuild = true; } dispose() { super.dispose(); this._text = null; this._font = null; this._fontData = null; } onAttach() { const { overrideUniforms } = this; overrideUniforms.uColor = this._color; overrideUniforms.uColorOutline = this._colorOutline; this.overrideUniforms = overrideUniforms; } onRender(gl, renderer, deltaTime, layer) { this.ensureVertices(renderer); const { _text, _fontData } = this; if (!!_text && _text !== '' && !!_fontData) { super.onRender(gl, renderer, deltaTime); } } onPropertySerialize(name, value) { if (name === 'color' || name === 'colorOutline') { return [ ...value ]; } else if (name === 'overrideUniforms') { const result = super.onPropertySerialize(name, value); if (!result) { return null; } delete result.uColor; delete result.uColorOutline; return Object.keys(result).length > 0 ? result : null; } else if (name === 'overrideSamplers') { const result = super.onPropertySerialize(name, value); if (!result) { return null; } for (const key in result) { if (key.indexOf('sPage') === 0) { delete result[key]; } } return Object.keys(result).length > 0 ? result : null; } else { return super.onPropertySerialize(name, value); } } ensureVertices(renderer) { if (!this._rebuild) { return; } this._rebuild = false; const { _text, _fontData, _halign, _valign, _filtering } = this; if (!_text || _text === '' || !_fontData) { this.vertices = [0]; this.indices = [0, 0, 0]; return; } const { common, pages, chars } = _fontData; const { lineHeight, scaleW, scaleH } = common; const vertices = []; const indices = []; const samplers = {}; for (let i = 0, c = pages.size; i < c; ++i) { samplers[`sPage${i}`] = { texture: pages.get(i).file, filtering: _filtering }; } let x = 0; let y = 0; let lineStartPos = 0; for (let i = 0, c = _text.length, pos = 0; i < c; ++i) { const code = _text.charCodeAt(i); if (code === 10 || code === 13) { const { length } = vertices; if (_halign === 'right') { for (let j = lineStartPos; j < length; j += 5) { vertices[j] -= x; } } else if (_halign === 'center') { for (let j = lineStartPos; j < length; j += 5) { vertices[j] -= x * 0.5; } } x = 0; y += lineHeight; lineStartPos = vertices.length; continue; } const char = chars.get(code); if (!char) { continue; } const xs = x + char.xoffset; const ys = y + char.yoffset; const xe = xs + char.width; const ye = ys + char.height; const txs = char.x / scaleW; const tys = char.y / scaleH; const txe = (char.x + char.width) / scaleW; const tye = (char.y + char.height) / scaleH; vertices.push( xs, ys, txs, tys, char.page | 0, xe, ys, txe, tys, char.page | 0, xe, ye, txe, tye, char.page | 0, xs, ye, txs, tye, char.page | 0 ); indices.push( pos, pos + 1, pos + 2, pos + 2, pos + 3, pos ); pos += 4; x += char.xadvance; } const { length } = vertices; if (_halign === 'right') { for (let j = lineStartPos; j < length; j += 5) { vertices[j] -= x; } } else if (_halign === 'center') { for (let j = lineStartPos; j < length; j += 5) { vertices[j] -= x * 0.5; } } if (_valign === 'bottom') { for (let j = 1; j < length; j += 5) { vertices[j] -= y + lineHeight; } } else if (_valign === 'middle') { for (let j = 1; j < length; j += 5) { vertices[j] -= (y + lineHeight) * 0.5; } } this.vertices = vertices; this.indices = indices; this.overrideSamplers = samplers; } }