Add floorplan stuff

This commit is contained in:
Bill 2021-05-04 00:19:15 -04:00
parent b4f4cc446b
commit d27a7debe8
9 changed files with 1210 additions and 0 deletions

View File

@ -0,0 +1,21 @@
import * as PIXI from 'pixi.js';
window.PIXI = PIXI;
export class CanvasTileRenderer
{
renderer: PIXI.Renderer;
tileAnim = [0, 0];
dontUseTransform = false;
constructor(renderer: PIXI.Renderer)
{
this.renderer = renderer;
this.tileAnim = [0, 0];
}
}
const cr = (window.PIXI as any).CanvasRenderer;
if(cr)
{
console.warn('REGISTER');
cr.registerPlugin('tilemap', CanvasTileRenderer);
}

View File

@ -0,0 +1,286 @@
import { Container, Matrix, Renderer, Texture } from 'pixi.js';
import { Constant } from './Constant';
import { RectTileLayer } from './RectTileLayer';
export class CompositeRectTileLayer extends Container
{
constructor(zIndex?: number, bitmaps?: Array<Texture>, texPerChild?: number, alpha?: number)
{
super();
// eslint-disable-next-line prefer-spread,prefer-rest-params
this.initialize.apply(this, arguments);
}
z: number;
zIndex: number;
modificationMarker = 0;
shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);
_globalMat: Matrix = null;
_lastLayer: RectTileLayer = null;
texPerChild: number;
initialize(zIndex?: number, bitmaps?: Array<Texture>, texPerChild?: number, alpha?: number)
{
if(texPerChild as any === true)
{
//old format, ignore it!
texPerChild = 0;
}
this.z = this.zIndex = zIndex;
this.alpha = alpha ?? 1.0;
this.texPerChild = texPerChild || Constant.boundCountPerBuffer * Constant.maxTextures;
if(bitmaps)
{
this.setBitmaps(bitmaps);
}
}
setBitmaps(bitmaps: Array<Texture>)
{
for(let i=0;i<bitmaps.length;i++)
{
if(bitmaps[i] && !bitmaps[i].baseTexture)
{
throw new Error('pixi-tilemap cannot use destroyed textures. '+
'Probably, you passed resources[\'myAtlas\'].texture in pixi > 5.2.1, it does not exist there.');
}
}
const texPerChild = this.texPerChild;
const len1 = this.children.length;
const len2 = Math.ceil(bitmaps.length / texPerChild);
let i: number;
for(i = 0; i < len1; i++)
{
(this.children[i] as RectTileLayer).textures = bitmaps.slice(i * texPerChild, (i + 1) * texPerChild);
}
for(i = len1; i < len2; i++)
{
const layer = new RectTileLayer(this.zIndex, bitmaps.slice(i * texPerChild, (i + 1) * texPerChild));
layer.compositeParent = true;
layer.offsetX = Constant.boundSize;
layer.offsetY = Constant.boundSize;
this.addChild(layer);
}
}
clear()
{
for(let i = 0; i < this.children.length; i++)
{
(this.children[i] as RectTileLayer).clear();
}
this.modificationMarker = 0;
}
addRect(textureIndex: number, u: number, v: number, x: number, y: number, tileWidth: number, tileHeight: number, animX?: number, animY?: number, rotate?: number, animWidth?: number, animHeight?: number, alpha?: number): this
{
const childIndex: number = textureIndex / this.texPerChild >> 0;
const textureId: number = textureIndex % this.texPerChild;
if(this.children[childIndex] && (this.children[childIndex] as RectTileLayer).textures)
{
this._lastLayer = (this.children[childIndex] as RectTileLayer);
const tileAlpha = this.worldAlpha * (alpha ?? 1.0);
this._lastLayer.addRect(textureId, u, v, x, y, tileWidth, tileHeight, animX, animY, rotate, animWidth, animHeight, tileAlpha);
}
else
{
this._lastLayer = null;
}
return this;
}
tileRotate(rotate: number): this
{
if(this._lastLayer)
{
this._lastLayer.tileRotate(rotate);
}
return this;
}
tileAnimX(offset: number, count: number): this
{
if(this._lastLayer)
{
this._lastLayer.tileAnimX(offset, count);
}
return this;
}
tileAnimY(offset: number, count: number): this
{
if(this._lastLayer)
{
this._lastLayer.tileAnimY(offset, count);
}
return this;
}
addFrame(texture_: Texture | string | number, x: number, y: number, animX?: number, animY?: number, animWidth?: number, animHeight?: number, alpha?: number, yaxis?: number, xaxis?: number): this
{
let texture: Texture;
let layer: RectTileLayer = null;
let ind = 0;
const children = this.children;
this._lastLayer = null;
if(typeof texture_ === 'number')
{
const childIndex = texture_ / this.texPerChild >> 0;
layer = children[childIndex] as RectTileLayer;
if(!layer)
{
layer = children[0] as RectTileLayer;
if(!layer)
{
return this;
}
ind = 0;
}
else
{
ind = texture_ % this.texPerChild;
}
texture = layer.textures[ind];
}
else
{
if(typeof texture_ === 'string')
{
texture = Texture.from(texture_);
}
else
{
texture = texture_ as Texture;
}
for(let i = 0; i < children.length; i++)
{
const child = children[i] as RectTileLayer;
const tex = child.textures;
for(let j = 0; j < tex.length; j++)
{
if(tex[j].baseTexture === texture.baseTexture)
{
layer = child;
ind = j;
break;
}
}
if(layer)
{
break;
}
}
if(!layer)
{
for(let i = 0; i < children.length; i++)
{
const child = children[i] as RectTileLayer;
if(child.textures.length < this.texPerChild)
{
layer = child;
ind = child.textures.length;
child.textures.push(texture);
break;
}
}
if(!layer)
{
layer = new RectTileLayer(this.zIndex, texture);
layer.compositeParent = true;
layer.offsetX = Constant.boundSize;
layer.offsetY = Constant.boundSize;
this.addChild(layer);
ind = 0;
}
}
}
this._lastLayer = layer;
const tileAlpha = this.worldAlpha * (alpha?? 1.0);
layer.addRect(ind, texture.frame.x, texture.frame.y, x, y, texture.orig.width, texture.orig.height, animX, animY, texture.rotate, animWidth, animHeight, tileAlpha, yaxis, xaxis);
return this;
}
renderCanvas(renderer: any)
{
if(!this.visible || this.worldAlpha <= 0 || !this.renderable)
{
return;
}
const plugin = renderer.plugins.tilemap;
if(!plugin.dontUseTransform)
{
const wt = this.worldTransform;
renderer.context.setTransform(
wt.a,
wt.b,
wt.c,
wt.d,
wt.tx * renderer.resolution,
wt.ty * renderer.resolution
);
}
const layers = this.children;
for(let i = 0; i < layers.length; i++)
{
(layers[i] as RectTileLayer).renderCanvasCore(renderer);
}
}
render(renderer: Renderer)
{
if(!this.visible || this.worldAlpha <= 0 || !this.renderable)
{
return;
}
const plugin = (renderer.plugins as any)['tilemap'];
const shader = plugin.getShader();
renderer.batch.setObjectRenderer(plugin);
//TODO: dont create new array, please
this._globalMat = shader.uniforms.projTransMatrix;
renderer.globalUniforms.uniforms.projectionMatrix.copyTo(this._globalMat).append(this.worldTransform);
shader.uniforms.shadowColor = this.shadowColor;
shader.uniforms.animationFrame = plugin.tileAnim;
renderer.shader.bind(shader, false);
const layers = this.children;
for(let i = 0; i < layers.length; i++)
{
(layers[i] as RectTileLayer).renderWebGLCore(renderer, plugin);
}
}
isModified(anim: boolean)
{
const layers = this.children;
if(this.modificationMarker !== layers.length)
{
return true;
}
for(let i = 0; i < layers.length; i++)
{
if((layers[i] as RectTileLayer).isModified(anim))
{
return true;
}
}
return false;
}
clearModify()
{
const layers = this.children;
this.modificationMarker = layers.length;
for(let i = 0; i < layers.length; i++)
{
(layers[i] as RectTileLayer).clearModify();
}
}
}

