/** * SG2DTransitions 1.0.0 * Generator of smooth transitions between different types of terrain. * Tiles or their constructors can specify the layer index (0..31) and the transition type (TRANSITIONS_STANDARD, ...) where the transition is generated. * The plugin only processes tiles with the static property useTransition=true and uses the texture from the static property of the tile! * https://github.com/VediX/sg2d.github.io * (c) 2019-2021 SG2D * SG2DTransitions may be freely distributed under the MIT license */ "use strict"; export default class SG2DTransitions extends SG2D.PluginBase { static code = "transitions"; static TRANSITIONS_STANDARD = 1; static TRANSITIONS_STRICT = 2; static K_STRICT = 1.1875; constructor(...args) { super(...args); class VirtualTransitionsTile extends SG2D.PluginBase.SG2DTile { static char = " "; static texture = "vtt"; static useTransitions = true; static altitude = -999; static _uid = 900000000; static isVirtualTransitionTile = true; } SG2DTransitions.VirtualTransitionsTile = VirtualTransitionsTile; SG2DTransitions.createMask(); SG2DTransitions.success(); //SG2DTransitions.failed("Test error: message error!"); } static createMask() { this.masks = []; this.masks[this.TRANSITIONS_STANDARD] = SG2D.PluginBase.SG2DUtils.addMask({ name: "mask_transitions_" + this.TRANSITIONS_STANDARD, iterate: (x, y, radius, p_index)=>{ return { r: 0, g: 0, b: 0, a: (radius >= SG2D.PluginBase.SG2DConsts.CELLSIZEPIX05 ? 255 : 0) }; }, width: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX, height: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX }); var rStrict = this.K_STRICT * SG2D.PluginBase.SG2DConsts.CELLSIZEPIX05; this.masks[this.TRANSITIONS_STRICT] = SG2D.PluginBase.SG2DUtils.addMask({ name: "mask_transitions_" + this.TRANSITIONS_STRICT, iterate: (x, y, radius, p_index)=>{ return { r: 0, g: 0, b: 0, a: (radius >= rStrict ? 255 : 0) }; }, width: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX, height: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX }); SG2D.PluginBase.SG2DUtils.addMask({ name: this.VirtualTransitionsTile.texture, iterate: (x, y, radius, p_index)=>{ return { r: 0, g: 0, b: 0, a: 0 }; }, width: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX, height: SG2D.PluginBase.SG2DConsts.CELLSIZEPIX }).virtualTile = true; } static transitionsCoord = [ [{dx:0,dy:-1},{dx:1,dy:-1},{dx:1,dy:0}], // 45 [{dx:-1,dy:0},{dx:-1,dy:-1},{dx:0,dy:-1}], // 135 [{dx:0,dy:1},{dx:-1,dy:1},{dx:-1,dy:0}], // 225 [{dx:1,dy:0},{dx:1,dy:1},{dx:0,dy:1}] // 315 ]; static nsca = [[],[],[],[]]; // Nearest Sprites Class for 45 corners static run(area) { this.area = area; this.canvasTemp = this.canvasTemp || SG2D.PluginBase.SG2DUtils.createCanvas( SG2D.PluginBase.SG2DConsts.CELLSIZEPIX05, SG2D.PluginBase.SG2DConsts.CELLSIZEPIX05 ); this.ctxTemp = this.canvasTemp.getContext("2d"); var tile, layer; // 1. Automatic detection of layers where there are tiles with transitions var layers = {}; this.area.each((cluster)=>{ for (tile of cluster.tiles) { if (tile.constructor.useTransitions) { layers[ tile.properties.layer === void 0 ? tile.constructor.layer : tile.properties.layer ] = true; } } }); // 2. Creating virtual tiles around real tiles var tiles = []; this.area.each((cluster)=>{ for (layer in layers) { cluster.getLayerTiles(layer, tiles); if (tiles.length === 0 && SG2D.PluginBase.SG2DClusters.nearestClusters45(cluster, (nc)=>{ nc.getLayerTiles(layer, tiles); for (var i = 0; i < tiles.length; i++) { if (tiles[i].constructor !== this.VirtualTransitionsTile) return true; } })) { tile = new this.VirtualTransitionsTile({id: ++this.VirtualTransitionsTile._uid, position: SG2D.PluginBase.SGModel.clone(cluster.position), layer: layer }); } } }); // 3. Detect transitions var nc, nsc; this.area.each((cluster)=>{ for (var tileMain of cluster.tiles) { if (! tileMain.constructor.texture || ! tileMain.constructor.useTransitions) continue; layer = tileMain.properties.layer === void 0 ? tileMain.constructor.layer : tileMain.properties.layer; var texture_name = tileMain.constructor.texture; for (var a = 0; a < 4; a++) { nsc = this.nsca[a]; nsc[0] = void 0; nsc[1] = void 0; nsc[2] = void 0; for (var i = 0; i < 3; i++) { if (nc = this.area.getCluster(cluster.x + this.transitionsCoord[a][i].dx, cluster.y + this.transitionsCoord[a][i].dy)) { nc.getLayerTiles(layer, tiles); for (var tile of tiles) { if (tile.constructor.useTransitions) { nsc[i] = tile.constructor; nsc[i].altitude = nsc[i].altitude || 0; nsc[i].transitionsMask = nsc[i].transitionsMask || this.TRANSITIONS_STANDARD; nsc[i].char = nsc[i].char || nsc[i].name[0]; } } } } if (! nsc[0] || ! nsc[1] || ! nsc[2]) continue; if ( tileMain.constructor !== nsc[0] && nsc[0] === nsc[2] && nsc[0] === nsc[1] || // самый распростарненный случай tileMain.constructor !== nsc[0] && nsc[0] === nsc[2] && nsc[1] !== nsc[0] && tileMain.constructor.altitude <= nsc[0].altitude || // пример: песчанная коса идет по диагонали tileMain.constructor !== nsc[0] && nsc[0] === nsc[2] && nsc[1] !== nsc[0] && tileMain.constructor.altitude > nsc[1].altitude ) { if (! tileMain.transitions) tileMain.transitions = []; tileMain.transitions[a] = nsc[0]; texture_name += "_"+nsc[0].char[0]+a; } } if (tileMain.transitions && tileMain.transitions.length) { var texture = PIXI.utils.TextureCache[texture_name]; if (! texture) { texture = this.genTransitions(tileMain, texture_name); } tileMain.set("texture", texture_name); // For simple cases when the tile does not have transitions with different alpha landscape! if (tileMain.constructor === this.VirtualTransitionsTile) { for (var a = 0; a < 4; a++) { if (tileMain.transitions[a] && tileMain.transitions[a].alpha !== void 0) { tileMain.alpha = tileMain.transitions[a].alpha; break; } } } } } }); // 4. Destroy unnecessary virtual tiles this.area.each((cluster)=>{ for (tile of cluster.tiles) { if (tile.constructor === SG2DTransitions.VirtualTransitionsTile && tile.texture_current === "vtt") { tile.destroy(); } } }); } static quarters = [[0.5,0],[0,0],[0,0.5],[0.5,0.5]]; static genTransitions(tileMain, texture_name) { var mainClass = tileMain.constructor; var imgMain = SG2D.PluginBase.SG2DUtils.getTextureAsCanvas(mainClass.texture); var w = imgMain.width, h = imgMain.height; var w05 = Math.round(w/2), h05 = Math.round(h/2); var outClass; var canvasResult = SG2D.PluginBase.SG2DUtils.createCanvas(w, h); var ctxResult = canvasResult.getContext("2d"); ctxResult.drawImage(imgMain, 0, 0); for (var a = 0; a < 4; a++) { if ((outClass = tileMain.transitions[a]) && outClass.texture) { var smx = Math.round(w * this.quarters[a][0]); var smy = Math.round(h * this.quarters[a][1]); var outCanvas = SG2D.PluginBase.SG2DUtils.getTextureAsCanvas(outClass.texture); var transitionsMask = (outClass.altitude > mainClass.altitude ? outClass.transitionsMask : mainClass.transitionsMask) || this.TRANSITIONS_STANDARD; if (outClass === this.VirtualTransitionsTile) { transitionsMask = mainClass.transitionsMask; } var mask = this.masks[transitionsMask]; if (outClass === this.VirtualTransitionsTile) { ctxResult.globalCompositeOperation = "destination-out"; ctxResult.drawImage(mask, smx, smy, w05, h05, smx, smy, w05, h05); } else if (mainClass === this.VirtualTransitionsTile) { this.canvasTemp.width = w05; this.canvasTemp.height = w05; this.ctxTemp.globalCompositeOperation = "source-over"; this.ctxTemp.drawImage(outCanvas, smx, smy, w05, h05, 0, 0, w05, h05); this.ctxTemp.globalCompositeOperation = "destination-in"; this.ctxTemp.drawImage(mask, smx, smy, w05, h05, 0, 0, w05, h05); ctxResult.drawImage(this.canvasTemp, 0, 0, w05, h05, smx, smy, w05, h05); } else { ctxResult.globalCompositeOperation = "destination-out"; try { ctxResult.drawImage(mask, smx, smy, w05, h05, smx, smy, w05, h05); } catch(e) { debugger; } //(new Image()).src = canvasResult.toDataURL(); // We work pixel by pixel due to alpha channel support, because standard modes (destination-out, destination-in, etc.) do not work with the alpha channel as required! var len = w05 * h05 * 4; var mainData = ctxResult.getImageData(smx, smy, w05, h05); var outData = (outCanvas.getContext("2d")).getImageData(smx, smy, w05, h05); for (var i = 0; i < len; i+=4) { // TODO: сделать через WebGL if (mainData.data[i+3]===0) { mainData.data[i] = outData.data[i]; mainData.data[i+1] = outData.data[i+1]; mainData.data[i+2] = outData.data[i+2]; mainData.data[i+3] = outData.data[i+3]; } } ctxResult.putImageData(mainData, smx, smy); } } } //(new Image()).src = canvasResult.toDataURL(); var texture = PIXI.Texture.from((canvasResult._pixiId = texture_name, canvasResult)); return texture; } }