entity/label/LabelHandler.tstypescript
import * as shaders from "../../shaders/label/label";
import { ALIGN, Label } from "./Label";
import { BaseBillboardHandler } from "../billboard/BaseBillboardHandler";
import { concatTypedArrays, spliceTypedArray } from "../../utils/shared";
import { EntityCollection } from "../EntityCollection";
import { LOCK_FREE } from "./LabelWorker";
import type { Planet } from "../../scene/Planet";
import type { WebGLBufferExt } from "../../webgl/Handler";
import type { ShaderProgram } from "../../webgl/ShaderProgram";
import { Vec2 } from "../../math/Vec2";
import { Vec3 } from "../../math/Vec3";
import { Vec4 } from "../../math/Vec4";
import { BaseBillboard } from "../billboard/BaseBillboard";
import { srgbToLinear } from "../../utils/colorSpace";
type LabelWorkerCallbackData = {
vertexArr: Float32Array;
texCoordArr: Float32Array;
gliphParamArr: Float32Array;
positionHighArr: Float32Array;
positionLowArr: Float32Array;
sizeArr: Float32Array;
offsetArr: Float32Array;
rgbaArr: Float32Array;
rotationArr: Float32Array;
fontIndexArr: Float32Array;
outlineArr: Float32Array;
outlineColorArr: Float32Array;
pickingColorArr: Float32Array;
};
const PICKINGCOLOR_BUFFER = 0;
const POSITION_BUFFER = 1;
const SIZE_BUFFER = 2;
const OFFSET_BUFFER = 3;
const RGBA_BUFFER = 4;
const ROTATION_BUFFER = 5;
const TEXCOORD_BUFFER = 6;
const VERTEX_BUFFER = 7;
const FONTINDEX_BUFFER = 8;
const OUTLINE_BUFFER = 9;
const OUTLINECOLOR_BUFFER = 10;
const EMPTY = -1.0;
const RTL = 1.0;
const OUTLINE_DEPTH_BIAS = 2.0;
class LabelPassHandler {
protected _owner: LabelHandler;
protected _isOutlinePass: boolean;
constructor(owner: LabelHandler, isOutlinePass: boolean) {
this._owner = owner;
this._isOutlinePass = isOutlinePass;
}
public drawOpaque() {
this._owner.drawPass(this._isOutlinePass, true);
}
public drawTransparent() {
this._owner.drawPass(this._isOutlinePass, false);
}
public drawTransparentForward() {
this._owner.drawPass(this._isOutlinePass, false, true);
}
}
class LabelHandler extends BaseBillboardHandler {
protected override _billboards: Label[];
protected _gliphParamBuffer: WebGLBufferExt | null;
protected _fontIndexBuffer: WebGLBufferExt | null;
protected _outlineBuffer: WebGLBufferExt | null;
protected _outlineColorBuffer: WebGLBufferExt | null;
protected _gliphParamArr: Float32Array;
protected _fontIndexArr: Float32Array;
protected _outlineArr: Float32Array;
protected _outlineColorArr: Float32Array;
public fillLabelHandler: LabelPassHandler;
public outlineLabelHandler: LabelPassHandler;
public _maxLetters: number;
constructor(entityCollection: EntityCollection, maxLetters: number = 21) {
super(entityCollection);
this._billboards = [];
this._gliphParamBuffer = null;
this._fontIndexBuffer = null;
this._outlineBuffer = null;
this._outlineColorBuffer = null;
this._gliphParamArr = new Float32Array([]);
this._fontIndexArr = new Float32Array([]);
this._outlineArr = new Float32Array([]);
this._outlineColorArr = new Float32Array([]);
this._buffersUpdateCallbacks[FONTINDEX_BUFFER] = this.createFontIndexBuffer;
this._buffersUpdateCallbacks[OUTLINE_BUFFER] = this.createOutlineBuffer;
this._buffersUpdateCallbacks[OUTLINECOLOR_BUFFER] = this.createOutlineColorBuffer;
this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);
this.fillLabelHandler = new LabelPassHandler(this, false);
this.outlineLabelHandler = new LabelPassHandler(this, true);
this._maxLetters = maxLetters;
}
public override initProgram() {
if (this._renderer && this._renderer.handler && this._renderer.handler.gl) {
this._renderer.addPrograms(shaders.label_webgl2(), shaders.labelPicking());
if (!this._renderer.deferredDisabled) {
this._renderer.addPrograms(shaders.label_woit());
}
}
}
public get labels(): Label[] {
return this._billboards;
}
public override add(label: Label) {
if (!label._handler) {
label._handler = this;
this.assignFontAtlas(label);
this.refresh();
}
}
public updateFonts() {
let l = [...this._billboards];
this._billboards = [];
for (let i = 0; i < l.length; i++) {
this.assignFontAtlas(l[i]);
}
}
protected _addLabelToArrays(label: Label) {
this._renderer && this._renderer.labelWorker.make({ handler: this, label: label });
}
public assignFontAtlas(label: Label) {
if (this._entityCollection && this._renderer) {
label.assignFontAtlas(this._renderer.fontAtlas);
this._addLabelToArrays(label);
} else {
this._billboards.push(label);
}
}
public workerCallback(data: LabelWorkerCallbackData, label: Label) {
if (label._lockId !== LOCK_FREE && label._handler && this.isEqual(label._handler)) {
label._isReady = true;
label._lockId = LOCK_FREE;
label._handlerIndex = this._billboards.length;
this._billboards.push(label);
this._vertexArr = concatTypedArrays(this._vertexArr, data.vertexArr) as Float32Array;
this._texCoordArr = concatTypedArrays(this._texCoordArr, data.texCoordArr) as Float32Array;
this._gliphParamArr = concatTypedArrays(this._gliphParamArr, data.gliphParamArr) as Float32Array;
this._positionHighArr = concatTypedArrays(this._positionHighArr, data.positionHighArr) as Float32Array;
this._positionLowArr = concatTypedArrays(this._positionLowArr, data.positionLowArr) as Float32Array;
this._sizeArr = concatTypedArrays(this._sizeArr, data.sizeArr) as Float32Array;
this._offsetArr = concatTypedArrays(this._offsetArr, data.offsetArr) as Float32Array;
this._rgbaArr = concatTypedArrays(this._rgbaArr, data.rgbaArr) as Float32Array;
this._rotationArr = concatTypedArrays(this._rotationArr, data.rotationArr) as Float32Array;
this._fontIndexArr = concatTypedArrays(this._fontIndexArr, data.fontIndexArr) as Float32Array;
this._outlineArr = concatTypedArrays(this._outlineArr, data.outlineArr) as Float32Array;
this._outlineColorArr = concatTypedArrays(this._outlineColorArr, data.outlineColorArr) as Float32Array;
this._pickingColorArr = concatTypedArrays(this._pickingColorArr, data.pickingColorArr) as Float32Array;
this._insertBillboardByOpacity(label._handlerIndex, this._isBillboardOpaque(label));
label.update();
this.refresh();
}
}
protected override _isBillboardOpaque(billboard: BaseBillboard): boolean {
const label = billboard as Label;
return label._color.w >= 1.0;
}
protected override _swapBillboardData(firstIndex: number, secondIndex: number) {
if (firstIndex === secondIndex) {
return;
}
this._swapArrayItems(this._rgbaArr, 24 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._outlineColorArr, 24 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._texCoordArr, 24 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._gliphParamArr, 24 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._positionHighArr, 18 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._positionLowArr, 18 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._offsetArr, 12 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._pickingColorArr, 18 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._vertexArr, 12 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._sizeArr, 6 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._rotationArr, 6 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._fontIndexArr, 6 * this._maxLetters, firstIndex, secondIndex);
this._swapArrayItems(this._outlineArr, 6 * this._maxLetters, firstIndex, secondIndex);
const firstLabel = this._billboards[firstIndex];
const secondLabel = this._billboards[secondIndex];
this._billboards[firstIndex] = secondLabel;
this._billboards[secondIndex] = firstLabel;
firstLabel._handlerIndex = secondIndex;
secondLabel._handlerIndex = firstIndex;
}
public override clear() {
// @ts-ignore
this._texCoordArr = null;
// @ts-ignore
this._gliphParamArr = null;
// @ts-ignore
this._vertexArr = null;
// @ts-ignore
this._positionHighArr = null;
// @ts-ignore
this._positionLowArr = null;
// @ts-ignore
this._sizeArr = null;
// @ts-ignore
this._offsetArr = null;
// @ts-ignore
this._rgbaArr = null;
// @ts-ignore
this._rotationArr = null;
// @ts-ignore
this._fontIndexArr = null;
// @ts-ignore
this._outlineArr = null;
// @ts-ignore
this._outlineColorArr = null;
this._texCoordArr = new Float32Array([]);
this._gliphParamArr = new Float32Array([]);
this._vertexArr = new Float32Array([]);
this._positionHighArr = new Float32Array([]);
this._positionLowArr = new Float32Array([]);
this._sizeArr = new Float32Array([]);
this._offsetArr = new Float32Array([]);
this._rgbaArr = new Float32Array([]);
this._rotationArr = new Float32Array([]);
this._fontIndexArr = new Float32Array([]);
this._outlineArr = new Float32Array([]);
this._outlineColorArr = new Float32Array([]);
this._removeBillboards();
this._deleteBuffers();
this.refresh();
}
protected override _deleteBuffers() {
if (this._renderer) {
let gl = this._renderer.handler.gl!;
gl.deleteBuffer(this._gliphParamBuffer!);
gl.deleteBuffer(this._sizeBuffer!);
gl.deleteBuffer(this._fontIndexBuffer!);
gl.deleteBuffer(this._texCoordBuffer!);
gl.deleteBuffer(this._outlineBuffer!);
gl.deleteBuffer(this._outlineColorBuffer!);
gl.deleteBuffer(this._positionHighBuffer!);
gl.deleteBuffer(this._positionLowBuffer!);
gl.deleteBuffer(this._sizeBuffer!);
gl.deleteBuffer(this._offsetBuffer!);
gl.deleteBuffer(this._rgbaBuffer!);
gl.deleteBuffer(this._rotationBuffer!);
gl.deleteBuffer(this._vertexBuffer!);
gl.deleteBuffer(this._texCoordBuffer!);
gl.deleteBuffer(this._pickingColorBuffer!);
this._gliphParamBuffer = null;
this._sizeBuffer = null;
this._fontIndexBuffer = null;
this._texCoordBuffer = null;
this._outlineBuffer = null;
this._outlineColorBuffer = null;
this._positionHighBuffer = null;
this._positionLowBuffer = null;
this._sizeBuffer = null;
this._offsetBuffer = null;
this._rgbaBuffer = null;
this._rotationBuffer = null;
this._vertexBuffer = null;
this._texCoordBuffer = null;
this._pickingColorBuffer = null;
}
}
protected override _getOpaqueProgram(): ShaderProgram {
return this._renderer!.handler.programs.label;
}
protected override _getTransparentProgram(): ShaderProgram {
return this._renderer!.handler.programs.labelWoit;
}
public override drawOpaque() {
if (this._billboards.length) {
this.update();
this.outlineLabelHandler.drawOpaque();
this.fillLabelHandler.drawOpaque();
}
}
public override drawTransparent() {
if (this._billboards.length) {
this.update();
this.outlineLabelHandler.drawTransparent();
this.fillLabelHandler.drawTransparent();
}
}
public override drawTransparentForward() {
if (this._billboards.length) {
this.update();
this.outlineLabelHandler.drawTransparentForward();
this.fillLabelHandler.drawTransparentForward();
}
}
public drawPass(isOutlinePass: boolean, opaquePass: boolean, drawInForward: boolean = false) {
if (!this._billboards.length) {
return;
}
const startBillboardIndex = opaquePass ? 0 : this._opaqueCounterIndex;
const endBillboardIndex = opaquePass ? this._opaqueCounterIndex : this._billboards.length;
if (endBillboardIndex <= startBillboardIndex) {
return;
}
const labelProgram = opaquePass || drawInForward ? this._getOpaqueProgram() : this._getTransparentProgram();
const depthWrite = opaquePass && !drawInForward;
this._drawLabelPass(startBillboardIndex, endBillboardIndex, labelProgram, isOutlinePass, depthWrite);
}
protected _displayOutlinePASS(startBillboardIndex: number, endBillboardIndex: number, labelProgram: ShaderProgram) {
this._drawLabelPass(startBillboardIndex, endBillboardIndex, labelProgram, true);
}
protected _displayFillPASS(startBillboardIndex: number, endBillboardIndex: number, labelProgram: ShaderProgram) {
this._drawLabelPass(startBillboardIndex, endBillboardIndex, labelProgram, false);
}
protected _drawLabelPass(
startBillboardIndex: number,
endBillboardIndex: number,
labelProgram: ShaderProgram,
isOutlinePass: boolean,
depthWrite: boolean = true
) {
let r = this._renderer!;
let h = r.handler;
labelProgram.activate();
let sh = labelProgram;
let sha = sh.attributes,
shu = sh.uniforms;
let gl = h.gl!,
ec = this._entityCollection;
const usePremultipliedBlend = labelProgram === h.programs.label;
let fontTextureArray = r.fontAtlas.textureArray;
if (!fontTextureArray) {
return;
}
gl.disable(gl.CULL_FACE);
this._configureDepthPass(depthWrite);
if (usePremultipliedBlend) {
r.enableBlendOneSrcAlpha();
}
if (labelProgram === h.programs.labelWoit) {
gl.uniform1f(shu.useReverseDepth, r.activeCamera.reverseDepthActive ? 1.0 : 0.0);
}
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D_ARRAY, fontTextureArray);
gl.uniform1i(shu.fontTextureArr, 0);
gl.uniform4fv(shu.sdfParamsArr, r.fontAtlas.sdfParamsArr);
gl.uniformMatrix4fv(shu.viewMatrix, false, r.activeCamera.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, r.activeCamera.getProjectionMatrix());
gl.uniform3fv(shu.eyePositionHigh, r.activeCamera.eyeHigh);
gl.uniform3fv(shu.eyePositionLow, r.activeCamera.eyeLow);
gl.uniform4f(
shu.scaleByDistance,
ec.scaleByDistance[0],
ec.scaleByDistance[1],
ec.scaleByDistance[2],
ec.scaleByDistance[3]
);
gl.uniform1f(shu.uFocusDistance, r.activeCamera.isOrthographic ? r.activeCamera.focusDistance : 0.0);
gl.uniform1f(shu.opacity, ec._fadingOpacity);
gl.uniform1f(shu.planetRadius, (ec.scene as Planet)._planetRadius2 || 0);
gl.uniform2fv(shu.viewport, [h.canvas!.clientWidth, h.canvas!.clientHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer!);
gl.vertexAttribPointer(sha.a_texCoord, this._texCoordBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gliphParamBuffer!);
gl.vertexAttribPointer(sha.a_gliphParam, this._gliphParamBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer!);
gl.vertexAttribPointer(sha.a_vertices, this._vertexBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionHighBuffer!);
gl.vertexAttribPointer(sha.a_positionsHigh, this._positionHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionLowBuffer!);
gl.vertexAttribPointer(sha.a_positionsLow, this._positionLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer!);
gl.vertexAttribPointer(sha.a_size, this._sizeBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer!);
gl.vertexAttribPointer(sha.a_rotation, this._rotationBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer!);
gl.vertexAttribPointer(sha.a_offset, this._offsetBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._fontIndexBuffer!);
gl.vertexAttribPointer(sha.a_fontIndex, this._fontIndexBuffer!.itemSize, gl.FLOAT, false, 0, 0);
const numLabels = endBillboardIndex - startBillboardIndex;
if (numLabels <= 0) {
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
if (usePremultipliedBlend) {
r.enableBlendDefault();
}
this._restoreDepthPass(depthWrite);
gl.enable(gl.CULL_FACE);
return;
}
const startVertexIndex = startBillboardIndex * 6 * this._maxLetters;
const vertexCount = numLabels * 6 * this._maxLetters;
const depthOffset = isOutlinePass ? ec.depthOffset + OUTLINE_DEPTH_BIAS : ec.depthOffset;
gl.uniform1i(shu.isOutlinePass, isOutlinePass ? 1 : 0);
gl.uniform1f(shu.depthOffset, depthOffset);
gl.uniform1f(shu.depthOffsetNear, r.activeCamera.frustum.depthOffsetNear);
if (isOutlinePass) {
gl.bindBuffer(gl.ARRAY_BUFFER, this._outlineColorBuffer!);
gl.vertexAttribPointer(sha.a_rgba, this._outlineColorBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._outlineBuffer!);
gl.vertexAttribPointer(sha.a_outline, this._outlineBuffer!.itemSize, gl.FLOAT, false, 0, 0);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, this._rgbaBuffer!);
gl.vertexAttribPointer(sha.a_rgba, this._rgbaBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._outlineBuffer!);
gl.vertexAttribPointer(sha.a_outline, this._outlineBuffer!.itemSize, gl.FLOAT, false, 0, 0);
}
gl.drawArrays(gl.TRIANGLES, startVertexIndex, vertexCount);
gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
if (usePremultipliedBlend) {
r.enableBlendDefault();
}
this._restoreDepthPass(depthWrite);
gl.enable(gl.CULL_FACE);
}
protected override _displayPASS(
startBillboardIndex: number,
endBillboardIndex: number,
labelProgram: ShaderProgram
) {
this._displayOutlinePASS(startBillboardIndex, endBillboardIndex, labelProgram);
this._displayFillPASS(startBillboardIndex, endBillboardIndex, labelProgram);
}
protected override _pickingPASS() {
let r = this._renderer!;
let h = r.handler;
h.programs.labelPicking.activate();
let sh = h.programs.labelPicking;
let sha = sh.attributes,
shu = sh.uniforms;
let gl = h.gl!,
ec = this._entityCollection;
let rn = ec.scene;
const disableDepthTest = (r.activeCamera as any).slope > 0.5;
if (disableDepthTest) {
gl.disable(gl.DEPTH_TEST);
}
gl.disable(gl.CULL_FACE);
gl.uniformMatrix4fv(shu.viewMatrix, false, r.activeCamera.getViewMatrix());
gl.uniformMatrix4fv(shu.projectionMatrix, false, r.activeCamera.getProjectionMatrix());
gl.uniform3fv(shu.eyePositionHigh, r.activeCamera.eyeHigh);
gl.uniform3fv(shu.eyePositionLow, r.activeCamera.eyeLow);
gl.uniform4f(
shu.scaleByDistance,
ec.scaleByDistance[0],
ec.scaleByDistance[1],
ec.scaleByDistance[2],
ec.scaleByDistance[3]
);
gl.uniform1f(shu.uFocusDistance, r.activeCamera.isOrthographic ? r.activeCamera.focusDistance : 0.0);
gl.uniform1f(shu.opacity, ec._fadingOpacity);
gl.uniform1f(shu.planetRadius, (rn as Planet)._planetRadius2 || 0);
gl.uniform2fv(shu.viewport, [h.canvas!.clientWidth, h.canvas!.clientHeight]);
gl.bindBuffer(gl.ARRAY_BUFFER, this._texCoordBuffer!);
gl.vertexAttribPointer(sha.a_texCoord, this._texCoordBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._gliphParamBuffer!);
gl.vertexAttribPointer(sha.a_gliphParam, this._gliphParamBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer!);
gl.vertexAttribPointer(sha.a_vertices, this._vertexBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionHighBuffer!);
gl.vertexAttribPointer(sha.a_positionsHigh, this._positionHighBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._positionLowBuffer!);
gl.vertexAttribPointer(sha.a_positionsLow, this._positionLowBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._sizeBuffer!);
gl.vertexAttribPointer(sha.a_size, this._sizeBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._rotationBuffer!);
gl.vertexAttribPointer(sha.a_rotation, this._rotationBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._offsetBuffer!);
gl.vertexAttribPointer(sha.a_offset, this._offsetBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this._pickingColorBuffer!);
gl.vertexAttribPointer(sha.a_rgba, this._pickingColorBuffer!.itemSize, gl.FLOAT, false, 0, 0);
gl.uniform1f(shu.depthOffset, ec.depthOffset);
gl.uniform1f(shu.depthOffsetNear, r.activeCamera.frustum.depthOffsetNear);
gl.drawArrays(gl.TRIANGLES, 0, this._vertexBuffer!.numItems);
if (disableDepthTest) {
gl.enable(gl.DEPTH_TEST);
}
gl.enable(gl.CULL_FACE);
}
protected override _removeBillboard(label: Label) {
let removeIndex = label._handlerIndex;
if (removeIndex < this._opaqueCounterIndex) {
this._opaqueCounterIndex--;
this._swapBillboardData(removeIndex, this._opaqueCounterIndex);
removeIndex = this._opaqueCounterIndex;
}
const lastIndex = this._billboards.length - 1;
this._swapBillboardData(removeIndex, lastIndex);
this._billboards.pop();
let ml = 24 * this._maxLetters;
let i = lastIndex * ml;
this._rgbaArr = spliceTypedArray<Float32Array>(this._rgbaArr, i, ml);
this._outlineColorArr = spliceTypedArray<Float32Array>(this._outlineColorArr, i, ml);
this._texCoordArr = spliceTypedArray<Float32Array>(this._texCoordArr, i, ml);
this._gliphParamArr = spliceTypedArray<Float32Array>(this._gliphParamArr, i, ml);
ml = 18 * this._maxLetters;
i = lastIndex * ml;
this._positionHighArr = spliceTypedArray<Float32Array>(this._positionHighArr, i, ml);
this._positionLowArr = spliceTypedArray<Float32Array>(this._positionLowArr, i, ml);
this._pickingColorArr = spliceTypedArray<Float32Array>(this._pickingColorArr, i, ml);
ml = 12 * this._maxLetters;
i = lastIndex * ml;
this._offsetArr = spliceTypedArray<Float32Array>(this._offsetArr, i, ml);
ml = 12 * this._maxLetters;
i = lastIndex * ml;
this._vertexArr = spliceTypedArray<Float32Array>(this._vertexArr, i, ml);
ml = 6 * this._maxLetters;
i = lastIndex * ml;
this._sizeArr = spliceTypedArray<Float32Array>(this._sizeArr, i, ml);
this._rotationArr = spliceTypedArray<Float32Array>(this._rotationArr, i, ml);
this._fontIndexArr = spliceTypedArray<Float32Array>(this._fontIndexArr, i, ml);
this._outlineArr = spliceTypedArray<Float32Array>(this._outlineArr, i, ml);
this.refresh();
label._handlerIndex = -1;
label._handler = null;
label._isReady = false;
}
public setText(
index: number,
text: string,
fontIndex: number,
align: number,
letterSpacing: number = 0,
isRTL: boolean = false
) {
text = text.normalize("NFKC");
let fa = this._renderer!.fontAtlas.atlasesArr[fontIndex];
if (!fa) return;
let i = index * 24 * this._maxLetters;
let a = this._texCoordArr,
g = this._gliphParamArr;
let c = 0;
let len = Math.min(this._maxLetters, text.length);
let _rtl_ = 0.0;
if (isRTL) {
_rtl_ = RTL;
}
let offset = 0.0;
let kern = fa.kernings;
const spaceGlyph = fa.get(32);
for (c = 0; c < len; c++) {
let j = i + c * 24;
let char = text[c];
let unicode = char.codePointAt(0) ?? 32;
let n = fa.get(unicode) || spaceGlyph;
if (!n) continue;
let tc = n.texCoords;
let m = n.metrics;
a[j] = tc[0];
a[j + 1] = tc[1];
a[j + 2] = offset;
a[j + 3] = _rtl_;
a[j + 4] = tc[2];
a[j + 5] = tc[3];
a[j + 6] = offset;
a[j + 7] = _rtl_;
a[j + 8] = tc[4];
a[j + 9] = tc[5];
a[j + 10] = offset;
a[j + 11] = _rtl_;
a[j + 12] = tc[6];
a[j + 13] = tc[7];
a[j + 14] = offset;
a[j + 15] = _rtl_;
a[j + 16] = tc[8];
a[j + 17] = tc[9];
a[j + 18] = offset;
a[j + 19] = _rtl_;
a[j + 20] = tc[10];
a[j + 21] = tc[11];
a[j + 22] = offset;
a[j + 23] = _rtl_;
//
// Gliph
//
g[j] = m.nWidth;
g[j + 1] = m.nHeight;
g[j + 2] = m.nXOffset;
g[j + 3] = m.nYOffset;
g[j + 4] = m.nWidth;
g[j + 5] = m.nHeight;
g[j + 6] = m.nXOffset;
g[j + 7] = m.nYOffset;
g[j + 8] = m.nWidth;
g[j + 9] = m.nHeight;
g[j + 10] = m.nXOffset;
g[j + 11] = m.nYOffset;
g[j + 12] = m.nWidth;
g[j + 13] = m.nHeight;
g[j + 14] = m.nXOffset;
g[j + 15] = m.nYOffset;
g[j + 16] = m.nWidth;
g[j + 17] = m.nHeight;
g[j + 18] = m.nXOffset;
g[j + 19] = m.nYOffset;
g[j + 20] = m.nWidth;
g[j + 21] = m.nHeight;
g[j + 22] = m.nXOffset;
g[j + 23] = m.nYOffset;
let k = kern[unicode];
if (k && text[c + 1]) {
let nextUnicode = text[c + 1].codePointAt(0) ?? 32;
let kk = k[nextUnicode];
if (kk) {
offset += m.nAdvance + kk + letterSpacing;
} else {
offset += m.nAdvance + letterSpacing;
}
} else {
offset += m.nAdvance + letterSpacing;
}
}
// 49/512 - font atlas left border letter offset
if (align === ALIGN.CENTER || align === ALIGN.RIGHT) {
offset *= align === ALIGN.CENTER ? -0.5 : -1.0;
for (c = 0; c < len; c++) {
let j = i + c * 24;
a[j + 2] += offset;
a[j + 6] += offset;
a[j + 10] += offset;
a[j + 14] += offset;
a[j + 18] += offset;
a[j + 22] += offset;
}
}
for (; c < this._maxLetters; c++) {
let j = i + c * 24;
a[j + 3] = EMPTY;
a[j + 7] = EMPTY;
a[j + 11] = EMPTY;
a[j + 15] = EMPTY;
a[j + 19] = EMPTY;
a[j + 23] = EMPTY;
}
this._setChangedBuffer(TEXCOORD_BUFFER);
}
public override setPositionArr(index: number, positionHigh: Vec3, positionLow: Vec3) {
let i = index * 18 * this._maxLetters;
let a = this._positionHighArr,
x = positionHigh.x,
y = positionHigh.y,
z = positionHigh.z,
b = this._positionLowArr,
xl = positionLow.x,
yl = positionLow.y,
zl = positionLow.z;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 18;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = x;
a[j + 4] = y;
a[j + 5] = z;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = z;
a[j + 9] = x;
a[j + 10] = y;
a[j + 11] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = x;
a[j + 16] = y;
a[j + 17] = z;
// low
b[j] = xl;
b[j + 1] = yl;
b[j + 2] = zl;
b[j + 3] = xl;
b[j + 4] = yl;
b[j + 5] = zl;
b[j + 6] = xl;
b[j + 7] = yl;
b[j + 8] = zl;
b[j + 9] = xl;
b[j + 10] = yl;
b[j + 11] = zl;
b[j + 12] = xl;
b[j + 13] = yl;
b[j + 14] = zl;
b[j + 15] = xl;
b[j + 16] = yl;
b[j + 17] = zl;
}
this._setChangedPositionRange(index * 18 * this._maxLetters, (index + 1) * 18 * this._maxLetters);
}
public override setPickingColorArr(index: number, color: Vec3) {
let i = index * 18 * this._maxLetters;
let a = this._pickingColorArr,
x = color.x / 255,
y = color.y / 255,
z = color.z / 255;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 18;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = x;
a[j + 4] = y;
a[j + 5] = z;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = z;
a[j + 9] = x;
a[j + 10] = y;
a[j + 11] = z;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = x;
a[j + 16] = y;
a[j + 17] = z;
}
this._setChangedBuffer(PICKINGCOLOR_BUFFER);
}
public override setSizeArr(index: number, size: number) {
let i = index * 6 * this._maxLetters;
let a = this._sizeArr;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 6;
a[j] = size;
a[j + 1] = size;
a[j + 2] = size;
a[j + 3] = size;
a[j + 4] = size;
a[j + 5] = size;
}
this._setChangedBuffer(SIZE_BUFFER);
}
public override setOffsetArr(index: number, offset: Vec2) {
let i = index * 12 * this._maxLetters;
let a = this._offsetArr,
x = offset.x,
y = offset.y;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 12;
a[j] = x;
a[j + 1] = y;
a[j + 2] = x;
a[j + 3] = y;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = x;
a[j + 7] = y;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = x;
a[j + 11] = y;
}
this._setChangedBuffer(OFFSET_BUFFER);
}
public override setRgbaArr(index: number, rgba: Vec4) {
let i = index * 24 * this._maxLetters;
let a = this._rgbaArr,
x = srgbToLinear(rgba.x),
y = srgbToLinear(rgba.y),
z = srgbToLinear(rgba.z),
w = rgba.w;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 24;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = w;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = z;
a[j + 7] = w;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = z;
a[j + 11] = w;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = w;
a[j + 16] = x;
a[j + 17] = y;
a[j + 18] = z;
a[j + 19] = w;
a[j + 20] = x;
a[j + 21] = y;
a[j + 22] = z;
a[j + 23] = w;
}
const opacityChanged = this._updateBillboardOpacityState(index, w >= 1.0);
if (opacityChanged) {
this.refresh();
} else {
this._setChangedBuffer(RGBA_BUFFER);
}
}
public setOutlineColorArr(index: number, rgba: Vec4) {
let i = index * 24 * this._maxLetters;
let a = this._outlineColorArr,
x = srgbToLinear(rgba.x),
y = srgbToLinear(rgba.y),
z = srgbToLinear(rgba.z),
w = rgba.w;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 24;
a[j] = x;
a[j + 1] = y;
a[j + 2] = z;
a[j + 3] = w;
a[j + 4] = x;
a[j + 5] = y;
a[j + 6] = z;
a[j + 7] = w;
a[j + 8] = x;
a[j + 9] = y;
a[j + 10] = z;
a[j + 11] = w;
a[j + 12] = x;
a[j + 13] = y;
a[j + 14] = z;
a[j + 15] = w;
a[j + 16] = x;
a[j + 17] = y;
a[j + 18] = z;
a[j + 19] = w;
a[j + 20] = x;
a[j + 21] = y;
a[j + 22] = z;
a[j + 23] = w;
}
const opacityOffset = index * 24 * this._maxLetters + 3;
const fillAlpha = this._rgbaArr[opacityOffset];
const opacityChanged = this._updateBillboardOpacityState(index, fillAlpha >= 1.0);
if (opacityChanged) {
this.refresh();
} else {
this._setChangedBuffer(OUTLINECOLOR_BUFFER);
}
}
public setOutlineArr(index: number, outline: number) {
let i = index * 6 * this._maxLetters;
let a = this._outlineArr;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 6;
a[j] = outline;
a[j + 1] = outline;
a[j + 2] = outline;
a[j + 3] = outline;
a[j + 4] = outline;
a[j + 5] = outline;
}
this._setChangedBuffer(OUTLINE_BUFFER);
}
public override setRotationArr(index: number, rotation: number) {
let i = index * 6 * this._maxLetters;
let a = this._rotationArr;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 6;
a[j] = rotation;
a[j + 1] = rotation;
a[j + 2] = rotation;
a[j + 3] = rotation;
a[j + 4] = rotation;
a[j + 5] = rotation;
}
this._setChangedBuffer(ROTATION_BUFFER);
}
public override setVisibility(index: number, visibility: boolean) {
let vArr;
if (visibility) {
vArr = [0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, 0];
} else {
vArr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
this.setVertexArr(index, vArr);
}
public override setVertexArr(index: number, vertexArr: number[] | Float32Array) {
let i = index * 12 * this._maxLetters;
let a = this._vertexArr;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 12;
a[j] = vertexArr[0];
a[j + 1] = vertexArr[1];
a[j + 2] = vertexArr[2];
a[j + 3] = vertexArr[3];
a[j + 4] = vertexArr[4];
a[j + 5] = vertexArr[5];
a[j + 6] = vertexArr[6];
a[j + 7] = vertexArr[7];
a[j + 8] = vertexArr[8];
a[j + 9] = vertexArr[9];
a[j + 10] = vertexArr[10];
a[j + 11] = vertexArr[11];
}
this._setChangedBuffer(VERTEX_BUFFER);
}
public setFontIndexArr(index: number, fontIndex: number) {
let i = index * 6 * this._maxLetters;
let a = this._fontIndexArr;
for (let q = 0; q < this._maxLetters; q++) {
let j = i + q * 6;
a[j] = fontIndex;
a[j + 1] = fontIndex;
a[j + 2] = fontIndex;
a[j + 3] = fontIndex;
a[j + 4] = fontIndex;
a[j + 5] = fontIndex;
}
this._setChangedBuffer(FONTINDEX_BUFFER);
}
public override createSizeBuffer() {
let h = this._renderer!.handler;
h.gl!.deleteBuffer(this._sizeBuffer as WebGLBuffer);
this._sizeBuffer = h.createArrayBuffer(this._sizeArr, 1, this._sizeArr.length);
}
public createFontIndexBuffer() {
let h = this._renderer!.handler;
h.gl!.deleteBuffer(this._fontIndexBuffer as WebGLBuffer);
this._fontIndexBuffer = h.createArrayBuffer(this._fontIndexArr, 1, this._fontIndexArr.length);
}
public override createTexCoordBuffer() {
let h = this._renderer!.handler;
h.gl!.deleteBuffer(this._texCoordBuffer as WebGLBuffer);
this._texCoordBuffer = h.createArrayBuffer(this._texCoordArr, 4, this._texCoordArr.length / 4);
h.gl!.deleteBuffer(this._gliphParamBuffer as WebGLBuffer);
this._gliphParamBuffer = h.createArrayBuffer(this._gliphParamArr, 4, this._gliphParamArr.length / 4);
}
public createOutlineBuffer() {
let h = this._renderer!.handler;
h.gl!.deleteBuffer(this._outlineBuffer as WebGLBuffer);
this._outlineBuffer = h.createArrayBuffer(this._outlineArr, 1, this._outlineArr.length);
}
public createOutlineColorBuffer() {
let h = this._renderer!.handler;
h.gl!.deleteBuffer(this._outlineColorBuffer as WebGLBuffer);
this._outlineColorBuffer = h.createArrayBuffer(this._outlineColorArr, 4, this._outlineColorArr.length / 4);
}
public setMaxLetters(c: number) {
this._maxLetters = c;
// TODO: ...
}
}
export { LabelHandler };