View File

@ -0,0 +1,11 @@
import { SCALE_MODES } from 'pixi.js';
export const Constant = {
maxTextures: 16,
bufferSize: 2048,
boundSize: 1024,
boundCountPerBuffer: 1,
use32bitIndex: false,
SCALE_MODE: SCALE_MODES.LINEAR,
DO_CLEAR: true
};

View File

@ -0,0 +1,61 @@
import { resources } from '@pixi/core';
import { BaseTexture, Sprite, Texture } from 'pixi.js';
export interface IMultiTextureOptions {
boundCountPerBuffer: number;
boundSize: number;
bufferSize: number;
DO_CLEAR?: boolean;
}
export class MultiTextureResource extends resources.Resource
{
constructor(options: IMultiTextureOptions)
{
super(options.bufferSize, options.bufferSize);
const bounds = this.boundSprites;
const dirties = this.dirties;
this.boundSize = options.boundSize;
for(let j = 0; j < options.boundCountPerBuffer; j++)
{
const spr = new Sprite();
spr.position.x = options.boundSize * (j & 1);
spr.position.y = options.boundSize * (j >> 1);
bounds.push(spr);
dirties.push(0);
}
this.DO_CLEAR = !!options.DO_CLEAR;
}
DO_CLEAR = false;
boundSize: number = 0;
_clearBuffer: Uint8Array = null;
bind(baseTexture: BaseTexture)
{
if(this.baseTex)
{
throw new Error('Only one baseTexture is allowed for this resource!');
}
this.baseTex = baseTexture;
super.bind(baseTexture);
}
baseTex: BaseTexture = null;
boundSprites: Array<Sprite> = [];
dirties: Array<number> = [];
setTexture(ind: number, texture: Texture)
{
const spr = this.boundSprites[ind];
if(spr.texture.baseTexture === texture.baseTexture)
{
return;
}
spr.texture = texture;
this.baseTex.update();
this.dirties[ind] = (this.baseTex as any).dirtyId;
}
}

