tslint changes

This commit is contained in:
Jeremy Simpson
2019-08-30 01:52:40 -07:00
parent 7603879d35
commit 7f7efbb94b
446 changed files with 70650 additions and 70661 deletions
+138 -138
View File
@@ -34,169 +34,169 @@ import { isIdentity } from '../common/mat2d';
const WHITE_FLOAT = colorToFloat(WHITE);
export function getColorFloat(color: number, alpha: number) {
return (color === WHITE && alpha === 1) ? WHITE_FLOAT : colorToFloatAlpha(color, alpha);
return (color === WHITE && alpha === 1) ? WHITE_FLOAT : colorToFloatAlpha(color, alpha);
}
export abstract class BaseSpriteBatch extends BaseStateBatch implements SpriteBatchBase {
tris = 0;
flushes = 0;
index = 0;
spritesCount = 0;
vertices!: Float32Array;
verticesUint32!: Uint32Array;
vao: VAO | undefined = undefined;
rectSprite: Sprite | undefined = undefined;
vertexBuffer: WebGLBuffer | undefined = undefined;
indexBuffer: WebGLBuffer | undefined = undefined;
spriteSheet: SpriteSheet | undefined = undefined;
floatsPerSprite: number;
batching = false;
private startBatchIndex = 0;
private startBatchSprites = 0;
constructor(
public gl: WebGLRenderingContext,
public capacity: number,
buffer: ArrayBuffer,
vertexBuffer: WebGLBuffer,
indexBuffer: WebGLBuffer,
public attributes: VAOAttributeDefinition[],
) {
super();
this.floatsPerSprite = getVAOAttributesSize(gl, attributes);
this.vertices = new Float32Array(buffer, 0, capacity * this.floatsPerSprite);
this.verticesUint32 = new Uint32Array(buffer, 0, capacity * this.floatsPerSprite);
this.vertexBuffer = vertexBuffer;
this.indexBuffer = indexBuffer;
this.vao = createVAO(gl, createVAOAttributes(gl, attributes, vertexBuffer), indexBuffer);
}
dispose() {
disposeBuffers(this.gl, this);
}
begin() {
if (!this.vao) {
throw new Error('Disposed');
}
tris = 0;
flushes = 0;
index = 0;
spritesCount = 0;
vertices!: Float32Array;
verticesUint32!: Uint32Array;
vao: VAO | undefined = undefined;
rectSprite: Sprite | undefined = undefined;
vertexBuffer: WebGLBuffer | undefined = undefined;
indexBuffer: WebGLBuffer | undefined = undefined;
spriteSheet: SpriteSheet | undefined = undefined;
floatsPerSprite: number;
batching = false;
private startBatchIndex = 0;
private startBatchSprites = 0;
constructor(
public gl: WebGLRenderingContext,
public capacity: number,
buffer: ArrayBuffer,
vertexBuffer: WebGLBuffer,
indexBuffer: WebGLBuffer,
public attributes: VAOAttributeDefinition[],
) {
super();
this.floatsPerSprite = getVAOAttributesSize(gl, attributes);
this.vertices = new Float32Array(buffer, 0, capacity * this.floatsPerSprite);
this.verticesUint32 = new Uint32Array(buffer, 0, capacity * this.floatsPerSprite);
this.vertexBuffer = vertexBuffer;
this.indexBuffer = indexBuffer;
this.vao = createVAO(gl, createVAOAttributes(gl, attributes, vertexBuffer), indexBuffer);
}
dispose() {
disposeBuffers(this.gl, this);
}
begin() {
if (!this.vao) {
throw new Error('Disposed');
}
this.batching = false;
this.vao.bind();
}
end() {
if (!this.vao) {
throw new Error('Disposed');
}
this.batching = false;
this.vao.bind();
}
end() {
if (!this.vao) {
throw new Error('Disposed');
}
this.flush();
this.vao.unbind();
}
drawBatch(batch: Batch) {
if (DEVELOPMENT && !isIdentity(this.transform)) {
throw new Error('Cannot transform batch');
}
this.flush();
this.vao.unbind();
}
drawBatch(batch: Batch) {
if (DEVELOPMENT && !isIdentity(this.transform)) {
throw new Error('Cannot transform batch');
}
const batchSpriteCount = (batch.length / this.floatsPerSprite) | 0;
const batchSpriteCount = (batch.length / this.floatsPerSprite) | 0;
if (this.capacity < (this.spritesCount + batchSpriteCount)) {
this.flush();
}
if (this.capacity < (this.spritesCount + batchSpriteCount)) {
this.flush();
}
this.vertices.set(batch, this.index);
this.index += batch.length;
this.spritesCount += batchSpriteCount;
this.tris += batchSpriteCount * 2;
}
startBatch() {
if (this.batching) {
throw new Error('Cannot start new batch');
}
this.vertices.set(batch, this.index);
this.index += batch.length;
this.spritesCount += batchSpriteCount;
this.tris += batchSpriteCount * 2;
}
startBatch() {
if (this.batching) {
throw new Error('Cannot start new batch');
}
this.startBatchIndex = this.index;
this.startBatchSprites = this.spritesCount;
this.batching = true;
}
finishBatch(): Batch | undefined {
if (!this.batching) {
throw new Error('Cannot finish batch');
}
this.startBatchIndex = this.index;
this.startBatchSprites = this.spritesCount;
this.batching = true;
}
finishBatch(): Batch | undefined {
if (!this.batching) {
throw new Error('Cannot finish batch');
}
this.batching = false;
this.batching = false;
try {
// const batch = aquireBuffer(this.index - this.startBatchIndex);
try {
// const batch = aquireBuffer(this.index - this.startBatchIndex);
// if (batch) {
// batch.set(this.vertices.subarray(this.startBatchIndex, this.index));
// }
// if (batch) {
// batch.set(this.vertices.subarray(this.startBatchIndex, this.index));
// }
// return batch;
return this.vertices.slice(this.startBatchIndex, this.index);
} catch {
return undefined;
}
}
releaseBatch(_batch: Batch) {
// releaseBuffer(batch);
}
flush() {
if (this.index === 0)
return;
// return batch;
return this.vertices.slice(this.startBatchIndex, this.index);
} catch {
return undefined;
}
}
releaseBatch(_batch: Batch) {
// releaseBuffer(batch);
}
flush() {
if (this.index === 0)
return;
if (!this.vao || !this.vertexBuffer) {
throw new Error('Disposed');
}
if (!this.vao || !this.vertexBuffer) {
throw new Error('Disposed');
}
const gl = this.gl;
const gl = this.gl;
if (this.batching) {
TIMING && timeStart('bufferSubData');
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices.subarray(0, this.startBatchIndex));
TIMING && timeEnd();
if (this.batching) {
TIMING && timeStart('bufferSubData');
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices.subarray(0, this.startBatchIndex));
TIMING && timeEnd();
TIMING && timeStart('vao.draw');
this.vao.draw(this.gl.TRIANGLES, this.startBatchSprites * 6, 0);
TIMING && timeEnd();
TIMING && timeStart('vao.draw');
this.vao.draw(this.gl.TRIANGLES, this.startBatchSprites * 6, 0);
TIMING && timeEnd();
this.spritesCount -= this.startBatchSprites;
this.index -= this.startBatchIndex;
this.vertices.copyWithin(0, this.startBatchIndex, this.startBatchIndex + this.index);
this.startBatchIndex = 0;
this.startBatchSprites = 0;
} else {
TIMING && timeStart('bufferSubData');
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices.subarray(0, this.index));
TIMING && timeEnd();
this.spritesCount -= this.startBatchSprites;
this.index -= this.startBatchIndex;
this.vertices.copyWithin(0, this.startBatchIndex, this.startBatchIndex + this.index);
this.startBatchIndex = 0;
this.startBatchSprites = 0;
} else {
TIMING && timeStart('bufferSubData');
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices.subarray(0, this.index));
TIMING && timeEnd();
TIMING && timeStart('vao.draw');
this.vao.draw(this.gl.TRIANGLES, this.spritesCount * 6, 0);
TIMING && timeEnd();
TIMING && timeStart('vao.draw');
this.vao.draw(this.gl.TRIANGLES, this.spritesCount * 6, 0);
TIMING && timeEnd();
this.spritesCount = 0;
this.index = 0;
}
this.spritesCount = 0;
this.index = 0;
}
this.flushes++;
}
this.flushes++;
}
}
function disposeBuffers(gl: WebGLRenderingContext, batch: BaseSpriteBatch) {
try {
if (batch.vao) {
batch.vao.dispose();
}
try {
if (batch.vao) {
batch.vao.dispose();
}
if (batch.vertexBuffer) {
gl.deleteBuffer(batch.vertexBuffer);
}
if (batch.vertexBuffer) {
gl.deleteBuffer(batch.vertexBuffer);
}
if (batch.indexBuffer) {
gl.deleteBuffer(batch.indexBuffer);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
if (batch.indexBuffer) {
gl.deleteBuffer(batch.indexBuffer);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
batch.vao = undefined;
batch.vertexBuffer = undefined;
batch.indexBuffer = undefined;
batch.vao = undefined;
batch.vertexBuffer = undefined;
batch.indexBuffer = undefined;
}
+59 -59
View File
@@ -4,73 +4,73 @@ import { createMat2D, copyMat2D, mulMat2D, rotateMat2D, scaleMat2D, translateMat
import { rect, copyRect } from '../common/rect';
interface SavedState {
globalAlpha: number;
transform: Matrix2D;
hasCrop: boolean;
cropRect: Rect;
globalAlpha: number;
transform: Matrix2D;
hasCrop: boolean;
cropRect: Rect;
}
function createEmptyState(): SavedState {
return {
transform: createMat2D(),
globalAlpha: 1,
hasCrop: false,
cropRect: rect(0, 0, 0, 0),
};
return {
transform: createMat2D(),
globalAlpha: 1,
hasCrop: false,
cropRect: rect(0, 0, 0, 0),
};
}
const stateCache = new ObjectCache<SavedState>(10, createEmptyState);
export abstract class BaseStateBatch {
globalAlpha = 1;
transform = createMat2D();
private savedStates: SavedState[] = [];
protected hasCrop = false;
protected cropRect = rect(0, 0, 0, 0);
crop(x: number, y: number, w: number, h: number) {
// console.error('Crop not supported');
this.hasCrop = true;
this.cropRect.x = x;
this.cropRect.y = y;
this.cropRect.w = w;
this.cropRect.h = h;
}
clearCrop() {
this.hasCrop = false;
}
save() {
const state = stateCache.get();
state.globalAlpha = this.globalAlpha;
state.hasCrop = this.hasCrop;
copyMat2D(state.transform, this.transform);
copyRect(state.cropRect, this.cropRect);
this.savedStates.push(state);
globalAlpha = 1;
transform = createMat2D();
private savedStates: SavedState[] = [];
protected hasCrop = false;
protected cropRect = rect(0, 0, 0, 0);
crop(x: number, y: number, w: number, h: number) {
// console.error('Crop not supported');
this.hasCrop = true;
this.cropRect.x = x;
this.cropRect.y = y;
this.cropRect.w = w;
this.cropRect.h = h;
}
clearCrop() {
this.hasCrop = false;
}
save() {
const state = stateCache.get();
state.globalAlpha = this.globalAlpha;
state.hasCrop = this.hasCrop;
copyMat2D(state.transform, this.transform);
copyRect(state.cropRect, this.cropRect);
this.savedStates.push(state);
if (DEVELOPMENT && this.savedStates.length > 100) {
console.error('More than 100 save states');
}
}
restore() {
const state = this.savedStates.pop();
if (DEVELOPMENT && this.savedStates.length > 100) {
console.error('More than 100 save states');
}
}
restore() {
const state = this.savedStates.pop();
if (state !== undefined) {
this.globalAlpha = state.globalAlpha;
this.hasCrop = state.hasCrop;
copyMat2D(this.transform, state.transform);
copyRect(this.cropRect, state.cropRect);
stateCache.put(state);
}
}
translate(x: number, y: number) {
translateMat2D(this.transform, this.transform, x, y);
}
scale(x: number, y: number) {
scaleMat2D(this.transform, this.transform, x, y);
}
rotate(angle: number) {
rotateMat2D(this.transform, this.transform, angle);
}
multiplyTransform(mat: Matrix2D) {
mulMat2D(this.transform, this.transform, mat);
}
if (state !== undefined) {
this.globalAlpha = state.globalAlpha;
this.hasCrop = state.hasCrop;
copyMat2D(this.transform, state.transform);
copyRect(this.cropRect, state.cropRect);
stateCache.put(state);
}
}
translate(x: number, y: number) {
translateMat2D(this.transform, this.transform, x, y);
}
scale(x: number, y: number) {
scaleMat2D(this.transform, this.transform, x, y);
}
rotate(angle: number) {
rotateMat2D(this.transform, this.transform, angle);
}
multiplyTransform(mat: Matrix2D) {
mulMat2D(this.transform, this.transform, mat);
}
}
+266 -266
View File
@@ -7,340 +7,340 @@ import { isTranslation } from '../common/mat2d';
import { TRANSPARENT } from '../common/colors';
export function drawBatch(
canvas: HTMLCanvasElement, sheet: SpriteSheet, bg: number | undefined, action: (batch: ContextSpriteBatch) => void
canvas: HTMLCanvasElement, sheet: SpriteSheet, bg: number | undefined, action: (batch: ContextSpriteBatch) => void
) {
const batch = new ContextSpriteBatch(canvas);
batch.start(sheet, bg || TRANSPARENT);
action(batch);
batch.end();
return canvas;
const batch = new ContextSpriteBatch(canvas);
batch.start(sheet, bg || TRANSPARENT);
action(batch);
batch.end();
return canvas;
}
export function drawCanvas(
width: number, height: number, sheet: SpriteSheet, bg: number | undefined, action: (batch: ContextSpriteBatch) => void
width: number, height: number, sheet: SpriteSheet, bg: number | undefined, action: (batch: ContextSpriteBatch) => void
) {
return drawBatch(createCanvas(width, height), sheet, bg, action);
return drawBatch(createCanvas(width, height), sheet, bg, action);
}
export class ContextSpriteBatch extends BaseStateBatch implements PaletteSpriteBatch, SpriteBatch {
pixelSize = 1;
disableShading = false;
ignoreColor = 0;
palette = false;
private started = false;
private data: ImageData | undefined = undefined;
private sheet: SpriteSheet | undefined = undefined;
private sheetData: ImageData | undefined = undefined;
constructor(public canvas: HTMLCanvasElement) {
super();
}
start(sheet: SpriteSheet, clearColor: number) {
if (!this.data || this.data.width !== this.canvas.width || this.data.height !== this.canvas.height) {
if (this.canvas && this.canvas.width && this.canvas.height) {
this.data = this.canvas.getContext('2d')!.getImageData(0, 0, this.canvas.width, this.canvas.height);
}
}
pixelSize = 1;
disableShading = false;
ignoreColor = 0;
palette = false;
private started = false;
private data: ImageData | undefined = undefined;
private sheet: SpriteSheet | undefined = undefined;
private sheetData: ImageData | undefined = undefined;
constructor(public canvas: HTMLCanvasElement) {
super();
}
start(sheet: SpriteSheet, clearColor: number) {
if (!this.data || this.data.width !== this.canvas.width || this.data.height !== this.canvas.height) {
if (this.canvas && this.canvas.width && this.canvas.height) {
this.data = this.canvas.getContext('2d')!.getImageData(0, 0, this.canvas.width, this.canvas.height);
}
}
if (this.data) {
const color = clearColor || 0;
const r = getR(color);
const g = getG(color);
const b = getB(color);
const a = getAlpha(color);
const data = this.data.data;
if (this.data) {
const color = clearColor || 0;
const r = getR(color);
const g = getG(color);
const b = getB(color);
const a = getAlpha(color);
const data = this.data.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
}
for (let i = 0; i < data.length; i += 4) {
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = a;
}
}
this.sheet = sheet;
this.sheetData = sheet.data;
this.palette = this.sheet.palette;
this.started = true;
}
// clear(color?: number) {
// this.end();
this.sheet = sheet;
this.sheetData = sheet.data;
this.palette = this.sheet.palette;
this.started = true;
}
// clear(color?: number) {
// this.end();
// const context = this.canvas.getContext('2d')!;
// const context = this.canvas.getContext('2d')!;
// if (color !== undefined) {
// context.fillStyle = colorToCSS(color);
// context.fillRect(0, 0, this.canvas.width, this.canvas.height);
// } else {
// context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// }
// }
end() {
if (this.started) {
if (this.data) {
this.canvas.getContext('2d')!.putImageData(this.data, 0, 0);
}
// if (color !== undefined) {
// context.fillStyle = colorToCSS(color);
// context.fillRect(0, 0, this.canvas.width, this.canvas.height);
// } else {
// context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// }
// }
end() {
if (this.started) {
if (this.data) {
this.canvas.getContext('2d')!.putImageData(this.data, 0, 0);
}
this.started = false;
}
}
drawSprite(s: Sprite | undefined, color: number, palette: Palette | undefined | number, x: number, y?: number) {
if (s !== undefined) {
if (y === undefined) {
y = x;
x = palette as number;
drawImageNormal(
this.sheetData, this.data, this.transform, this.globalAlpha,
color, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
} else {
drawImagePalette(
this.sheetData, this.data, this.transform, this.globalAlpha,
this.ignoreColor, this.disableShading,
s.type, color, palette as Palette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
}
}
}
drawImage( // SpriteBatch
color: number,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
): void;
drawImage( // PaletteSpriteBatch3
type: number, color: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
drawImage(
colorOrType: number, sxOrColor: number, syOrPalette: Palette | undefined | number,
swOrSx: number, shOrSy: number, dxOrSw: number, dyOrSh: number,
dwOrDx: number, dhOrDy: number, _OrDw?: number, _OrDh?: number,
) {
if (_OrDh === undefined) {
drawImageNormal(
this.sheetData, this.data, this.transform, this.globalAlpha,
colorOrType, sxOrColor, syOrPalette as number,
swOrSx, shOrSy, dxOrSw, dyOrSh, dwOrDx, dhOrDy);
} else {
drawImagePalette(
this.sheetData, this.data, this.transform, this.globalAlpha,
this.ignoreColor, this.disableShading,
colorOrType, sxOrColor, syOrPalette as Palette | undefined,
swOrSx, shOrSy, dxOrSw, dyOrSh, dwOrDx, dhOrDy, _OrDw!, _OrDh!);
}
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
drawRect(this.data, this.transform, this.globalAlpha, color, x, y, w, h);
}
drawBatch() {
throw new Error('drawBatch not supported');
}
startBatch() {
}
finishBatch() {
return undefined;
}
releaseBatch() {
}
this.started = false;
}
}
drawSprite(s: Sprite | undefined, color: number, palette: Palette | undefined | number, x: number, y?: number) {
if (s !== undefined) {
if (y === undefined) {
y = x;
x = palette as number;
drawImageNormal(
this.sheetData, this.data, this.transform, this.globalAlpha,
color, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
} else {
drawImagePalette(
this.sheetData, this.data, this.transform, this.globalAlpha,
this.ignoreColor, this.disableShading,
s.type, color, palette as Palette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
}
}
}
drawImage( // SpriteBatch
color: number,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
): void;
drawImage( // PaletteSpriteBatch3
type: number, color: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
drawImage(
colorOrType: number, sxOrColor: number, syOrPalette: Palette | undefined | number,
swOrSx: number, shOrSy: number, dxOrSw: number, dyOrSh: number,
dwOrDx: number, dhOrDy: number, _OrDw?: number, _OrDh?: number,
) {
if (_OrDh === undefined) {
drawImageNormal(
this.sheetData, this.data, this.transform, this.globalAlpha,
colorOrType, sxOrColor, syOrPalette as number,
swOrSx, shOrSy, dxOrSw, dyOrSh, dwOrDx, dhOrDy);
} else {
drawImagePalette(
this.sheetData, this.data, this.transform, this.globalAlpha,
this.ignoreColor, this.disableShading,
colorOrType, sxOrColor, syOrPalette as Palette | undefined,
swOrSx, shOrSy, dxOrSw, dyOrSh, dwOrDx, dhOrDy, _OrDw!, _OrDh!);
}
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
drawRect(this.data, this.transform, this.globalAlpha, color, x, y, w, h);
}
drawBatch() {
throw new Error('drawBatch not supported');
}
startBatch() {
}
finishBatch() {
return undefined;
}
releaseBatch() {
}
}
const min = Math.min;
const typeOffsets = [0, 2, 3, 0, 1, 2, 3];
function drawRect(
dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
color: number, x: number, y: number, w: number, h: number
dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
color: number, x: number, y: number, w: number, h: number
) {
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (!dst)
return;
if (!dst)
return;
x = Math.round(x + transform[4]);
y = Math.round(y + transform[5]);
x = Math.round(x + transform[4]);
y = Math.round(y + transform[5]);
const xx = min(0, x, x);
w += xx;
x -= xx;
const xx = min(0, x, x);
w += xx;
x -= xx;
const yy = min(0, y, y);
h += yy;
y -= yy;
const yy = min(0, y, y);
h += yy;
y -= yy;
w += min(0, dst.width - (x + w));
h += min(0, dst.height - (y + h));
w += min(0, dst.width - (x + w));
h += min(0, dst.height - (y + h));
if (w <= 0 && h <= 0)
return;
if (w <= 0 && h <= 0)
return;
const { r, g, b, a } = colorToRGBA(color);
const alpha = (globalAlpha * a) | 0;
const { r, g, b, a } = colorToRGBA(color);
const alpha = (globalAlpha * a) | 0;
if (alpha === 0)
return;
if (alpha === 0)
return;
const dstData = dst.data;
const dstWidth = dst.width | 0;
const dstData = dst.data;
const dstWidth = dst.width | 0;
for (let iy = 0; iy < h; iy++) {
for (let ix = 0; ix < w; ix++) {
const dst0 = ((ix + x) + (iy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, r, g, b, alpha);
}
}
for (let iy = 0; iy < h; iy++) {
for (let ix = 0; ix < w; ix++) {
const dst0 = ((ix + x) + (iy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, r, g, b, alpha);
}
}
}
function drawImageNormal(
src: ImageData | undefined, dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
tint: number, sx: number, sy: number, sw: number, sh: number,
dx: number, dy: number, dw: number, dh: number
src: ImageData | undefined, dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
tint: number, sx: number, sy: number, sw: number, sh: number,
dx: number, dy: number, dw: number, dh: number
) {
if (sw !== dw || sh !== dh)
throw new Error('Different dimentions not supported');
if (sw !== dw || sh !== dh)
throw new Error('Different dimentions not supported');
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (!src || !dst)
return;
if (!src || !dst)
return;
dx = Math.round(dx + transform[4]);
dy = Math.round(dy + transform[5]);
dx = Math.round(dx + transform[4]);
dy = Math.round(dy + transform[5]);
let w = sw;
let h = sh;
let w = sw;
let h = sh;
const xx = min(0, sx, dx);
w += xx;
dx -= xx;
sx -= xx;
const xx = min(0, sx, dx);
w += xx;
dx -= xx;
sx -= xx;
const yy = min(0, sy, dy);
h += yy;
dy -= yy;
sy -= yy;
const yy = min(0, sy, dy);
h += yy;
dy -= yy;
sy -= yy;
w += min(0, src.width - (sx + w), dst.width - (dx + w));
h += min(0, src.height - (sy + h), dst.height - (dy + h));
w += min(0, src.width - (sx + w), dst.width - (dx + w));
h += min(0, src.height - (sy + h), dst.height - (dy + h));
if (w <= 0 && h <= 0)
return;
if (w <= 0 && h <= 0)
return;
const { r, g, b, a } = colorToRGBA(tint);
const alpha = (globalAlpha * a) | 0;
const dstData = dst.data;
const srcData = src.data;
const dstWidth = dst.width | 0;
const srcWidth = src.width | 0;
const { r, g, b, a } = colorToRGBA(tint);
const alpha = (globalAlpha * a) | 0;
const dstData = dst.data;
const srcData = src.data;
const dstWidth = dst.width | 0;
const srcWidth = src.width | 0;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const srcO = ((sx + x) + (sy + y) * srcWidth) << 2;
const sr = srcData[srcO];
const sg = srcData[srcO + 1];
const sb = srcData[srcO + 2];
const sa = srcData[srcO + 3];
const srcAlpha = blendColor(alpha, sa, 255);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const srcO = ((sx + x) + (sy + y) * srcWidth) << 2;
const sr = srcData[srcO];
const sg = srcData[srcO + 1];
const sb = srcData[srcO + 2];
const sa = srcData[srcO + 3];
const srcAlpha = blendColor(alpha, sa, 255);
if (srcAlpha !== 0) {
const rr = blendColor(r, sr, 255);
const gg = blendColor(g, sg, 255);
const bb = blendColor(b, sb, 255);
const dst0 = ((dx + x) + (dy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, rr, gg, bb, srcAlpha);
}
}
}
if (srcAlpha !== 0) {
const rr = blendColor(r, sr, 255);
const gg = blendColor(g, sg, 255);
const bb = blendColor(b, sb, 255);
const dst0 = ((dx + x) + (dy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, rr, gg, bb, srcAlpha);
}
}
}
}
function drawImagePalette(
src: ImageData | undefined, dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
ignoreColorOption: number, disableShadingOption: boolean,
type: number, tint: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
src: ImageData | undefined, dst: ImageData | undefined, transform: Matrix2D, globalAlpha: number,
ignoreColorOption: number, disableShadingOption: boolean,
type: number, tint: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
) {
if (sw !== dw || sh !== dh)
throw new Error('Different dimentions not supported');
if (sw !== dw || sh !== dh)
throw new Error('Different dimentions not supported');
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (DEVELOPMENT && !isTranslation(transform)) {
console.error('Transform not supported');
}
if (palette === undefined) {
palette = commonPalettes.defaultPalette;
}
if (palette === undefined) {
palette = commonPalettes.defaultPalette;
}
if (!src || !dst)
return;
if (!src || !dst)
return;
dx = Math.round(dx + transform[4]);
dy = Math.round(dy + transform[5]);
dx = Math.round(dx + transform[4]);
dy = Math.round(dy + transform[5]);
let w = sw;
let h = sh;
let w = sw;
let h = sh;
const xx = min(0, sx, dx);
w += xx;
dx -= xx;
sx -= xx;
const xx = min(0, sx, dx);
w += xx;
dx -= xx;
sx -= xx;
const yy = min(0, sy, dy);
h += yy;
dy -= yy;
sy -= yy;
const yy = min(0, sy, dy);
h += yy;
dy -= yy;
sy -= yy;
w += min(0, src.width - (sx + w), dst.width - (dx + w));
h += min(0, src.height - (sy + h), dst.height - (dy + h));
w += min(0, src.width - (sx + w), dst.width - (dx + w));
h += min(0, src.height - (sy + h), dst.height - (dy + h));
if (w <= 0 && h <= 0)
return;
if (w <= 0 && h <= 0)
return;
const { r, g, b, a } = colorToRGBA(tint);
const alpha = (globalAlpha * a) | 0;
const colors = palette.colors;
const dstData = dst.data;
const srcData = src.data;
const dstWidth = dst.width | 0;
const srcWidth = src.width | 0;
const ignoreColor = ignoreColorOption >>> 0;
const disableShading = disableShadingOption || type > 2;
const offset = typeOffsets[type];
const { r, g, b, a } = colorToRGBA(tint);
const alpha = (globalAlpha * a) | 0;
const colors = palette.colors;
const dstData = dst.data;
const srcData = src.data;
const dstWidth = dst.width | 0;
const srcWidth = src.width | 0;
const ignoreColor = ignoreColorOption >>> 0;
const disableShading = disableShadingOption || type > 2;
const offset = typeOffsets[type];
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const srcO = ((sx + x) + (sy + y) * srcWidth) << 2;
const index = srcData[srcO + offset];
const color = colors[index];
const srcAlpha = ignoreColor === color ? 0 : blendColor(getAlpha(color), alpha, 255);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const srcO = ((sx + x) + (sy + y) * srcWidth) << 2;
const index = srcData[srcO + offset];
const color = colors[index];
const srcAlpha = ignoreColor === color ? 0 : blendColor(getAlpha(color), alpha, 255);
if (srcAlpha !== 0) {
const shade = disableShading ? 255 : srcData[srcO + 1];
const rr = blendColor(getR(color), r, shade);
const gg = blendColor(getG(color), g, shade);
const bb = blendColor(getB(color), b, shade);
const dst0 = ((dx + x) + (dy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, rr, gg, bb, srcAlpha);
}
}
}
if (srcAlpha !== 0) {
const shade = disableShading ? 255 : srcData[srcO + 1];
const rr = blendColor(getR(color), r, shade);
const gg = blendColor(getG(color), g, shade);
const bb = blendColor(getB(color), b, shade);
const dst0 = ((dx + x) + (dy + y) * dstWidth) << 2;
blendPrecise(dstData, dst0, rr, gg, bb, srcAlpha);
}
}
}
}
function blendColor(base: number, tint: number, shade: number) {
return (((((base * tint) | 0) * shade) | 0) / 65025) | 0;
return (((((base * tint) | 0) * shade) | 0) / 65025) | 0;
}
function blendPrecise(dstData: Uint8ClampedArray, dst0: number, r: number, g: number, b: number, alpha: number) {
if (alpha === 0xff || dstData[dst0 + 3] === 0) {
dstData[dst0] = r;
dstData[dst0 + 1] = g;
dstData[dst0 + 2] = b;
dstData[dst0 + 3] = alpha;
} else {
const dstAlpha = (0xff - alpha) | 0;
dstData[dst0] = ((((r * alpha) | 0) / 255) | 0) + ((((dstData[dst0] * dstAlpha) | 0) / 255) | 0);
dstData[dst0 + 1] = ((((g * alpha) | 0) / 255) | 0) + ((((dstData[dst0 + 1] * dstAlpha) | 0) / 255) | 0);
dstData[dst0 + 2] = ((((b * alpha) | 0) / 255) | 0) + ((((dstData[dst0 + 2] * dstAlpha) | 0) / 255) | 0);
const a = (alpha + ((((dstData[dst0 + 3] * dstAlpha) | 0) / 255) | 0)) | 0;
dstData[dst0 + 3] = a > 0xff ? 0xff : a;
}
if (alpha === 0xff || dstData[dst0 + 3] === 0) {
dstData[dst0] = r;
dstData[dst0 + 1] = g;
dstData[dst0 + 2] = b;
dstData[dst0 + 3] = alpha;
} else {
const dstAlpha = (0xff - alpha) | 0;
dstData[dst0] = ((((r * alpha) | 0) / 255) | 0) + ((((dstData[dst0] * dstAlpha) | 0) / 255) | 0);
dstData[dst0 + 1] = ((((g * alpha) | 0) / 255) | 0) + ((((dstData[dst0 + 1] * dstAlpha) | 0) / 255) | 0);
dstData[dst0 + 2] = ((((b * alpha) | 0) / 255) | 0) + ((((dstData[dst0 + 2] * dstAlpha) | 0) / 255) | 0);
const a = (alpha + ((((dstData[dst0 + 3] * dstAlpha) | 0) / 255) | 0)) | 0;
dstData[dst0 + 3] = a > 0xff ? 0xff : a;
}
}
// function blendFast(dstData: Uint8ClampedArray, dst0: number, r: number, g: number, b: number, alpha: number) {
+394 -394
View File
@@ -1,13 +1,13 @@
import {
Says, Rect, SpriteBatch, Sprite, SpriteBorder, MessageType, Entity, Point, Pony, SpriteBatchCommons,
PaletteSpriteBatch, Palette, isPartyMessage, PartyInfo, Camera, CommonPalettes, FontPalettes, PaletteManager,
isWhisperTo, isWhisper, isThinking, isPublicMessage
Says, Rect, SpriteBatch, Sprite, SpriteBorder, MessageType, Entity, Point, Pony, SpriteBatchCommons,
PaletteSpriteBatch, Palette, isPartyMessage, PartyInfo, Camera, CommonPalettes, FontPalettes, PaletteManager,
isWhisperTo, isWhisper, isThinking, isPublicMessage
} from '../common/interfaces';
import { clamp, contains, intersect, toInt, hasFlag } from '../common/utils';
import { BLACK, WHITE, OUTLINE_COLOR, getMessageColor, PARTY_COLOR, MESSAGE_COLOR, FRIENDS_COLOR } from '../common/colors';
import { tileWidth, tileHeight, tileElevation, PONY_TYPE } from '../common/constants';
import {
HAlign, VAlign, TextOptions, lineBreak, drawTextAligned, measureText, drawText, drawOutlinedText
HAlign, VAlign, TextOptions, lineBreak, drawTextAligned, measureText, drawText, drawOutlinedText
} from '../graphics/spriteFont';
import * as sprites from '../generated/sprites';
import { fontPal, fontSmallPal } from '../client/fonts';
@@ -20,15 +20,15 @@ import { mockPaletteManager } from '../common/ponyInfo';
import { sortEntities, isHidden, isFriend } from '../common/entityUtils';
const baloonTaper = [
{ w: 1, y: 2 },
{ w: 1, y: 1 },
{ w: 1, y: 2 },
{ w: 1, y: 1 },
];
const roundTaper = [
{ w: 1, y: 5 },
{ w: 1, y: 3 },
{ w: 2, y: 2 },
{ w: 4, y: 1 },
{ w: 1, y: 5 },
{ w: 1, y: 3 },
{ w: 2, y: 2 },
{ w: 4, y: 1 },
];
export const commonPalettes = createCommonPalettes(mockPaletteManager);
@@ -36,551 +36,551 @@ export const commonPalettes = createCommonPalettes(mockPaletteManager);
type AnyBatch = SpriteBatch | PaletteSpriteBatch;
export function drawTaperedRect(
batch: AnyBatch, color: number, x: number, y: number, w: number, h: number, taper: { w: number; y: number; }[]
batch: AnyBatch, color: number, x: number, y: number, w: number, h: number, taper: { w: number; y: number; }[]
) {
x = Math.round(x) | 0;
y = Math.round(y) | 0;
w = Math.round(w) | 0;
h = Math.round(h) | 0;
x = Math.round(x) | 0;
y = Math.round(y) | 0;
w = Math.round(w) | 0;
h = Math.round(h) | 0;
let gap = 0;
let gap = 0;
for (let i = 0; i < taper.length; i++) {
const t = taper[i];
const th = h - t.y * 2;
batch.drawRect(color, x + gap, y + t.y, t.w, th);
batch.drawRect(color, x + w - gap - t.w, y + t.y, t.w, th);
gap += t.w;
}
for (let i = 0; i < taper.length; i++) {
const t = taper[i];
const th = h - t.y * 2;
batch.drawRect(color, x + gap, y + t.y, t.w, th);
batch.drawRect(color, x + w - gap - t.w, y + t.y, t.w, th);
gap += t.w;
}
batch.drawRect(color, x + gap, y, Math.max(0, w - gap * 2), h);
batch.drawRect(color, x + gap, y, Math.max(0, w - gap * 2), h);
}
export function drawRectBaloon(batch: AnyBatch, color: number, x: number, y: number, w: number, h: number) {
drawTaperedRect(batch, color, x, y, w, h, baloonTaper);
drawTaperedRect(batch, color, x, y, w, h, baloonTaper);
}
export function drawRoundBaloon(batch: AnyBatch, color: number, x: number, y: number, w: number, h: number) {
drawTaperedRect(batch, color, x, y, w, h, roundTaper);
drawTaperedRect(batch, color, x, y, w, h, roundTaper);
}
function getMessagePalette(type: MessageType, palettes: FontPalettes) {
if (type === MessageType.Supporter2) {
return palettes.supporter2;
} else if (type === MessageType.Supporter3) {
return palettes.supporter3;
} else {
return undefined;
}
if (type === MessageType.Supporter2) {
return palettes.supporter2;
} else if (type === MessageType.Supporter3) {
return palettes.supporter3;
} else {
return undefined;
}
}
export function drawBaloon(
batch: PaletteSpriteBatch, { message, type = MessageType.Chat, timer = 1, total = 10 }: Says,
x: number, y: number, bounds: Rect, palettes: CommonPalettes
batch: PaletteSpriteBatch, { message, type = MessageType.Chat, timer = 1, total = 10 }: Says,
x: number, y: number, bounds: Rect, palettes: CommonPalettes
) {
if (!fontPal)
return;
if (!fontPal)
return;
let { w, h } = measureText(message, fontPal);
let { w, h } = measureText(message, fontPal);
w = Math.max(w, 4);
w = Math.max(w, 4);
const screenPad = 8;
const availableWidth = bounds.w - screenPad * 2;
const screenPad = 8;
const availableWidth = bounds.w - screenPad * 2;
if (w > availableWidth) {
message = lineBreak(message, fontPal, availableWidth);
const size = measureText(message, fontPal);
w = size.w;
h = size.h;
}
if (w > availableWidth) {
message = lineBreak(message, fontPal, availableWidth);
const size = measureText(message, fontPal);
w = size.w;
h = size.h;
}
const { dy, alpha } = calcAnimation(timer, total);
const { dy, alpha } = calcAnimation(timer, total);
y += dy;
y += dy;
const nippleX = x;
const toTheLeft = Math.max(0, screenPad - x);
const toTheRight = Math.max(0, x - bounds.w + screenPad);
const nippleX = x;
const toTheLeft = Math.max(0, screenPad - x);
const toTheRight = Math.max(0, x - bounds.w + screenPad);
x = clamp(x, screenPad + w / 2 - toTheLeft, bounds.w - screenPad - w / 2 + toTheRight);
x = clamp(x, screenPad + w / 2 - toTheLeft, bounds.w - screenPad - w / 2 + toTheRight);
if (intersect(0, 0, bounds.w, bounds.h, x - w / 2, y - h / 2, w, h)) {
const palette = getMessagePalette(type, palettes.mainFont);
const color = palette ? WHITE : getMessageColor(type);
const options: TextOptions = {
palette: palette || palettes.mainFont.white,
emojiPalette: palettes.mainFont.emoji,
};
if (intersect(0, 0, bounds.w, bounds.h, x - w / 2, y - h / 2, w, h)) {
const palette = getMessagePalette(type, palettes.mainFont);
const color = palette ? WHITE : getMessageColor(type);
const options: TextOptions = {
palette: palette || palettes.mainFont.white,
emojiPalette: palettes.mainFont.emoji,
};
if (isThinking(type)) {
drawThinkingBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
} else if (isWhisper(type) || isWhisperTo(type)) {
drawWhisperBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
} else {
drawSpeechBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
}
}
if (isThinking(type)) {
drawThinkingBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
} else if (isWhisper(type) || isWhisperTo(type)) {
drawWhisperBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
} else {
drawSpeechBaloon(batch, message, color, options, x, y, w, h, alpha, nippleX);
}
}
}
export function drawSpeechBaloon(
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
) {
const pad = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const nipple = sprites.nipple_2.color;
const pad = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const nipple = sprites.nipple_2.color;
nippleX = clamp(nippleX, xx + pad, xx + w - pad);
nippleX = clamp(nippleX, xx + pad, xx + w - pad);
batch.globalAlpha = 0.6 * alpha;
drawRectBaloon(batch, BLACK, xx - pad, yy - pad, w + pad * 2, h + pad * 2);
batch.drawSprite(nipple, BLACK, undefined, nippleX - Math.round(nipple.w / 2), y + pad);
batch.globalAlpha = 0.6 * alpha;
drawRectBaloon(batch, BLACK, xx - pad, yy - pad, w + pad * 2, h + pad * 2);
batch.drawSprite(nipple, BLACK, undefined, nippleX - Math.round(nipple.w / 2), y + pad);
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
}
export function drawWhisperBaloon(
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
) {
const pad = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const nipple = sprites.nipple_alt_2.color;
const pad = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const nipple = sprites.nipple_alt_2.color;
nippleX = clamp(nippleX, xx + pad, xx + w - pad);
nippleX = clamp(nippleX, xx + pad, xx + w - pad);
batch.globalAlpha = 0.6 * alpha;
batch.globalAlpha = 0.6 * alpha;
const left = xx - pad;
const top = yy - pad;
const width = w + pad * 2;
const height = h + pad * 2;
const left = xx - pad;
const top = yy - pad;
const width = w + pad * 2;
const height = h + pad * 2;
batch.drawRect(BLACK, left + 2, top, width - 4, 1);
batch.drawRect(BLACK, left + 1, top + 1, width - 2, 1);
batch.drawRect(BLACK, left, top + 2, width, height - 4);
batch.drawRect(BLACK, left + 1, top + height - 2, width - 2, 1);
batch.drawRect(BLACK, left + 2, top + height - 1, width - 4, 1);
batch.drawRect(BLACK, left + 2, top, width - 4, 1);
batch.drawRect(BLACK, left + 1, top + 1, width - 2, 1);
batch.drawRect(BLACK, left, top + 2, width, height - 4);
batch.drawRect(BLACK, left + 1, top + height - 2, width - 2, 1);
batch.drawRect(BLACK, left + 2, top + height - 1, width - 4, 1);
const yyy = top + height + 1;
const right = left + width - 1;
const yyy = top + height + 1;
const right = left + width - 1;
for (let tx = nippleX - 7; (tx + 5) > (left + 1); tx -= 5) {
const shorten = Math.max(0, (left + 1) - tx);
batch.drawRect(BLACK, tx + shorten, yyy, 4 - shorten, 1);
}
for (let tx = nippleX - 7; (tx + 5) > (left + 1); tx -= 5) {
const shorten = Math.max(0, (left + 1) - tx);
batch.drawRect(BLACK, tx + shorten, yyy, 4 - shorten, 1);
}
for (let tx = nippleX + 2; tx < right; tx += 5) {
const shorten = Math.max(0, (tx + 5) - right);
batch.drawRect(BLACK, tx, yyy, Math.min(4, 5 - shorten), 1);
}
for (let tx = nippleX + 2; tx < right; tx += 5) {
const shorten = Math.max(0, (tx + 5) - right);
batch.drawRect(BLACK, tx, yyy, Math.min(4, 5 - shorten), 1);
}
batch.drawSprite(nipple, BLACK, undefined, nippleX - 3, y + pad + 2);
batch.drawSprite(nipple, BLACK, undefined, nippleX - 3, y + pad + 2);
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
}
export function drawThinkingBaloon(
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
batch: PaletteSpriteBatch, text: string, color: number, options: TextOptions, x: number, y: number, w: number, h: number,
alpha: number, nippleX: number,
) {
const padX = 6;
const padY = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const ox = clamp(nippleX, xx, xx + w) - 1;
const oy = y + 12;
const padX = 6;
const padY = 4;
const xx = x - Math.round(w / 2);
const yy = y - h;
const ox = clamp(nippleX, xx, xx + w) - 1;
const oy = y + 12;
batch.globalAlpha = 0.6 * alpha;
drawRoundBaloon(batch, BLACK, xx - padX, yy - padY, w + padX * 2, h + padY * 2);
batch.drawRect(BLACK, ox, oy, 1, 1);
batch.drawRect(BLACK, ox - 1, oy - 3, 2, 2);
batch.drawRect(BLACK, ox, oy - 7, 3, 3);
batch.globalAlpha = 0.6 * alpha;
drawRoundBaloon(batch, BLACK, xx - padX, yy - padY, w + padX * 2, h + padY * 2);
batch.drawRect(BLACK, ox, oy, 1, 1);
batch.drawRect(BLACK, ox - 1, oy - 3, 2, 2);
batch.drawRect(BLACK, ox, oy - 7, 3, 3);
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
batch.globalAlpha = alpha;
drawText(batch, text, fontPal, color, xx, yy, options);
batch.globalAlpha = 1;
}
export enum DrawNameFlags {
None = 0,
Party = 1,
Friend = 2,
None = 0,
Party = 1,
Friend = 2,
}
function getNameColor(flags: DrawNameFlags) {
if (hasFlag(flags, DrawNameFlags.Party)) {
return PARTY_COLOR;
} else if (hasFlag(flags, DrawNameFlags.Friend)) {
return FRIENDS_COLOR;
} else {
return WHITE;
}
if (hasFlag(flags, DrawNameFlags.Party)) {
return PARTY_COLOR;
} else if (hasFlag(flags, DrawNameFlags.Friend)) {
return FRIENDS_COLOR;
} else {
return WHITE;
}
}
export function drawNamePlate(
batch: PaletteSpriteBatch, text: string, x: number, y: number, flags: DrawNameFlags,
palettes: CommonPalettes, tagId?: string,
batch: PaletteSpriteBatch, text: string, x: number, y: number, flags: DrawNameFlags,
palettes: CommonPalettes, tagId?: string,
) {
const tag = getTag(tagId);
const size = measureText(text, fontPal);
const xx = x - Math.round(size.w / 2);
const yy = y - size.h + 6 - (tag ? 3 : 0);
const color = getNameColor(flags);
const options = { palette: palettes.mainFont.white, emojiPalette: palettes.mainFont.emoji };
drawOutlinedText(batch, text, fontPal, color, OUTLINE_COLOR, xx, yy, options);
const tag = getTag(tagId);
const size = measureText(text, fontPal);
const xx = x - Math.round(size.w / 2);
const yy = y - size.h + 6 - (tag ? 3 : 0);
const color = getNameColor(flags);
const options = { palette: palettes.mainFont.white, emojiPalette: palettes.mainFont.emoji };
drawOutlinedText(batch, text, fontPal, color, OUTLINE_COLOR, xx, yy, options);
if (tag) {
const tagSize = measureText(tag.label, fontSmallPal);
const textX = x - Math.round(tagSize.w / 2);
const palette = getTagPalette(tag, palettes.smallFont);
drawOutlinedText(batch, tag.label, fontSmallPal, tag.color, OUTLINE_COLOR, textX, yy + 11, { palette });
}
if (tag) {
const tagSize = measureText(tag.label, fontSmallPal);
const textX = x - Math.round(tagSize.w / 2);
const palette = getTagPalette(tag, palettes.smallFont);
drawOutlinedText(batch, tag.label, fontSmallPal, tag.color, OUTLINE_COLOR, textX, yy + 11, { palette });
}
}
export function drawBounds(batch: PaletteSpriteBatch, e: Entity, r: Rect | undefined, color: number) {
if (r) {
batch.drawRect(
color,
Math.round(e.x * tileWidth + r.x), Math.round(e.y * tileHeight + r.y),
Math.round(r.w), Math.round(r.h));
}
if (r) {
batch.drawRect(
color,
Math.round(e.x * tileWidth + r.x), Math.round(e.y * tileHeight + r.y),
Math.round(r.w), Math.round(r.h));
}
}
export function drawWorldBounds(batch: PaletteSpriteBatch, e: Entity, r: Rect | undefined, color: number) {
if (r) {
batch.drawRect(
color,
Math.round((e.x + r.x) * tileWidth), Math.round((e.y + r.y) * tileHeight),
Math.round(r.w * tileWidth), Math.round(r.h * tileHeight));
}
if (r) {
batch.drawRect(
color,
Math.round((e.x + r.x) * tileWidth), Math.round((e.y + r.y) * tileHeight),
Math.round(r.w * tileWidth), Math.round(r.h * tileHeight));
}
}
export function drawBoundsOutline(batch: PaletteSpriteBatch, e: Entity, r: Rect | undefined, color: number, thickness = 1) {
if (r) {
drawOutline(
batch, color,
Math.round(e.x * tileWidth + r.x), Math.round(e.y * tileHeight + r.y),
Math.round(r.w), Math.round(r.h),
thickness);
}
if (r) {
drawOutline(
batch, color,
Math.round(e.x * tileWidth + r.x), Math.round(e.y * tileHeight + r.y),
Math.round(r.w), Math.round(r.h),
thickness);
}
}
export function drawOutlineRect(batch: SpriteBatchCommons, color: number, { x, y, w, h }: Rect, thickness = 1) {
drawOutline(batch, color, x, y, w, h, thickness);
drawOutline(batch, color, x, y, w, h, thickness);
}
export function drawOutline(batch: SpriteBatchCommons, color: number, x: number, y: number, w: number, h: number, thickness = 1) {
batch.drawRect(color, x - thickness, y - thickness, w + thickness * 2, thickness); // top
batch.drawRect(color, x - thickness, y + h, w + thickness * 2, thickness); // bottom
batch.drawRect(color, x - thickness, y, thickness, h); // left
batch.drawRect(color, x + w, y, thickness, h); // right
batch.drawRect(color, x - thickness, y - thickness, w + thickness * 2, thickness); // top
batch.drawRect(color, x - thickness, y + h, w + thickness * 2, thickness); // bottom
batch.drawRect(color, x - thickness, y, thickness, h); // left
batch.drawRect(color, x + w, y, thickness, h); // right
}
export type DrawRect = (color: number, x: number, y: number, w: number, h: number) => void;
export function drawCharacter(drawRect: DrawRect, x: number, y: number, color: number, char: string) {
switch (char) {
case '0':
drawRect(color, x, y, 1, 5);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 4, 1, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '1':
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x + 1, y, 1, 4);
drawRect(color, x, y + 4, 3, 1);
return 3;
case '2':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x + 2, y + 1, 1, 1);
drawRect(color, x, y + 3, 1, 1);
return 3;
case '3':
drawRect(color, x, y, 2, 1);
drawRect(color, x, y + 2, 2, 1);
drawRect(color, x, y + 4, 2, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '4':
drawRect(color, x, y, 1, 3);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '5':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x + 2, y + 3, 1, 1);
return 3;
case '6':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x, y, 1, 5);
drawRect(color, x + 2, y + 3, 1, 1);
return 3;
case '7':
drawRect(color, x, y, 3, 1);
drawRect(color, x + 2, y + 1, 1, 4);
return 3;
case '8':
drawRect(color, x, y, 1, 5);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 2, 1, 1);
drawRect(color, x + 1, y + 4, 1, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '9':
drawRect(color, x, y, 1, 3);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 2, 1, 1);
drawRect(color, x, y + 4, 2, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case ':':
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x, y + 3, 1, 1);
return 1;
case '.':
drawRect(color, x, y + 4, 1, 1);
return 1;
case '-':
drawRect(color, x, y + 2, 3, 1);
return 3;
case ' ':
return 2;
default:
drawRect(color, x, y, 3, 5);
return 3;
}
switch (char) {
case '0':
drawRect(color, x, y, 1, 5);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 4, 1, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '1':
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x + 1, y, 1, 4);
drawRect(color, x, y + 4, 3, 1);
return 3;
case '2':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x + 2, y + 1, 1, 1);
drawRect(color, x, y + 3, 1, 1);
return 3;
case '3':
drawRect(color, x, y, 2, 1);
drawRect(color, x, y + 2, 2, 1);
drawRect(color, x, y + 4, 2, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '4':
drawRect(color, x, y, 1, 3);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '5':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x + 2, y + 3, 1, 1);
return 3;
case '6':
drawRect(color, x, y, 3, 1);
drawRect(color, x, y + 2, 3, 1);
drawRect(color, x, y + 4, 3, 1);
drawRect(color, x, y, 1, 5);
drawRect(color, x + 2, y + 3, 1, 1);
return 3;
case '7':
drawRect(color, x, y, 3, 1);
drawRect(color, x + 2, y + 1, 1, 4);
return 3;
case '8':
drawRect(color, x, y, 1, 5);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 2, 1, 1);
drawRect(color, x + 1, y + 4, 1, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case '9':
drawRect(color, x, y, 1, 3);
drawRect(color, x + 1, y, 1, 1);
drawRect(color, x + 1, y + 2, 1, 1);
drawRect(color, x, y + 4, 2, 1);
drawRect(color, x + 2, y, 1, 5);
return 3;
case ':':
drawRect(color, x, y + 1, 1, 1);
drawRect(color, x, y + 3, 1, 1);
return 1;
case '.':
drawRect(color, x, y + 4, 1, 1);
return 1;
case '-':
drawRect(color, x, y + 2, 3, 1);
return 3;
case ' ':
return 2;
default:
drawRect(color, x, y, 3, 5);
return 3;
}
}
export function drawPixelTextBase(drawRect: DrawRect, x: number, y: number, color: number, text: string) {
for (let i = 0; i < text.length; i++) {
x += drawCharacter(drawRect, x, y, color, text.charAt(i)) + 1;
}
for (let i = 0; i < text.length; i++) {
x += drawCharacter(drawRect, x, y, color, text.charAt(i)) + 1;
}
}
export function drawPixelText(batch: PaletteSpriteBatch, x: number, y: number, color: number, text: string) {
drawPixelTextBase(batch.drawRect.bind(batch), x, y, color, text);
drawPixelTextBase(batch.drawRect.bind(batch), x, y, color, text);
}
export function fillRect(context: CanvasRenderingContext2D, color: string, x: number, y: number, w: number, h: number) {
context.fillStyle = color;
context.fillRect(x, y, w, h);
context.fillStyle = color;
context.fillRect(x, y, w, h);
}
export function drawPixelTextOnCanvas(context: CanvasRenderingContext2D, x: number, y: number, color: number, text: string) {
drawPixelTextBase((color, x, y, w, h) => fillRect(context, colorToCSS(color), x, y, w, h), x, y, color, text);
drawPixelTextBase((color, x, y, w, h) => fillRect(context, colorToCSS(color), x, y, w, h), x, y, color, text);
}
interface Say {
message: Says;
x: number;
y: number;
message: Says;
x: number;
y: number;
}
export function compareSays(a: Say, b: Say) {
return a.message.created - b.message.created;
return a.message.created - b.message.created;
}
function isPartyMember(entity: Entity, party: PartyInfo | undefined) {
return entity.type === PONY_TYPE && party !== undefined && party.members.some(p => p.id === entity.id && !p.pending);
return entity.type === PONY_TYPE && party !== undefined && party.members.some(p => p.id === entity.id && !p.pending);
}
export function drawNames(
batch: PaletteSpriteBatch, entities: Entity[], player: Pony | undefined, party: PartyInfo | undefined,
camera: Camera, hover: Point, drawHidden: boolean, palettes: CommonPalettes
batch: PaletteSpriteBatch, entities: Entity[], player: Pony | undefined, party: PartyInfo | undefined,
camera: Camera, hover: Point, drawHidden: boolean, palettes: CommonPalettes
) {
sortEntities(entities);
sortEntities(entities);
for (const e of entities) {
if ((!isHidden(e) || drawHidden) && e.name && e !== player) {
const nameOffsetBase = 12;
const bounds = e.interactBounds || e.bounds;
const chatBounds = e.chatBounds || bounds;
for (const e of entities) {
if ((!isHidden(e) || drawHidden) && e.name && e !== player) {
const nameOffsetBase = 12;
const bounds = e.interactBounds || e.bounds;
const chatBounds = e.chatBounds || bounds;
if (chatBounds !== undefined && bounds !== undefined && contains(e.x, e.y, bounds, hover)) {
const { x, y } = worldToScreen(camera, e);
const nameOffset = nameOffsetBase - getChatHeight(e);
const tag = (isHidden(e) && drawHidden) ? 'hidden' : e.tag;
const flags = DrawNameFlags.None |
(isPartyMember(e, party) ? DrawNameFlags.Party : 0) |
(isFriend(e) ? DrawNameFlags.Friend : 0);
drawNamePlate(batch, e.name, x, y + chatBounds.y - nameOffset, flags, palettes, tag);
}
}
}
if (chatBounds !== undefined && bounds !== undefined && contains(e.x, e.y, bounds, hover)) {
const { x, y } = worldToScreen(camera, e);
const nameOffset = nameOffsetBase - getChatHeight(e);
const tag = (isHidden(e) && drawHidden) ? 'hidden' : e.tag;
const flags = DrawNameFlags.None |
(isPartyMember(e, party) ? DrawNameFlags.Party : 0) |
(isFriend(e) ? DrawNameFlags.Friend : 0);
drawNamePlate(batch, e.name, x, y + chatBounds.y - nameOffset, flags, palettes, tag);
}
}
}
}
export function getChatBallonXY(e: Entity, camera: Camera): Point {
const nameOffsetBase = 12;
const bounds = e.interactBounds || e.bounds;
const chatBounds = e.chatBounds || bounds;
const screen = worldToScreen(camera, e);
const nameOffset = nameOffsetBase - getChatHeight(e);
const offset = (nameOffset + 6) + (e.tag ? 5 : 0);
const yy = screen.y + (chatBounds ? chatBounds.y : 0) - offset;
const x = screen.x + toInt(e.chatX);
const y = yy + toInt(e.chatY);
return { x, y };
const nameOffsetBase = 12;
const bounds = e.interactBounds || e.bounds;
const chatBounds = e.chatBounds || bounds;
const screen = worldToScreen(camera, e);
const nameOffset = nameOffsetBase - getChatHeight(e);
const offset = (nameOffset + 6) + (e.tag ? 5 : 0);
const yy = screen.y + (chatBounds ? chatBounds.y : 0) - offset;
const x = screen.x + toInt(e.chatX);
const y = yy + toInt(e.chatY);
return { x, y };
}
function drawChatBaloon(batch: PaletteSpriteBatch, entity: Entity, camera: Camera, palettes: CommonPalettes) {
const { x, y } = getChatBallonXY(entity, camera);
drawBaloon(batch, entity.says!, x, y, camera, palettes);
const { x, y } = getChatBallonXY(entity, camera);
drawBaloon(batch, entity.says!, x, y, camera, palettes);
}
export function drawChat(
batch: PaletteSpriteBatch, entities: Entity[], camera: Camera, drawHidden: boolean, palettes: CommonPalettes,
hidePublic: boolean
batch: PaletteSpriteBatch, entities: Entity[], camera: Camera, drawHidden: boolean, palettes: CommonPalettes,
hidePublic: boolean
) {
sortEntities(entities);
sortEntities(entities);
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && !isPartyMessage(entity.says!.type || MessageType.Chat)) {
if (!hidePublic || !isPublicMessage(entity.says!.type || MessageType.Chat)) {
drawChatBaloon(batch, entity, camera, palettes);
}
}
}
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && !isPartyMessage(entity.says!.type || MessageType.Chat)) {
if (!hidePublic || !isPublicMessage(entity.says!.type || MessageType.Chat)) {
drawChatBaloon(batch, entity, camera, palettes);
}
}
}
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && isPartyMessage(entity.says!.type || MessageType.Chat)) {
drawChatBaloon(batch, entity, camera, palettes);
}
}
for (const entity of entities) {
if ((!isHidden(entity) || drawHidden) && isPartyMessage(entity.says!.type || MessageType.Chat)) {
drawChatBaloon(batch, entity, camera, palettes);
}
}
}
function getChatHeight(entity: Entity): number {
return isPony(entity) ? getPonyChatHeight(entity) : 0;
return isPony(entity) ? getPonyChatHeight(entity) : 0;
}
export const chatAnimationDuration = 0.2;
export function dismissSays(says: Says) {
if (says.timer !== undefined) {
says.timer = Math.min(says.timer, chatAnimationDuration);
}
if (says.timer !== undefined) {
says.timer = Math.min(says.timer, chatAnimationDuration);
}
}
function calcAnimation(timer: number, total: number) {
const start = (total - timer) / chatAnimationDuration;
const end = timer / chatAnimationDuration;
const start = (total - timer) / chatAnimationDuration;
const end = timer / chatAnimationDuration;
const dys = [3, 2, 1, 0, -1, 0];
const dys2 = [-4, -3, -2, -1];
const dyd = start * dys.length;
const dyd2 = end * dys2.length;
const dyi = clamp(Math.round(dyd), 0, dys.length - 1);
const dyi2 = clamp(Math.round(dyd2), 0, dys2.length);
const dy = dyi2 < dys2.length ? dys2[dyi2] : dys[dyi];
const alpha = Math.min(start, end, 1);
const dys = [3, 2, 1, 0, -1, 0];
const dys2 = [-4, -3, -2, -1];
const dyd = start * dys.length;
const dyd2 = end * dys2.length;
const dyi = clamp(Math.round(dyd), 0, dys.length - 1);
const dyi2 = clamp(Math.round(dyd2), 0, dys2.length);
const dy = dyi2 < dys2.length ? dys2[dyi2] : dys[dyi];
const alpha = Math.min(start, end, 1);
return { alpha, dy };
return { alpha, dy };
}
export function drawBox(
batch: SpriteBatchCommons, color: number, shadowColor: number, x: number, y: number, z: number,
w: number, l: number, h: number
batch: SpriteBatchCommons, color: number, shadowColor: number, x: number, y: number, z: number,
w: number, l: number, h: number
) {
const darker = multiplyColor(color, 0.8);
const left = (x - w / 2) * tileWidth;
const bottom = y * tileHeight;
const elevation = z * tileElevation;
const width = w * tileWidth;
const frontHeight = h * tileElevation;
const topHeight = l * tileHeight;
const darker = multiplyColor(color, 0.8);
const left = (x - w / 2) * tileWidth;
const bottom = y * tileHeight;
const elevation = z * tileElevation;
const width = w * tileWidth;
const frontHeight = h * tileElevation;
const topHeight = l * tileHeight;
batch.drawRect(shadowColor, left, bottom - topHeight, width, topHeight); // shadow
batch.drawRect(darker, left, bottom - elevation - frontHeight, width, frontHeight); // front
batch.drawRect(color, left, bottom - elevation - frontHeight - topHeight, width, topHeight); // top
batch.drawRect(shadowColor, left, bottom - topHeight, width, topHeight); // shadow
batch.drawRect(darker, left, bottom - elevation - frontHeight, width, frontHeight); // front
batch.drawRect(color, left, bottom - elevation - frontHeight - topHeight, width, topHeight); // top
}
export function drawSpriteBorder(
batch: SpriteBatch, border: SpriteBorder, color: number, x: number, y: number, w: number, h: number
batch: SpriteBatch, border: SpriteBorder, color: number, x: number, y: number, w: number, h: number
) {
x = Math.round(x) | 0;
y = Math.round(y) | 0;
w = Math.round(w) | 0;
h = Math.round(h) | 0;
x = Math.round(x) | 0;
y = Math.round(y) | 0;
w = Math.round(w) | 0;
h = Math.round(h) | 0;
const size = border.border;
const right = x + w - size;
const bottom = y + h - size;
const bgWidth = w - size * 2;
const bgHeight = h - size * 2;
const size = border.border;
const right = x + w - size;
const bottom = y + h - size;
const bgWidth = w - size * 2;
const bgHeight = h - size * 2;
batch.drawSprite(border.topLeft, color, x, y);
batch.drawSprite(border.topRight, color, right, y);
batch.drawSprite(border.bottomLeft, color, x, bottom);
batch.drawSprite(border.bottomRight, color, right, bottom);
batch.drawSprite(border.topLeft, color, x, y);
batch.drawSprite(border.topRight, color, right, y);
batch.drawSprite(border.bottomLeft, color, x, bottom);
batch.drawSprite(border.bottomRight, color, right, bottom);
drawStretched(batch, border.top, color, x + size, y, bgWidth, size);
drawStretched(batch, border.left, color, x, y + size, size, bgHeight);
//drawStretched(batch, border.bg, color, x + size, y + size, bgWidth, bgHeight);
batch.drawRect(color, x + size, y + size, bgWidth, bgHeight);
drawStretched(batch, border.right, color, x + w - size, y + size, size, bgHeight);
drawStretched(batch, border.bottom, color, x + size, y + h - size, bgWidth, size);
drawStretched(batch, border.top, color, x + size, y, bgWidth, size);
drawStretched(batch, border.left, color, x, y + size, size, bgHeight);
//drawStretched(batch, border.bg, color, x + size, y + size, bgWidth, bgHeight);
batch.drawRect(color, x + size, y + size, bgWidth, bgHeight);
drawStretched(batch, border.right, color, x + w - size, y + size, size, bgHeight);
drawStretched(batch, border.bottom, color, x + size, y + h - size, bgWidth, size);
}
function drawStretched(batch: SpriteBatch, sprite: Sprite, color: number, x: number, y: number, w: number, h: number) {
if (sprite.h && sprite.w) {
const sw = Math.min(sprite.w, w);
const sh = Math.min(sprite.h, h);
batch.drawImage(color, sprite.x, sprite.y, sw, sh, x, y, w, h);
}
if (sprite.h && sprite.w) {
const sw = Math.min(sprite.w, w);
const sh = Math.min(sprite.h, h);
batch.drawImage(color, sprite.x, sprite.y, sw, sh, x, y, w, h);
}
}
export function drawSpriteCropped(
batch: PaletteSpriteBatch, s: Sprite, color: number, palette: Palette | undefined, x: number, y: number,
maxY: number
batch: PaletteSpriteBatch, s: Sprite, color: number, palette: Palette | undefined, x: number, y: number,
maxY: number
) {
const top = y + s.oy;
const bottom = Math.min(top + s.h, maxY);
const top = y + s.oy;
const bottom = Math.min(top + s.h, maxY);
if (bottom > top) {
batch.drawImage(s.type, color, palette, s.x, s.y, s.w, s.h, x + s.ox, top, s.w, bottom - top);
}
if (bottom > top) {
batch.drawImage(s.type, color, palette, s.x, s.y, s.w, s.h, x + s.ox, top, s.w, bottom - top);
}
}
export function drawFullScreenMessage(batch: PaletteSpriteBatch, camera: Camera, text: string, palette: Palette) {
const messageHeight = 100;
const messageY = Math.round((camera.h - messageHeight) / 2);
batch.drawRect(MESSAGE_COLOR, 0, messageY, camera.w, messageHeight);
const textRect = rect(0, messageY, camera.w, messageHeight);
drawTextAligned(batch, text, fontPal, WHITE, textRect, HAlign.Center, VAlign.Middle, { palette });
const messageHeight = 100;
const messageY = Math.round((camera.h - messageHeight) / 2);
batch.drawRect(MESSAGE_COLOR, 0, messageY, camera.w, messageHeight);
const textRect = rect(0, messageY, camera.w, messageHeight);
drawTextAligned(batch, text, fontPal, WHITE, textRect, HAlign.Center, VAlign.Middle, { palette });
}
export function createCommonPalettes(paletteManager: PaletteManager): CommonPalettes {
return {
defaultPalette: paletteManager.addArray(sprites.defaultPalette),
mainFont: {
emoji: paletteManager.addArray(sprites.emojiPalette),
white: paletteManager.addArray(sprites.fontPalette),
supporter1: paletteManager.addArray(sprites.fontSupporter1Palette),
supporter2: paletteManager.addArray(sprites.fontSupporter2Palette),
supporter3: paletteManager.addArray(sprites.fontSupporter3Palette),
},
smallFont: {
emoji: paletteManager.addArray(sprites.emojiPalette),
white: paletteManager.addArray(sprites.fontSmallPalette),
supporter1: paletteManager.addArray(sprites.fontSmallSupporter1Palette),
supporter2: paletteManager.addArray(sprites.fontSmallSupporter2Palette),
supporter3: paletteManager.addArray(sprites.fontSmallSupporter3Palette),
},
};
return {
defaultPalette: paletteManager.addArray(sprites.defaultPalette),
mainFont: {
emoji: paletteManager.addArray(sprites.emojiPalette),
white: paletteManager.addArray(sprites.fontPalette),
supporter1: paletteManager.addArray(sprites.fontSupporter1Palette),
supporter2: paletteManager.addArray(sprites.fontSupporter2Palette),
supporter3: paletteManager.addArray(sprites.fontSupporter3Palette),
},
smallFont: {
emoji: paletteManager.addArray(sprites.emojiPalette),
white: paletteManager.addArray(sprites.fontSmallPalette),
supporter1: paletteManager.addArray(sprites.fontSmallSupporter1Palette),
supporter2: paletteManager.addArray(sprites.fontSmallSupporter2Palette),
supporter3: paletteManager.addArray(sprites.fontSmallSupporter3Palette),
},
};
}
+193 -193
View File
@@ -6,242 +6,242 @@ const INITIAL_SIZE = 512;
const MAX_SIZE = 2048;
export function createPalette(colors: Uint32Array): Palette {
return { x: 0, y: 0, u: 0, v: 0, refs: 1, colors };
return { x: 0, y: 0, u: 0, v: 0, refs: 1, colors };
}
export function releasePalette(palette: Palette | undefined) {
if (palette && palette.refs) {
palette.refs--;
}
if (palette && palette.refs) {
palette.refs--;
}
}
export function colorsEqual(a: Uint32Array, b: Uint32Array): boolean {
if (a === b) {
return true;
}
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
return true;
}
function isInUse(palette: Palette) {
return palette.refs > 0;
return palette.refs > 0;
}
export class PaletteManager implements IPaletteManager {
private paletteTexture?: Texture2D;
private palettes = times<Palette[]>(512, () => []);
private dirty: Palette[] = [];
private dirtyMinY = 0;
private dirtyMaxY = -1;
private lastX = 0;
private lastY = 0;
private initialized = true;
deduplicate = true;
constructor(private size = INITIAL_SIZE) {
}
get texture() {
return this.paletteTexture;
}
get textureSize() {
return this.size;
}
get pixelSize() {
return 256 / this.size;
}
activePalettes() {
return this.palettes.reduce((sum, p) => sum + p.reduce((sum, p) => sum + (p.refs > 0 ? 1 : 0), 0), 0);
}
add(colorValues: number[]): Palette {
const colors = new Uint32Array(colorValues.length);
private paletteTexture?: Texture2D;
private palettes = times<Palette[]>(512, () => []);
private dirty: Palette[] = [];
private dirtyMinY = 0;
private dirtyMaxY = -1;
private lastX = 0;
private lastY = 0;
private initialized = true;
deduplicate = true;
constructor(private size = INITIAL_SIZE) {
}
get texture() {
return this.paletteTexture;
}
get textureSize() {
return this.size;
}
get pixelSize() {
return 256 / this.size;
}
activePalettes() {
return this.palettes.reduce((sum, p) => sum + p.reduce((sum, p) => sum + (p.refs > 0 ? 1 : 0), 0), 0);
}
add(colorValues: number[]): Palette {
const colors = new Uint32Array(colorValues.length);
for (let i = 0; i < colorValues.length; i++) {
colors[i] = colorValues[i] >>> 0;
}
for (let i = 0; i < colorValues.length; i++) {
colors[i] = colorValues[i] >>> 0;
}
return this.addArray(colors);
}
addArray(colors: Uint32Array): Palette {
const hash = computeCRC(colors) & 0x1ff;
const palettes = this.palettes[hash];
return this.addArray(colors);
}
addArray(colors: Uint32Array): Palette {
const hash = computeCRC(colors) & 0x1ff;
const palettes = this.palettes[hash];
if (this.deduplicate) {
for (let i = 0; i < palettes.length; i++) {
const existing = palettes[i];
if (this.deduplicate) {
for (let i = 0; i < palettes.length; i++) {
const existing = palettes[i];
if (colorsEqual(existing.colors, colors)) {
existing.refs = (existing.refs + 1) | 0;
return existing;
}
}
}
if (colorsEqual(existing.colors, colors)) {
existing.refs = (existing.refs + 1) | 0;
return existing;
}
}
}
const palette = createPalette(colors);
palettes.push(palette);
this.dirty.push(palette);
return palette;
}
commit(gl: WebGLRenderingContext): boolean {
let changed = this.initialized;
const palette = createPalette(colors);
palettes.push(palette);
this.dirty.push(palette);
return palette;
}
commit(gl: WebGLRenderingContext): boolean {
let changed = this.initialized;
if (!this.paletteTexture) {
this.initializeTexture(gl, this.size);
changed = true;
}
if (!this.paletteTexture) {
this.initializeTexture(gl, this.size);
changed = true;
}
if (this.dirty.length) {
if (!this.arrange(this.dirty)) {
this.cleanupPalettes();
changed = true;
if (this.dirty.length) {
if (!this.arrange(this.dirty)) {
this.cleanupPalettes();
changed = true;
while (!this.arrange(this.dirty)) {
if (this.size < MAX_SIZE) {
this.initializeTexture(gl, this.size * 2);
} else {
throw new Error('Exceeded maximum palettes limit');
}
}
}
while (!this.arrange(this.dirty)) {
if (this.size < MAX_SIZE) {
this.initializeTexture(gl, this.size * 2);
} else {
throw new Error('Exceeded maximum palettes limit');
}
}
}
this.updateTexture(gl);
this.dirty = [];
}
this.updateTexture(gl);
this.dirty = [];
}
this.initialized = false;
return changed;
}
init(gl: WebGLRenderingContext) {
this.initialized = true;
this.paletteTexture = undefined;
this.initializeTexture(gl, this.size);
}
dispose(gl: WebGLRenderingContext | undefined) {
this.paletteTexture = disposeTexture(gl, this.paletteTexture);
this.initialized = false;
return changed;
}
init(gl: WebGLRenderingContext) {
this.initialized = true;
this.paletteTexture = undefined;
this.initializeTexture(gl, this.size);
}
dispose(gl: WebGLRenderingContext | undefined) {
this.paletteTexture = disposeTexture(gl, this.paletteTexture);
for (let i = 0; i < this.palettes.length; i++) {
if (this.palettes[i].length > 0) {
this.palettes[i] = [];
}
}
for (let i = 0; i < this.palettes.length; i++) {
if (this.palettes[i].length > 0) {
this.palettes[i] = [];
}
}
this.size = INITIAL_SIZE;
this.resetPalettes();
}
cleanup() {
this.cleanupPalettes();
}
private resetPalettes() {
this.dirty = flatten(this.palettes);
this.lastX = 0;
this.lastY = 0;
}
private cleanupPalettes() {
const palettes = this.palettes;
this.size = INITIAL_SIZE;
this.resetPalettes();
}
cleanup() {
this.cleanupPalettes();
}
private resetPalettes() {
this.dirty = flatten(this.palettes);
this.lastX = 0;
this.lastY = 0;
}
private cleanupPalettes() {
const palettes = this.palettes;
for (let i = 0; i < palettes.length; i++) {
if (palettes[i].length > 0) {
palettes[i] = palettes[i].filter(isInUse);
}
}
for (let i = 0; i < palettes.length; i++) {
if (palettes[i].length > 0) {
palettes[i] = palettes[i].filter(isInUse);
}
}
this.resetPalettes();
}
private initializeTexture(gl: WebGLRenderingContext, size: number) {
try {
if (!this.paletteTexture) {
this.paletteTexture = createEmptyTexture(gl, size, size, gl.RGBA, gl.UNSIGNED_BYTE);
} else if (this.paletteTexture.width !== size) {
resizeTexture(gl, this.paletteTexture, size, size);
}
} catch (e) {
throw new Error(`Failed to create/resize texture (${size}) ${e.stack}`);
}
this.resetPalettes();
}
private initializeTexture(gl: WebGLRenderingContext, size: number) {
try {
if (!this.paletteTexture) {
this.paletteTexture = createEmptyTexture(gl, size, size, gl.RGBA, gl.UNSIGNED_BYTE);
} else if (this.paletteTexture.width !== size) {
resizeTexture(gl, this.paletteTexture, size, size);
}
} catch (e) {
throw new Error(`Failed to create/resize texture (${size}) ${e.stack}`);
}
this.size = size;
this.resetPalettes();
}
private arrange(palettes: Palette[]) {
if (!palettes.length) {
return true;
}
this.size = size;
this.resetPalettes();
}
private arrange(palettes: Palette[]) {
if (!palettes.length) {
return true;
}
const size = this.size | 0;
let x = this.lastX | 0;
let y = this.lastY | 0;
let minY = -1;
let maxY = -1;
const size = this.size | 0;
let x = this.lastX | 0;
let y = this.lastY | 0;
let minY = -1;
let maxY = -1;
for (let i = 0; i < palettes.length; i++) {
const p = palettes[i];
const colorCount = p.colors.length | 0;
for (let i = 0; i < palettes.length; i++) {
const p = palettes[i];
const colorCount = p.colors.length | 0;
if ((size - x) < colorCount) {
x = 0;
y++;
if ((size - x) < colorCount) {
x = 0;
y++;
if (y >= size) {
return false;
}
}
if (y >= size) {
return false;
}
}
p.x = x;
p.y = y;
p.u = (x + 0.5) / size;
p.v = (y + 0.5) / size;
x = (x + colorCount) | 0;
p.x = x;
p.y = y;
p.u = (x + 0.5) / size;
p.v = (y + 0.5) / size;
x = (x + colorCount) | 0;
minY = minY === -1 ? y : minY;
maxY = Math.max(maxY, y);
}
minY = minY === -1 ? y : minY;
maxY = Math.max(maxY, y);
}
this.lastX = x;
this.lastY = y;
this.dirtyMinY = minY;
this.dirtyMaxY = maxY;
return true;
}
private updateTexture(gl: WebGLRenderingContext) {
if (!this.paletteTexture || this.dirtyMinY > this.dirtyMaxY)
return;
this.lastX = x;
this.lastY = y;
this.dirtyMinY = minY;
this.dirtyMaxY = maxY;
return true;
}
private updateTexture(gl: WebGLRenderingContext) {
if (!this.paletteTexture || this.dirtyMinY > this.dirtyMaxY)
return;
const width = this.size;
const height = (this.dirtyMaxY - this.dirtyMinY) + 1;
const data = new Uint8Array(width * height * 4);
const width = this.size;
const height = (this.dirtyMaxY - this.dirtyMinY) + 1;
const data = new Uint8Array(width * height * 4);
for (let k = 0; k < this.palettes.length; k++) {
const palettes = this.palettes[k];
for (let k = 0; k < this.palettes.length; k++) {
const palettes = this.palettes[k];
for (let i = 0; i < palettes.length; i++) {
const { x, y, colors } = palettes[i];
for (let i = 0; i < palettes.length; i++) {
const { x, y, colors } = palettes[i];
if (y < this.dirtyMinY || y > this.dirtyMaxY)
continue;
if (y < this.dirtyMinY || y > this.dirtyMaxY)
continue;
let offset = (x + (y - this.dirtyMinY) * width) << 2;
let offset = (x + (y - this.dirtyMinY) * width) << 2;
for (let j = 0; j < colors.length; j++) {
const c = colors[j];
data[offset++] = (c >>> 24) & 0xff;
data[offset++] = (c >>> 16) & 0xff;
data[offset++] = (c >>> 8) & 0xff;
data[offset++] = (c >>> 0) & 0xff;
}
}
}
for (let j = 0; j < colors.length; j++) {
const c = colors[j];
data[offset++] = (c >>> 24) & 0xff;
data[offset++] = (c >>> 16) & 0xff;
data[offset++] = (c >>> 8) & 0xff;
data[offset++] = (c >>> 0) & 0xff;
}
}
}
gl.bindTexture(gl.TEXTURE_2D, this.paletteTexture.handle);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, this.dirtyMinY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.bindTexture(gl.TEXTURE_2D, this.paletteTexture.handle);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, this.dirtyMinY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
this.dirtyMinY = 0;
this.dirtyMaxY = -1;
}
this.dirtyMinY = 0;
this.dirtyMaxY = -1;
}
}
+186 -186
View File
@@ -7,123 +7,123 @@ import { createPalette } from './paletteManager';
const defaultRectSprite = createSprite(0, 0, 1, 1, 0, 0, 3);
const types = new Float32Array([
colorToFloat(colorFromRGBA(255, 0, 0, 0)), // type 0 shade
colorToFloat(colorFromRGBA(0, 0, 255, 0)), // type 2 shade
colorToFloat(colorFromRGBA(0, 0, 0, 0)), // type 3 shade
colorToFloat(colorFromRGBA(255, 0, 0, 255)), // type 0
colorToFloat(colorFromRGBA(0, 255, 0, 255)), // type 1
colorToFloat(colorFromRGBA(0, 0, 255, 255)), // type 2
colorToFloat(colorFromRGBA(0, 0, 0, 255)), // type 3
colorToFloat(colorFromRGBA(255, 0, 0, 0)), // type 0 shade
colorToFloat(colorFromRGBA(0, 0, 255, 0)), // type 2 shade
colorToFloat(colorFromRGBA(0, 0, 0, 0)), // type 3 shade
colorToFloat(colorFromRGBA(255, 0, 0, 255)), // type 0
colorToFloat(colorFromRGBA(0, 255, 0, 255)), // type 1
colorToFloat(colorFromRGBA(0, 0, 255, 255)), // type 2
colorToFloat(colorFromRGBA(0, 0, 0, 255)), // type 3
]);
/*
function pushVertex(
vertices: Float32Array, _verticesUint32: Uint32Array, index: number,
x: number, y: number, u: number, v: number, pu: number, pv: number, c: number, c1: number, transform: Matrix2D
vertices: Float32Array, _verticesUint32: Uint32Array, index: number,
x: number, y: number, u: number, v: number, pu: number, pv: number, c: number, c1: number, transform: Matrix2D
) {
vertices[index++] = transform[0] * x + transform[2] * y + transform[4];
vertices[index++] = transform[1] * x + transform[3] * y + transform[5];
vertices[index++] = u;
vertices[index++] = v;
vertices[index++] = pu;
vertices[index++] = pv;
vertices[index++] = c;
vertices[index++] = c1;
vertices[index++] = transform[0] * x + transform[2] * y + transform[4];
vertices[index++] = transform[1] * x + transform[3] * y + transform[5];
vertices[index++] = u;
vertices[index++] = v;
vertices[index++] = pu;
vertices[index++] = pv;
vertices[index++] = c;
vertices[index++] = c1;
}
function pushQuad(
vertices: Float32Array, verticesUint32: Uint32Array,
transform: mat2d, index: number, type: number, color: number, palette: Palette,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
vertices: Float32Array, verticesUint32: Uint32Array,
transform: mat2d, index: number, type: number, color: number, palette: Palette,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
) {
const c1 = types[type];
const c1 = types[type];
const y2 = dy + dh;
const x2 = dx + dw;
const y2 = dy + dh;
const x2 = dx + dw;
const u1 = sx + 0.1;
const v1 = sy + 0.1;
const u2 = sx + sw;
const v2 = sy + sh;
const u1 = sx + 0.1;
const v1 = sy + 0.1;
const u2 = sx + sw;
const v2 = sy + sh;
const pu = palette.u;
const pv = palette.v;
const pu = palette.u;
const pv = palette.v;
pushVertex(vertices, verticesUint32, index, dx, dy, u1, v1, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 8, x2, dy, u2, v1, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 16, x2, y2, u2, v2, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 24, dx, y2, u1, v2, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index, dx, dy, u1, v1, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 8, x2, dy, u2, v1, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 16, x2, y2, u2, v2, pu, pv, color, c1, transform);
pushVertex(vertices, verticesUint32, index + 24, dx, y2, u1, v2, pu, pv, color, c1, transform);
return index + 32;
return index + 32;
}
*/
function pushQuad(
vertices: Float32Array, //_verticesUint32: Uint32Array,
transform: Matrix2D, index: number, type: number, color: number, palette: Palette,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number,
vertices: Float32Array, //_verticesUint32: Uint32Array,
transform: Matrix2D, index: number, type: number, color: number, palette: Palette,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number,
) {
const c1 = types[type];
const c1 = types[type];
const y2 = dy + dh;
const x2 = dx + dw;
const y2 = dy + dh;
const x2 = dx + dw;
const u1 = sx + 0.1;
const v1 = sy + 0.1;
const u2 = sx + sw;
const v2 = sy + sh;
const u1 = sx + 0.1;
const v1 = sy + 0.1;
const u2 = sx + sw;
const v2 = sy + sh;
const pu = palette.u; // + palette.v * 1024;
const pv = palette.v;
const pu = palette.u; // + palette.v * 1024;
const pv = palette.v;
const t0 = transform[0];
const t1 = transform[1];
const t2 = transform[2];
const t3 = transform[3];
const t4 = transform[4];
const t5 = transform[5];
const t0 = transform[0];
const t1 = transform[1];
const t2 = transform[2];
const t3 = transform[3];
const t4 = transform[4];
const t5 = transform[5];
// pushVertex(vertices, index, dx, dy, u1, v1, pu, pv, color, c1, transform);
vertices[(index + 0) | 0] = t0 * dx + t2 * dy + t4;
vertices[(index + 1) | 0] = t1 * dx + t3 * dy + t5;
vertices[(index + 2) | 0] = u1;
vertices[(index + 3) | 0] = v1;
vertices[(index + 4) | 0] = pu;
vertices[(index + 5) | 0] = pv;
vertices[(index + 6) | 0] = color;
vertices[(index + 7) | 0] = c1;
// pushVertex(vertices, index, dx, dy, u1, v1, pu, pv, color, c1, transform);
vertices[(index + 0) | 0] = t0 * dx + t2 * dy + t4;
vertices[(index + 1) | 0] = t1 * dx + t3 * dy + t5;
vertices[(index + 2) | 0] = u1;
vertices[(index + 3) | 0] = v1;
vertices[(index + 4) | 0] = pu;
vertices[(index + 5) | 0] = pv;
vertices[(index + 6) | 0] = color;
vertices[(index + 7) | 0] = c1;
// pushVertex(vertices, index + 8, x2, dy, u2, v1, pu, pv, color, c1, transform);
vertices[(index + 8) | 0] = t0 * x2 + t2 * dy + t4;
vertices[(index + 9) | 0] = t1 * x2 + t3 * dy + t5;
vertices[(index + 10) | 0] = u2;
vertices[(index + 11) | 0] = v1;
vertices[(index + 12) | 0] = pu;
vertices[(index + 13) | 0] = pv;
vertices[(index + 14) | 0] = color;
vertices[(index + 15) | 0] = c1;
// pushVertex(vertices, index + 8, x2, dy, u2, v1, pu, pv, color, c1, transform);
vertices[(index + 8) | 0] = t0 * x2 + t2 * dy + t4;
vertices[(index + 9) | 0] = t1 * x2 + t3 * dy + t5;
vertices[(index + 10) | 0] = u2;
vertices[(index + 11) | 0] = v1;
vertices[(index + 12) | 0] = pu;
vertices[(index + 13) | 0] = pv;
vertices[(index + 14) | 0] = color;
vertices[(index + 15) | 0] = c1;
// pushVertex(vertices, index + 16, x2, y2, u2, v2, pu, pv, color, c1, transform);
vertices[(index + 16) | 0] = t0 * x2 + t2 * y2 + t4;
vertices[(index + 17) | 0] = t1 * x2 + t3 * y2 + t5;
vertices[(index + 18) | 0] = u2;
vertices[(index + 19) | 0] = v2;
vertices[(index + 20) | 0] = pu;
vertices[(index + 21) | 0] = pv;
vertices[(index + 22) | 0] = color;
vertices[(index + 23) | 0] = c1;
// pushVertex(vertices, index + 16, x2, y2, u2, v2, pu, pv, color, c1, transform);
vertices[(index + 16) | 0] = t0 * x2 + t2 * y2 + t4;
vertices[(index + 17) | 0] = t1 * x2 + t3 * y2 + t5;
vertices[(index + 18) | 0] = u2;
vertices[(index + 19) | 0] = v2;
vertices[(index + 20) | 0] = pu;
vertices[(index + 21) | 0] = pv;
vertices[(index + 22) | 0] = color;
vertices[(index + 23) | 0] = c1;
// pushVertex(vertices, index + 24, dx, y2, u1, v2, pu, pv, color, c1, transform);
vertices[(index + 24) | 0] = t0 * dx + t2 * y2 + t4;
vertices[(index + 25) | 0] = t1 * dx + t3 * y2 + t5;
vertices[(index + 26) | 0] = u1;
vertices[(index + 27) | 0] = v2;
vertices[(index + 28) | 0] = pu;
vertices[(index + 29) | 0] = pv;
vertices[(index + 30) | 0] = color;
vertices[(index + 31) | 0] = c1;
// pushVertex(vertices, index + 24, dx, y2, u1, v2, pu, pv, color, c1, transform);
vertices[(index + 24) | 0] = t0 * dx + t2 * y2 + t4;
vertices[(index + 25) | 0] = t1 * dx + t3 * y2 + t5;
vertices[(index + 26) | 0] = u1;
vertices[(index + 27) | 0] = v2;
vertices[(index + 28) | 0] = pu;
vertices[(index + 29) | 0] = pv;
vertices[(index + 30) | 0] = color;
vertices[(index + 31) | 0] = c1;
return index + 32;
return index + 32;
}
// function colorWithAlpha(color: number, alpha: number) {
@@ -133,111 +133,111 @@ function pushQuad(
export const PALETTE_BATCH_BYTES_PER_VERTEX = 2 * 4 + 4 * 4 + 4 + 4;
export class PaletteSpriteBatch extends BaseSpriteBatch implements IPaletteSpriteBatch {
palette = true;
defaultPalette: Palette = createPalette(new Uint32Array(0));
constructor(
gl: WebGLRenderingContext, capacity: number, buffer: ArrayBuffer, vertexBuffer: WebGLBuffer, indexBuffer: WebGLBuffer
) {
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
{ name: 'position', size: 2 },
{ name: 'texcoord0', size: 4 }, //, type: gl.UNSIGNED_SHORT },
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
{ name: 'color1', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
]);
}
drawImage(
type: number, color: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
palette = true;
defaultPalette: Palette = createPalette(new Uint32Array(0));
constructor(
gl: WebGLRenderingContext, capacity: number, buffer: ArrayBuffer, vertexBuffer: WebGLBuffer, indexBuffer: WebGLBuffer
) {
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
{ name: 'position', size: 2 },
{ name: 'texcoord0', size: 4 }, //, type: gl.UNSIGNED_SHORT },
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
{ name: 'color1', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
]);
}
drawImage(
type: number, color: number, palette: Palette | undefined,
sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number
) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, sx, sy, sw, sh, dx, dy, dw, dh,
);
this.spritesCount++;
this.tris += 2;
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
if (w !== 0 && h !== 0) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, sx, sy, sw, sh, dx, dy, dw, dh,
);
this.spritesCount++;
this.tris += 2;
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
if (w !== 0 && h !== 0) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
const s = this.rectSprite || defaultRectSprite;
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
this.defaultPalette, s.x, s.y, s.w, s.h, x, y, w, h,
);
this.spritesCount++;
this.tris += 2;
}
}
drawSprite(s: Sprite, color: number, palette: Palette | undefined, x: number, y: number) {
if (s.w !== 0 && s.h !== 0) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
const s = this.rectSprite || defaultRectSprite;
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
this.defaultPalette, s.x, s.y, s.w, s.h, x, y, w, h,
);
this.spritesCount++;
this.tris += 2;
}
}
drawSprite(s: Sprite, color: number, palette: Palette | undefined, x: number, y: number) {
if (s.w !== 0 && s.h !== 0) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
if (this.hasCrop) {
const crop = this.cropRect;
if (this.hasCrop) {
const crop = this.cropRect;
let sx = s.x;
let sy = s.y;
let w = s.w;
let h = s.h;
let dx = x + s.ox;
let dy = y + s.oy;
let sx = s.x;
let sy = s.y;
let w = s.w;
let h = s.h;
let dx = x + s.ox;
let dy = y + s.oy;
const cropX = crop.x; // - this.transform[4];
const cropY = crop.y; // - this.transform[5];
const shiftLeft = cropX - dx;
const shiftTop = cropY - dy;
const shiftRight = (dx + w) - (cropX + crop.w);
const shiftBottom = (dy + h) - (cropY + crop.h);
const cropX = crop.x; // - this.transform[4];
const cropY = crop.y; // - this.transform[5];
const shiftLeft = cropX - dx;
const shiftTop = cropY - dy;
const shiftRight = (dx + w) - (cropX + crop.w);
const shiftBottom = (dy + h) - (cropY + crop.h);
if (shiftLeft > 0) {
sx += shiftLeft;
dx += shiftLeft;
w -= shiftLeft;
}
if (shiftLeft > 0) {
sx += shiftLeft;
dx += shiftLeft;
w -= shiftLeft;
}
if (shiftRight > 0) {
w -= shiftRight;
}
if (shiftRight > 0) {
w -= shiftRight;
}
if (shiftTop > 0) {
sy += shiftTop;
dy += shiftTop;
h -= shiftTop;
}
if (shiftTop > 0) {
sy += shiftTop;
dy += shiftTop;
h -= shiftTop;
}
if (shiftBottom > 0) {
h -= shiftBottom;
}
if (shiftBottom > 0) {
h -= shiftBottom;
}
if (w > 0 && h > 0) {
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, sx, sy, w, h, dx, dy, w, h,
);
this.spritesCount++;
this.tris += 2;
}
} else {
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h,
);
this.spritesCount++;
this.tris += 2;
}
}
}
if (w > 0 && h > 0) {
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, sx, sy, w, h, dx, dy, w, h,
);
this.spritesCount++;
this.tris += 2;
}
} else {
this.index = pushQuad(
this.vertices, //this.verticesUint32,
this.transform, this.index, s.type, getColorFloat(color, this.globalAlpha),
palette || this.defaultPalette, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h,
);
this.spritesCount++;
this.tris += 2;
}
}
}
}
+63 -63
View File
@@ -2,14 +2,14 @@ import { Sprite, SpriteBatch as ISpriteBatch, Matrix2D } from '../common/interfa
import { BaseSpriteBatch, getColorFloat } from './baseSpriteBatch';
function vertex(
vertices: Float32Array, _verticesUint32: Uint32Array, index: number,
x: number, y: number, u: number, v: number, c: number, transform: Matrix2D
vertices: Float32Array, _verticesUint32: Uint32Array, index: number,
x: number, y: number, u: number, v: number, c: number, transform: Matrix2D
) {
vertices[index++] = transform[0] * x + transform[2] * y + transform[4];
vertices[index++] = transform[1] * x + transform[3] * y + transform[5];
vertices[index++] = u;
vertices[index++] = v;
vertices[index++] = c;
vertices[index++] = transform[0] * x + transform[2] * y + transform[4];
vertices[index++] = transform[1] * x + transform[3] * y + transform[5];
vertices[index++] = u;
vertices[index++] = v;
vertices[index++] = c;
}
// function colorWithAlpha(color: number, alpha: number) {
@@ -17,68 +17,68 @@ function vertex(
// }
export class SpriteBatch extends BaseSpriteBatch implements ISpriteBatch {
palette = false;
depth = 0;
constructor(
gl: WebGLRenderingContext,
capacity: number,
buffer: ArrayBuffer,
vertexBuffer: WebGLBuffer,
indexBuffer: WebGLBuffer
) {
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
{ name: 'position', size: 2 },
{ name: 'texcoord0', size: 2 }, // , type: gl.UNSIGNED_SHORT },
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
]);
}
drawImage(
color: number, sx: number, sy: number, sw: number, sh: number,
dx: number, dy: number, dw: number, dh: number
) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
palette = false;
depth = 0;
constructor(
gl: WebGLRenderingContext,
capacity: number,
buffer: ArrayBuffer,
vertexBuffer: WebGLBuffer,
indexBuffer: WebGLBuffer
) {
super(gl, capacity, buffer, vertexBuffer, indexBuffer, [
{ name: 'position', size: 2 },
{ name: 'texcoord0', size: 2 }, // , type: gl.UNSIGNED_SHORT },
{ name: 'color', size: 4, type: gl.UNSIGNED_BYTE, normalized: true },
]);
}
drawImage(
color: number, sx: number, sy: number, sw: number, sh: number,
dx: number, dy: number, dw: number, dh: number
) {
if (this.capacity <= this.spritesCount) {
this.flush();
}
const c = getColorFloat(color, this.globalAlpha);
const c = getColorFloat(color, this.globalAlpha);
const x2 = dx + dw;
const y2 = dy + dh;
const x2 = dx + dw;
const y2 = dy + dh;
const u1 = sx;
const v1 = sy;
const u2 = sx + sw;
const v2 = sy + sh;
const u1 = sx;
const v1 = sy;
const u2 = sx + sw;
const v2 = sy + sh;
const vertices = this.vertices;
const verticesUint32 = this.verticesUint32;
const transform = this.transform;
const vertices = this.vertices;
const verticesUint32 = this.verticesUint32;
const transform = this.transform;
const index = this.index;
const index = this.index;
vertex(vertices, verticesUint32, index, dx, dy, u1, v1, c, transform);
vertex(vertices, verticesUint32, index + 5, x2, dy, u2, v1, c, transform);
vertex(vertices, verticesUint32, index + 10, x2, y2, u2, v2, c, transform);
vertex(vertices, verticesUint32, index + 15, dx, y2, u1, v2, c, transform);
vertex(vertices, verticesUint32, index, dx, dy, u1, v1, c, transform);
vertex(vertices, verticesUint32, index + 5, x2, dy, u2, v1, c, transform);
vertex(vertices, verticesUint32, index + 10, x2, y2, u2, v2, c, transform);
vertex(vertices, verticesUint32, index + 15, dx, y2, u1, v2, c, transform);
this.index += 20;
this.spritesCount++;
this.tris += 2;
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
if (w && h) {
const rect = this.rectSprite;
this.index += 20;
this.spritesCount++;
this.tris += 2;
}
drawRect(color: number, x: number, y: number, w: number, h: number) {
if (w && h) {
const rect = this.rectSprite;
if (rect) {
this.drawImage(color, rect.x, rect.y, rect.w, rect.h, x, y, w, h);
} else {
this.drawImage(color, 0, 0, 1, 1, x, y, w, h);
}
}
}
drawSprite(s: Sprite, color: number, x: number, y: number) {
if (s && s.w && s.h) {
this.drawImage(color, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
}
}
if (rect) {
this.drawImage(color, rect.x, rect.y, rect.w, rect.h, x, y, w, h);
} else {
this.drawImage(color, 0, 0, 1, 1, x, y, w, h);
}
}
}
drawSprite(s: Sprite, color: number, x: number, y: number) {
if (s && s.w && s.h) {
this.drawImage(color, s.x, s.y, s.w, s.h, x + s.ox, y + s.oy, s.w, s.h);
}
}
}
+175 -175
View File
@@ -4,24 +4,24 @@ import { stringToCodesTemp, codesBuffer } from '../common/stringUtils';
import { createSprite } from '../client/spriteUtils';
export const enum HAlign {
Left,
Right,
Center,
Left,
Right,
Center,
}
export const enum VAlign {
Top,
Bottom,
Middle,
Top,
Bottom,
Middle,
}
export interface TextOptions {
skipEmotes?: boolean;
colorEmotes?: boolean;
palette?: Palette;
emojiPalette?: Palette;
lineSpacing?: number;
monospace?: boolean;
skipEmotes?: boolean;
colorEmotes?: boolean;
palette?: Palette;
emojiPalette?: Palette;
lineSpacing?: number;
monospace?: boolean;
}
type Charset = { code: number; sprite: Sprite; }[];
@@ -34,243 +34,243 @@ const LINEFEED = '\n'.charCodeAt(0);
const defaultOptions: TextOptions = {};
export interface SpriteFont {
lineSpacing: number;
letterSpacing: number;
letterShiftX: number;
letterShiftY: number;
letterShiftWidth: number;
letterShiftHeight: number;
letterHeight: number;
letterHeightReal: number;
letterWidth: number;
chars: Map<number, Sprite>;
emoji: Map<number, Sprite>;
defaultChar: Sprite;
lineSpacing: number;
letterSpacing: number;
letterShiftX: number;
letterShiftY: number;
letterShiftWidth: number;
letterShiftHeight: number;
letterHeight: number;
letterHeightReal: number;
letterWidth: number;
chars: Map<number, Sprite>;
emoji: Map<number, Sprite>;
defaultChar: Sprite;
}
export function createSpriteFont(charset: Charset, emojiCharset: Charset, spaceWidth: number): SpriteFont {
const lineSpacing = 2;
const letterSpacing = 1;
const letterShiftX = 0;
const letterShiftY = 0;
const letterShiftWidth = 0;
const letterShiftHeight = 0;
const lineSpacing = 2;
const letterSpacing = 1;
const letterShiftX = 0;
const letterShiftY = 0;
const letterShiftWidth = 0;
const letterShiftHeight = 0;
const chars = new Map<number, Sprite>();
const emoji = new Map<number, Sprite>();
const chars = new Map<number, Sprite>();
const emoji = new Map<number, Sprite>();
charset.filter(c => !!c).forEach(c => chars.set(c.code, c.sprite));
emojiCharset.filter(e => !!e && !!e.code).forEach(e => emoji.set(e.code, e.sprite));
charset.filter(c => !!c).forEach(c => chars.set(c.code, c.sprite));
emojiCharset.filter(e => !!e && !!e.code).forEach(e => emoji.set(e.code, e.sprite));
const char0 = chars.get(0)!;
const letterHeight = char0.h;
const letterWidth = char0.w;
const letterHeightReal = char0.h + char0.oy;
chars.set(SPACE, createSprite(0, 0, 0, 0, spaceWidth, char0.h, 0));
chars.set(TAB, createSprite(0, 0, 0, 0, spaceWidth * 4, char0.h, 0));
const defaultChar = chars.get(DEFAULT)!;
const char0 = chars.get(0)!;
const letterHeight = char0.h;
const letterWidth = char0.w;
const letterHeightReal = char0.h + char0.oy;
chars.set(SPACE, createSprite(0, 0, 0, 0, spaceWidth, char0.h, 0));
chars.set(TAB, createSprite(0, 0, 0, 0, spaceWidth * 4, char0.h, 0));
const defaultChar = chars.get(DEFAULT)!;
return {
lineSpacing, letterSpacing, letterShiftX, letterShiftY, letterShiftWidth, letterShiftHeight,
letterHeight, letterHeightReal, letterWidth, chars, emoji, defaultChar,
};
return {
lineSpacing, letterSpacing, letterShiftX, letterShiftY, letterShiftWidth, letterShiftHeight,
letterHeight, letterHeightReal, letterWidth, chars, emoji, defaultChar,
};
}
function drawChars(
batch: Batch, chars: Uint32Array, length: number, font: SpriteFont, color: number, x: number, y: number,
options: TextOptions
batch: Batch, chars: Uint32Array, length: number, font: SpriteFont, color: number, x: number, y: number,
options: TextOptions
) {
const { lineSpacing = font.lineSpacing, monospace = false } = options;
x = Math.round(x) | 0;
y = Math.round(y) | 0;
let currentX = x;
const { lineSpacing = font.lineSpacing, monospace = false } = options;
x = Math.round(x) | 0;
y = Math.round(y) | 0;
let currentX = x;
for (let i = 0; i < length; i++) {
const code = chars[i];
for (let i = 0; i < length; i++) {
const code = chars[i];
if (code === LINEFEED) {
currentX = x;
y += font.letterHeight + lineSpacing;
} else {
const charWidth = drawChar(batch, font, code, color, currentX, y, options);
currentX += (monospace ? font.letterWidth : charWidth) + font.letterSpacing;
}
}
if (code === LINEFEED) {
currentX = x;
y += font.letterHeight + lineSpacing;
} else {
const charWidth = drawChar(batch, font, code, color, currentX, y, options);
currentX += (monospace ? font.letterWidth : charWidth) + font.letterSpacing;
}
}
}
export function drawText(
batch: Batch, text: string, font: SpriteFont, color: number, x: number, y: number, options = defaultOptions
batch: Batch, text: string, font: SpriteFont, color: number, x: number, y: number, options = defaultOptions
) {
const length = stringToCodesTemp(text);
drawChars(batch, codesBuffer, length, font, color, x, y, options);
const length = stringToCodesTemp(text);
drawChars(batch, codesBuffer, length, font, color, x, y, options);
}
export function drawOutlinedText(
batch: Batch, text: string, font: SpriteFont, color: number, outlineColor: number, x: number, y: number,
options = defaultOptions
batch: Batch, text: string, font: SpriteFont, color: number, outlineColor: number, x: number, y: number,
options = defaultOptions
) {
const length = stringToCodesTemp(text);
const length = stringToCodesTemp(text);
options.skipEmotes = true;
options.skipEmotes = true;
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y + 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y + 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y + 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y + 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x, y + 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x - 1, y, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x, y - 1, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x + 1, y, options);
drawChars(batch, codesBuffer, length, font, outlineColor, x, y + 1, options);
options.skipEmotes = false;
options.skipEmotes = false;
drawChars(batch, codesBuffer, length, font, color, x, y, options);
drawChars(batch, codesBuffer, length, font, color, x, y, options);
}
export function drawTextAligned(
spriteBatch: Batch, text: string, font: SpriteFont, color: number, rect: Rect, halign = HAlign.Left, valign = VAlign.Top,
options: TextOptions = defaultOptions
spriteBatch: Batch, text: string, font: SpriteFont, color: number, rect: Rect, halign = HAlign.Left, valign = VAlign.Top,
options: TextOptions = defaultOptions
) {
const length = stringToCodesTemp(text);
const { x, y } = alignChars(font, codesBuffer, length, rect, halign, valign);
drawChars(spriteBatch, codesBuffer, length, font, color, x, y, options);
const length = stringToCodesTemp(text);
const { x, y } = alignChars(font, codesBuffer, length, rect, halign, valign);
drawChars(spriteBatch, codesBuffer, length, font, color, x, y, options);
}
export function lineBreak(text: string, font: SpriteFont, width: number) {
const lines: string[][] = [];
const spaceWidth = measureChar(font, SPACE) + font.letterSpacing * 2;
const lines: string[][] = [];
const spaceWidth = measureChar(font, SPACE) + font.letterSpacing * 2;
let line: string[] = [];
let lineWidth = 0;
let line: string[] = [];
let lineWidth = 0;
for (const word of text.split(' ')) {
const { w } = measureText(word, font);
for (const word of text.split(' ')) {
const { w } = measureText(word, font);
if (lineWidth) {
lineWidth += spaceWidth;
if (lineWidth) {
lineWidth += spaceWidth;
if (lineWidth + w > width) {
lines.push(line);
line = [];
lineWidth = 0;
}
}
if (lineWidth + w > width) {
lines.push(line);
line = [];
lineWidth = 0;
}
}
line.push(word);
lineWidth += w;
}
line.push(word);
lineWidth += w;
}
if (line.length) {
lines.push(line);
}
if (line.length) {
lines.push(line);
}
return lines.map(x => x.join(' ')).join('\n');
return lines.map(x => x.join(' ')).join('\n');
}
function measureChars(chars: Uint32Array, length: number, font: SpriteFont): { w: number; h: number; } {
let maxW = 0;
let lines = 1;
let w = 0;
let maxW = 0;
let lines = 1;
let w = 0;
for (let i = 0; i < length; i++) {
const code = chars[i];
for (let i = 0; i < length; i++) {
const code = chars[i];
if (code === LINEFEED) {
maxW = Math.max(maxW, w);
w = 0;
lines++;
} else {
if (w) {
w += font.letterSpacing;
}
w += measureChar(font, code);
}
}
if (code === LINEFEED) {
maxW = Math.max(maxW, w);
w = 0;
lines++;
} else {
if (w) {
w += font.letterSpacing;
}
w += measureChar(font, code);
}
}
return {
w: Math.max(maxW, w),
h: lines * font.letterHeight + (lines - 1) * font.lineSpacing
};
return {
w: Math.max(maxW, w),
h: lines * font.letterHeight + (lines - 1) * font.lineSpacing
};
}
export function measureText(text: string, font: SpriteFont) {
const length = stringToCodesTemp(text);
return measureChars(codesBuffer, length, font);
const length = stringToCodesTemp(text);
return measureChars(codesBuffer, length, font);
}
export function getCharacterSprite(char: string, font: SpriteFont): Sprite | undefined {
let sprite: Sprite | undefined;
let unset = false;
const length = stringToCodesTemp(char);
let sprite: Sprite | undefined;
let unset = false;
const length = stringToCodesTemp(char);
for (let i = 0; i < length; i++) {
const code = codesBuffer[i];
unset = !!sprite;
sprite = font.emoji.get(code) || getChar(font, code);
}
for (let i = 0; i < length; i++) {
const code = codesBuffer[i];
unset = !!sprite;
sprite = font.emoji.get(code) || getChar(font, code);
}
return unset ? undefined : sprite;
return unset ? undefined : sprite;
}
function measureChar(font: SpriteFont, code: number): number {
const sprite = font.emoji.get(code) || getChar(font, code);
return sprite.w + sprite.ox;
const sprite = font.emoji.get(code) || getChar(font, code);
return sprite.w + sprite.ox;
}
function drawChar(
batch: Batch, font: SpriteFont, code: number, color: number, x: number, y: number, options: TextOptions
batch: Batch, font: SpriteFont, code: number, color: number, x: number, y: number, options: TextOptions
): number {
const emote = font.emoji.get(code);
const px = x + font.letterShiftX;
const py = y + font.letterShiftY;
const emote = font.emoji.get(code);
const px = x + font.letterShiftX;
const py = y + font.letterShiftY;
if (emote) {
const skipEmote = !!options.skipEmotes;
const colorEmote = !!options.colorEmotes;
const emoteColor = colorEmote ? color : WHITE;
if (emote) {
const skipEmote = !!options.skipEmotes;
const colorEmote = !!options.colorEmotes;
const emoteColor = colorEmote ? color : WHITE;
if (!skipEmote) {
if (isPaletteSpriteBatch(batch)) {
batch.drawSprite(emote, emoteColor, options.emojiPalette, px, py);
} else {
batch.drawSprite(emote, emoteColor, px, py);
}
}
if (!skipEmote) {
if (isPaletteSpriteBatch(batch)) {
batch.drawSprite(emote, emoteColor, options.emojiPalette, px, py);
} else {
batch.drawSprite(emote, emoteColor, px, py);
}
}
return emote.w + emote.ox;
} else {
const c = getChar(font, code);
return emote.w + emote.ox;
} else {
const c = getChar(font, code);
if (isPaletteSpriteBatch(batch)) {
batch.drawSprite(c, color, options.palette, px, py);
} else {
batch.drawSprite(c, color, px, py);
}
if (isPaletteSpriteBatch(batch)) {
batch.drawSprite(c, color, options.palette, px, py);
} else {
batch.drawSprite(c, color, px, py);
}
return c.w + c.ox + font.letterShiftWidth;
}
return c.w + c.ox + font.letterShiftWidth;
}
}
function alignChars(font: SpriteFont, chars: Uint32Array, length: number, rect: Rect, halign: HAlign, valign: VAlign) {
let x = rect.x;
let y = rect.y;
let x = rect.x;
let y = rect.y;
if (halign !== HAlign.Left || valign !== VAlign.Top) {
const size = measureChars(chars, length, font);
if (halign !== HAlign.Left || valign !== VAlign.Top) {
const size = measureChars(chars, length, font);
if (halign !== HAlign.Left) {
x += halign === HAlign.Center ? (rect.w - size.w) / 2 : (rect.w - size.w);
}
if (halign !== HAlign.Left) {
x += halign === HAlign.Center ? (rect.w - size.w) / 2 : (rect.w - size.w);
}
if (valign !== VAlign.Top) {
y += valign === VAlign.Middle ? (rect.h - size.h) / 2 : (rect.h - size.h);
}
}
if (valign !== VAlign.Top) {
y += valign === VAlign.Middle ? (rect.h - size.h) / 2 : (rect.h - size.h);
}
}
return { x, y };
return { x, y };
}
function getChar(font: SpriteFont, code: number): Sprite {
return font.chars.get(code) || font.defaultChar;
return font.chars.get(code) || font.defaultChar;
}
+8 -8
View File
@@ -2,15 +2,15 @@ import { SpriteSheet } from '../common/interfaces';
import { createTexture, disposeTexture } from './webgl/texture2d';
export function createTexturesForSpriteSheets(gl: WebGLRenderingContext, sheets: SpriteSheet[], texture = createTexture) {
sheets.forEach(sheet => {
if (sheet.data) {
sheet.texture = texture(gl, sheet.data);
}
});
sheets.forEach(sheet => {
if (sheet.data) {
sheet.texture = texture(gl, sheet.data);
}
});
}
export function disposeTexturesForSpriteSheets(gl: WebGLRenderingContext, sheets: SpriteSheet[]) {
sheets.forEach(sheet => {
sheet.texture = disposeTexture(gl, sheet.texture);
});
sheets.forEach(sheet => {
sheet.texture = disposeTexture(gl, sheet.texture);
});
}
+26 -26
View File
@@ -1,48 +1,48 @@
import { Texture2D, createEmptyTexture, resizeTexture } from './texture2d';
export interface FrameBuffer {
handle: WebGLFramebuffer;
texture: Texture2D;
width: number;
height: number;
handle: WebGLFramebuffer;
texture: Texture2D;
width: number;
height: number;
}
export function createFrameBuffer(gl: WebGLRenderingContext, width: number, height: number): FrameBuffer {
const handle = gl.createFramebuffer();
const handle = gl.createFramebuffer();
if (!handle) {
throw new Error('Failed to create frame buffer');
}
if (!handle) {
throw new Error('Failed to create frame buffer');
}
const texture = createEmptyTexture(gl, width, height, gl.RGB);
return { handle, texture, width, height };
const texture = createEmptyTexture(gl, width, height, gl.RGB);
return { handle, texture, width, height };
}
export function disposeFrameBuffer(gl: WebGLRenderingContext | undefined, buffer: FrameBuffer | undefined) {
try {
if (gl && buffer) {
gl.deleteFramebuffer(buffer.handle);
gl.deleteTexture(buffer.texture.handle);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
try {
if (gl && buffer) {
gl.deleteFramebuffer(buffer.handle);
gl.deleteTexture(buffer.texture.handle);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
return undefined;
return undefined;
}
export function resizeFrameBuffer(gl: WebGLRenderingContext, frameBuffer: FrameBuffer, width: number, height: number) {
resizeTexture(gl, frameBuffer.texture, width, height);
frameBuffer.width = width;
frameBuffer.height = height;
resizeTexture(gl, frameBuffer.texture, width, height);
frameBuffer.width = width;
frameBuffer.height = height;
}
export function bindFrameBuffer(gl: WebGLRenderingContext, { handle, texture }: FrameBuffer) {
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture.handle, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture.handle, 0);
}
export function unbindFrameBuffer(gl: WebGLRenderingContext) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
+218 -218
View File
@@ -2,16 +2,16 @@ import { Texture2D, resizeTexture, disposeTexture, createEmptyTexture } from './
import { array } from '../../common/utils';
export interface FBOOptions {
/* Upgrade to floating point if available, otherwise fallback to 8bit. (default false) */
preferFloat?: boolean;
/* Use floating point textures (default false) */
float?: boolean;
/* The number of color buffers to create (default 1) */
color?: number;
/* If fbo has a depth buffer (default: true) */
depth?: boolean;
/* If fbo has a stencil buffer (default: false) */
stencil?: boolean;
/* Upgrade to floating point if available, otherwise fallback to 8bit. (default false) */
preferFloat?: boolean;
/* Use floating point textures (default false) */
float?: boolean;
/* The number of color buffers to create (default 1) */
color?: number;
/* If fbo has a depth buffer (default: true) */
depth?: boolean;
/* If fbo has a stencil buffer (default: false) */
stencil?: boolean;
}
type WebGL = WebGLRenderingContext;
@@ -20,289 +20,289 @@ type FBOState = [WebGLFramebuffer | null, WebGLRenderbuffer | null, WebGLTexture
let colorAttachmentArrays: number[][] | null = null;
export interface FrameBuffer {
color: (Texture2D | undefined)[];
depth: Texture2D | undefined;
handle: WebGLFramebuffer | null;
colorRenderBuffer: WebGLRenderbuffer | null;
depthRenderBuffer: WebGLRenderbuffer | null;
width: number;
height: number;
gl: WebGLRenderingContext;
colorType: number;
useDepth: boolean;
useStencil: boolean;
ext: WEBGL_draw_buffers | null;
color: (Texture2D | undefined)[];
depth: Texture2D | undefined;
handle: WebGLFramebuffer | null;
colorRenderBuffer: WebGLRenderbuffer | null;
depthRenderBuffer: WebGLRenderbuffer | null;
width: number;
height: number;
gl: WebGLRenderingContext;
colorType: number;
useDepth: boolean;
useStencil: boolean;
ext: WEBGL_draw_buffers | null;
}
export function createFrameBuffer(gl: WebGL, width: number, height: number, options: FBOOptions = {}): FrameBuffer {
const { color = 1, depth = true, stencil = false, float = false, preferFloat = false } = options;
const ext = gl.getExtension('WEBGL_draw_buffers');
const { color = 1, depth = true, stencil = false, float = false, preferFloat = false } = options;
const ext = gl.getExtension('WEBGL_draw_buffers');
width = width | 0;
height = height | 0;
width = width | 0;
height = height | 0;
if (!colorAttachmentArrays && ext) {
lazyInitColorAttachments(gl, ext);
}
if (!colorAttachmentArrays && ext) {
lazyInitColorAttachments(gl, ext);
}
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
if (maxFBOSize != null && (width < 0 || width > maxFBOSize || height < 0 || height > maxFBOSize)) {
throw new Error('Parameters are too large for FBO');
}
if (maxFBOSize != null && (width < 0 || width > maxFBOSize || height < 0 || height > maxFBOSize)) {
throw new Error('Parameters are too large for FBO');
}
const numColors = Math.max(color, 0);
const numColors = Math.max(color, 0);
if (numColors < 0) {
throw new Error('Must specify a nonnegative number of colors');
} else if (numColors > 1) {
if (!ext) {
throw new Error('Multiple draw buffer extension not supported');
} else if (numColors > gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL)) {
throw new Error(`Context does not support ${numColors} draw buffers`);
}
}
if (numColors < 0) {
throw new Error('Must specify a nonnegative number of colors');
} else if (numColors > 1) {
if (!ext) {
throw new Error('Multiple draw buffer extension not supported');
} else if (numColors > gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL)) {
throw new Error(`Context does not support ${numColors} draw buffers`);
}
}
let colorType = gl.UNSIGNED_BYTE;
const OES_texture_float = gl.getExtension('OES_texture_float');
let colorType = gl.UNSIGNED_BYTE;
const OES_texture_float = gl.getExtension('OES_texture_float');
if (float && numColors > 0) {
if (!OES_texture_float) {
throw new Error('Context does not support floating point textures');
}
if (float && numColors > 0) {
if (!OES_texture_float) {
throw new Error('Context does not support floating point textures');
}
colorType = gl.FLOAT;
} else if (preferFloat && numColors > 0) {
if (OES_texture_float) {
colorType = gl.FLOAT;
}
}
colorType = gl.FLOAT;
} else if (preferFloat && numColors > 0) {
if (OES_texture_float) {
colorType = gl.FLOAT;
}
}
const fbo: FrameBuffer = {
gl, width, height, colorType, color: array(numColors, undefined), useDepth: depth, useStencil: stencil, ext,
colorRenderBuffer: null, depth: undefined, depthRenderBuffer: null, handle: null,
};
const fbo: FrameBuffer = {
gl, width, height, colorType, color: array(numColors, undefined), useDepth: depth, useStencil: stencil, ext,
colorRenderBuffer: null, depth: undefined, depthRenderBuffer: null, handle: null,
};
rebuild(fbo);
return fbo;
rebuild(fbo);
return fbo;
}
export function resizeFrameBuffer(fbo: FrameBuffer, w: number, h: number) {
if (fbo.width === w && fbo.height === h) {
return;
}
if (fbo.width === w && fbo.height === h) {
return;
}
const gl = fbo.gl;
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
const gl = fbo.gl;
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
if (maxFBOSize != null && (w < 0 || w > maxFBOSize || h < 0 || h > maxFBOSize)) {
throw new Error(`Can't resize FBO, invalid dimensions`);
}
if (maxFBOSize != null && (w < 0 || w > maxFBOSize || h < 0 || h > maxFBOSize)) {
throw new Error(`Can't resize FBO, invalid dimensions`);
}
fbo.width = w;
fbo.height = h;
fbo.width = w;
fbo.height = h;
const state = saveFBOState(gl);
const state = saveFBOState(gl);
for (const color of fbo.color) {
if (color) {
resizeTexture(gl, color, w, h);
}
}
for (const color of fbo.color) {
if (color) {
resizeTexture(gl, color, w, h);
}
}
if (fbo.colorRenderBuffer) {
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.colorRenderBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, w, h);
}
if (fbo.colorRenderBuffer) {
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.colorRenderBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, w, h);
}
if (fbo.depth) {
resizeTexture(gl, fbo.depth, w, h);
}
if (fbo.depth) {
resizeTexture(gl, fbo.depth, w, h);
}
if (fbo.depthRenderBuffer) {
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.depthRenderBuffer);
if (fbo.depthRenderBuffer) {
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.depthRenderBuffer);
if (fbo.useDepth && fbo.useStencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h);
} else if (fbo.useDepth) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
} else if (fbo.useStencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h);
}
}
if (fbo.useDepth && fbo.useStencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h);
} else if (fbo.useDepth) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
} else if (fbo.useStencil) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h);
}
}
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.handle);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.handle);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
disposeFrameBuffer(fbo);
restoreFBOState(gl, state);
throwFBOError(gl, status, `reshape(${w}, ${h})`);
}
if (status !== gl.FRAMEBUFFER_COMPLETE) {
disposeFrameBuffer(fbo);
restoreFBOState(gl, state);
throwFBOError(gl, status, `reshape(${w}, ${h})`);
}
restoreFBOState(gl, state);
restoreFBOState(gl, state);
}
export function bindFrameBuffer(fbo: FrameBuffer) {
fbo.gl.bindFramebuffer(fbo.gl.FRAMEBUFFER, fbo.handle);
fbo.gl.viewport(0, 0, fbo.width, fbo.height);
fbo.gl.bindFramebuffer(fbo.gl.FRAMEBUFFER, fbo.handle);
fbo.gl.viewport(0, 0, fbo.width, fbo.height);
}
export function disposeFrameBuffer(fbo: FrameBuffer | undefined): undefined {
if (fbo) {
fbo.gl.deleteFramebuffer(fbo.handle);
fbo.handle = null;
fbo.depth = disposeTexture(fbo.gl, fbo.depth);
if (fbo) {
fbo.gl.deleteFramebuffer(fbo.handle);
fbo.handle = null;
fbo.depth = disposeTexture(fbo.gl, fbo.depth);
if (fbo.depthRenderBuffer) {
fbo.gl.deleteRenderbuffer(fbo.depthRenderBuffer);
fbo.depthRenderBuffer = null;
}
if (fbo.depthRenderBuffer) {
fbo.gl.deleteRenderbuffer(fbo.depthRenderBuffer);
fbo.depthRenderBuffer = null;
}
for (let i = 0; i < fbo.color.length; i++) {
fbo.color[i] = disposeTexture(fbo.gl, fbo.color[i]);
}
for (let i = 0; i < fbo.color.length; i++) {
fbo.color[i] = disposeTexture(fbo.gl, fbo.color[i]);
}
if (fbo.colorRenderBuffer) {
fbo.gl.deleteRenderbuffer(fbo.colorRenderBuffer);
fbo.colorRenderBuffer = null;
}
}
if (fbo.colorRenderBuffer) {
fbo.gl.deleteRenderbuffer(fbo.colorRenderBuffer);
fbo.colorRenderBuffer = null;
}
}
return undefined;
return undefined;
}
function rebuild(fbo: FrameBuffer) {
const state = saveFBOState(fbo.gl);
const gl = fbo.gl;
const handle = fbo.handle = gl.createFramebuffer();
const numColors = fbo.color.length;
const { width, height, ext, useStencil, useDepth, colorType } = fbo;
const state = saveFBOState(fbo.gl);
const gl = fbo.gl;
const handle = fbo.handle = gl.createFramebuffer();
const numColors = fbo.color.length;
const { width, height, ext, useStencil, useDepth, colorType } = fbo;
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
for (let i = 0; i < numColors; ++i) {
fbo.color[i] = initTexture(gl, width, height, colorType, gl.RGBA, gl.COLOR_ATTACHMENT0 + i);
}
for (let i = 0; i < numColors; ++i) {
fbo.color[i] = initTexture(gl, width, height, colorType, gl.RGBA, gl.COLOR_ATTACHMENT0 + i);
}
if (numColors === 0) {
fbo.colorRenderBuffer = initRenderBuffer(gl, width, height, gl.RGBA4, gl.COLOR_ATTACHMENT0);
if (numColors === 0) {
fbo.colorRenderBuffer = initRenderBuffer(gl, width, height, gl.RGBA4, gl.COLOR_ATTACHMENT0);
if (ext) {
ext.drawBuffersWEBGL(colorAttachmentArrays![0]);
}
} else if (numColors > 1) {
if (ext) {
ext.drawBuffersWEBGL(colorAttachmentArrays![numColors]);
}
}
if (ext) {
ext.drawBuffersWEBGL(colorAttachmentArrays![0]);
}
} else if (numColors > 1) {
if (ext) {
ext.drawBuffersWEBGL(colorAttachmentArrays![numColors]);
}
}
const WEBGL_depth_texture = gl.getExtension('WEBGL_depth_texture');
const WEBGL_depth_texture = gl.getExtension('WEBGL_depth_texture');
if (WEBGL_depth_texture) {
if (useStencil) {
fbo.depth = initTexture(
gl, width, height, WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
} else if (useDepth) {
fbo.depth = initTexture(gl, width, height, gl.UNSIGNED_SHORT, gl.DEPTH_COMPONENT, gl.DEPTH_ATTACHMENT);
}
} else {
if (useDepth && useStencil) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
} else if (useDepth) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_COMPONENT16, gl.DEPTH_ATTACHMENT);
} else if (useStencil) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.STENCIL_INDEX8, gl.STENCIL_ATTACHMENT);
}
}
if (WEBGL_depth_texture) {
if (useStencil) {
fbo.depth = initTexture(
gl, width, height, WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
} else if (useDepth) {
fbo.depth = initTexture(gl, width, height, gl.UNSIGNED_SHORT, gl.DEPTH_COMPONENT, gl.DEPTH_ATTACHMENT);
}
} else {
if (useDepth && useStencil) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
} else if (useDepth) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_COMPONENT16, gl.DEPTH_ATTACHMENT);
} else if (useStencil) {
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.STENCIL_INDEX8, gl.STENCIL_ATTACHMENT);
}
}
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.deleteFramebuffer(fbo.handle);
fbo.handle = null;
fbo.depth = disposeTexture(gl, fbo.depth);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.deleteFramebuffer(fbo.handle);
fbo.handle = null;
fbo.depth = disposeTexture(gl, fbo.depth);
if (fbo.depthRenderBuffer) {
gl.deleteRenderbuffer(fbo.depthRenderBuffer);
fbo.depthRenderBuffer = null;
}
if (fbo.depthRenderBuffer) {
gl.deleteRenderbuffer(fbo.depthRenderBuffer);
fbo.depthRenderBuffer = null;
}
for (let i = 0; i < fbo.color.length; i++) {
fbo.color[i] = disposeTexture(gl, fbo.color[i]);
}
for (let i = 0; i < fbo.color.length; i++) {
fbo.color[i] = disposeTexture(gl, fbo.color[i]);
}
if (fbo.colorRenderBuffer) {
gl.deleteRenderbuffer(fbo.colorRenderBuffer);
fbo.colorRenderBuffer = null;
}
if (fbo.colorRenderBuffer) {
gl.deleteRenderbuffer(fbo.colorRenderBuffer);
fbo.colorRenderBuffer = null;
}
restoreFBOState(gl, state);
throwFBOError(gl, status);
}
restoreFBOState(gl, state);
throwFBOError(gl, status);
}
restoreFBOState(gl, state);
restoreFBOState(gl, state);
}
function saveFBOState(gl: WebGL): FBOState {
const fbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
const rbo = gl.getParameter(gl.RENDERBUFFER_BINDING);
const tex = gl.getParameter(gl.TEXTURE_BINDING_2D);
return [fbo, rbo, tex];
const fbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
const rbo = gl.getParameter(gl.RENDERBUFFER_BINDING);
const tex = gl.getParameter(gl.TEXTURE_BINDING_2D);
return [fbo, rbo, tex];
}
function restoreFBOState(gl: WebGL, [fbo, rbo, tex]: FBOState) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
gl.bindTexture(gl.TEXTURE_2D, tex);
}
function lazyInitColorAttachments(gl: WebGL, ext: WEBGL_draw_buffers) {
const maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
colorAttachmentArrays = [];
const maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
colorAttachmentArrays = [];
for (let i = 0; i <= maxColorAttachments; ++i) {
const x: number[] = [];
for (let i = 0; i <= maxColorAttachments; ++i) {
const x: number[] = [];
for (let j = 0; j < i; ++j) {
x.push(gl.COLOR_ATTACHMENT0 + j);
}
for (let j = 0; j < i; ++j) {
x.push(gl.COLOR_ATTACHMENT0 + j);
}
for (let j = i; j < maxColorAttachments; ++j) {
x.push(gl.NONE);
}
for (let j = i; j < maxColorAttachments; ++j) {
x.push(gl.NONE);
}
colorAttachmentArrays.push(x);
}
colorAttachmentArrays.push(x);
}
}
function throwFBOError(gl: WebGL, status: number, message = '') {
switch (status) {
case gl.FRAMEBUFFER_UNSUPPORTED:
throw new Error('Framebuffer unsupported');
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
throw new Error('Framebuffer incomplete attachment');
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
throw new Error('Framebuffer incomplete dimensions');
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
throw new Error('Framebuffer incomplete missing attachment');
default:
throw new Error(`Framebuffer failed for unspecified reason [${status}] (${message})`);
}
switch (status) {
case gl.FRAMEBUFFER_UNSUPPORTED:
throw new Error('Framebuffer unsupported');
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
throw new Error('Framebuffer incomplete attachment');
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
throw new Error('Framebuffer incomplete dimensions');
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
throw new Error('Framebuffer incomplete missing attachment');
default:
throw new Error(`Framebuffer failed for unspecified reason [${status}] (${message})`);
}
}
function initTexture(gl: WebGL, width: number, height: number, type: number, format: number, attachment: number) {
const texture = createEmptyTexture(gl, width, height, format, type);
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture.handle, 0);
return texture;
const texture = createEmptyTexture(gl, width, height, format, type);
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture.handle, 0);
return texture;
}
function initRenderBuffer(gl: WebGL, width: number, height: number, component: number, attachment: number) {
const result = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, result);
gl.renderbufferStorage(gl.RENDERBUFFER, component, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, result);
return result;
const result = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, result);
gl.renderbufferStorage(gl.RENDERBUFFER, component, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, result);
return result;
}
+123 -123
View File
@@ -1,155 +1,155 @@
import { timeStart, timeEnd } from '../../client/timing';
export interface VAOAttributes {
name: string;
buffer: WebGLBuffer;
size?: number;
type?: number;
normalized?: boolean;
stride?: number;
offset?: number;
divisor?: number;
name: string;
buffer: WebGLBuffer;
size?: number;
type?: number;
normalized?: boolean;
stride?: number;
offset?: number;
divisor?: number;
}
export interface VAO {
gl: WebGLRenderingContext;
bind(): void;
unbind(): void;
dispose(): void;
update(attributes: VAOAttributes[], elements: WebGLBuffer, elementsType?: number): void;
draw(mode: number, count: number, offset?: number): void;
gl: WebGLRenderingContext;
bind(): void;
unbind(): void;
dispose(): void;
update(attributes: VAOAttributes[], elements: WebGLBuffer, elementsType?: number): void;
draw(mode: number, count: number, offset?: number): void;
}
interface WebGL extends WebGLRenderingContext {
bindVertexArray?: any;
createVertexArray?: any;
deleteVertexArray?: any;
bindVertexArray?: any;
createVertexArray?: any;
deleteVertexArray?: any;
}
function extensionShim(gl: WebGL): OES_vertex_array_object {
return {
bindVertexArrayOES: gl.bindVertexArray.bind(gl),
createVertexArrayOES: gl.createVertexArray.bind(gl),
deleteVertexArrayOES: gl.deleteVertexArray.bind(gl),
} as any;
return {
bindVertexArrayOES: gl.bindVertexArray.bind(gl),
createVertexArrayOES: gl.createVertexArray.bind(gl),
deleteVertexArrayOES: gl.deleteVertexArray.bind(gl),
} as any;
}
export function createVAO(gl: WebGL, attributes: VAOAttributes[], elements: WebGLBuffer, elementsType?: number): VAO {
const ext = gl.createVertexArray ? extensionShim(gl) : gl.getExtension('OES_vertex_array_object');
const handle = ext && ext.createVertexArrayOES();
const vao = (ext && handle) ? new VAONative(gl, ext, handle) : new VAOEmulated(gl);
vao.update(attributes, elements, elementsType);
return vao;
const ext = gl.createVertexArray ? extensionShim(gl) : gl.getExtension('OES_vertex_array_object');
const handle = ext && ext.createVertexArrayOES();
const vao = (ext && handle) ? new VAONative(gl, ext, handle) : new VAOEmulated(gl);
vao.update(attributes, elements, elementsType);
return vao;
}
class VAONative implements VAO {
private useElements = false;
private elementsType: number;
private maxAttribs: number;
constructor(
public gl: WebGLRenderingContext,
private ext: OES_vertex_array_object,
public handle: WebGLVertexArrayObjectOES,
) {
this.elementsType = gl.UNSIGNED_SHORT;
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
}
bind() {
this.ext.bindVertexArrayOES(this.handle);
}
unbind() {
this.ext.bindVertexArrayOES(null);
}
dispose() {
this.ext.deleteVertexArrayOES(this.handle);
}
update(attributes: VAOAttributes[], elements: WebGLBuffer | null, elementsType?: number) {
this.bind();
bindAttribs(this.gl, elements, attributes, this.maxAttribs);
this.unbind();
this.useElements = !!elements;
this.elementsType = elementsType || this.gl.UNSIGNED_SHORT;
}
draw(mode: number, count: number, offset = 0) {
TIMING && timeStart('VAONative.draw');
if (this.useElements) {
this.gl.drawElements(mode, count, this.elementsType, offset);
} else {
this.gl.drawArrays(mode, offset, count);
}
TIMING && timeEnd();
}
private useElements = false;
private elementsType: number;
private maxAttribs: number;
constructor(
public gl: WebGLRenderingContext,
private ext: OES_vertex_array_object,
public handle: WebGLVertexArrayObjectOES,
) {
this.elementsType = gl.UNSIGNED_SHORT;
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
}
bind() {
this.ext.bindVertexArrayOES(this.handle);
}
unbind() {
this.ext.bindVertexArrayOES(null);
}
dispose() {
this.ext.deleteVertexArrayOES(this.handle);
}
update(attributes: VAOAttributes[], elements: WebGLBuffer | null, elementsType?: number) {
this.bind();
bindAttribs(this.gl, elements, attributes, this.maxAttribs);
this.unbind();
this.useElements = !!elements;
this.elementsType = elementsType || this.gl.UNSIGNED_SHORT;
}
draw(mode: number, count: number, offset = 0) {
TIMING && timeStart('VAONative.draw');
if (this.useElements) {
this.gl.drawElements(mode, count, this.elementsType, offset);
} else {
this.gl.drawArrays(mode, offset, count);
}
TIMING && timeEnd();
}
}
class VAOEmulated implements VAO {
private elements: WebGLBuffer | null = null;
private attributes: VAOAttributes[] | null = null;
private elementsType: number;
private maxAttribs: number;
constructor(public gl: WebGLRenderingContext) {
this.elementsType = gl.UNSIGNED_SHORT;
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
}
bind() {
bindAttribs(this.gl, this.elements, this.attributes, this.maxAttribs);
}
update(attributes: VAOAttributes[], elements: WebGLBuffer | null, elementsType?: number) {
this.elements = elements;
this.attributes = attributes;
this.elementsType = elementsType || this.gl.UNSIGNED_SHORT;
}
dispose() {
}
unbind() {
}
draw(mode: number, count: number, offset = 0) {
TIMING && timeStart('VAOEmulated.draw');
if (this.elements) {
this.gl.drawElements(mode, count, this.elementsType, offset);
} else {
this.gl.drawArrays(mode, offset, count);
}
TIMING && timeEnd();
}
private elements: WebGLBuffer | null = null;
private attributes: VAOAttributes[] | null = null;
private elementsType: number;
private maxAttribs: number;
constructor(public gl: WebGLRenderingContext) {
this.elementsType = gl.UNSIGNED_SHORT;
this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
}
bind() {
bindAttribs(this.gl, this.elements, this.attributes, this.maxAttribs);
}
update(attributes: VAOAttributes[], elements: WebGLBuffer | null, elementsType?: number) {
this.elements = elements;
this.attributes = attributes;
this.elementsType = elementsType || this.gl.UNSIGNED_SHORT;
}
dispose() {
}
unbind() {
}
draw(mode: number, count: number, offset = 0) {
TIMING && timeStart('VAOEmulated.draw');
if (this.elements) {
this.gl.drawElements(mode, count, this.elementsType, offset);
} else {
this.gl.drawArrays(mode, offset, count);
}
TIMING && timeEnd();
}
}
function bindAttribs(
gl: WebGLRenderingContext, elements: WebGLBuffer | null, attributes: VAOAttributes[] | null, maxAttribs: number
gl: WebGLRenderingContext, elements: WebGLBuffer | null, attributes: VAOAttributes[] | null, maxAttribs: number
) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
if (attributes) {
if (maxAttribs != null && attributes.length > maxAttribs) {
throw new Error(`Too many vertex attributes ${attributes.length}/${maxAttribs}`);
}
if (attributes) {
if (maxAttribs != null && attributes.length > maxAttribs) {
throw new Error(`Too many vertex attributes ${attributes.length}/${maxAttribs}`);
}
let i = 0;
let i = 0;
for (; i < attributes.length; ++i) {
const attrib = attributes[i];
const buffer = attrib.buffer;
const size = attrib.size || 4;
const type = attrib.type || gl.FLOAT;
const normalized = !!attrib.normalized;
const stride = attrib.stride || 0;
const offset = attrib.offset || 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(i);
gl.vertexAttribPointer(i, size, type, normalized, stride, offset);
for (; i < attributes.length; ++i) {
const attrib = attributes[i];
const buffer = attrib.buffer;
const size = attrib.size || 4;
const type = attrib.type || gl.FLOAT;
const normalized = !!attrib.normalized;
const stride = attrib.stride || 0;
const offset = attrib.offset || 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(i);
gl.vertexAttribPointer(i, size, type, normalized, stride, offset);
if (attrib.divisor !== undefined) {
(gl as any).vertexAttribDivisor(i, attrib.divisor);
}
}
if (attrib.divisor !== undefined) {
(gl as any).vertexAttribDivisor(i, attrib.divisor);
}
}
for (; i < maxAttribs; ++i) {
gl.disableVertexAttribArray(i);
}
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, null);
for (; i < maxAttribs; ++i) {
gl.disableVertexAttribArray(i);
}
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, null);
for (let i = 0; i < maxAttribs; ++i) {
gl.disableVertexAttribArray(i);
}
}
for (let i = 0; i < maxAttribs; ++i) {
gl.disableVertexAttribArray(i);
}
}
}
+67 -67
View File
@@ -1,100 +1,100 @@
export interface Shader {
program: WebGLProgram;
vertexShader: WebGLShader;
fragmentShader: WebGLShader;
uniforms: { [key: string]: WebGLUniformLocation; };
program: WebGLProgram;
vertexShader: WebGLShader;
fragmentShader: WebGLShader;
uniforms: { [key: string]: WebGLUniformLocation; };
}
export function createShader(gl: WebGLRenderingContext, source: string | { vertex: string; fragment: string; }): Shader {
if (typeof source === 'string') {
const index = source.indexOf('// FRAGMENT');
if (typeof source === 'string') {
const index = source.indexOf('// FRAGMENT');
if (index === -1) {
throw new Error(`Missing fragment shader separator`);
}
if (index === -1) {
throw new Error(`Missing fragment shader separator`);
}
source = {
vertex: source.substring(0, index),
fragment: source.substring(index),
};
}
source = {
vertex: source.substring(0, index),
fragment: source.substring(index),
};
}
const vertexShader = createWebGLShader(gl, gl.VERTEX_SHADER, source.vertex);
const fragmentShader = createWebGLShader(gl, gl.FRAGMENT_SHADER, source.fragment);
const program = gl.createProgram();
const vertexShader = createWebGLShader(gl, gl.VERTEX_SHADER, source.vertex);
const fragmentShader = createWebGLShader(gl, gl.FRAGMENT_SHADER, source.fragment);
const program = gl.createProgram();
if (!program) {
throw new Error('Failed to create shader program');
}
if (!program) {
throw new Error('Failed to create shader program');
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
const attribs = source.vertex.match(/^attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/mg)!;
const attribs = source.vertex.match(/^attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/mg)!;
for (var i = 0; i < attribs.length; ++i) {
const [, name] = /attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/.exec(attribs[i])!;
gl.bindAttribLocation(program, i, name);
}
for (var i = 0; i < attribs.length; ++i) {
const [, name] = /attribute [a-zA-Z0-9_]+ ([a-zA-Z0-9_]+)/.exec(attribs[i])!;
gl.bindAttribLocation(program, i, name);
}
gl.linkProgram(program);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error('Failed to link shader program');
}
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error('Failed to link shader program');
}
gl.useProgram(program);
gl.useProgram(program);
const uniforms: any = {};
const samplers: string[] = [];
const uniforms: any = {};
const samplers: string[] = [];
for (let i = 0; i < gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); i++) {
const info = gl.getActiveUniform(program, i)!;
uniforms[info.name] = gl.getUniformLocation(program, info.name);
for (let i = 0; i < gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); i++) {
const info = gl.getActiveUniform(program, i)!;
uniforms[info.name] = gl.getUniformLocation(program, info.name);
if (!uniforms[info.name]) {
throw new Error(`Failed to get uniform location (${info.name})`);
}
if (!uniforms[info.name]) {
throw new Error(`Failed to get uniform location (${info.name})`);
}
if (info.type === gl.SAMPLER_2D) {
samplers.push(info.name);
}
}
if (info.type === gl.SAMPLER_2D) {
samplers.push(info.name);
}
}
samplers.sort().forEach((name, i) => gl.uniform1i(uniforms[name], i));
samplers.sort().forEach((name, i) => gl.uniform1i(uniforms[name], i));
gl.useProgram(null);
gl.useProgram(null);
return { program, vertexShader, fragmentShader, uniforms };
return { program, vertexShader, fragmentShader, uniforms };
}
export function disposeShader(gl: WebGLRenderingContext | undefined, shader: Shader | undefined) {
try {
if (gl && shader) {
gl.deleteProgram(shader.program);
gl.deleteShader(shader.vertexShader);
gl.deleteShader(shader.fragmentShader);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
try {
if (gl && shader) {
gl.deleteProgram(shader.program);
gl.deleteShader(shader.vertexShader);
gl.deleteShader(shader.fragmentShader);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
return undefined;
return undefined;
}
function createWebGLShader(gl: WebGLRenderingContext, type: number, source: string) {
const shader = gl.createShader(type);
const shader = gl.createShader(type);
if (!shader) {
throw new Error('Failed to create shader');
}
if (!shader) {
throw new Error('Failed to create shader');
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader) || 'Shader error');
}
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader) || 'Shader error');
}
return shader;
return shader;
}
+61 -61
View File
@@ -2,97 +2,97 @@ type WebGL = WebGLRenderingContext;
type Pixels = ImageBitmap | ImageData | HTMLVideoElement | HTMLImageElement | HTMLCanvasElement;
export interface Texture2D {
handle: WebGLTexture;
width: number;
height: number;
format: number;
type: number;
handle: WebGLTexture;
width: number;
height: number;
format: number;
type: number;
}
export function createEmptyTexture(gl: WebGL, width: number, height: number, format?: number, type?: number): Texture2D {
if (format === undefined) {
format = gl.RGBA;
}
if (format === undefined) {
format = gl.RGBA;
}
if (type === undefined) {
type = gl.UNSIGNED_BYTE;
}
if (type === undefined) {
type = gl.UNSIGNED_BYTE;
}
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
if (maxTextureSize != null && (width < 0 || width > maxTextureSize || height < 0 || height > maxTextureSize)) {
throw new Error('Invalid texture shape');
}
if (maxTextureSize != null && (width < 0 || width > maxTextureSize || height < 0 || height > maxTextureSize)) {
throw new Error('Invalid texture shape');
}
if (type === gl.FLOAT && !gl.getExtension('OES_texture_float')) {
throw new Error('Floating point textures not supported on this platform');
}
if (type === gl.FLOAT && !gl.getExtension('OES_texture_float')) {
throw new Error('Floating point textures not supported on this platform');
}
const handle = createTextureHandle(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
return { handle, width, height, format, type };
const handle = createTextureHandle(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
return { handle, width, height, format, type };
}
export function createTexture(gl: WebGL, data: Pixels, format?: number, type?: number): Texture2D {
if (format === undefined) {
format = gl.RGBA;
}
if (format === undefined) {
format = gl.RGBA;
}
if (type === undefined) {
type = gl.UNSIGNED_BYTE;
}
if (type === undefined) {
type = gl.UNSIGNED_BYTE;
}
const handle = createTextureHandle(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, format, format, type, data);
return { handle, width: data.width, height: data.height, format, type };
const handle = createTextureHandle(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, format, format, type, data);
return { handle, width: data.width, height: data.height, format, type };
}
export function disposeTexture(gl: WebGL | undefined, texture: Texture2D | undefined): undefined {
try {
if (gl && texture) {
gl.deleteTexture(texture.handle);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
try {
if (gl && texture) {
gl.deleteTexture(texture.handle);
}
} catch (e) {
DEVELOPMENT && console.error(e);
}
return undefined;
return undefined;
}
export function bindTexture(gl: WebGL, unit: number, texture: Texture2D | undefined) {
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture ? texture.handle : null);
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture ? texture.handle : null);
}
export function resizeTexture(gl: WebGL, texture: Texture2D, width: number, height: number) {
width = width | 0;
height = height | 0;
width = width | 0;
height = height | 0;
const { format, type } = texture;
const maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
const { format, type } = texture;
const maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null;
if (maxSize != null && (width < 0 || width > maxSize || height < 0 || height > maxSize)) {
throw new Error('Invalid texture size');
}
if (maxSize != null && (width < 0 || width > maxSize || height < 0 || height > maxSize)) {
throw new Error('Invalid texture size');
}
texture.width = width;
texture.height = height;
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
texture.width = width;
texture.height = height;
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, null);
}
function createTextureHandle(gl: WebGL) {
const texture = gl.createTexture();
const texture = gl.createTexture();
if (!texture) {
throw new Error('Failed to create texture');
}
if (!texture) {
throw new Error('Failed to create texture');
}
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
return texture;
return texture;
}
+28 -28
View File
@@ -1,44 +1,44 @@
import { VAOAttributes } from './glVao';
export interface VAOAttributeDefinition {
name: string;
size: number;
type?: number;
normalized?: boolean;
divisor?: number;
name: string;
size: number;
type?: number;
normalized?: boolean;
divisor?: number;
}
export function getVAOAttributesSize(gl: WebGLRenderingContext, attributes: VAOAttributeDefinition[]) {
return attributes.reduce((sum, a) => sum + a.size * sizeOfType(gl, a.type), 0);
return attributes.reduce((sum, a) => sum + a.size * sizeOfType(gl, a.type), 0);
}
export function createVAOAttributes(
gl: WebGLRenderingContext, attributes: VAOAttributeDefinition[], buffer: WebGLBuffer
gl: WebGLRenderingContext, attributes: VAOAttributeDefinition[], buffer: WebGLBuffer
): VAOAttributes[] {
const result: VAOAttributes[] = [];
const stride = getVAOAttributesSize(gl, attributes);
let offset = 0;
const result: VAOAttributes[] = [];
const stride = getVAOAttributesSize(gl, attributes);
let offset = 0;
for (const a of attributes) {
result.push({ ...a, stride, buffer, offset });
offset += a.size * sizeOfType(gl, a.type);
}
for (const a of attributes) {
result.push({ ...a, stride, buffer, offset });
offset += a.size * sizeOfType(gl, a.type);
}
return result;
return result;
}
function sizeOfType(gl: WebGLRenderingContext, type: number | undefined) {
switch (type) {
case gl.BYTE:
case gl.UNSIGNED_BYTE:
return 1;
case gl.SHORT:
case gl.UNSIGNED_SHORT:
return 2;
case gl.FLOAT:
case undefined:
return 4;
default:
throw new Error(`Invalid attribute type (${type})`);
}
switch (type) {
case gl.BYTE:
case gl.UNSIGNED_BYTE:
return 1;
case gl.SHORT:
case gl.UNSIGNED_SHORT:
return 2;
case gl.FLOAT:
case undefined:
return 4;
default:
throw new Error(`Invalid attribute type (${type})`);
}
}
+43 -43
View File
@@ -1,67 +1,67 @@
import { WEBGL_CREATION_ERROR } from '../../common/errors';
export function getRenderTargetSize(width: number, height: number) {
const max = Math.max(width, height);
let pow = 256;
const max = Math.max(width, height);
let pow = 256;
while (pow < max) {
pow *= 2;
}
while (pow < max) {
pow *= 2;
}
return pow;
return pow;
}
export function getWebGLContext(canvas: HTMLCanvasElement): WebGLRenderingContext {
const options: WebGLContextAttributes = {
alpha: false,
premultipliedAlpha: false,
antialias: false,
};
const options: WebGLContextAttributes = {
alpha: false,
premultipliedAlpha: false,
antialias: false,
};
const gl = canvas.getContext('webgl2', options)
|| canvas.getContext('webgl', options)
|| canvas.getContext('experimental-webgl', options);
const gl = canvas.getContext('webgl2', options)
|| canvas.getContext('webgl', options)
|| canvas.getContext('experimental-webgl', options);
if (!gl) {
throw new Error(WEBGL_CREATION_ERROR);
}
if (!gl) {
throw new Error(WEBGL_CREATION_ERROR);
}
return gl;
return gl;
}
export function isWebGL2(gl: WebGLRenderingContext | undefined) {
return !!(gl && gl.MAX_ELEMENT_INDEX);
return !!(gl && gl.MAX_ELEMENT_INDEX);
}
export function getWebGLError(gl: WebGLRenderingContext) {
const error = gl.getError();
const error = gl.getError();
switch (error) {
case gl.NO_ERROR: return 'NO_ERROR';
case gl.INVALID_ENUM: return 'INVALID_ENUM';
case gl.INVALID_VALUE: return 'INVALID_VALUE';
case gl.INVALID_OPERATION: return 'INVALID_OPERATION';
case gl.INVALID_FRAMEBUFFER_OPERATION: return 'INVALID_FRAMEBUFFER_OPERATION';
case gl.OUT_OF_MEMORY: return 'OUT_OF_MEMORY';
case gl.CONTEXT_LOST_WEBGL: return 'CONTEXT_LOST_WEBGL';
default: return `${error}`;
}
switch (error) {
case gl.NO_ERROR: return 'NO_ERROR';
case gl.INVALID_ENUM: return 'INVALID_ENUM';
case gl.INVALID_VALUE: return 'INVALID_VALUE';
case gl.INVALID_OPERATION: return 'INVALID_OPERATION';
case gl.INVALID_FRAMEBUFFER_OPERATION: return 'INVALID_FRAMEBUFFER_OPERATION';
case gl.OUT_OF_MEMORY: return 'OUT_OF_MEMORY';
case gl.CONTEXT_LOST_WEBGL: return 'CONTEXT_LOST_WEBGL';
default: return `${error}`;
}
}
export function unbindAllTexturesAndBuffers(gl: WebGLRenderingContext) {
try {
const numTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) | 0;
try {
const numTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) | 0;
for (let i = 0; i < numTextureUnits; i++) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, null);
}
for (let i = 0; i < numTextureUnits; i++) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, null);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
} catch (e) {
DEVELOPMENT && console.error(e);
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
} catch (e) {
DEVELOPMENT && console.error(e);
}
}