import { arraySet, noop } from "../utils/helpers";
import { Item } from "../display/Item";
import { Image } from "../display/Image";
import { Container } from "../display/Container";
import { StageContainer } from "../display/StageContainer";
import { Matrix3Utilities } from "../geom/Matrix3Utilities";
import { Buffer } from "../utils/Buffer";
import { Utils } from "../utils/Utils";
import { BatchRenderer } from "./BatchRenderer";
import { BaseItem } from "../display/BaseItem";
import { BaseDrawable } from "../display/BaseDrawable";
import "../geom/PointType";
/**
* @typedef {Object} Stage2DRendererConfig
* @extends {RendererConfig}
*/
/**
* <pre>
* Stage2D renderer
* - Renders multiple textures
* </pre>
* @extends {BatchRenderer}
*/
export class Stage2D extends BatchRenderer {
/**
* Creates an instance of Stage2D.
* @constructor
* @param {Stage2DRendererConfig} options
*/
constructor(options) {
options = {
...{
useTint: true,
},
...(options || {}),
};
options.config = Utils.initRendererConfig(options.config);
// prettier-ignore
options.config.locations = options.config.locations.concat([
"aDt",
"aDst",
"uWCl",
"uWA"
]);
const maxBatchItems = (options.maxBatchItems =
options.maxBatchItems || 10000);
super(options);
this.container = new StageContainer(this);
this._batchItems = 0;
this["_draw" + Item.TYPE] = noop;
this["_draw" + Image.TYPE] = this._drawImage.bind(this);
this["_draw" + Container.TYPE] = this._drawContainer.bind(this);
this._batchDraw = this._batchDraw.bind(this);
this._drawItem = this._drawItem.bind(this);
this._mousePosition = { x: 0, y: 0 };
this._dataBuffer = new Buffer("aDt", maxBatchItems, 3, 4);
this._distortionBuffer = new Buffer("aDst", maxBatchItems, 4, 2);
this._onMouseEventHandler = this._onMouseEventHandler.bind(this);
const body = document.body;
body.addEventListener("mousemove", this._onMouseEventHandler);
body.addEventListener("mousedown", this._onMouseEventHandler);
body.addEventListener("mouseup", this._onMouseEventHandler);
body.addEventListener("click", this._onMouseEventHandler);
body.addEventListener("touchstart", this._onMouseEventHandler);
body.addEventListener("touchmove", this._onMouseEventHandler);
body.addEventListener("touchend", this._onMouseEventHandler);
}
/**
* Renders the scene
*/
render() {
this._eventTarget = null;
super.render();
this._isMousePositionSet = false;
this._handleMouseEvent();
}
/**
* Description placeholder
*/
destruct() {
const body = document.body;
body.removeEventListener("mousemove", this._onMouseEventHandler);
body.removeEventListener("mousedown", this._onMouseEventHandler);
body.removeEventListener("mouseup", this._onMouseEventHandler);
body.removeEventListener("click", this._onMouseEventHandler);
body.removeEventListener("touchstart", this._onMouseEventHandler);
body.removeEventListener("touchmove", this._onMouseEventHandler);
body.removeEventListener("touchend", this._onMouseEventHandler);
}
/**
* @ignore
*/
_handleMouseEvent() {
if (this._latestEvent) {
if (this._eventTarget !== this._previousEventTarget) {
const newEvent = {};
for (let key in this._latestEvent)
newEvent[key] = this._latestEvent[key];
this._previousEventTarget &&
this._previousEventTarget.callEventHandler(
this._previousEventTarget,
{
...newEvent,
type: "mouseout",
}
);
this._eventTarget &&
this._eventTarget.callEventHandler(this._eventTarget, {
...newEvent,
type: "mouseover",
});
}
this._eventTarget &&
this._eventTarget.callEventHandler(
this._eventTarget,
this._latestEvent
);
this._previousEventTarget = this._eventTarget;
}
this._latestEvent = null;
}
/**
* @param {*} x
* @param {*} y
* @ignore
*/
_setMousePosition(x, y) {
this._isMousePositionSet = true;
const matrixCache = this.container.parent.matrixCache;
this._mousePosition.x = (x - this.widthHalf) * matrixCache[0];
this._mousePosition.y = (y - this.heightHalf) * matrixCache[3];
}
/**
* @param {BaseItem} item
* @ignore
*/
_drawItem(item) {
item.update(this.$renderTime);
item.callback(item, this.$renderTime);
item.renderable && this["_draw" + item.TYPE](item);
}
/**
* @param {Container} container
* @ignore
*/
_drawContainer(container) {
this.$gl.uniform4fv(this.$locations.uWCl, container.colorCache);
this.$gl.uniform1f(this.$locations.uWA, container.premultipliedAlpha);
const children = container.children;
for (let i = 0, l = children.length; i < l; ++i)
this._drawItem(children[i]);
}
/**
* @param {BaseDrawable} item
* @ignore
*/
_drawImage(item) {
this.context.setBlendMode(item.blendMode, this._batchDraw);
if (
this._isMousePositionSet &&
item.interactive &&
item.isContainsPoint(this._mousePosition)
)
this._eventTarget = item;
const twId = this._batchItems * 12;
const matId = this._batchItems * 16;
arraySet(this._dataBuffer.data, item.colorCache, twId);
arraySet(this._dataBuffer.data, item.textureRepeatRandomCache, twId + 8);
this._dataBuffer.data[twId + 4] = item.props.alpha;
this._dataBuffer.data[twId + 5] = item.tintType;
this._dataBuffer.data[twId + 6] = this.context.useTexture(
item.texture,
this.$renderTime,
false,
this._batchDraw
);
this._dataBuffer.data[twId + 7] = item.distortionProps.distortTexture;
arraySet(this.$matrixBuffer.data, item.matrixCache, matId);
arraySet(this.$matrixBuffer.data, item.textureMatrixCache, matId + 6);
arraySet(this.$matrixBuffer.data, item.textureCropCache, matId + 12);
arraySet(
this._distortionBuffer.data,
item.distortionPropsCache,
this._batchItems * 8
);
++this._batchItems === this.$MAX_BATCH_ITEMS && this._batchDraw();
}
/**
* @ignore
*/
_batchDraw() {
if (this._batchItems > 0) {
this.$uploadBuffers();
this.$gl.uniform1iv(this.$locations.uTex, this.context.textureIds);
this.$drawInstanced(this._batchItems);
this._batchItems = 0;
}
}
/**
* @ignore
*/
_onMouseEventHandler(event) {
const canvas = this.context.canvas;
if (event.target === canvas) {
this._latestEvent = event;
const scaleX = canvas.width / canvas.offsetWidth;
const scaleY = canvas.height / canvas.offsetHeight;
const offsetX = scaleX * event.offsetX;
const offsetY = scaleY * event.offsetY;
this._setMousePosition(offsetX, offsetY);
}
}
/**
* @ignore
*/
$render() {
this._drawItem(this.container);
this._batchDraw();
}
/**
* @ignore
*/
$resize() {
super.$resize();
Matrix3Utilities.projection(this.container.parent.matrixCache, this);
++this.container.parent.propsUpdateId;
}
/**
* @ignore
*/
$uploadBuffers() {
this._dataBuffer.upload(this.$gl, this.$enableBuffers);
this._distortionBuffer.upload(this.$gl, this.$enableBuffers);
super.$uploadBuffers();
}
/**
* @ignore
*/
$createBuffers() {
super.$createBuffers();
this._dataBuffer.create(this.$gl, this.$locations);
this._distortionBuffer.create(this.$gl, this.$locations);
}
// prettier-ignore
/**
* @param {Stage2DRendererConfig} options
* @returns {string}
* @ignore
*/
$createVertexShader(options) {
const useRepeatTextures = options.useRepeatTextures;
return Utils.GLSL.VERSION +
"precision highp float;\n" +
"in vec2 " +
"aPos;" +
"in mat4x2 " +
"aDst;" +
"in mat3x4 " +
"aDt;" +
"in mat4 " +
"aMt;" +
"uniform float " +
"uFlpY," +
"uWA;" +
"uniform vec4 " +
"uWCl;" +
"out float " +
"vACl," +
"vTId," +
"vTTp;" +
"out vec2 " +
"vTUv;" +
"out vec4 " +
"vTCrop" +
(useRepeatTextures
? ",vRR;"
: ";") +
"out mat2x4 " +
"vCl;" +
"vec2 clcQd(vec2 p){" +
"return vec2(" +
"mix(" +
"mix(aDst[0].x,aDst[1].x,p.x)," +
"mix(aDst[3].x,aDst[2].x,p.x)," +
"p.y" +
")," +
"mix(" +
"mix(aDst[0].y,aDst[3].y,p.y)," +
"mix(aDst[1].y,aDst[2].y,p.y)," +
"p.x" +
")" +
");" +
"}" +
"void main(void){" +
"mat3 " +
"mt=mat3(aMt[0].xy,0,aMt[0].zw,0,aMt[1].xy,1)," +
"tMt=mat3(aMt[1].zw,0,aMt[2].xy,0,aMt[2].zw,1);" +
"vec3 " +
"tPos=vec3(" +
"clcQd(aPos)," +
"1" +
");" +
"gl_Position=vec4(mt*tPos,1);" +
"gl_Position.y*=uFlpY;" +
"float dt=aDt[1].w;" +
"vTUv=(tMt*((dt*vec3(aPos,1))+((1.-dt)*tPos))).xy;" +
"vTCrop=aMt[3];" +
"vCl=mat2x4(uWCl,aDt[0].rgb*aDt[0].a,1.-aDt[0].a);" +
"vACl=uWA*aDt[1].x;" +
"vTTp=aDt[1].y;" +
"vTId=aDt[1].z;" +
(useRepeatTextures
? "vRR=aDt[2].xyzw;" +
"vRR.w=vRR.x+vRR.y;"
: "") +
"}";
}
// prettier-ignore
/**
* @param {Stage2DRendererConfig} options
* @returns {string}
* @ignore
*/
$createFragmentShader(options) {
const maxTextureImageUnits = Utils.INFO.maxTextureImageUnits;
const useRepeatTextures = options.useRepeatTextures;
const useTint = options.useTint;
const createGetTextureFunction = (maxTextureImageUnits) => {
let func = "vec4 gtTexCl(float i,vec2 c){";
for (let i = 0; i < maxTextureImageUnits; ++i)
func += "if(i<" + (i + 1) + ".)return texture(uTex[" + i + "],c);";
func += "return vec4(1);" +
"}";
return func;
}
const getSimpleTexColor = (modCoordName) =>
"gtTexCl(vTId,vTCrop.xy+vTCrop.zw*" + modCoordName + ")";
return Utils.GLSL.VERSION +
"precision highp float;\n" +
Utils.GLSL.DEFINE.RADIAN_360 +
Utils.GLSL.DEFINE.ZO +
"in float " +
"vACl," +
"vTId," +
"vTTp;" +
"in vec2 " +
"vTUv;" +
"in vec4 " +
"vTCrop" +
(useRepeatTextures
? ",vRR;"
: ";") +
"in mat2x4 " +
"vCl;" +
"uniform sampler2D " +
"uTex[" + maxTextureImageUnits + "];" +
"out vec4 " +
"oCl;" +
createGetTextureFunction(maxTextureImageUnits) +
"float cosine(float a,float b,float v){" +
"v=abs(v);" +
"v=v<.5?2.*v*v:1.-pow(-2.*v+2.,2.)/2.;" +
"return a*(1.-v)+b*v;" +
"}" +
(useRepeatTextures
? Utils.GLSL.RANDOM +
"vec4 gtClBUv(vec2 st){" +
"vec2 " +
"uv=vTUv;" +
"float " +
"rnd=rand(floor(uv+st))," +
"rndDg=rnd*RADIAN_360*vRR.x;" +
"if(rndDg>0.){" +
"vec2 " +
"rt=vec2(sin(rndDg),cos(rndDg));" +
"uv=vec2(uv.x*rt.y-uv.y*rt.x,uv.x*rt.x+uv.y*rt.y);" +
"}" +
"return " + getSimpleTexColor("mod(uv,vec2(1))") + ";" +
"}" +
"float gtRClBUv(vec2 st,vec2 uv){" +
"float " +
"rnd=rand(floor(vTUv+st));" +
"return (1.-(2.*rnd-1.)*vRR.y)*" +
"cosine(0.,1.,1.-st.x-uv.x)*cosine(0.,1.,1.-st.y-uv.y);" +
"}"
: "") +
"void main(void){" +
"if(vTId>-1.){" +
"vec2 " +
"uv=mod(vTUv,vec2(1));" +
(useRepeatTextures
? "if(vRR.w>0.){" +
"float " +
"rca," +
"rcb," +
"rcc," +
"rcd;" +
"if(vRR.y+vRR.z>0.){" +
"rca=gtRClBUv(ZO.xx,uv);" +
"rcb=gtRClBUv(ZO.yx,uv);" +
"rcc=gtRClBUv(ZO.xy,uv);" +
"rcd=gtRClBUv(ZO.yy,uv);" +
"}" +
"oCl=vRR.z>0." +
"?clamp(" +
"gtClBUv(ZO.xx)*rca+" +
"gtClBUv(ZO.yx)*rcb+" +
"gtClBUv(ZO.xy)*rcc+" +
"gtClBUv(ZO.yy)*rcd" +
",0.,1.)" +
":gtClBUv(ZO);" +
"if(vRR.y>0.)" +
"oCl.a=clamp(" +
"oCl.a*(" +
"rca+" +
"rcb+" +
"rcc+" +
"rcd" +
"),0.,1.);" +
"}else oCl=" + getSimpleTexColor("uv") + ";"
: "oCl=" + getSimpleTexColor("uv") + ";") +
"}else oCl+=1.;" +
"oCl.a*=vACl;" +
"if(oCl.a<=0.)discard;" +
(useTint
? "if(vTTp>0.){" +
"vec3 cl=vCl[1].rgb+oCl.rgb*vCl[1].a;" +
"if(vTTp==1.||(vTTp==2.&&oCl.r==oCl.g&&oCl.r==oCl.b))" +
"oCl.rgb*=cl+oCl.rgb*vCl[1].a;" +
"else if(vTTp==3.)" +
"oCl.rgb=cl;" +
"else if(vTTp==4.)" +
"oCl.rgb+=cl;" +
"}"
: "") +
"oCl*=vCl[0];" +
"}";
}
}