View File

@ -0,0 +1,491 @@
import { Container, DRAW_MODES, groupD8, Matrix, Renderer, Texture } from 'pixi.js';
import { Constant } from './Constant';
import { RectTileGeom } from './RectTileShader';
import { TileRenderer } from './TileRenderer';
enum PointStruct {
U = 0,
V,
X,
Y,
TileWidth,
TileHeight,
Rotate,
AnimX,
AnimY,
TextureIndex,
AnimCountX,
AnimCountY,
Alpha,
YAxis,
XAxis
}
export const POINT_STRUCT_SIZE_TWO = (Object.keys(PointStruct).length / 2);
//export const POINT_STRUCT_SIZE = 12;
export class RectTileLayer extends Container
{
constructor(zIndex: number, texture: Texture | Array<Texture>)
{
super();
this.initialize(zIndex, texture);
}
zIndex = 0;
modificationMarker = 0;
_$_localBounds = new PIXI.Bounds();
shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);
_globalMat: Matrix = null;
pointsBuf: Array<number> = [];
hasAnim = false;
textures: Array<Texture>;
offsetX = 0;
offsetY = 0;
compositeParent = false;
initialize(zIndex: number, textures: Texture | Array<Texture>)
{
if(!textures)
{
textures = [];
}
else if(!(textures instanceof Array) && (textures as Texture).baseTexture)
{
textures = [textures as Texture];
}
this.textures = textures as Array<Texture>;
this.zIndex = zIndex;
// this.visible = false;
}
clear()
{
this.pointsBuf.length = 0;
this.modificationMarker = 0;
this._$_localBounds.clear();
this.hasAnim = false;
}
addFrame(texture_: Texture | string | number, x: number, y: number, animX: number, animY: number)
{
let texture: Texture;
let textureIndex = 0;
if(typeof texture_ === 'number')
{
textureIndex = texture_;
texture = this.textures[textureIndex];
}
else
{
if(typeof texture_ === 'string')
{
texture = Texture.from(texture_);
}
else
{
texture = texture_ as Texture;
}
let found = false;
const textureList = this.textures;
for(let i = 0; i < textureList.length; i++)
{
if(textureList[i].baseTexture === texture.baseTexture)
{
textureIndex = i;
found = true;
break;
}
}
if(!found)
{
// textureIndex = this.textures.length;
// this.textures.push(texture);
return false;
}
}
this.addRect(textureIndex, texture.frame.x, texture.frame.y, x, y, texture.orig.width, texture.orig.height, animX, animY, texture.rotate);
return true;
}
addRect(textureIndex: number, u: number, v: number, x: number, y: number, tileWidth: number, tileHeight: number,
animX: number = 0, animY: number = 0, rotate: number = 0, animCountX: number = 1024, animCountY: number = 1024, alpha: number = 1, yaxis?: number, xaxis?: number): this
{
const pb = this.pointsBuf;
this.hasAnim = this.hasAnim || animX > 0 || animY > 0;
pb.push(u);
pb.push(v);
pb.push(x);
pb.push(y);
pb.push(tileWidth);
pb.push(tileHeight);
pb.push(rotate);
pb.push(animX | 0);
pb.push(animY | 0);
pb.push(textureIndex);
pb.push(animCountX);
pb.push(animCountY);
pb.push(alpha);
pb.push(yaxis);
pb.push(xaxis);
this._$_localBounds.addFramePad(x, y, x+tileWidth, y+tileHeight, 0, 0);
return this;
}
tileRotate(rotate: number)
{
const pb = this.pointsBuf;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.TextureIndex)] = rotate;
}
tileAnimX(offset: number, count: number)
{
const pb = this.pointsBuf;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.AnimX)] = offset;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.AnimCountX)] = count;
}
tileAnimY(offset: number, count: number)
{
const pb = this.pointsBuf;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.AnimY)] = offset;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.AnimCountY)] = count;
}
tileAlpha(alpha: number)
{
const pb = this.pointsBuf;
pb[pb.length - (POINT_STRUCT_SIZE_TWO - PointStruct.Alpha)] = alpha;
}
renderCanvas(renderer: any)
{
const plugin = renderer.plugins.tilemap;
if(!plugin.dontUseTransform)
{
const wt = this.worldTransform;
renderer.context.setTransform(
wt.a,
wt.b,
wt.c,
wt.d,
wt.tx * renderer.resolution,
wt.ty * renderer.resolution
);
}
this.renderCanvasCore(renderer);
}
renderCanvasCore(renderer: any)
{
if(this.textures.length === 0) return;
const points = this.pointsBuf;
renderer.context.fillStyle = '#000000';
for(let i = 0, n = points.length; i < n; i += POINT_STRUCT_SIZE_TWO)
{
let x1 = points[i + PointStruct.U], y1 = points[i + PointStruct.V];
const x2 = points[i + PointStruct.X], y2 = points[i + PointStruct.Y];
const w = points[i + PointStruct.TileWidth];
const h = points[i + PointStruct.TileHeight];
const rotate = points[i + PointStruct.Rotate];
x1 += points[i + PointStruct.AnimX] * renderer.plugins.tilemap.tileAnim[0];
y1 += points[i + PointStruct.AnimY] * renderer.plugins.tilemap.tileAnim[1];
const textureIndex = points[i + PointStruct.TextureIndex];
const alpha = points[i + PointStruct.Alpha];
// canvas does not work with rotate yet
if(textureIndex >= 0)
{
renderer.context.globalAlpha = alpha;
renderer.context.drawImage((this.textures[textureIndex].baseTexture as any).getDrawableSource(), x1, y1, w, h, x2, y2, w, h);
}
else
{
renderer.context.globalAlpha = 0.5;
renderer.context.fillRect(x2, y2, w, h);
renderer.context.globalAlpha = 1;
}
renderer.context.globalAlpha = 1;
}
}
vbId = 0;
vb: RectTileGeom = null;
vbBuffer: ArrayBuffer = null;
vbArray: Float32Array = null;
vbInts: Uint32Array = null;
destroyVb()
{
if(this.vb)
{
this.vb.destroy();
this.vb = null;
}
}
render(renderer: Renderer)
{
const plugin = (renderer.plugins as any)['tilemap'];
const shader = plugin.getShader();
renderer.batch.setObjectRenderer(plugin);
this._globalMat = shader.uniforms.projTransMatrix;
renderer.globalUniforms.uniforms.projectionMatrix.copyTo(this._globalMat).append(this.worldTransform);
shader.uniforms.shadowColor = this.shadowColor;
shader.uniforms.animationFrame = plugin.tileAnim;
this.renderWebGLCore(renderer, plugin);
}
renderWebGLCore(renderer: Renderer, plugin: TileRenderer)
{
const points = this.pointsBuf;
if(points.length === 0) return;
const rectsCount = points.length / POINT_STRUCT_SIZE_TWO;
const shader = plugin.getShader();
const textures = this.textures;
if(textures.length === 0) return;
plugin.bindTextures(renderer, shader, textures);
renderer.shader.bind(shader, false);
//lost context! recover!
let vb = this.vb;
if(!vb)
{
vb = plugin.createVb();
this.vb = vb;
this.vbId = (vb as any).id;
this.vbBuffer = null;
this.modificationMarker = 0;
}
plugin.checkIndexBuffer(rectsCount, vb);
const boundCountPerBuffer = Constant.boundCountPerBuffer;
const vertexBuf = vb.getBuffer('aVertexPosition');
//if layer was changed, re-upload vertices
const vertices = rectsCount * vb.vertPerQuad;
if(vertices === 0) return;
if(this.modificationMarker !== vertices)
{
this.modificationMarker = vertices;
const vs = vb.stride * vertices;
if(!this.vbBuffer || this.vbBuffer.byteLength < vs)
{
//!@#$ happens, need resize
let bk = vb.stride;
while(bk < vs)
{
bk *= 2;
}
this.vbBuffer = new ArrayBuffer(bk);
this.vbArray = new Float32Array(this.vbBuffer);
this.vbInts = new Uint32Array(this.vbBuffer);
vertexBuf.update(this.vbBuffer);
}
const arr = this.vbArray, ints = this.vbInts;
//upload vertices!
let sz = 0;
//let tint = 0xffffffff;
let textureId = 0;
let shiftU: number = this.offsetX;
let shiftV: number = this.offsetY;
//let tint = 0xffffffff;
const tint = -1;
for(let i = 0; i < points.length; i += POINT_STRUCT_SIZE_TWO)
{
const eps = 0.5;
if(this.compositeParent)
{
const textureIndex = points[i + PointStruct.TextureIndex];
if(boundCountPerBuffer > 1)
{
//TODO: what if its more than 4?
textureId = (textureIndex >> 2);
shiftU = this.offsetX * (textureIndex & 1);
shiftV = this.offsetY * ((textureIndex >> 1) & 1);
}
else
{
textureId = textureIndex;
shiftU = 0;
shiftV = 0;
}
}
const x = points[i + PointStruct.X], y = points[i + PointStruct.Y];
const w = points[i + PointStruct.TileWidth], h = points[i + PointStruct.TileHeight];
const u = points[i + PointStruct.U] + shiftU, v = points[i + PointStruct.V] + shiftV;
let rotate = points[i + PointStruct.Rotate];
const animX = points[i + PointStruct.AnimX], animY = points[i + PointStruct.AnimY];
const animWidth = points[i + PointStruct.AnimCountX] || 1024, animHeight = points[i + PointStruct.AnimCountY] || 1024;
const animXEncoded = animX + (animWidth * 2048);
const animYEncoded = animY + (animHeight * 2048);
const alpha = points[i + PointStruct.Alpha];
let u0: number, v0: number, u1: number, v1: number, u2: number, v2: number, u3: number, v3: number;
if(rotate === 0)
{
u0 = u;
v0 = v;
u1 = u + w;
v1 = v;
u2 = u + w;
v2 = v + h;
u3 = u;
v3 = v + h;
}
else
{
let w2 = w / 2;
let h2 = h / 2;
if(rotate % 4 !== 0)
{
w2 = h / 2;
h2 = w / 2;
}
const cX = u + w2;
const cY = v + h2;
rotate = groupD8.add(rotate, groupD8.NW);
u0 = cX + (w2 * groupD8.uX(rotate));
v0 = cY + (h2 * groupD8.uY(rotate));
rotate = groupD8.add(rotate, 2); // rotate 90 degrees clockwise
u1 = cX + (w2 * groupD8.uX(rotate));
v1 = cY + (h2 * groupD8.uY(rotate));
rotate = groupD8.add(rotate, 2);
u2 = cX + (w2 * groupD8.uX(rotate));
v2 = cY + (h2 * groupD8.uY(rotate));
rotate = groupD8.add(rotate, 2);
u3 = cX + (w2 * groupD8.uX(rotate));
v3 = cY + (h2 * groupD8.uY(rotate));
}
const test= true;
arr[sz++] = x;
arr[sz++] = y;
arr[sz++] = u0;
arr[sz++] = v0;
arr[sz++] = u + eps;
arr[sz++] = v + eps;
arr[sz++] = u + w - eps;
arr[sz++] = v + h - eps;
arr[sz++] = animXEncoded;
arr[sz++] = animYEncoded;
arr[sz++] = textureId;
if(test)
{
arr[sz++] = alpha;
}
arr[sz++] = x + w;
arr[sz++] = y;
arr[sz++] = u1;
arr[sz++] = v1;
arr[sz++] = u + eps;
arr[sz++] = v + eps;
arr[sz++] = u + w - eps;
arr[sz++] = v + h - eps;
arr[sz++] = animXEncoded;
arr[sz++] = animYEncoded;
arr[sz++] = textureId;
if(test)
{
arr[sz++] = alpha;
}
arr[sz++] = x + w;
arr[sz++] = y + h;
arr[sz++] = u2;
arr[sz++] = v2;
arr[sz++] = u + eps;
arr[sz++] = v + eps;
arr[sz++] = u + w - eps;
arr[sz++] = v + h - eps;
arr[sz++] = animXEncoded;
arr[sz++] = animYEncoded;
arr[sz++] = textureId;
if(test)
{
arr[sz++] = alpha;
}
arr[sz++] = x;
arr[sz++] = y + h;
arr[sz++] = u3;
arr[sz++] = v3;
arr[sz++] = u + eps;
arr[sz++] = v + eps;
arr[sz++] = u + w - eps;
arr[sz++] = v + h - eps;
arr[sz++] = animXEncoded;
arr[sz++] = animYEncoded;
arr[sz++] = textureId;
if(test)
{
arr[sz++] = alpha;
}
}
vertexBuf.update(arr);
}
(renderer.geometry as any).bind(vb, shader);
renderer.geometry.draw(DRAW_MODES.TRIANGLES, rectsCount * 6, 0);
}
isModified(anim: boolean)
{
if(this.modificationMarker !== this.pointsBuf.length ||
anim && this.hasAnim)
{
return true;
}
return false;
}
clearModify()
{
this.modificationMarker = this.pointsBuf.length;
}
protected _calculateBounds(): void
{
const { minX, minY, maxX, maxY } = this._$_localBounds;
this._bounds.addFrame(this.transform, minX, minY, maxX, maxY);
}
public getLocalBounds(rect?: PIXI.Rectangle): PIXI.Rectangle
{
// we can do a fast local bounds if the sprite has no children!
if(this.children.length === 0)
{
return this._$_localBounds.getRectangle(rect);
}
return super.getLocalBounds.call(this, rect);
}
destroy(options?: any)
{
super.destroy(options);
this.destroyVb();
}
}

