import { TiledRenderLayer } from "./TiledRenderLayer.js";
import { Exception, Warning } from "./Exception.js";
import { ERROR_CODES, WARNING_CODES } from "../constants.js";
import { WebGlInterface } from "./WebGlInterface.js";
import { SystemSettings } from "../configs.js";
import { ScreenPageData } from "./ScreenPageData.js";
import AssetsManager from "../../modules/assetsm/dist/assetsm.min.js";
//import { calculateBufferData } from "../wa/release.js";
import { CONST } from "../constants.js";
import { DrawImageObject } from "./DrawImageObject.js";
import { DrawCircleObject } from "./DrawCircleObject.js";
import { DrawConusObject } from "./DrawConusObject.js";
import { DrawLineObject } from "./DrawLineObject.js";
import { DrawPolygonObject } from "./DrawPolygonObject.js";
import { DrawRectObject } from "./DrawRectObject.js";
import { DrawTextObject } from "./DrawTextObject.js";
const INDEX_TOP_LINE = 0,
INDEX_RIGHT_LINE = 1,
INDEX_BOTTOM_LINE = 2,
INDEX_LEFT_LINE = 3;
const INDEX_X1 = 0,
INDEX_Y1 = 1,
INDEX_X2 = 2,
INDEX_Y2 = 3;
/**
* Canvas view represents each canvas on the page<br>
* Should be created via ScreenPage.createCanvasView(),<br>
* Contains draw logic and holds DrawObjects and Tile
* Can retrieved by ScreenPage.getView()
* @see {@link ScreenPage} a part of ScreenPage
* @hideconstructor
*/
export class RenderInterface {
/**
* @type {HTMLCanvasElement}
*/
#canvas;
/**
* @type {WebGLRenderingContext}
*/
#drawContext;
/**
* @type {boolean}
*/
#isCleared;
/**
* @type {WebGlInterface}
*/
#webGlInterface;
/**
* SystemInterface.systemSettings
* @type {SystemSettings}
*/
#systemSettingsReference;
/**
* @type {ScreenPageData}
*/
#screenPageData;
/**
* A reference to the systemInterface.loader
* @type {AssetsManager}
*/
#loaderReference;
#bindRenderLayerMethod;
constructor(name, systemSettings, loader) {
this.#isCleared = false;
this.#canvas = document.createElement("canvas");
this.#canvas.id = name;
this.#drawContext = this.#canvas.getContext("webgl", {stencil: true});
this.#systemSettingsReference = systemSettings;
this.#loaderReference = loader;
this.#screenPageData = new ScreenPageData();
this.#webGlInterface = new WebGlInterface(this.#drawContext, this.#systemSettingsReference.gameOptions.checkWebGlErrors);
switch (this.systemSettings.gameOptions.optimization) {
case CONST.OPTIMIZATION.NATIVE_JS.OPTIMIZED:
this.#bindRenderLayerMethod = this.#bindRenderLayer;
break;
case CONST.OPTIMIZATION.NATIVE_JS.NOT_OPTIMIZED:
this.#bindRenderLayerMethod = this.#bindRenderLayerOld;
break;
case CONST.OPTIMIZATION.WEB_ASSEMBLY.WASM:
this.#bindRenderLayerMethod = this.#bindRenderLayerWM;
break;
case CONST.OPTIMIZATION.WEB_ASSEMBLY.ASSEMBLY_SCRIPT:
Warning("Sorry, " + CONST.OPTIMIZATION.WEB_ASSEMBLY.ASSEMBLY_SCRIPT + ", is not supported, switching to default");
default:
this.#bindRenderLayerMethod = this.#bindRenderLayer;
}
}
/**
*
* @returns {Promise<void>}
*/
initiateWasm = () => {
return new Promise((resolve, reject) => {
this.layerData = new WebAssembly.Memory({initial:50});
this.layerDataFloat32 = new Float32Array(this.layerData.buffer);
const importObject = {
env: {
data: this.layerData,
logi: console.log,
logf: console.log
}
};
fetch("/src/wa/calculateBufferData.wasm")
.then((response) => response.arrayBuffer())
.then((module) => WebAssembly.instantiate(module, importObject))
.then((obj) => {
this.calculateBufferData = obj.instance.exports.calculateBufferData;
resolve();
});
})
}
get screenPageData() {
return this.#screenPageData;
}
get systemSettings() {
return this.#systemSettingsReference;
}
get loader() {
return this.#loaderReference;
}
get canvas() {
return this.#canvas;
}
get drawContext() {
return this.#drawContext;
}
initiateContext = () => {
return Promise.all([this.#webGlInterface._initiateImagesDrawProgram(),
this.#webGlInterface._initPrimitivesDrawProgram(), this.#webGlInterface._initWebGlAttributes()]);
}
clearContext() {
this.#webGlInterface._clearView();
}
setCanvasSize(width, height) {
this.#canvas.width = width;
this.#canvas.height = height;
if (this.#webGlInterface) {
this.#webGlInterface._fixCanvasSize(width, height);
}
}
/**
* @returns {Promise<void>}
*/
async render() {
return new Promise(async(resolve, reject) => {
//if (!this._isCleared) {
// this.#clearWebGlContext();
//}
/*const renderLayers = this._renderLayers;
if (renderLayers.length !== 0) {
let renderLayerPromises = [];
for (const layer of renderLayers) {
renderLayerPromises.push(this.#bindRenderLayerMethod(layer));
}
const bindResults = await Promise.allSettled(renderLayerPromises);
bindResults.forEach((result) => {
if (result.status === "rejected") {
reject("reason: " + result.reason);
}
});
await this.#webGlInterface._executeTileImagesDraw();
}*/
let renderObjectsPromises = [];
const renderObjects = this.screenPageData.renderObjects;
if (renderObjects.length !== 0) {
//this.#checkCollisions(view.renderObjects);
for (let i = 0; i < renderObjects.length; i++) {
const object = renderObjects[i];
if (object.isRemoved) {
renderObjects.splice(i, 1);
i--;
continue;
}
if (object.isAnimations) {
object._processActiveAnimations();
}
const promise = await this._bindRenderObject(object).catch((err) => {
reject(err);
});
renderObjectsPromises.push(promise);
}
if (this.systemSettings.gameOptions.boundaries.drawLayerBoundaries) {
renderObjectsPromises.push(this.#drawBoundariesWebGl().catch((err) => {
reject(err);
}));
}
const bindResults = await Promise.allSettled(renderObjectsPromises);
bindResults.forEach((result) => {
if (result.status === "rejected") {
reject(result.reason);
}
});
//await this.#webGlInterface._executeImagesDraw();
this.#postRenderActions();
this._isCleared = false;
}
const bindResults = await Promise.allSettled(renderObjectsPromises);
bindResults.forEach((result) => {
if (result.status === "rejected") {
reject(result.reason);
}
});
this.#postRenderActions();
this._isCleared = false;
resolve();
});
}
_setCanvasSize(width, height) {
if (this.#webGlInterface) {
this.#webGlInterface._fixCanvasSize(width, height);
}
}
set _isCleared(value) {
this.#isCleared = value;
}
get _isCleared() {
return this.#isCleared;
}
_createBoundariesPrecalculations() {
//const promises = [];
//for (const layer of this.#renderLayers) {
// promises.push(this.#layerBoundariesPrecalculation(layer).catch((err) => {
// Exception(ERROR_CODES.UNHANDLED_PREPARE_EXCEPTION, err);
// }));
//}
//return promises;
}
#clearWebGlContext() {
this.#webGlInterface._clearView();
this.#isCleared = true;
}
/**
*
* @param {TiledRenderLayer} renderLayer
* @returns {Promise<void>}
*/
#bindRenderLayerWM = (renderLayer) => {
return new Promise((resolve, reject) => {
const tilemap = this.loader.getTileMap(renderLayer.tileMapKey),
tilesets = tilemap.tilesets,
tilesetImages = tilesets.map((tileset) => this.loader.getImage(tileset.data.name)),
layerData = tilemap.layers.find((layer) => layer.name === renderLayer.layerKey),
{ tileheight:dtheight, tilewidth:dtwidth } = tilemap,
offsetDataItemsFullNum = layerData.data.length,
offsetDataItemsFilteredNum = layerData.data.filter((item) => item !== 0).length,
setBoundaries = false, //renderLayer.setBoundaries,
[ settingsWorldWidth, settingsWorldHeight ] = this.screenPageData.worldDimensions;
//[ canvasW, canvasH ] = this.screenPageData.drawDimensions,
//[ xOffset, yOffset ] = this.screenPageData.worldOffset;
//clear data
//this.layerDataFloat32.fill(0);
this.layerDataFloat32.set(layerData.data);
if (!layerData) {
Warning(WARNING_CODES.NOT_FOUND, "check tilemap and layers name");
reject();
}
for (let i = 0; i < tilesets.length; i++) {
const tileset = tilesets[i].data,
//tilesetImages = this.loader.getTilesetImageArray(tileset.name),
tilewidth = tileset.tilewidth,
tileheight = tileset.tileheight,
//atlasRows = tileset.imageheight / tileheight,
atlasColumns = tileset.imagewidth / tilewidth,
layerCols = layerData.width,
layerRows = layerData.height,
//visibleCols = Math.ceil(canvasW / tilewidth),
//visibleRows = Math.ceil(canvasH / tileheight),
//offsetCols = layerCols - visibleCols,
//offsetRows = layerRows - visibleRows,
worldW = tilewidth * layerCols,
worldH = tileheight * layerRows,
atlasImage = tilesetImages[i],
atlasWidth = atlasImage.width,
atlasHeight = atlasImage.height,
items = layerRows * layerCols,
dataCellSizeBytes = 4,
vectorCoordsItemsNum = 12,
texturesCoordsItemsNum = 12,
vectorDataItemsNum = offsetDataItemsFilteredNum * vectorCoordsItemsNum,
texturesDataItemsNum = offsetDataItemsFilteredNum * texturesCoordsItemsNum;
if (worldW !== settingsWorldWidth || worldH !== settingsWorldHeight) {
Warning(WARNING_CODES.UNEXPECTED_WORLD_SIZE, " World size from tilemap is different than settings one, fixing...");
this.screenPageData._setWorldDimensions(worldW, worldH);
}
//if (this.canvas.width !== worldW || this.canvas.height !== worldH) {
// this._setCanvasSize(worldW, worldH);
//}
// boundaries cleanups every draw circle, we need to set world boundaries again
if (this.screenPageData.isWorldBoundariesEnabled) {
this.screenPageData._setMapBoundaries();
}
this.calculateBufferData(dataCellSizeBytes, offsetDataItemsFullNum, vectorDataItemsNum, layerRows, layerCols, dtwidth, dtheight, tilewidth, tileheight, atlasColumns, atlasWidth, atlasHeight, setBoundaries);
//const [verticesBufferData, texturesBufferData] = calculateBufferData(layerRows, layerCols, layerData.data, dtwidth, dtheight, tilewidth, tileheight, atlasColumns, atlasWidth, atlasHeight, setBoundaries);
const verticesBufferData = this.layerDataFloat32.slice(offsetDataItemsFullNum, vectorDataItemsNum + offsetDataItemsFullNum),
texturesBufferData = this.layerDataFloat32.slice(vectorDataItemsNum + offsetDataItemsFullNum, vectorDataItemsNum + texturesDataItemsNum + offsetDataItemsFullNum);
//console.log(verticesBufferData);
//console.log(texturesBufferData);
this.#bindTileImages(verticesBufferData, texturesBufferData, atlasImage, tileset.name);
if (setBoundaries) {
this.screenPageData._mergeBoundaries();
renderLayer.setBoundaries = false;
}
resolve();
}
});
}
#bindRenderLayerOld = (renderLayer) => {
return new Promise((resolve, reject) => {
const tilemap = this.loader.getTileMap(renderLayer.tileMapKey),
tilesets = tilemap.tilesets,
tilesetImages = tilesets.map((tileset) => this.loader.getImage(tileset.data.name)),
layerData = tilemap.layers.find((layer) => layer.name === renderLayer.layerKey),
{ tileheight:dtheight, tilewidth:dtwidth } = tilemap,
setBoundaries = renderLayer.setBoundaries,
[ settingsWorldWidth, settingsWorldHeight ] = this.screenPageData.worldDimensions,
[ canvasW, canvasH ] = this.screenPageData.canvasDimensions,
[ xOffset, yOffset ] = this.screenPageData.worldOffset,
verticesBufferData = [],
texturesBufferData = [];
if (!layerData) {
Warning(WARNING_CODES.NOT_FOUND, "check tilemap and layers name");
reject();
}
for (let i = 0; i <= tilesets.length - 1; i++) {
const tileset = tilesets[i].data,
//tilesetImages = this.loader.getTilesetImageArray(tileset.name),
tilewidth = tileset.tilewidth,
tileheight = tileset.tileheight,
atlasRows = tileset.imageheight / tileheight,
atlasColumns = tileset.imagewidth / tilewidth,
layerCols = layerData.width,
layerRows = layerData.height,
worldW = tilewidth * layerCols,
worldH = tileheight * layerRows,
visibleCols = Math.ceil(canvasW / tilewidth),
visibleRows = Math.ceil(canvasH / tileheight),
offsetCols = layerCols - visibleCols,
offsetRows = layerRows - visibleRows,
atlasImage = tilesetImages[i],
atlasWidth = atlasImage.width,
atlasHeight = atlasImage.height;
let mapIndex = 0;
if (worldW !== settingsWorldWidth || worldH !== settingsWorldHeight) {
Warning(WARNING_CODES.UNEXPECTED_WORLD_SIZE, " World size from tilemap is different than settings one, fixing...");
this.screenPageData._setWorldDimensions(worldW, worldH);
}
for (let row = 0; row < layerRows; row++) {
for (let col = 0; col < layerCols; col++) {
let tile = layerData.data[mapIndex],
mapPosX = col * dtwidth,
mapPosY = row * dtheight,
mapPosXWithOffset = col * dtwidth - xOffset,
mapPosYWithOffset = row * dtheight - yOffset;
if (tile !== 0) {
tile -= 1;
const atlasPosX = tile % atlasColumns * tilewidth,
atlasPosY = Math.floor(tile / atlasColumns) * tileheight,
vecX1 = mapPosXWithOffset,
vecY1 = mapPosYWithOffset,
vecX2 = mapPosXWithOffset + tilewidth,
vecY2 = mapPosYWithOffset + tileheight,
texX1 = 1 / atlasWidth * atlasPosX,
texY1 = 1 / atlasHeight * atlasPosY,
texX2 = texX1 + (1 / atlasWidth * tilewidth),
texY2 = texY1 + (1 / atlasHeight * tileheight);
verticesBufferData.push(
vecX1, vecY1,
vecX2, vecY1,
vecX1, vecY2,
vecX1, vecY2,
vecX2, vecY1,
vecX2, vecY2);
texturesBufferData.push(
texX1, texY1,
texX2, texY1,
texX1, texY2,
texX1, texY2,
texX2, texY1,
texX2, texY2
);
}
mapIndex++;
}
}
const v = new Float32Array(verticesBufferData);
const t = new Float32Array(texturesBufferData);
this.#bindTileImages(v, t, atlasImage, tileset.name);
}
resolve();
});
}
/**
*
* @param {TiledRenderLayer} renderLayer
* @returns {Promise<void>}
*/
#bindRenderLayer(renderLayer) {
return new Promise((resolve, reject) => {
const tilemap = this.loader.getTileMap(renderLayer.tileMapKey),
tilesets = tilemap.tilesets,
tilesetImages = tilesets.map((tileset) => this.loader.getImage(tileset.data.name)),
layerData = tilemap.layers.find((layer) => layer.name === renderLayer.layerKey),
{ tileheight:dtheight, tilewidth:dtwidth } = tilemap,
tilewidth = dtwidth,
tileheight = dtheight,
[ settingsWorldWidth, settingsWorldHeight ] = this.screenPageData.worldDimensions,
[ canvasW, canvasH ] = this.screenPageData.canvasDimensions,
[ xOffset, yOffset ] = renderLayer.isOffsetTurnedOff === true ? [0,0] : this.screenPageData.worldOffset,
boundariesCalculations = this.systemSettings.gameOptions.render.boundaries.realtimeCalculations,
setBoundaries = renderLayer.setBoundaries && boundariesCalculations;
let boundariesRowsIndexes = new Map(),
boundaries = [];
if (!layerData) {
Warning(WARNING_CODES.NOT_FOUND, "check tilemap and layers name");
reject();
}
for (let i = 0; i < tilesets.length; i++) {
const tileset = tilesets[i].data,
firstgid = tilesets[i].firstgid,
nextTileset = tilesets[i + 1],
nextgid = nextTileset ? nextTileset.firstgid : null,
tilesetwidth = tileset.tilewidth,
tilesetheight = tileset.tileheight,
atlasImage = tilesetImages[i],
//atlasWidth = atlasImage.width,
//atlasHeight = atlasImage.height,
atlasWidth = tileset.imagewidth,
atlasHeight = tileset.imageheight,
//atlasRows = atlasHeight / tileheight,
atlasColumns = Math.floor(atlasWidth / tilesetwidth),
layerCols = layerData.width,
layerRows = layerData.height,
worldW = tilewidth * layerCols,
worldH = tileheight * layerRows,
moduloTop = yOffset % tileheight,
moduleLeft = xOffset % tilewidth,
skipRowsTop = yOffset !== 0 ? Math.floor(yOffset / tileheight) : 0,
skipColsLeft = xOffset !== 0 ? Math.floor(xOffset / tilewidth) : 0,
// sometimes canvasW/H may be bigger than world itself
screenRows = worldH > canvasH ? Math.ceil(canvasH / tileheight) + 1 : layerRows,
screenCols = worldW > canvasW ? Math.ceil(canvasW / tilewidth) + 1 : layerCols,
skipColsRight = layerCols - screenCols - skipColsLeft,
verticesBufferData = [],
texturesBufferData = [];
if (setBoundaries) {
if (worldW !== settingsWorldWidth || worldH !== settingsWorldHeight) {
Warning(WARNING_CODES.UNEXPECTED_WORLD_SIZE, " World size from tilemap is different than settings one, fixing...");
this.screenPageData._setWorldDimensions(worldW, worldH);
}
// boundaries cleanups every draw circle, we need to set world boundaries again
if (this.screenPageData.isWorldBoundariesEnabled) {
this.screenPageData._setMapBoundaries();
}
}
let mapIndex = skipRowsTop * layerCols;
for (let row = 0; row < screenRows; row++) {
mapIndex += skipColsLeft;
let currentRowIndexes = new Map();
for (let col = 0; col < screenCols; col++) {
let tile = layerData.data[mapIndex];
//if (tile !== 0)
if (tile >= firstgid && (nextgid === null || tile < nextgid)) {
const mapPosX = col * dtwidth - moduleLeft,
mapPosY = row * dtheight - moduloTop;
tile -= firstgid;
const atlasPosX = tile % atlasColumns * tilesetwidth,
atlasPosY = Math.floor(tile / atlasColumns) * tilesetheight,
vecX1 = mapPosX,
vecY1 = mapPosY,
vecX2 = mapPosX + tilesetwidth,
vecY2 = mapPosY + tilesetheight,
texX1 = 1 / atlasWidth * atlasPosX,
texY1 = 1 / atlasHeight * atlasPosY,
texX2 = texX1 + (1 / atlasWidth * tilesetwidth),
texY2 = texY1 + (1 / atlasHeight * tilesetheight);
verticesBufferData.push(
vecX1, vecY1,
vecX2, vecY1,
vecX1, vecY2,
vecX1, vecY2,
vecX2, vecY1,
vecX2, vecY2);
texturesBufferData.push(
texX1, texY1,
texX2, texY1,
texX1, texY2,
texX1, texY2,
texX2, texY1,
texX2, texY2
);
if (setBoundaries) {
let rightLine = [ mapPosX + tilesetwidth, mapPosY, mapPosX + tilesetwidth, mapPosY + tilesetheight ],
bottomLine = [ mapPosX + tilesetwidth, mapPosY + tilesetheight, mapPosX, mapPosY + tilesetheight ],
topLine = [ mapPosX, mapPosY, mapPosX + tilesetwidth, mapPosY],
leftLine = [ mapPosX, mapPosY + tilesetheight, mapPosX, mapPosY ],
currentAddedCellIndexes = [null, null, null, null];
const topRow = row !== 0 ? boundariesRowsIndexes.get(row - 1) : undefined;
if (topRow ) {
const topCellIndexes = topRow.get(col);
if (topCellIndexes) {
//remove double lines from top
const bottomTopCellIndex = topCellIndexes[INDEX_BOTTOM_LINE],
bottomTopCell = boundaries[bottomTopCellIndex];
if (bottomTopCell) {
const bottomTopCellX1 = bottomTopCell[INDEX_X1],
bottomTopCellY1 = bottomTopCell[INDEX_Y1],
bottomTopCellX2 = bottomTopCell[INDEX_X2],
bottomTopCellY2 = bottomTopCell[INDEX_Y2],
topX1 = topLine[INDEX_X1],
topY1 = topLine[INDEX_Y1],
topX2 = topLine[INDEX_X2],
topY2 = topLine[INDEX_Y2];
if (topX1 === bottomTopCellX2 && topY1 === bottomTopCellY2 &&
topX2 === bottomTopCellX1 && topY2 === bottomTopCellY1) {
boundaries[bottomTopCellIndex] = undefined;
topLine = undefined;
}
}
// merge line from top right
const rightTopCellIndex = topCellIndexes[INDEX_RIGHT_LINE],
rightTopCell = boundaries[rightTopCellIndex];
if (rightTopCell) {
const rightTopCellX1 = rightTopCell[INDEX_X1],
rightTopCellY1 = rightTopCell[INDEX_Y1],
rightTopCellX2 = rightTopCell[INDEX_X2],
rightX1 = rightLine[INDEX_X1],
rightX2 = rightLine[INDEX_X2];
if (rightTopCellX1 === rightX2 && rightTopCellX2 === rightX1) {
boundaries[rightTopCellIndex] = undefined;
rightLine[INDEX_X1] = rightTopCellX1;
rightLine[INDEX_Y1] = rightTopCellY1;
}
}
// merge line from top left
const leftTopCellIndex = topCellIndexes[INDEX_LEFT_LINE],
leftTopCell = boundaries[leftTopCellIndex];
if (leftTopCell) {
const leftTopCellX1 = leftTopCell[INDEX_X1],
leftTopCellX2 = leftTopCell[INDEX_X2],
leftTopCellY2 = leftTopCell[INDEX_Y2],
leftX1 = leftLine[INDEX_X1],
leftX2 = leftLine[INDEX_X2];
if (leftTopCellX1 === leftX2 && leftTopCellX2 === leftX1) {
boundaries[leftTopCellIndex] = undefined;
leftLine[INDEX_X2] = leftTopCellX2;
leftLine[INDEX_Y2] = leftTopCellY2;
}
}
}
}
const leftCellIndexes = col !== 0 ? currentRowIndexes.get(col - 1) : undefined;
if (leftCellIndexes) {
//remove double lines from left
const rightLeftCellIndex = leftCellIndexes[INDEX_RIGHT_LINE],
rightLeftCell = boundaries[rightLeftCellIndex],
rightLeftCellX1 = rightLeftCell[INDEX_X1],
rightLeftCellY1 = rightLeftCell[INDEX_Y1],
rightLeftCellX2 = rightLeftCell[INDEX_X2],
rightLeftCellY2 = rightLeftCell[INDEX_Y2],
leftX1 = leftLine[INDEX_X1],
leftY1 = leftLine[INDEX_Y1],
leftX2 = leftLine[INDEX_X2],
leftY2 = leftLine[INDEX_Y2];
if (leftX1 === rightLeftCellX2 && leftY1 === rightLeftCellY2 &&
leftX2 === rightLeftCellX1 && leftY2 === rightLeftCellY1) {
boundaries[rightLeftCellIndex] = undefined;
leftLine = undefined;
}
//merge long lines from left top
const topLeftCellIndex = leftCellIndexes[INDEX_TOP_LINE],
topLeftCell = boundaries[topLeftCellIndex];
if (topLeftCell && topLine) {
const topLeftCellX1 = topLeftCell[INDEX_X1],
topLeftCellY1 = topLeftCell[INDEX_Y1],
topLeftCellY2 = topLeftCell[INDEX_Y2],
topY1 = topLine[INDEX_Y1],
topY2 = topLine[INDEX_Y2];
if (topLeftCellY1 === topY2 && topLeftCellY2 === topY1 ) {
boundaries[topLeftCellIndex] = undefined;
topLine[INDEX_X1] = topLeftCellX1;
topLine[INDEX_Y1] = topLeftCellY1;
}
}
// merge long lines from left bottom
const bottomLeftCellIndex = leftCellIndexes[INDEX_BOTTOM_LINE],
bottomLeftCell = boundaries[bottomLeftCellIndex];
if (bottomLeftCell) {
const bottomLeftCellY1 = bottomLeftCell[INDEX_Y1],
bottomLeftCellX2 = bottomLeftCell[INDEX_X2],
bottomLeftCellY2 = bottomLeftCell[INDEX_Y2],
bottomY1 = bottomLine[INDEX_Y1],
bottomY2 = bottomLine[INDEX_Y2];
if (bottomLeftCellY1 === bottomY2 && bottomLeftCellY2 === bottomY1 ) {
boundaries[bottomLeftCellIndex] = undefined;
//opposite direction
bottomLine[INDEX_X2] = bottomLeftCellX2;
bottomLine[INDEX_Y2] = bottomLeftCellY2;
}
}
}
if (topLine) {
boundaries.push(topLine);
currentAddedCellIndexes[INDEX_TOP_LINE] = boundaries.length - 1;
}
boundaries.push(rightLine);
currentAddedCellIndexes[INDEX_RIGHT_LINE] = boundaries.length - 1;
boundaries.push(bottomLine);
currentAddedCellIndexes[INDEX_BOTTOM_LINE] = boundaries.length - 1;
if (leftLine) {
boundaries.push(leftLine);
currentAddedCellIndexes[INDEX_LEFT_LINE] = boundaries.length - 1;
}
//save values indexes cols info
currentRowIndexes.set(col, currentAddedCellIndexes);
}
}
mapIndex++;
}
if (currentRowIndexes.size > 0) {
//save values indexes rows info
boundariesRowsIndexes.set(row, currentRowIndexes);
}
mapIndex += skipColsRight;
}
if (verticesBufferData.length > 0 && texturesBufferData.length > 0) {
//this.#bindTileImages(verticesBufferData, texturesBufferData, atlasImage, tileset.name, renderLayer._maskId);
const v = new Float32Array(verticesBufferData);
const t = new Float32Array(texturesBufferData);
this.#bindTileImages(v, t, atlasImage, tileset.name, renderLayer._maskId);
}
}
if (setBoundaries) {
// filter undefined value
const filtered = boundaries.filter(array => array);
this.screenPageData._addBoundariesArray(filtered);
}
resolve();
});
}
#postRenderActions() {
const images = this.screenPageData.getObjectsByInstance(DrawImageObject);
for (let i = 0; i < images.length; i++) {
const object = images[i];
if (object.isAnimations) {
object._processActiveAnimations();
}
}
}
#bindTileImages(verticesBufferData, texturesBufferData, atlasImage, image_name, shapeMaskId, drawMask, rotation, translation) {
this.#webGlInterface._bindTileImages(verticesBufferData, texturesBufferData, atlasImage, image_name, shapeMaskId, drawMask, rotation, translation);
}
//#clearTileMapPromises() {
// this.#bindTileMapPromises = [];
//}
/**
*
* @param {TiledRenderLayer} renderLayer
* @returns {Promise<void>}
*/
#layerBoundariesPrecalculation(renderLayer) {
return new Promise((resolve, reject) => {
if (renderLayer.setBoundaries) {
const tilemap = this.loader.getTileMap(renderLayer.tileMapKey),
tilesets = tilemap.tilesets,
layerData = tilemap.layers.find((layer) => layer.name === renderLayer.layerKey),
{ tileheight:dtheight, tilewidth:dtwidth } = tilemap,
tilewidth = dtwidth,
tileheight = dtheight,
[ settingsWorldWidth, settingsWorldHeight ] = this.screenPageData.worldDimensions;
let boundaries = [];
if (!layerData) {
Warning(WARNING_CODES.NOT_FOUND, "check tilemap and layers name");
reject();
}
for (let i = 0; i < tilesets.length; i++) {
const layerCols = layerData.width,
layerRows = layerData.height,
worldW = tilewidth * layerCols,
worldH = tileheight * layerRows;
if (worldW !== settingsWorldWidth || worldH !== settingsWorldHeight) {
Warning(WARNING_CODES.UNEXPECTED_WORLD_SIZE, " World size from tilemap is different than settings one, fixing...");
this.screenPageData._setWorldDimensions(worldW, worldH);
}
if (this.screenPageData.isWorldBoundariesEnabled) {
this.screenPageData._setWholeWorldMapBoundaries();
}
//calculate boundaries
let mapIndex = 0;
for (let row = 0; row < layerRows; row++) {
for (let col = 0; col < layerCols; col++) {
let tile = layerData.data[mapIndex],
mapPosX = col * tilewidth,
mapPosY = row * tileheight;
if (tile !== 0) {
tile -= 1;
boundaries.push([mapPosX, mapPosY, mapPosX + tilewidth, mapPosY]);
boundaries.push([mapPosX + tilewidth, mapPosY, mapPosX + tilewidth, mapPosY + tileheight]);
boundaries.push([mapPosX + tilewidth, mapPosY + tileheight, mapPosX, mapPosY + tileheight]);
boundaries.push([mapPosX, mapPosY + tileheight, mapPosX, mapPosY ]);
}
mapIndex++;
}
}
}
this.screenPageData._setWholeMapBoundaries(boundaries);
this.screenPageData._mergeBoundaries(true);
console.warn("precalculated boundaries set");
console.log(this.screenPageData.getWholeWorldBoundaries());
resolve();
} else {
resolve();
}
});
}
/**
*
* @param {DrawImageObject | DrawCircleObject | DrawConusObject | DrawLineObject | DrawPolygonObject | DrawRectObject | DrawTextObject | TiledRenderLayer} renderObject
* @returns {Promise<void>}
*/
_bindRenderObject(renderObject) {
if (renderObject instanceof TiledRenderLayer) {
return this.#bindRenderLayerMethod(renderObject)
.then(() => this.#webGlInterface._executeTileImagesDraw());
} else {
return new Promise((resolve) => {
const [ xOffset, yOffset ] = renderObject.isOffsetTurnedOff === true ? [0,0] : this.screenPageData.worldOffset,
x = renderObject.x - xOffset,
y = renderObject.y - yOffset;
if (renderObject.type === CONST.DRAW_TYPE.IMAGE) {
const atlasImage = this.loader.getImage(renderObject.key),
animationIndex = renderObject.imageIndex;
let imageX = 0,
imageY = 0;
if (animationIndex !== 0) {
const imageColsNumber = atlasImage.width / renderObject.width;
imageX = animationIndex % imageColsNumber * renderObject.width,
imageY = Math.floor(animationIndex / imageColsNumber) * renderObject.height;
}
const posX = x - renderObject.width / 2,
posY = y - renderObject.height / 2;
const vecX1 = posX,
vecY1 = posY,
vecX2 = vecX1 + renderObject.width,
vecY2 = vecY1 + renderObject.height,
texX1 = 1 / atlasImage.width * imageX,
texY1 = 1 / atlasImage.height * imageY,
texX2 = texX1 + (1 / atlasImage.width * renderObject.width),
texY2 = texY1 + (1 / atlasImage.height * renderObject.height);
const verticesBufferData = [
vecX1, vecY1,
vecX2, vecY1,
vecX1, vecY2,
vecX1, vecY2,
vecX2, vecY1,
vecX2, vecY2
],
texturesBufferData = [
texX1, texY1,
texX2, texY1,
texX1, texY2,
texX1, texY2,
texX2, texY1,
texX2, texY2
];
this.#webGlInterface._bindAndDrawTileImages(verticesBufferData, texturesBufferData, atlasImage, renderObject.key, renderObject.rotation, [x, y], [1, 1], renderObject._maskId);
if (renderObject.vertices && this.systemSettings.gameOptions.boundaries.drawObjectBoundaries) {
const shiftX = x,// - renderObject.boundaries[0],
shiftY = y,// - renderObject.boundaries[1],
rotation = renderObject.rotation ? renderObject.rotation : 0;
this.#webGlInterface._drawPolygon(renderObject.vertices, this.systemSettings.gameOptions.boundaries.boundariesColor, this.systemSettings.gameOptions.boundaries.boundariesWidth, rotation, [shiftX, shiftY]);
}
//ctx.restore();
} else if (renderObject.type === CONST.DRAW_TYPE.TEXT) {
this.#webGlInterface._bindText(x, y, renderObject);
} else if (renderObject.type === CONST.DRAW_TYPE.CIRCLE || renderObject.type === CONST.DRAW_TYPE.CONUS) {
this.#webGlInterface._bindConus(renderObject, renderObject.rotation, [x, y]);
} else if (renderObject.type === CONST.DRAW_TYPE.LINE) {
this.#webGlInterface._drawLines(renderObject.vertices, renderObject.bgColor, this.systemSettings.gameOptions.boundariesWidth, renderObject.rotation, [x, y]);
} else {
this.#webGlInterface._bindPrimitives(renderObject, renderObject.rotation, [x, y]);
}
return resolve();
});
}
}
//#clearRenderObjectPromises() {
// this.#bindRenderObjectPromises = [];
//}
/**
*
* @returns {Promise<void>}
*/
#drawBoundariesWebGl() {
return new Promise((resolve) => {
const b = this.screenPageData.getBoundaries(),
len = b.length,
linesArray = [];
for (let i = 0; i < len; i++) {
const item = b[i];
linesArray.push(item[0], item[1]);
linesArray.push(item[2], item[3]);
}
this.#webGlInterface._drawLines(linesArray, this.systemSettings.gameOptions.boundaries.boundariesColor, this.systemSettings.gameOptions.boundaries.boundariesWidth);
resolve();
});
}
}