View File

@ -0,0 +1,110 @@
import { Buffer, Geometry, Matrix, Shader } from 'pixi.js';
import * as shaderGenerator from './shaderGenerator';
const rectShaderFrag = `
varying vec2 vTextureCoord;
varying vec4 vFrame;
varying float vTextureId;
varying float vAlpha;
uniform vec4 shadowColor;
uniform sampler2D uSamplers[%count%];
uniform vec2 uSamplerSize[%count%];
void main(void){
vec2 textureCoord = clamp(vTextureCoord, vFrame.xy, vFrame.zw);
float textureId = floor(vTextureId + 0.5);
vec4 color;
vec3 tint = vec3(1, 0.8, 0.5); // reddish
%forloop%
gl_FragColor = (color * vAlpha) + (gl_FragColor * (1.0 - vAlpha));
}
`;
const rectShaderVert = `
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
attribute vec4 aFrame;
attribute vec2 aAnim;
attribute float aTextureId;
attribute float aAlpha;
uniform mat3 projTransMatrix;
uniform vec2 animationFrame;
varying vec2 vTextureCoord;
varying float vTextureId;
varying vec4 vFrame;
varying float vAlpha;
void main(void){
gl_Position = vec4((projTransMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vec2 animCount = floor((aAnim + 0.5) / 2048.0);
vec2 animFrameOffset = aAnim - animCount * 2048.0;
vec2 animOffset = animFrameOffset * floor(mod(animationFrame + 0.5, animCount));
vTextureCoord = aTextureCoord + animOffset;
vFrame = aFrame + vec4(animOffset, animOffset);
vTextureId = aTextureId;
vAlpha = aAlpha;
}
`;
export abstract class TilemapShader extends Shader
{
maxTextures = 0;
constructor(maxTextures: number, shaderVert: string, shaderFrag: string)
{
super(
new PIXI.Program(
shaderVert,
shaderFrag),
{
animationFrame: new Float32Array(2),
uSamplers: [],
uSamplerSize: [],
projTransMatrix: new Matrix()
}
);
this.maxTextures = maxTextures;
shaderGenerator.fillSamplers(this, this.maxTextures);
}
}
export class RectTileShader extends TilemapShader
{
constructor(maxTextures: number)
{
super(
maxTextures,
rectShaderVert,
shaderGenerator.generateFragmentSrc(maxTextures, rectShaderFrag)
);
shaderGenerator.fillSamplers(this, this.maxTextures);
}
}
export class RectTileGeom extends Geometry
{
vertSize = 12;
vertPerQuad = 4;
stride = this.vertSize * 4;
lastTimeAccess = 0;
constructor()
{
super();
const buf = this.buf = new Buffer(new Float32Array(2), true, false);
this.addAttribute('aVertexPosition', buf, 0, false, 0, this.stride, 0)
.addAttribute('aTextureCoord', buf, 0, false, 0, this.stride, 2 * 4)
.addAttribute('aFrame', buf, 0, false, 0, this.stride, 4 * 4)
.addAttribute('aAnim', buf, 0, false, 0, this.stride, 8 * 4)
.addAttribute('aTextureId', buf, 0, false, 0, this.stride, 10 * 4)
.addAttribute('aTextureId', buf, 0, false, 0, this.stride, 10 * 4)
.addAttribute('aAlpha', buf, 0, false, 0, this.stride, 11 * 4);
}
buf: PIXI.Buffer;
}

View File

@ -0,0 +1,165 @@
import { BaseTexture, Buffer, ObjectRenderer, Renderer, Texture, WRAP_MODES } from 'pixi.js';
import { Constant } from './Constant';
import { MultiTextureResource } from './MultiTextureResource';
import type { TilemapShader } from './RectTileShader';
import { RectTileGeom, RectTileShader } from './RectTileShader';
/**
* Renderer for rectangle tiles.
*/
export class TileRenderer extends ObjectRenderer
{
renderer: Renderer;
gl: WebGLRenderingContext;
sn: number = -1;
indexBuffer: PIXI.Buffer = null;
ibLen: number = 0;
tileAnim = [0, 0];
texLoc: Array<number> = [];
rectShader: RectTileShader;
texResources: Array<MultiTextureResource> = [];
constructor(renderer: Renderer)
{
super(renderer);
this.rectShader = new RectTileShader(Constant.maxTextures);
this.indexBuffer = new Buffer(undefined, true, true);
this.checkIndexBuffer(2000);
this.initBounds();
}
initBounds()
{
if(Constant.boundCountPerBuffer <= 1)
{
return;
}
const maxTextures = Constant.maxTextures;
for(let i = 0; i < maxTextures; i++)
{
const resource = new MultiTextureResource(Constant);
// @ts-ignore
const baseTex = new BaseTexture(resource);
baseTex.scaleMode = Constant.SCALE_MODE;
baseTex.wrapMode = WRAP_MODES.CLAMP;
this.texResources.push(resource);
}
}
bindTexturesWithoutRT(renderer: Renderer, shader: TilemapShader, textures: Array<Texture>)
{
const samplerSize: Array<number> = (shader as any).uniforms.uSamplerSize;
this.texLoc.length = 0;
for(let i = 0; i < textures.length; i++)
{
const texture = textures[i];
if(!texture || !texture.valid)
{
return;
}
renderer.texture.bind(textures[i], i);
//TODO: add resolution here
samplerSize[i * 2] = 1.0 / textures[i].baseTexture.width;
samplerSize[i * 2 + 1] = 1.0 / textures[i].baseTexture.height;
}
(shader as any).uniforms.uSamplerSize = samplerSize;
}
bindTextures(renderer: Renderer, shader: TilemapShader, textures: Array<Texture>)
{
const len = textures.length;
const maxTextures = Constant.maxTextures;
if(len > Constant.boundCountPerBuffer * maxTextures)
{
return;
}
if(Constant.boundCountPerBuffer <= 1)
{
this.bindTexturesWithoutRT(renderer, shader, textures);
return;
}
let i = 0;
for(; i < len; i++)
{
const texture = textures[i];
if(!texture || !texture.valid) continue;
const multi = this.texResources[i >> 2];
multi.setTexture(i & 3, texture);
}
const gltsUsed = (i + 3) >> 2;
for(i = 0; i < gltsUsed; i++)
{
//remove "i, true" after resolving a bug
renderer.texture.bind(this.texResources[i].baseTex, i);
}
}
start()
{
//sorry, nothing
}
createVb()
{
const geom = new RectTileGeom();
geom.addIndex(this.indexBuffer);
geom.lastTimeAccess = Date.now();
return geom;
}
checkIndexBuffer(size: number, vb: RectTileGeom = null)
{
const totalIndices = size * 6;
if(totalIndices <= this.ibLen)
{
return;
}
let len = totalIndices;
while(len < totalIndices)
{
len <<= 1;
}
this.ibLen = totalIndices;
this.indexBuffer.update((PIXI as any).utils.createIndicesForQuads(size,
Constant.use32bitIndex ? new Uint32Array(size * 6) : undefined));
// TODO: create new index buffer instead?
// if (vb) {
// const curIndex = vb.getIndex();
// if (curIndex !== this.indexBuffer && (curIndex.data as any).length < totalIndices) {
// this.swapIndex(vb, this.indexBuffer);
// }
// }
}
// swapIndex(geom: PIXI.Geometry, indexBuf: PIXI.Buffer) {
// let buffers = (geom as any).buffers;
// const oldIndex = geom.getIndex();
// let ind = buffers.indexOf(oldIndex);
// if (ind >= 0) {
// buffers.splice(ind, 1);
// }
// geom.addIndex(indexBuf);
// }
getShader(): TilemapShader
{
return this.rectShader;
}
destroy()
{
super.destroy();
// this.rectShader.destroy();
this.rectShader = null;
}
}
Renderer.registerPlugin('tilemap', TileRenderer as any);

View File

@ -0,0 +1,8 @@
export * from './CanvasTileRenderer';
export * from './CompositeRectTileLayer';
export * from './Constant';
export * from './MultiTextureResource';
export * from './RectTileLayer';
export * from './RectTileShader';
export * from './shaderGenerator';
export * from './TileRenderer';

View File

@ -0,0 +1,57 @@
import { Constant } from './Constant';
import type { TilemapShader } from './RectTileShader';
export function fillSamplers(shader: TilemapShader, maxTextures: number)
{
const sampleValues: Array<number> = [];
for(let i = 0; i < maxTextures; i++)
{
sampleValues[i] = i;
}
shader.uniforms.uSamplers = sampleValues;
const samplerSize: Array<number> = [];
for(let i = 0; i < maxTextures; i++)
{
samplerSize.push(1.0 / Constant.bufferSize);
samplerSize.push(1.0 / Constant.bufferSize);
}
shader.uniforms.uSamplerSize = samplerSize;
}
export function generateFragmentSrc(maxTextures: number, fragmentSrc: string)
{
return fragmentSrc.replace(/%count%/gi, maxTextures + '')
.replace(/%forloop%/gi, generateSampleSrc(maxTextures));
}
export function generateSampleSrc(maxTextures: number)
{
let src = '';
src += '\n';
src += '\n';
src += 'if(vTextureId <= -1.0) {';
src += '\n\tcolor = shadowColor;';
src += '\n}';
for(let i = 0; i < maxTextures; i++)
{
src += '\nelse ';
if(i < maxTextures-1)
{
src += 'if(textureId == ' + i + '.0)';
}
src += '\n{';
src += '\n\tcolor = texture2D(uSamplers['+i+'], textureCoord * uSamplerSize['+i+']);';
src += '\n}';
}
src += '\n';
src += '\n';
return src;
}