rapid-render 0.1.1 → 0.1.2-1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/rapid.js CHANGED
@@ -1 +1,4814 @@
1
- const t=(t,e,r)=>{const i=t.createShader(r);if(!i)throw new Error("Unable to create webgl shader");t.shaderSource(i,e),t.compileShader(i);if(!t.getShaderParameter(i,t.COMPILE_STATUS)){const r=t.getShaderInfoLog(i);throw console.error("Shader compilation failed:",r),new Error("Unable to compile shader: "+r+e)}return i};const e=5126;class r{constructor(t){this.usedElemNum=0,this.maxElemNum=512,this.bytePerElem=t.BYTES_PER_ELEMENT,this.arrayType=t,this.arraybuffer=new ArrayBuffer(this.maxElemNum*this.bytePerElem),this.typedArray=new t(this.arraybuffer),t==Float32Array&&(this.uint32=new Uint32Array(this.arraybuffer))}clear(){this.usedElemNum=0}resize(t=0){if((t+=this.usedElemNum)>this.maxElemNum){for(;t>this.maxElemNum;)this.maxElemNum<<=1;this.setMaxSize(this.maxElemNum)}}setMaxSize(t=this.maxElemNum){const e=this.typedArray;this.maxElemNum=t,this.arraybuffer=new ArrayBuffer(t*this.bytePerElem),this.typedArray=new this.arrayType(this.arraybuffer),this.uint32&&(this.uint32=new Uint32Array(this.arraybuffer)),this.typedArray.set(e)}push(t){this.typedArray[this.usedElemNum++]=t}pushUint(t){this.uint32[this.usedElemNum++]=t}pop(t){this.usedElemNum-=t}getArray(t=0,e){return null==e?this.typedArray:this.typedArray.subarray(t,e)}get length(){return this.typedArray.length}}class i extends r{constructor(t,e,r=t.ARRAY_BUFFER){super(e),this.dirty=!0,this.webglBufferSize=0,this.gl=t,this.buffer=t.createBuffer(),this.type=r}push(t){super.push(t),this.dirty=!0}bindBuffer(){this.gl.bindBuffer(this.type,this.buffer)}bufferData(){if(this.dirty){const t=this.gl;this.maxElemNum>this.webglBufferSize?(t.bufferData(this.type,this.getArray(),t.STATIC_DRAW),this.webglBufferSize=this.maxElemNum):t.bufferSubData(this.type,0,this.getArray(0,this.usedElemNum)),this.dirty=!1}}}class s extends r{constructor(){super(Float32Array)}pushMat(){const t=this.usedElemNum-6,e=this.typedArray;this.resize(6),this.push(e[t+0]),this.push(e[t+1]),this.push(e[t+2]),this.push(e[t+3]),this.push(e[t+4]),this.push(e[t+5])}popMat(){this.pop(6)}pushIdentity(){this.resize(6),this.push(1),this.push(0),this.push(0),this.push(1),this.push(0),this.push(0)}translate(t,e){if(t instanceof a)return this.translate(t.x,t.y);const r=this.usedElemNum-6,i=this.typedArray;i[r+4]=i[r+0]*t+i[r+2]*e+i[r+4],i[r+5]=i[r+1]*t+i[r+3]*e+i[r+5]}rotate(t){const e=this.usedElemNum-6,r=this.typedArray,i=Math.cos(t),s=Math.sin(t),n=r[e+0],h=r[e+1],a=r[e+2],o=r[e+3];r[e+0]=n*i-h*s,r[e+1]=n*s+h*i,r[e+2]=a*i-o*s,r[e+3]=a*s+o*i}scale(t,e){if(t instanceof a)return this.scale(t.x,t.y);e||(e=t);const r=this.usedElemNum-6,i=this.typedArray;i[r+0]=i[r+0]*t,i[r+1]=i[r+1]*t,i[r+2]=i[r+2]*e,i[r+3]=i[r+3]*e}apply(t,e){if(t instanceof a)return new a(...this.apply(t.x,t.y));const r=this.usedElemNum-6,i=this.typedArray;return[i[r+0]*t+i[r+2]*e+i[r+4],i[r+1]*t+i[r+3]*e+i[r+5]]}getInverse(){const t=this.usedElemNum-6,e=this.typedArray,r=e[t+0],i=e[t+1],s=e[t+2],n=e[t+3],h=e[t+4],a=e[t+5],o=r*n-i*s;return new Float32Array([n/o,-i/o,-s/o,r/o,(s*a-n*h)/o,(i*h-r*a)/o])}getTransform(){const t=this.usedElemNum-6,e=this.typedArray;return new Float32Array([e[t+0],e[t+1],e[t+2],e[t+3],e[t+4],e[t+5]])}setTransform(t){const e=this.usedElemNum-6,r=this.typedArray;r[e+0]=t[0],r[e+1]=t[1],r[e+2]=t[2],r[e+3]=t[3],r[e+4]=t[4],r[e+5]=t[5]}}class n extends i{constructor(t,e,r,i){super(t,Uint16Array,t.ELEMENT_ARRAY_BUFFER),this.setMaxSize(e*i);for(let t=0;t<i;t++)this.addObject(t*r);this.bindBuffer(),this.bufferData()}addObject(t){}}class h{constructor(t,e,r,i){this._r=t,this._g=e,this._b=r,this._a=i,this.updateUint()}get r(){return this._r}set r(t){this._r=t,this.updateUint()}get g(){return this._g}set g(t){this._g=t,this.updateUint()}get b(){return this._b}set b(t){this._b=t,this.updateUint()}get a(){return this._a}set a(t){this._a=t,this.updateUint()}updateUint(){this.uint32=(this._a<<24|this._b<<16|this._g<<8|this._r)>>>0}setRGBA(t,e,r,i){this.r=t,this.g=e,this.b=r,this.a=i,this.updateUint()}copy(t){this.setRGBA(t.r,t.g,t.b,t.a)}equals(t){return t.r===this.r&&t.g===this.g&&t.b===this.b&&t.a===this.a}static fromHex(t){t.startsWith("#")&&(t=t.slice(1));const e=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),i=parseInt(t.slice(4,6),16);let s=255;return t.length>=8&&(s=parseInt(t.slice(6,8),16)),new h(e,r,i,s)}add(t){return new h(Math.min(this.r+t.r,255),Math.min(this.g+t.g,255),Math.min(this.b+t.b,255),Math.min(this.a+t.a,255))}subtract(t){return new h(Math.max(this.r-t.r,0),Math.max(this.g-t.g,0),Math.max(this.b-t.b,0),Math.max(this.a-t.a,0))}}class a{constructor(t,e){this.x=void 0!==t?t:0,this.y=void 0!==e?e:0}scalarMult(t){return this.x*=t,this.y*=t,this}perpendicular(){const t=this.x;return this.x=-this.y,this.y=t,this}invert(){return this.x=-this.x,this.y=-this.y,this}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}normalize(){const t=this.length();return this.x/=t,this.y/=t,this}angle(){return this.y/this.x}static Angle(t,e){return Math.atan2(e.y-t.y,e.x-t.x)}static Add(t,e){return new a(t.x+e.x,t.y+e.y)}static Sub(t,e){return new a(t.x-e.x,t.y-e.y)}static Middle(t,e){return a.Add(t,e).scalarMult(.5)}static FormArray(t){return t.map((t=>new a(t[0],t[1])))}}class o{constructor(t){this.cache=new Map,this.render=t}async textureFromUrl(t,e=!1){let r=this.cache.get(t);if(!r){const i=await this.loadImage(t);r=u.fromImageSource(this.render,i,e),this.cache.set(t,r)}return new l(r)}async loadImage(t){return new Promise((e=>{const r=new Image;r.onload=()=>{e(r)},r.src=t}))}createText(t){return new c(this.render,t)}}class u{constructor(t,e,r){this.texture=t,this.width=e,this.height=r}static fromImageSource(t,e,r=!1){return new u(function(t,e,r){const i=t.createTexture();if(t.bindTexture(t.TEXTURE_2D,i),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,r?t.LINEAR:t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,r?t.LINEAR:t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,e),!i)throw new Error("unable to create texture");return i}(t.gl,e,r),e.width,e.height)}}class l{constructor(t){this.scale=1,this.setBaseTextur(t)}setBaseTextur(t){t&&(this.base=t,this.setClipRegion(0,0,t.width,t.height))}setClipRegion(t,e,r,i){this.base&&(this.clipX=t/this.base.width,this.clipY=e/this.base.width,this.clipW=this.clipX+r/this.base.width,this.clipH=this.clipY+i/this.base.height,this.width=r*this.scale,this.height=i*this.scale)}static fromImageSource(t,e,r=!1){return new l(u.fromImageSource(t,e,r))}static fromUrl(t,e){return t.textures.textureFromUrl(e)}}const d=2;class c extends l{constructor(t,e){super(),this.scale=.5,this.rapid=t,this.options=e,this.text=e.text||"",this.updateTextImage()}updateTextImage(){const t=this.createTextCanvas();this.setBaseTextur(u.fromImageSource(this.rapid,t,!0))}createTextCanvas(){const t=document.createElement("canvas"),e=t.getContext("2d");if(!e)throw new Error("Failed to get canvas context");e.font=`${this.options.fontSize||16}px ${this.options.fontFamily||"Arial"}`,e.fillStyle=this.options.color||"#000",e.textAlign=this.options.textAlign||"left",e.textBaseline=this.options.textBaseline||"top";const r=this.text.split("\n");let i=0,s=0;for(const t of r){const r=e.measureText(t);i=Math.max(i,r.width),s+=this.options.fontSize||16}t.width=2*i,t.height=2*s,e.scale(2,2),e.font=`${this.options.fontSize||16}px ${this.options.fontFamily||"Arial"}`,e.fillStyle=this.options.color||"#000",e.textAlign=this.options.textAlign||"left",e.textBaseline=this.options.textBaseline||"top";let n=0;for(const t of r)e.fillText(t,0,n),n+=this.options.fontSize||16;return t}setText(t){this.text!=t&&(this.text=t,this.updateTextImage())}}class p{constructor(e,r,i){this.attributeLoc={},this.uniformLoc={};const s=function(t,e){if(t.includes("%TEXTURE_NUM%")&&(t=t.replace("%TEXTURE_NUM%",e.toString())),t.includes("%GET_COLOR%")){let r="";for(let t=0;t<e;t++)r+=0==t?`if(vTextureId == ${t}.0)`:t==e-1?"else":`else if(vTextureId == ${t}.0)`,r+=`{color = texture2D(uTextures[${t}], vRegion);}`;t=t.replace("%GET_COLOR%",r)}return t}(i,e.maxTextureUnits);this.program=((e,r,i)=>{var s=e.createProgram(),n=t(e,r,35633),h=t(e,i,35632);if(!s)throw new Error("Unable to create program shader");return e.attachShader(s,n),e.attachShader(s,h),e.linkProgram(s),s})(e.gl,r,s),this.gl=e.gl,this.parseShader(r),this.parseShader(s)}setUniforms(t,e){var r;const i=this.gl;for(const s in t){const n=t[s],h=this.getUniform(s);if(n instanceof l&&(null===(r=n.base)||void 0===r?void 0:r.texture))i.activeTexture(i.TEXTURE0+e),i.bindTexture(i.TEXTURE_2D,n.base.texture),i.uniform1i(h,e),e+=1;else if("number"==typeof n)i.uniform1f(h,n);else if(Array.isArray(n))switch(n.length){case 1:Number.isInteger(n[0])?i.uniform1i(h,n[0]):i.uniform1f(h,n[0]);break;case 2:Number.isInteger(n[0])?i.uniform2iv(h,n):i.uniform2fv(h,n);break;case 3:Number.isInteger(n[0])?i.uniform3iv(h,n):i.uniform3fv(h,n);break;case 4:Number.isInteger(n[0])?i.uniform4iv(h,n):i.uniform4fv(h,n);break;case 9:i.uniformMatrix3fv(h,!1,n);break;case 16:i.uniformMatrix4fv(h,!1,n);break;default:console.error(`Unsupported uniform array length for ${s}:`,n.length)}else"boolean"==typeof n?i.uniform1i(h,n?1:0):console.error(`Unsupported uniform type for ${s}:`,typeof n)}}getUniform(t){return this.uniformLoc[t]}use(){this.gl.useProgram(this.program)}parseShader(t){const e=this.gl,r=t.match(/attribute\s+\w+\s+(\w+)/g);if(r)for(const t of r){const r=t.split(" ")[2],i=e.getAttribLocation(this.program,r);-1!=i&&(this.attributeLoc[r]=i)}const i=t.match(/uniform\s+\w+\s+(\w+)/g);if(i)for(const t of i){const r=t.split(" ")[2];this.uniformLoc[r]=e.getUniformLocation(this.program,r)}}setAttribute(t){const e=this.attributeLoc[t.name];if(void 0!==e){const r=this.gl;r.vertexAttribPointer(e,t.size,t.type,t.normalized||!1,t.stride,t.offset||0),r.enableVertexAttribArray(e)}}}class f{constructor(t,e){this.usedTextures=[],this.needBind=new Set,this.attribute=e,this.rapid=t,this.gl=t.gl,this.webglArrayBuffer=new i(t.gl,Float32Array,t.gl.ARRAY_BUFFER),this.TEXTURE_UNITS_ARRAY=Array.from({length:t.maxTextureUnits},((t,e)=>e))}addVertex(t,e,...r){const[i,s]=this.rapid.transformPoint(t,e);this.webglArrayBuffer.push(i),this.webglArrayBuffer.push(s)}useTexture(t){let e=this.usedTextures.indexOf(t);return-1===e&&(this.usedTextures.length>=this.rapid.maxTextureUnits&&this.render(),this.usedTextures.push(t),e=this.usedTextures.length-1,this.needBind.add(e)),e}enterRegion(t){this.currentShader=null!=t?t:this.defaultShader,this.currentShader.use(),this.initializeForNextRender(),this.webglArrayBuffer.bindBuffer();for(const t of this.attribute)this.currentShader.setAttribute(t);this.gl.uniformMatrix4fv(this.currentShader.uniformLoc.uProjectionMatrix,!1,this.rapid.projection)}exitRegion(){}initDefaultShader(t,e){this.webglArrayBuffer.bindBuffer(),this.defaultShader=new p(this.rapid,t,e)}render(){this.executeRender(),this.initializeForNextRender()}executeRender(){this.webglArrayBuffer.bufferData();const t=this.gl;for(const e of this.needBind)t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,this.usedTextures[e]);this.needBind.clear()}initializeForNextRender(){this.webglArrayBuffer.clear(),this.usedTextures=[]}}class g extends f{constructor(t){super(t,m),this.vertex=0,this.drawType=t.gl.TRIANGLE_FAN,this.initDefaultShader("precision mediump float;\r\n\r\nattribute vec2 aPosition;\r\nattribute vec4 aColor;\r\nvarying vec4 vColor;\r\nuniform mat4 uProjectionMatrix;\r\nuniform vec4 uColor;\r\n\r\nvoid main(void) {\r\n gl_Position = uProjectionMatrix * vec4(aPosition, 0.0, 1.0);\r\n vColor = aColor;\r\n}\r\n","precision mediump float;\r\n\r\nvarying vec4 vColor;\r\nvoid main(void) {\r\n gl_FragColor = vColor;//vColor;\r\n}\r\n")}startRender(){this.vertex=0,this.webglArrayBuffer.clear()}addVertex(t,e,r){this.webglArrayBuffer.resize(3),super.addVertex(t,e),this.webglArrayBuffer.pushUint(r),this.vertex+=1}drawCircle(t,e,r,i){const s=2*Math.PI/30;this.startRender(),this.addVertex(t,e,i);for(let n=0;n<=30;n++){const h=n*s,a=t+r*Math.cos(h),o=e+r*Math.sin(h);this.addVertex(a,o,i)}this.executeRender()}executeRender(){super.executeRender();this.gl.drawArrays(this.drawType,0,this.vertex),this.drawType=this.rapid.gl.TRIANGLE_FAN,this.vertex=0}}const m=[{name:"aPosition",size:2,type:e,stride:12},{name:"aColor",size:4,type:5121,stride:12,offset:2*Float32Array.BYTES_PER_ELEMENT,normalized:!0}];class x extends n{constructor(t,e){super(t,6,4,e)}addObject(t){super.addObject(),this.push(t),this.push(t+3),this.push(t+2),this.push(t),this.push(t+1),this.push(t+2)}}class y extends f{constructor(t){const e=t.gl;super(t,E),this.batchSprite=0,this.initDefaultShader("precision mediump float;\r\n\r\nattribute vec2 aPosition;\r\nattribute vec2 aRegion;\r\nattribute float aTextureId;\r\nattribute vec4 aColor;\r\n\r\nuniform mat4 uProjectionMatrix;\r\n\r\nvarying vec2 vRegion;\r\nvarying float vTextureId;\r\nvarying vec4 vColor;\r\n\r\nvoid main(void) {\r\n vRegion = aRegion;\r\n vTextureId = aTextureId;\r\n vColor = aColor;\r\n gl_Position = uProjectionMatrix * vec4(aPosition, 0.0, 1.0);\r\n}","precision mediump float;\r\nuniform sampler2D uTextures[%TEXTURE_NUM%];\r\n\r\nvarying vec2 vRegion;\r\nvarying float vTextureId;\r\nvarying vec4 vColor;\r\n\r\nvoid main(void) {\r\n vec4 color;\r\n %GET_COLOR%\r\n\r\n gl_FragColor = color*vColor;\r\n}"),this.MAX_BATCH=Math.floor(16384),this.indexBuffer=new x(e,this.MAX_BATCH)}addVertex(t,e,r,i,s,n){super.addVertex(t,e),this.webglArrayBuffer.push(r),this.webglArrayBuffer.push(i),this.webglArrayBuffer.push(s),this.webglArrayBuffer.pushUint(n)}renderSprite(t,e,r,i,s,n,h,a,o,u,l){var d;l&&(null===(d=this.currentShader)||void 0===d||d.setUniforms(l,1)),this.batchSprite>=this.MAX_BATCH&&this.render(),this.batchSprite++,this.webglArrayBuffer.resize(24);const c=this.useTexture(t),p=a+e,f=o+r,g=i+n,m=s+h;this.addVertex(a,o,i,s,c,u),this.addVertex(p,o,g,s,c,u),this.addVertex(p,f,g,m,c,u),this.addVertex(a,f,i,m,c,u)}executeRender(){super.executeRender();const t=this.gl;t.drawElements(t.TRIANGLES,6*this.batchSprite,t.UNSIGNED_SHORT,0)}enterRegion(t){super.enterRegion(t),this.indexBuffer.bindBuffer(),this.gl.uniform1iv(this.currentShader.uniformLoc.uTextures,this.TEXTURE_UNITS_ARRAY)}initializeForNextRender(){super.initializeForNextRender(),this.batchSprite=0}}const E=[{name:"aPosition",size:2,type:e,stride:24},{name:"aRegion",size:2,type:e,stride:24,offset:2*Float32Array.BYTES_PER_ELEMENT},{name:"aTextureId",size:1,type:e,stride:24,offset:4*Float32Array.BYTES_PER_ELEMENT},{name:"aColor",size:4,type:5121,stride:24,offset:5*Float32Array.BYTES_PER_ELEMENT,normalized:!0}];var b,A;!function(t){t[t.BEVEL=0]="BEVEL",t[t.ROUND=1]="ROUND",t[t.MITER=2]="MITER"}(b||(b={})),function(t){t[t.BUTT=0]="BUTT",t[t.ROUND=1]="ROUND",t[t.SQUARE=2]="SQUARE"}(A||(A={}));const T=1e-4,R=(t,e,r,i)=>{i.push(t),i.push(a.Add(t,r)),i.push(a.Add(e,r)),i.push(e),i.push(a.Add(e,r)),i.push(t)},w=(t,e,r,i,s)=>{const n=a.Sub(t,e).length();let h=Math.atan2(r.y-t.y,r.x-t.x),o=Math.atan2(e.y-t.y,e.x-t.x);const u=h;o>h?o-h>=Math.PI-T&&(o-=2*Math.PI):h-o>=Math.PI-T&&(h-=2*Math.PI);let l=o-h;if(Math.abs(l)>=Math.PI-T&&Math.abs(l)<=Math.PI+T){const e=a.Sub(t,i);0===e.x?e.y>0&&(l=-l):e.x>=-1e-4&&(l=-l)}const d=(Math.abs(l*n)/7|0)+1,c=l/d;for(let e=0;e<d;e++)s.push(new a(t.x,t.y)),s.push(new a(t.x+n*Math.cos(u+c*e),t.y+n*Math.sin(u+c*e))),s.push(new a(t.x+n*Math.cos(u+c*(1+e)),t.y+n*Math.sin(u+c*(1+e))))},S=(t,e,r,i,s,n,h)=>{const o=a.Sub(e,t).perpendicular(),u=a.Sub(r,e).perpendicular();((t,e,r)=>(e.x-t.x)*(r.y-t.y)-(r.x-t.x)*(e.y-t.y))(t,e,r)>0&&(o.invert(),u.invert()),o.normalize().scalarMult(s),u.normalize().scalarMult(s);const l=((t,e,r,i)=>{const s=e.y-t.y,n=t.x-e.x,h=i.y-r.y,o=r.x-i.x,u=s*o-h*n;if(u>-1e-4&&u<T)return null;{const e=s*t.x+n*t.y,i=h*r.x+o*r.y;return new a((o*e-n*i)/u,(s*i-h*e)/u)}})(a.Add(o,t),a.Add(o,e),a.Add(u,r),a.Add(u,e));let d=null,c=Number.MAX_VALUE;l&&(d=a.Sub(l,e),c=d.length());const p=c/s|0,f=a.Sub(t,e).length(),g=a.Sub(e,r).length();if(c>f||c>g)i.push(a.Add(t,o)),i.push(a.Sub(t,o)),i.push(a.Add(e,o)),i.push(a.Sub(t,o)),i.push(a.Add(e,o)),i.push(a.Sub(e,o)),n===b.ROUND?w(e,a.Add(e,o),a.Add(e,u),r,i):n===b.BEVEL||n===b.MITER&&p>=h?(i.push(e),i.push(a.Add(e,o)),i.push(a.Add(e,u))):n===b.MITER&&p<h&&l&&(i.push(a.Add(e,o)),i.push(e),i.push(l),i.push(a.Add(e,u)),i.push(e),i.push(l)),i.push(a.Add(r,u)),i.push(a.Sub(e,u)),i.push(a.Add(e,u)),i.push(a.Add(r,u)),i.push(a.Sub(e,u)),i.push(a.Sub(r,u));else{if(i.push(a.Add(t,o)),i.push(a.Sub(t,o)),i.push(a.Sub(e,d)),i.push(a.Add(t,o)),i.push(a.Sub(e,d)),i.push(a.Add(e,o)),n===b.ROUND){const t=a.Add(e,o),r=a.Add(e,u),s=a.Sub(e,d),n=e;i.push(t),i.push(n),i.push(s),w(n,t,r,s,i),i.push(n),i.push(r),i.push(s)}else(n===b.BEVEL||n===b.MITER&&p>=h)&&(i.push(a.Add(e,o)),i.push(a.Add(e,u)),i.push(a.Sub(e,d))),n===b.MITER&&p<h&&(i.push(l),i.push(a.Add(e,o)),i.push(a.Add(e,u)));i.push(a.Add(r,u)),i.push(a.Sub(e,d)),i.push(a.Add(e,u)),i.push(a.Add(r,u)),i.push(a.Sub(e,d)),i.push(a.Sub(r,u))}};class v{constructor(t){this.projectionDirty=!0,this.matrixStack=new s,this.textures=new o(this),this.devicePixelRatio=window.devicePixelRatio||1,this.defaultColor=new h(255,255,255,255),this.defaultColorBlack=new h(0,0,0,255),this.regions=new Map;const e=(t=>{const e=t.getContext("webgl2")||t.getContext("webgl");if(!e)throw new Error("TODO");return e})(t.canvas);this.gl=e,this.canvas=t.canvas,this.maxTextureUnits=e.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS),this.width=t.width||this.canvas.width,this.height=t.width||this.canvas.height,this.backgroundColor=t.backgroundColor||new h(255,255,255,255),this.registerBuildInRegion(),this.initWebgl(e)}initWebgl(t){this.resize(this.width,this.height),t.enable(t.BLEND),t.disable(t.DEPTH_TEST),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA)}registerBuildInRegion(){this.registerRegion("sprite",y),this.registerRegion("graphic",g)}registerRegion(t,e){this.regions.set(t,new e(this))}setRegion(t,e){if(t!=this.currentRegionName||e&&e!==this.currentRegion.currentShader){const r=this.regions.get(t);this.currentRegion&&(this.currentRegion.render(),this.currentRegion.exitRegion()),this.currentRegion=r,this.currentRegionName=t,r.enterRegion(e)}}save(){this.matrixStack.pushMat()}restore(){this.matrixStack.popMat()}startRender(t=!0){t&&this.matrixStack.clear(),this.matrixStack.pushIdentity(),this.currentRegion=void 0,this.currentRegionName=void 0}endRender(){var t;null===(t=this.currentRegion)||void 0===t||t.render(),this.projectionDirty=!1}renderSprite(t,e=0,r=0,i){if(t.base){if(i instanceof h)return this.renderSprite(t,e,r,{color:i});this.setRegion("sprite",null==i?void 0:i.shader),this.currentRegion.renderSprite(t.base.texture,t.width,t.height,t.clipX,t.clipY,t.clipW,t.clipH,e,r,((null==i?void 0:i.color)||this.defaultColor).uint32,null==i?void 0:i.uniforms)}}renderLine(t=0,e=0,r){const i=((t,e)=>{if(t.length<2)return[];let r=e.cap||A.BUTT,i=e.join||b.BEVEL,s=(e.width||1)/2,n=e.miterLimit||10,h=[],o=[];if(2===t.length)i=b.BEVEL,S(t[0],a.Middle(t[0],t[1]),t[1],h,s,i,n);else{for(let e=0;e<t.length-1;e++)0===e?o.push(t[0]):e===t.length-2?o.push(t[t.length-1]):o.push(a.Middle(t[e],t[e+1]));for(let e=1;e<o.length;e++)S(o[e-1],t[e],o[e],h,s,i,n)}if(r===A.ROUND){let e=h[0],r=h[1],i=t[1],s=h[h.length-1],n=h[h.length-3],a=t[t.length-2];w(t[0],e,r,i,h),w(t[t.length-1],s,n,a,h)}else if(r===A.SQUARE){let e=h[h.length-1],r=h[h.length-3];R(h[0],h[1],a.Sub(t[0],t[1]).normalize().scalarMult(a.Sub(t[0],h[0]).length()),h),R(e,r,a.Sub(t[t.length-1],t[t.length-2]).normalize().scalarMult(a.Sub(r,t[t.length-1]).length()),h)}return h})(r.points,r);this.renderGraphic(t,e,{color:r.color,drawType:this.gl.TRIANGLES,points:i})}renderGraphic(t=0,e=0,r){if(r instanceof Array)return this.renderGraphic(t,e,{points:r});this.startGraphicDraw(),r.drawType&&(this.currentRegion.drawType=r.drawType),r.points.forEach((i=>{this.addGraphicVertex(i.x+t,i.y+e,r.color||this.defaultColorBlack)})),this.endGraphicDraw()}startGraphicDraw(t){this.setRegion("graphic",t),this.currentRegion.startRender()}addGraphicVertex(t,e,r){if(t instanceof a)return this.addGraphicVertex(t.x,t.y,e);this.currentRegion.addVertex(t,e,r.uint32)}endGraphicDraw(){this.currentRegion.render()}resize(t,e){this.width=t,this.height=e;const r=t*this.devicePixelRatio,i=e*this.devicePixelRatio;this.canvas.width=r,this.canvas.height=i,this.gl.viewport(0,0,r,i),this.projection=this.createOrthMatrix(0,t,e,0),this.projectionDirty=!0}clear(){const t=this.gl,e=this.backgroundColor;t.clearColor(e.r,e.g,e.b,e.a),t.clear(t.COLOR_BUFFER_BIT)}createOrthMatrix(t,e,r,i){return new Float32Array([2/(e-t),0,0,0,0,2/(i-r),0,0,0,0,-1,0,-(e+t)/(e-t),-(i+r)/(i-r),0,1])}transformPoint(t,e){return this.matrixStack.apply(t,e)}}export{u as BaseTexture,A as CapTyps,h as Color,r as DynamicArrayBuffer,p as GLShader,b as JoinTyps,s as MatrixStack,v as Rapid,d as SCALEFACTOR,c as Text,l as Texture,o as TextureCache,a as Vec2,i as WebglBufferArray,n as WebglElementBufferArray,m as graphicAttributes,E as spriteAttributes};
1
+ var ScaleRadio = /* @__PURE__ */ ((ScaleRadio2) => {
2
+ ScaleRadio2["KEEP"] = "keep";
3
+ ScaleRadio2["KEEP_H"] = "keep_h";
4
+ ScaleRadio2["KEEP_W"] = "keep_w";
5
+ ScaleRadio2["IGNORE"] = "ignore";
6
+ ScaleRadio2["EXPAND"] = "expand";
7
+ return ScaleRadio2;
8
+ })(ScaleRadio || {});
9
+ var LineTextureMode = /* @__PURE__ */ ((LineTextureMode2) => {
10
+ LineTextureMode2["STRETCH"] = "stretch";
11
+ LineTextureMode2["REPEAT"] = "repeat";
12
+ return LineTextureMode2;
13
+ })(LineTextureMode || {});
14
+ var TextureWrapMode = /* @__PURE__ */ ((TextureWrapMode2) => {
15
+ TextureWrapMode2["REPEAT"] = "repeat";
16
+ TextureWrapMode2["CLAMP"] = "clamp";
17
+ TextureWrapMode2["MIRROR"] = "mirror";
18
+ return TextureWrapMode2;
19
+ })(TextureWrapMode || {});
20
+ var MaskType = /* @__PURE__ */ ((MaskType2) => {
21
+ MaskType2["Include"] = "normal";
22
+ MaskType2["Exclude"] = "inverse";
23
+ return MaskType2;
24
+ })(MaskType || {});
25
+ var TilemapShape = /* @__PURE__ */ ((TilemapShape2) => {
26
+ TilemapShape2["SQUARE"] = "square";
27
+ TilemapShape2["ISOMETRIC"] = "isometric";
28
+ return TilemapShape2;
29
+ })(TilemapShape || {});
30
+ var ShaderType = /* @__PURE__ */ ((ShaderType2) => {
31
+ ShaderType2["SPRITE"] = "sprite";
32
+ ShaderType2["GRAPHIC"] = "graphic";
33
+ return ShaderType2;
34
+ })(ShaderType || {});
35
+ var BlendMode = /* @__PURE__ */ ((BlendMode2) => {
36
+ BlendMode2["Additive"] = "additive";
37
+ BlendMode2["Subtractive"] = "subtractive";
38
+ BlendMode2["Mix"] = "mix";
39
+ return BlendMode2;
40
+ })(BlendMode || {});
41
+ var ParticleShape = /* @__PURE__ */ ((ParticleShape2) => {
42
+ ParticleShape2["POINT"] = "point";
43
+ ParticleShape2["CIRCLE"] = "circle";
44
+ ParticleShape2["RECT"] = "rect";
45
+ return ParticleShape2;
46
+ })(ParticleShape || {});
47
+ const MATRIX_SIZE = 6;
48
+ var ArrayType = /* @__PURE__ */ ((ArrayType2) => {
49
+ ArrayType2[ArrayType2["Float32"] = 0] = "Float32";
50
+ ArrayType2[ArrayType2["Uint32"] = 1] = "Uint32";
51
+ ArrayType2[ArrayType2["Uint16"] = 2] = "Uint16";
52
+ return ArrayType2;
53
+ })(ArrayType || {});
54
+ class DynamicArrayBuffer {
55
+ constructor(arrayType) {
56
+ this.usedElemNum = 0;
57
+ this.maxElemNum = 512;
58
+ this.bytePerElem = this.getArrayType(arrayType).BYTES_PER_ELEMENT;
59
+ this.arrayType = arrayType;
60
+ this.arraybuffer = new ArrayBuffer(this.maxElemNum * this.bytePerElem);
61
+ this.updateTypedArray();
62
+ }
63
+ getArrayType(arrayType) {
64
+ switch (arrayType) {
65
+ case 0:
66
+ return Float32Array;
67
+ case 1:
68
+ return Uint32Array;
69
+ case 2:
70
+ return Uint16Array;
71
+ }
72
+ }
73
+ updateTypedArray() {
74
+ this.uint32 = new Uint32Array(this.arraybuffer);
75
+ this.float32 = new Float32Array(this.arraybuffer);
76
+ this.uint16 = new Uint16Array(this.arraybuffer);
77
+ switch (this.arrayType) {
78
+ case 0:
79
+ this.typedArray = this.float32;
80
+ break;
81
+ case 1:
82
+ this.typedArray = this.uint32;
83
+ break;
84
+ case 2:
85
+ this.typedArray = this.uint16;
86
+ break;
87
+ }
88
+ }
89
+ clear() {
90
+ this.usedElemNum = 0;
91
+ }
92
+ /**
93
+ * resize the array
94
+ * @param size
95
+ * @returns
96
+ */
97
+ resize(size = 0) {
98
+ size += this.usedElemNum;
99
+ if (size > this.maxElemNum) {
100
+ while (size > this.maxElemNum) {
101
+ this.maxElemNum <<= 1;
102
+ }
103
+ this.setMaxSize(this.maxElemNum);
104
+ }
105
+ }
106
+ setMaxSize(size = this.maxElemNum) {
107
+ const data = this.typedArray;
108
+ this.maxElemNum = size;
109
+ this.arraybuffer = new ArrayBuffer(size * this.bytePerElem);
110
+ this.updateTypedArray();
111
+ this.typedArray.set(data);
112
+ }
113
+ pushUint32(value) {
114
+ this.uint32[this.usedElemNum++] = value;
115
+ }
116
+ pushFloat32(value) {
117
+ this.float32[this.usedElemNum++] = value;
118
+ }
119
+ pushUint16(value) {
120
+ this.uint16[this.usedElemNum++] = value;
121
+ }
122
+ /**
123
+ * pop a element
124
+ * @param num
125
+ */
126
+ pop(num) {
127
+ this.usedElemNum -= num;
128
+ }
129
+ /**
130
+ * get the array
131
+ * @param begin
132
+ * @param end
133
+ * @returns
134
+ */
135
+ getArray(begin = 0, end) {
136
+ if (end == void 0) {
137
+ return this.typedArray;
138
+ }
139
+ return this.typedArray.subarray(begin, end);
140
+ }
141
+ /**
142
+ * length of the array
143
+ */
144
+ get length() {
145
+ return this.usedElemNum;
146
+ }
147
+ }
148
+ class WebglBufferArray extends DynamicArrayBuffer {
149
+ constructor(gl, arrayType, type = gl.ARRAY_BUFFER, usage = gl.STATIC_DRAW) {
150
+ super(arrayType);
151
+ this.dirty = true;
152
+ this.webglBufferSize = 0;
153
+ this.gl = gl;
154
+ this.buffer = gl.createBuffer();
155
+ this.type = type;
156
+ this.usage = usage;
157
+ }
158
+ pushFloat32(value) {
159
+ super.pushFloat32(value);
160
+ this.dirty = true;
161
+ }
162
+ pushUint32(value) {
163
+ super.pushUint32(value);
164
+ this.dirty = true;
165
+ }
166
+ pushUint16(value) {
167
+ super.pushUint16(value);
168
+ this.dirty = true;
169
+ }
170
+ /**
171
+ * bind buffer to gpu
172
+ */
173
+ bindBuffer() {
174
+ this.gl.bindBuffer(this.type, this.buffer);
175
+ }
176
+ /**
177
+ * array data to gpu
178
+ */
179
+ bufferData() {
180
+ if (this.dirty) {
181
+ const gl = this.gl;
182
+ if (this.maxElemNum > this.webglBufferSize) {
183
+ gl.bufferData(this.type, this.getArray(), this.usage);
184
+ this.webglBufferSize = this.maxElemNum;
185
+ } else {
186
+ gl.bufferSubData(this.type, 0, this.getArray(0, this.usedElemNum));
187
+ }
188
+ this.dirty = false;
189
+ }
190
+ }
191
+ }
192
+ class MatrixStack extends DynamicArrayBuffer {
193
+ constructor() {
194
+ super(
195
+ 0
196
+ /* Float32 */
197
+ );
198
+ }
199
+ /**
200
+ * push a matrix to the stack
201
+ */
202
+ pushMat() {
203
+ const offset = this.usedElemNum - MATRIX_SIZE;
204
+ const arr = this.typedArray;
205
+ this.resize(MATRIX_SIZE);
206
+ this.pushFloat32(arr[offset + 0]);
207
+ this.pushFloat32(arr[offset + 1]);
208
+ this.pushFloat32(arr[offset + 2]);
209
+ this.pushFloat32(arr[offset + 3]);
210
+ this.pushFloat32(arr[offset + 4]);
211
+ this.pushFloat32(arr[offset + 5]);
212
+ }
213
+ /**
214
+ * pop a matrix from the stack
215
+ */
216
+ popMat() {
217
+ this.pop(MATRIX_SIZE);
218
+ }
219
+ /**
220
+ * push a matrix and indentiy it
221
+ */
222
+ pushIdentity() {
223
+ this.resize(MATRIX_SIZE);
224
+ this.pushFloat32(1);
225
+ this.pushFloat32(0);
226
+ this.pushFloat32(0);
227
+ this.pushFloat32(1);
228
+ this.pushFloat32(0);
229
+ this.pushFloat32(0);
230
+ }
231
+ /**
232
+ * Translates the current matrix by the specified x and y values.
233
+ * @param x - The amount to translate horizontally.
234
+ * @param y - The amount to translate vertically.
235
+ */
236
+ translate(x, y) {
237
+ if (typeof x !== "number") {
238
+ return this.translate(x.x, x.y);
239
+ }
240
+ const offset = this.usedElemNum - MATRIX_SIZE;
241
+ const arr = this.typedArray;
242
+ arr[offset + 4] = arr[offset + 0] * x + arr[offset + 2] * y + arr[offset + 4];
243
+ arr[offset + 5] = arr[offset + 1] * x + arr[offset + 3] * y + arr[offset + 5];
244
+ }
245
+ /**
246
+ * Rotates the current matrix by the specified angle.
247
+ * @param angle - The angle, in radians, to rotate the matrix by.
248
+ */
249
+ rotate(angle) {
250
+ const offset = this.usedElemNum - MATRIX_SIZE;
251
+ const arr = this.typedArray;
252
+ const cos = Math.cos(angle);
253
+ const sin = Math.sin(angle);
254
+ const a = arr[offset + 0];
255
+ const b = arr[offset + 1];
256
+ const c = arr[offset + 2];
257
+ const d = arr[offset + 3];
258
+ arr[offset + 0] = a * cos - b * sin;
259
+ arr[offset + 1] = a * sin + b * cos;
260
+ arr[offset + 2] = c * cos - d * sin;
261
+ arr[offset + 3] = c * sin + d * cos;
262
+ }
263
+ /**
264
+ * Multiplies the current matrix on the top of the stack by a scaling transformation.
265
+ * @param x - The amount to scale the matrix horizontally.
266
+ * @param y - The amount to scale the matrix vertically. If not specified, x is used for both horizontal and vertical scaling.
267
+ */
268
+ scale(x, y) {
269
+ if (typeof x !== "number") {
270
+ return this.scale(x.x, x.y);
271
+ }
272
+ if (!y) y = x;
273
+ const offset = this.usedElemNum - MATRIX_SIZE;
274
+ const arr = this.typedArray;
275
+ arr[offset + 0] = arr[offset + 0] * x;
276
+ arr[offset + 1] = arr[offset + 1] * x;
277
+ arr[offset + 2] = arr[offset + 2] * y;
278
+ arr[offset + 3] = arr[offset + 3] * y;
279
+ }
280
+ /**
281
+ * Transforms a point by applying the current matrix stack.
282
+ * @returns The transformed point as an array `[newX, newY]`.
283
+ */
284
+ apply(x, y) {
285
+ if (typeof x !== "number") {
286
+ return new Vec2(...this.apply(x.x, x.y));
287
+ }
288
+ const offset = this.usedElemNum - MATRIX_SIZE;
289
+ const arr = this.typedArray;
290
+ return [
291
+ arr[offset + 0] * x + arr[offset + 2] * y + arr[offset + 4],
292
+ arr[offset + 1] * x + arr[offset + 3] * y + arr[offset + 5]
293
+ ];
294
+ }
295
+ /**
296
+ * Obtain the inverse matrix of the current matrix
297
+ * @returns inverse matrix
298
+ */
299
+ getInverse() {
300
+ const offset = this.usedElemNum - MATRIX_SIZE;
301
+ const arr = this.typedArray;
302
+ const a = arr[offset + 0];
303
+ const b = arr[offset + 1];
304
+ const c = arr[offset + 2];
305
+ const d = arr[offset + 3];
306
+ const e = arr[offset + 4];
307
+ const f = arr[offset + 5];
308
+ const det = a * d - b * c;
309
+ return new Float32Array([
310
+ d / det,
311
+ -b / det,
312
+ -c / det,
313
+ a / det,
314
+ (c * f - d * e) / det,
315
+ (b * e - a * f) / det
316
+ ]);
317
+ }
318
+ /**
319
+ * Get a copy of the current transformation matrix
320
+ * @returns matrix
321
+ */
322
+ getTransform() {
323
+ const offset = this.usedElemNum - MATRIX_SIZE;
324
+ const arr = this.typedArray;
325
+ return new Float32Array([
326
+ arr[offset + 0],
327
+ arr[offset + 1],
328
+ arr[offset + 2],
329
+ arr[offset + 3],
330
+ arr[offset + 4],
331
+ arr[offset + 5]
332
+ ]);
333
+ }
334
+ /**
335
+ * Set the current transformation matrix
336
+ * @param array
337
+ */
338
+ setTransform(array) {
339
+ const offset = this.usedElemNum - MATRIX_SIZE;
340
+ const arr = this.typedArray;
341
+ arr[offset + 0] = array[0];
342
+ arr[offset + 1] = array[1];
343
+ arr[offset + 2] = array[2];
344
+ arr[offset + 3] = array[3];
345
+ arr[offset + 4] = array[4];
346
+ arr[offset + 5] = array[5];
347
+ }
348
+ /**
349
+ * Get the global position in world space
350
+ * @returns The current matrix position in world space
351
+ */
352
+ getGlobalPosition() {
353
+ const offset = this.usedElemNum - MATRIX_SIZE;
354
+ const arr = this.typedArray;
355
+ return new Vec2(arr[offset + 4], arr[offset + 5]);
356
+ }
357
+ /**
358
+ * Set the global position in world space
359
+ * @param x - x coordinate or Vec2 object
360
+ * @param y - y coordinate (ignored if first parameter is Vec2)
361
+ */
362
+ setGlobalPosition(x, y) {
363
+ if (typeof x !== "number") {
364
+ this.setGlobalPosition(x.x, x.y);
365
+ return;
366
+ }
367
+ const offset = this.usedElemNum - MATRIX_SIZE;
368
+ const arr = this.typedArray;
369
+ arr[offset + 4] = x;
370
+ arr[offset + 5] = y;
371
+ }
372
+ /**
373
+ * Get the global rotation angle
374
+ * @returns The current matrix rotation angle in radians
375
+ */
376
+ getGlobalRotation() {
377
+ const offset = this.usedElemNum - MATRIX_SIZE;
378
+ const arr = this.typedArray;
379
+ return Math.atan2(arr[offset + 1], arr[offset + 0]);
380
+ }
381
+ /**
382
+ * Set the global rotation angle
383
+ * @param angle - rotation angle in radians
384
+ */
385
+ setGlobalRotation(angle) {
386
+ const offset = this.usedElemNum - MATRIX_SIZE;
387
+ const arr = this.typedArray;
388
+ const scale = this.getGlobalScale();
389
+ const cos = Math.cos(angle);
390
+ const sin = Math.sin(angle);
391
+ arr[offset + 0] = cos * scale.x;
392
+ arr[offset + 1] = sin * scale.x;
393
+ arr[offset + 2] = -sin * scale.y;
394
+ arr[offset + 3] = cos * scale.y;
395
+ }
396
+ /**
397
+ * Get the global scale
398
+ * @returns The current matrix scale values
399
+ */
400
+ getGlobalScale() {
401
+ const offset = this.usedElemNum - MATRIX_SIZE;
402
+ const arr = this.typedArray;
403
+ const scaleX = Math.sqrt(arr[offset + 0] * arr[offset + 0] + arr[offset + 1] * arr[offset + 1]);
404
+ const scaleY = Math.sqrt(arr[offset + 2] * arr[offset + 2] + arr[offset + 3] * arr[offset + 3]);
405
+ return new Vec2(scaleX, scaleY);
406
+ }
407
+ /**
408
+ * Set the global scale
409
+ * @param x - x scale or Vec2 object
410
+ * @param y - y scale (ignored if first parameter is Vec2)
411
+ */
412
+ setGlobalScale(x, y) {
413
+ if (typeof x !== "number") {
414
+ this.setGlobalScale(x.x, x.y);
415
+ return;
416
+ }
417
+ const rotation = this.getGlobalRotation();
418
+ const cos = Math.cos(rotation);
419
+ const sin = Math.sin(rotation);
420
+ const offset = this.usedElemNum - MATRIX_SIZE;
421
+ const arr = this.typedArray;
422
+ arr[offset + 0] = cos * x;
423
+ arr[offset + 1] = sin * x;
424
+ arr[offset + 2] = -sin * y;
425
+ arr[offset + 3] = cos * y;
426
+ }
427
+ globalToLocal(global) {
428
+ const inv = this.getInverse();
429
+ return new Vec2(
430
+ inv[0] * global.x + inv[2] * global.y + inv[4],
431
+ inv[1] * global.x + inv[3] * global.y + inv[5]
432
+ );
433
+ }
434
+ localToGlobal(local) {
435
+ return this.apply(local);
436
+ }
437
+ /**
438
+ * Convert the current matrix to a CSS transform string
439
+ * @returns CSS transform string representation of the matrix
440
+ */
441
+ toCSSTransform() {
442
+ const offset = this.usedElemNum - MATRIX_SIZE;
443
+ const arr = this.typedArray;
444
+ return `matrix(${arr[offset + 0]}, ${arr[offset + 1]}, ${arr[offset + 2]}, ${arr[offset + 3]}, ${arr[offset + 4]}, ${arr[offset + 5]})`;
445
+ }
446
+ /**
447
+ * Reset the current matrix to identity matrix
448
+ */
449
+ identity() {
450
+ const offset = this.usedElemNum - MATRIX_SIZE;
451
+ const arr = this.typedArray;
452
+ arr[offset + 0] = 1;
453
+ arr[offset + 1] = 0;
454
+ arr[offset + 2] = 0;
455
+ arr[offset + 3] = 1;
456
+ arr[offset + 4] = 0;
457
+ arr[offset + 5] = 0;
458
+ }
459
+ /**
460
+ * Apply the transform to the current matrix
461
+ * @param transform - The transform to apply
462
+ * @returns offset position
463
+ */
464
+ applyTransform(transform, width = 0, height = 0) {
465
+ if (transform.saveTransform ?? true) {
466
+ this.pushMat();
467
+ }
468
+ transform.afterSave && transform.afterSave();
469
+ const x = transform.x || 0;
470
+ const y = transform.y || 0;
471
+ if (x || y) {
472
+ this.translate(x, y);
473
+ }
474
+ transform.position && this.translate(transform.position);
475
+ transform.rotation && this.rotate(transform.rotation);
476
+ transform.scale && this.scale(transform.scale);
477
+ let offsetX = transform.offsetX || 0;
478
+ let offsetY = transform.offsetY || 0;
479
+ if (transform.offset) {
480
+ offsetX += transform.offset.x;
481
+ offsetY += transform.offset.y;
482
+ }
483
+ const origin = transform.origin;
484
+ if (origin) {
485
+ if (typeof origin == "number") {
486
+ offsetX -= origin * width;
487
+ offsetY -= origin * height;
488
+ } else {
489
+ offsetX -= origin.x * width;
490
+ offsetY -= origin.y * height;
491
+ }
492
+ }
493
+ return {
494
+ offsetX,
495
+ offsetY
496
+ };
497
+ }
498
+ applyTransformAfter(transform) {
499
+ if (transform.beforRestore) {
500
+ transform.beforRestore();
501
+ }
502
+ if (transform.restoreTransform ?? true) {
503
+ this.popMat();
504
+ }
505
+ }
506
+ }
507
+ class WebglElementBufferArray extends WebglBufferArray {
508
+ constructor(gl, elemVertPerObj, vertexPerObject, maxBatch) {
509
+ super(gl, 2, gl.ELEMENT_ARRAY_BUFFER, gl.STATIC_DRAW);
510
+ this.setMaxSize(elemVertPerObj * maxBatch);
511
+ for (let index = 0; index < maxBatch; index++) {
512
+ this.addObject(index * vertexPerObject);
513
+ }
514
+ this.bindBuffer();
515
+ this.bufferData();
516
+ }
517
+ addObject(_vertex) {
518
+ }
519
+ }
520
+ const _Color = class _Color {
521
+ /**
522
+ * Creates an instance of Color.
523
+ * @param r - The red component (0-255).
524
+ * @param g - The green component (0-255).
525
+ * @param b - The blue component (0-255).
526
+ * @param a - The alpha component (0-255).
527
+ */
528
+ constructor(r, g, b, a = 255) {
529
+ this._r = r;
530
+ this._g = g;
531
+ this._b = b;
532
+ this._a = a;
533
+ this.updateUint();
534
+ }
535
+ /**
536
+ * Gets the red component.
537
+ */
538
+ get r() {
539
+ return this._r;
540
+ }
541
+ /**
542
+ * Sets the red component and updates the uint32 representation.
543
+ * @param value - The new red component (0-255).
544
+ */
545
+ set r(value) {
546
+ this._r = value;
547
+ this.updateUint();
548
+ }
549
+ /**
550
+ * Gets the green component.
551
+ */
552
+ get g() {
553
+ return this._g;
554
+ }
555
+ /**
556
+ * Sets the green component and updates the uint32 representation.
557
+ * @param value - The new green component (0-255).
558
+ */
559
+ set g(value) {
560
+ this._g = value;
561
+ this.updateUint();
562
+ }
563
+ /**
564
+ * Gets the blue component.
565
+ */
566
+ get b() {
567
+ return this._b;
568
+ }
569
+ /**
570
+ * Sets the blue component and updates the uint32 representation.
571
+ * @param value - The new blue component (0-255).
572
+ */
573
+ set b(value) {
574
+ this._b = value;
575
+ this.updateUint();
576
+ }
577
+ /**
578
+ * Gets the alpha component.
579
+ */
580
+ get a() {
581
+ return this._a;
582
+ }
583
+ /**
584
+ * Sets the alpha component and updates the uint32 representation.
585
+ * @param value - The new alpha component (0-255).
586
+ */
587
+ set a(value) {
588
+ this._a = value;
589
+ this.updateUint();
590
+ }
591
+ /**
592
+ * Updates the uint32 representation of the color.
593
+ * @private
594
+ */
595
+ updateUint() {
596
+ this.uint32 = (this._a << 24 | this._b << 16 | this._g << 8 | this._r) >>> 0;
597
+ }
598
+ /**
599
+ * Sets the RGBA values of the color and updates the uint32 representation.
600
+ * @param r - The red component (0-255).
601
+ * @param g - The green component (0-255).
602
+ * @param b - The blue component (0-255).
603
+ * @param a - The alpha component (0-255).
604
+ */
605
+ setRGBA(r, g, b, a) {
606
+ this.r = r;
607
+ this.g = g;
608
+ this.b = b;
609
+ this.a = a;
610
+ this.updateUint();
611
+ }
612
+ /**
613
+ * Copies the RGBA values from another color.
614
+ * @param color - The color to copy from.
615
+ */
616
+ copy(color) {
617
+ this.setRGBA(color.r, color.g, color.b, color.a);
618
+ }
619
+ /**
620
+ * Clone the current color
621
+ * @returns A new `Color` instance with the same RGBA values.
622
+ */
623
+ clone() {
624
+ return new _Color(this._r, this._g, this._b, this._a);
625
+ }
626
+ /**
627
+ * Converts the color to a hexadecimal string representation (e.g., '#RRGGBBAA').
628
+ * @param includeAlpha - Whether to include the alpha channel in the hex string.
629
+ * @returns The hexadecimal string representation of the color.
630
+ */
631
+ toHexString(includeAlpha = true) {
632
+ const r = this.r.toString(16).padStart(2, "0");
633
+ const g = this.g.toString(16).padStart(2, "0");
634
+ const b = this.b.toString(16).padStart(2, "0");
635
+ const a = this.a.toString(16).padStart(2, "0");
636
+ return `#${r}${g}${b}${includeAlpha ? a : ""}`;
637
+ }
638
+ /**
639
+ * Checks if the current color is equal to another color.
640
+ * @param color - The color to compare with.
641
+ * @returns True if the colors are equal, otherwise false.
642
+ */
643
+ equal(color) {
644
+ return color.r === this.r && color.g === this.g && color.b === this.b && color.a === this.a;
645
+ }
646
+ /**
647
+ * Creates a Color instance from a hexadecimal color string.
648
+ * @param hexString - The hexadecimal color string, e.g., '#RRGGBB' or '#RRGGBBAA'.
649
+ * @returns A new Color instance.
650
+ */
651
+ static fromHex(hexString) {
652
+ if (hexString.startsWith("#")) {
653
+ hexString = hexString.slice(1);
654
+ }
655
+ const r = parseInt(hexString.slice(0, 2), 16);
656
+ const g = parseInt(hexString.slice(2, 4), 16);
657
+ const b = parseInt(hexString.slice(4, 6), 16);
658
+ let a = 255;
659
+ if (hexString.length >= 8) {
660
+ a = parseInt(hexString.slice(6, 8), 16);
661
+ }
662
+ return new _Color(r, g, b, a);
663
+ }
664
+ /**
665
+ * Adds the components of another color to this color, clamping the result to 255.
666
+ * @param color - The color to add.
667
+ * @returns A new Color instance with the result of the addition.
668
+ */
669
+ add(color) {
670
+ return new _Color(
671
+ Math.min(this.r + color.r, 255),
672
+ Math.min(this.g + color.g, 255),
673
+ Math.min(this.b + color.b, 255),
674
+ Math.min(this.a + color.a, 255)
675
+ );
676
+ }
677
+ /**
678
+ * Subtracts the components of another color from this color, clamping the result to 0.
679
+ * @param color - The color to subtract.
680
+ * @returns A new Color instance with the result of the subtraction.
681
+ */
682
+ subtract(color) {
683
+ return new _Color(
684
+ Math.max(0, this.r - color.r),
685
+ // Clamp to 0
686
+ Math.max(0, this.g - color.g),
687
+ // Clamp to 0
688
+ Math.max(0, this.b - color.b),
689
+ // Clamp to 0
690
+ Math.max(0, this.a - color.a)
691
+ // Clamp to 0
692
+ );
693
+ }
694
+ divide(color) {
695
+ if (color instanceof _Color) {
696
+ return new _Color(
697
+ this.r / color.r,
698
+ this.g / color.g,
699
+ this.b / color.b,
700
+ this.a / color.a
701
+ );
702
+ } else {
703
+ return new _Color(
704
+ this.r / color,
705
+ this.g / color,
706
+ this.b / color,
707
+ this.a / color
708
+ );
709
+ }
710
+ }
711
+ multiply(color) {
712
+ if (color instanceof _Color) {
713
+ return new _Color(
714
+ this.r * color.r,
715
+ this.g * color.g,
716
+ this.b * color.b,
717
+ this.a * color.a
718
+ );
719
+ } else {
720
+ return new _Color(
721
+ this.r * color,
722
+ this.g * color,
723
+ this.b * color,
724
+ this.a * color
725
+ );
726
+ }
727
+ }
728
+ clamp() {
729
+ this.r = Math.max(0, Math.min(255, this.r));
730
+ this.g = Math.max(0, Math.min(255, this.g));
731
+ this.b = Math.max(0, Math.min(255, this.b));
732
+ this.a = Math.max(0, Math.min(255, this.a));
733
+ }
734
+ };
735
+ _Color.Red = new _Color(255, 0, 0, 255);
736
+ _Color.Green = new _Color(0, 255, 0, 255);
737
+ _Color.Blue = new _Color(0, 0, 255, 255);
738
+ _Color.Yellow = new _Color(255, 255, 0, 255);
739
+ _Color.Purple = new _Color(128, 0, 128, 255);
740
+ _Color.Orange = new _Color(255, 165, 0, 255);
741
+ _Color.Pink = new _Color(255, 192, 203, 255);
742
+ _Color.Gray = new _Color(128, 128, 128, 255);
743
+ _Color.Brown = new _Color(139, 69, 19, 255);
744
+ _Color.Cyan = new _Color(0, 255, 255, 255);
745
+ _Color.Magenta = new _Color(255, 0, 255, 255);
746
+ _Color.Lime = new _Color(192, 255, 0, 255);
747
+ _Color.White = new _Color(255, 255, 255, 255);
748
+ _Color.Black = new _Color(0, 0, 0, 255);
749
+ _Color.TRANSPARENT = new _Color(0, 0, 0, 0);
750
+ let Color = _Color;
751
+ const _Vec2 = class _Vec2 {
752
+ /**
753
+ * Creates an instance of Vec2.
754
+ * @param x - The x coordinate (default is 0).
755
+ * @param y - The y coordinate (default is 0).
756
+ */
757
+ constructor(x, y) {
758
+ this.x = x !== void 0 ? x : 0;
759
+ this.y = y !== void 0 ? y : 0;
760
+ }
761
+ set(x, y) {
762
+ if (Array.isArray(x)) {
763
+ this.x = x[0];
764
+ this.y = x[1];
765
+ } else if (y) {
766
+ this.x = x;
767
+ this.y = y;
768
+ } else {
769
+ this.x = x;
770
+ this.y = x;
771
+ }
772
+ return this;
773
+ }
774
+ /**
775
+ * Adds another vector to this vector.
776
+ * @param v - The vector to add.
777
+ * @returns A new Vec2 instance with the result of the addition.
778
+ */
779
+ add(v) {
780
+ return new _Vec2(this.x + v.x, this.y + v.y);
781
+ }
782
+ /**
783
+ * Subtracts another vector from this vector.
784
+ * @param v - The vector to subtract.
785
+ * @returns A new Vec2 instance with the result of the subtraction.
786
+ */
787
+ subtract(v) {
788
+ return new _Vec2(this.x - v.x, this.y - v.y);
789
+ }
790
+ /**
791
+ * Multiplies the vector by a scalar or another vector.
792
+ * @param f - The scalar value or vector to multiply by.
793
+ * @returns A new Vec2 instance with the result of the multiplication.
794
+ */
795
+ multiply(f) {
796
+ if (f instanceof _Vec2) {
797
+ return new _Vec2(this.x * f.x, this.y * f.y);
798
+ }
799
+ return new _Vec2(this.x * f, this.y * f);
800
+ }
801
+ /**
802
+ * Divides the vector by a scalar or another vector.
803
+ * @param f - The scalar value or vector to divide by.
804
+ * @returns A new Vec2 instance with the result of the division.
805
+ */
806
+ divide(f) {
807
+ if (f instanceof _Vec2) {
808
+ return new _Vec2(this.x / f.x, this.y / f.y);
809
+ }
810
+ return new _Vec2(this.x / f, this.y / f);
811
+ }
812
+ /**
813
+ * Calculates the dot product of this vector with another vector.
814
+ * @param v - The other vector.
815
+ * @returns The dot product result.
816
+ */
817
+ dot(v) {
818
+ return this.x * v.x + this.y * v.y;
819
+ }
820
+ /**
821
+ * Calculates the cross product of this vector with another vector.
822
+ * @param v - The other vector.
823
+ * @returns The cross product result.
824
+ */
825
+ cross(v) {
826
+ return this.x * v.y - this.y * v.x;
827
+ }
828
+ /**
829
+ * Calculates the distance to another point.
830
+ * @param v - The other point.
831
+ * @returns The distance between the two points.
832
+ */
833
+ distanceTo(v) {
834
+ const dx = this.x - v.x;
835
+ const dy = this.y - v.y;
836
+ return Math.sqrt(dx * dx + dy * dy);
837
+ }
838
+ /**
839
+ * Clones the vector.
840
+ * @returns A new Vec2 instance with the same x and y values.
841
+ */
842
+ clone() {
843
+ return new _Vec2(this.x, this.y);
844
+ }
845
+ /**
846
+ * Copy the vector
847
+ * @param vec - The vector to copy.
848
+ */
849
+ copy(vec) {
850
+ this.x = vec.x;
851
+ this.y = vec.y;
852
+ }
853
+ /**
854
+ * Check if the vector is equal to another vector.
855
+ * @param vec - The vector to compare with.
856
+ * @returns True if the vectors are equal, otherwise false.
857
+ */
858
+ equal(vec) {
859
+ return vec.x == this.x && vec.y == this.y;
860
+ }
861
+ /**
862
+ * Rotates the vector 90 degrees counterclockwise.
863
+ * @returns The current instance with updated values.
864
+ */
865
+ perpendicular() {
866
+ const x = this.x;
867
+ this.x = -this.y;
868
+ this.y = x;
869
+ return this;
870
+ }
871
+ /**
872
+ * Inverts the direction of the vector.
873
+ * @returns The current instance with updated values.
874
+ */
875
+ invert() {
876
+ this.x = -this.x;
877
+ this.y = -this.y;
878
+ return this;
879
+ }
880
+ /**
881
+ * Calculates the length of the vector.
882
+ * @returns The length of the vector.
883
+ */
884
+ length() {
885
+ return Math.sqrt(this.x * this.x + this.y * this.y);
886
+ }
887
+ /**
888
+ * Normalizes the vector to have a length of 1.
889
+ * @returns The current instance with updated values.
890
+ */
891
+ normalize() {
892
+ const mod = this.length();
893
+ this.x = this.x / mod || 0;
894
+ this.y = this.y / mod || 0;
895
+ return this;
896
+ }
897
+ /**
898
+ * Calculates the angle of the vector relative to the x-axis.
899
+ * @returns The angle of the vector.
900
+ */
901
+ angle() {
902
+ return Math.atan2(this.y, this.x);
903
+ }
904
+ /**
905
+ * Calculates the middle point between the current vector and another vector.
906
+ * @param other - The other vector.
907
+ * @returns A new vector representing the middle point between the current vector and the other vector.
908
+ */
909
+ middle(other) {
910
+ return new _Vec2((this.x + other.x) / 2, (this.y + other.y) / 2);
911
+ }
912
+ /**
913
+ * Returns a new vector with the absolute values of the components.
914
+ * @returns A new vector with absolute values.
915
+ */
916
+ abs() {
917
+ return new _Vec2(Math.abs(this.x), Math.abs(this.y));
918
+ }
919
+ /**
920
+ * Returns a new vector with floored components.
921
+ * @returns A new vector with components rounded down to the nearest integer.
922
+ */
923
+ floor() {
924
+ return new _Vec2(Math.floor(this.x), Math.floor(this.y));
925
+ }
926
+ /**
927
+ * Returns a new vector with ceiled components.
928
+ * @returns A new vector with components rounded up to the nearest integer.
929
+ */
930
+ ceil() {
931
+ return new _Vec2(Math.ceil(this.x), Math.ceil(this.y));
932
+ }
933
+ /**
934
+ * Snaps the vector components to the nearest increment.
935
+ * @param increment - The increment to snap to.
936
+ * @returns A new vector with components snapped to the nearest increment.
937
+ */
938
+ snap(increment) {
939
+ return new _Vec2(
940
+ Math.round(this.x / increment) * increment,
941
+ Math.round(this.y / increment) * increment
942
+ );
943
+ }
944
+ /**
945
+ * Converts the vector to a string representation.
946
+ * @returns A string containing the vector's x and y coordinates.
947
+ */
948
+ stringify() {
949
+ return `Vec2(${this.x}, ${this.y})`;
950
+ }
951
+ /**
952
+ * Converts an array of coordinate pairs into an array of Vec2 instances.
953
+ * @param array - An array of [x, y] coordinate pairs.
954
+ * @returns An array of Vec2 instances.
955
+ */
956
+ static FromArray(array) {
957
+ return array.map((pair) => new _Vec2(pair[0], pair[1]));
958
+ }
959
+ static fromAngle(angle) {
960
+ return new _Vec2(Math.cos(angle), Math.sin(angle));
961
+ }
962
+ /**
963
+ * Calculates the angle between two vectors.
964
+ * @param v - The other vector.
965
+ * @returns The angle between the two vectors in radians.
966
+ */
967
+ angleBetween(v) {
968
+ const dotProduct = this.dot(v);
969
+ const magnitudeProduct = this.length() * v.length();
970
+ const cosTheta = Math.max(-1, Math.min(1, dotProduct / magnitudeProduct));
971
+ return Math.acos(cosTheta);
972
+ }
973
+ lerp(target, factor) {
974
+ this.x += (target.x - this.x) * factor;
975
+ this.y += (target.y - this.y) * factor;
976
+ return this;
977
+ }
978
+ };
979
+ _Vec2.ZERO = new _Vec2(0, 0);
980
+ _Vec2.ONE = new _Vec2(1, 1);
981
+ _Vec2.UP = new _Vec2(0, 1);
982
+ _Vec2.DOWN = new _Vec2(0, -1);
983
+ _Vec2.LEFT = new _Vec2(-1, 0);
984
+ _Vec2.RIGHT = new _Vec2(1, 0);
985
+ let Vec2 = _Vec2;
986
+ class MathUtils {
987
+ /**
988
+ * Converts degrees to radians.
989
+ * @param degrees - The angle in degrees.
990
+ * @returns The equivalent angle in radians.
991
+ */
992
+ static deg2rad(degrees) {
993
+ return degrees * (Math.PI / 180);
994
+ }
995
+ /**
996
+ * Converts radians to degrees.
997
+ * @param rad - The angle in radians.
998
+ * @returns The equivalent angle in degrees.
999
+ */
1000
+ static rad2deg(rad) {
1001
+ return rad / (Math.PI / 180);
1002
+ }
1003
+ /**
1004
+ * Normalizes an angle in degrees to be within the range of 0 to 360 degrees.
1005
+ * @param degrees - The angle in degrees to be normalized.
1006
+ * @returns The normalized angle in degrees.
1007
+ */
1008
+ static normalizeDegrees(degrees) {
1009
+ return (degrees % 360 + 360) % 360;
1010
+ }
1011
+ }
1012
+ class Random {
1013
+ /**
1014
+ * 生成指定范围内的随机浮点数
1015
+ * @param min - 最小值
1016
+ * @param max - 最大值
1017
+ * @returns 随机浮点数
1018
+ */
1019
+ static float(min, max) {
1020
+ return Math.random() * (max - min) + min;
1021
+ }
1022
+ /**
1023
+ * 生成指定范围内的随机整数
1024
+ * @param min - 最小值
1025
+ * @param max - 最大值
1026
+ * @returns 随机整数
1027
+ */
1028
+ static int(min, max) {
1029
+ return Math.floor(Math.random() * (max - min + 1)) + min;
1030
+ }
1031
+ /**
1032
+ * 生成随机角度(0-360度)
1033
+ * @returns 随机角度(弧度)
1034
+ */
1035
+ static angle() {
1036
+ return Math.random() * Math.PI * 2;
1037
+ }
1038
+ /**
1039
+ * 生成指定范围内的随机向量
1040
+ * @param minX - 最小X值
1041
+ * @param maxX - 最大X值
1042
+ * @param minY - 最小Y值
1043
+ * @param maxY - 最大Y值
1044
+ * @returns 随机向量
1045
+ */
1046
+ static vector(minX, maxX, minY, maxY) {
1047
+ return new Vec2(
1048
+ Random.float(minX, maxX),
1049
+ Random.float(minY, maxY)
1050
+ );
1051
+ }
1052
+ /**
1053
+ * 生成具有随机方向和指定长度的向量
1054
+ * @param length - 向量长度
1055
+ * @returns 随机方向向量
1056
+ */
1057
+ static direction(length) {
1058
+ const angle = Random.angle();
1059
+ return new Vec2(
1060
+ Math.cos(angle) * length,
1061
+ Math.sin(angle) * length
1062
+ );
1063
+ }
1064
+ static randomColor(minColor, maxColor) {
1065
+ return new Color(
1066
+ Random.float(minColor.r, maxColor.r),
1067
+ Random.float(minColor.g, maxColor.g),
1068
+ Random.float(minColor.b, maxColor.b),
1069
+ Random.float(minColor.a, maxColor.a)
1070
+ );
1071
+ }
1072
+ static pick(array) {
1073
+ return array[Random.int(0, array.length - 1)];
1074
+ }
1075
+ static pickWeight(array) {
1076
+ if (!array || array.length === 0) {
1077
+ return null;
1078
+ }
1079
+ let totalWeight = 0;
1080
+ for (const item of array) {
1081
+ totalWeight += item[1];
1082
+ }
1083
+ const random = Math.random() * totalWeight;
1084
+ let currentWeight = 0;
1085
+ for (const item of array) {
1086
+ currentWeight += item[1];
1087
+ if (random <= currentWeight) {
1088
+ return item[0];
1089
+ }
1090
+ }
1091
+ return array[array.length - 1][0];
1092
+ }
1093
+ static scalarOrRange(range, defaultValue) {
1094
+ if (range === void 0) {
1095
+ return defaultValue;
1096
+ }
1097
+ if (Array.isArray(range)) {
1098
+ if (typeof range[0] === "number") {
1099
+ return Random.float(range[0], range[1]);
1100
+ } else if (range[0] instanceof Vec2) {
1101
+ return Random.vector(range[0].x, range[1].x, range[0].y, range[1].y);
1102
+ } else if (range[0] instanceof Color) {
1103
+ return Random.randomColor(range[0], range[1]);
1104
+ }
1105
+ }
1106
+ if (typeof range === "number") {
1107
+ return range;
1108
+ }
1109
+ return range.clone();
1110
+ }
1111
+ }
1112
+ class LightManager {
1113
+ constructor(render) {
1114
+ this.render = render;
1115
+ }
1116
+ createLightShadowMaskPolygon(occlusion, lightSource, baseProjectionLength) {
1117
+ const occlusionSide = [];
1118
+ occlusion.forEach((shape) => {
1119
+ for (let i = 0; i < shape.length; i++) {
1120
+ const start = shape[i];
1121
+ const end = shape[(i + 1) % shape.length];
1122
+ occlusionSide.push([start, end]);
1123
+ }
1124
+ });
1125
+ baseProjectionLength = baseProjectionLength || Math.sqrt(Math.pow(this.render.width, 2) + Math.pow(this.render.height, 2));
1126
+ const shadowPolygons = [];
1127
+ occlusionSide.forEach(([start, end]) => {
1128
+ const toStart = new Vec2(start.x - lightSource.x, start.y - lightSource.y);
1129
+ const toEnd = new Vec2(end.x - lightSource.x, end.y - lightSource.y);
1130
+ const edge = end.subtract(start);
1131
+ const normal = edge.perpendicular();
1132
+ const rateStart = Math.abs(normal.dot(toStart)) / (normal.length() * toStart.length()) + 0.01;
1133
+ const rateEnd = Math.abs(normal.dot(toEnd)) / (normal.length() * toEnd.length()) + 0.01;
1134
+ const startProjectionLength = baseProjectionLength / rateStart;
1135
+ const endProjectionLength = baseProjectionLength / rateEnd;
1136
+ const startRay = new Vec2(toStart.x, toStart.y).normalize();
1137
+ const endRay = new Vec2(toEnd.x, toEnd.y).normalize();
1138
+ const startProjection = new Vec2(
1139
+ start.x + startRay.x * startProjectionLength,
1140
+ start.y + startRay.y * startProjectionLength
1141
+ );
1142
+ const endProjection = new Vec2(
1143
+ end.x + endRay.x * endProjectionLength,
1144
+ end.y + endRay.y * endProjectionLength
1145
+ );
1146
+ shadowPolygons.push([
1147
+ start,
1148
+ end,
1149
+ endProjection,
1150
+ startProjection
1151
+ ]);
1152
+ });
1153
+ return shadowPolygons;
1154
+ }
1155
+ }
1156
+ const LINE_ROUND_SEGMENTS = 10;
1157
+ const addSemicircle = (center, normal, width, isStart) => {
1158
+ const result = [];
1159
+ const startAngle = isStart ? Math.atan2(normal.y, normal.x) : Math.atan2(-normal.y, -normal.x);
1160
+ const totalAngle = Math.PI;
1161
+ for (let i = 0; i < LINE_ROUND_SEGMENTS; i++) {
1162
+ const t1 = i / LINE_ROUND_SEGMENTS;
1163
+ const t2 = (i + 1) / LINE_ROUND_SEGMENTS;
1164
+ const angle1 = startAngle + t1 * totalAngle;
1165
+ const angle2 = startAngle + t2 * totalAngle;
1166
+ const x1 = Math.cos(angle1) * width;
1167
+ const y1 = Math.sin(angle1) * width;
1168
+ const x2 = Math.cos(angle2) * width;
1169
+ const y2 = Math.sin(angle2) * width;
1170
+ result.push(center);
1171
+ result.push(center.add(new Vec2(x1, y1)));
1172
+ result.push(center.add(new Vec2(x2, y2)));
1173
+ }
1174
+ return result;
1175
+ };
1176
+ const getLineNormal = (points, closed = false) => {
1177
+ const normals = [];
1178
+ if (points.length < 2 || closed && points.length < 3) return { normals, length: 0 };
1179
+ const maxMiterLength = 4;
1180
+ const epsilon = 1e-3;
1181
+ const n = points.length;
1182
+ let length = 0;
1183
+ if (closed) {
1184
+ for (let i = 0; i < n; i++) {
1185
+ const p1 = points[i];
1186
+ const p2 = points[(i + 1) % n];
1187
+ length += p1.distanceTo(p2);
1188
+ }
1189
+ } else {
1190
+ for (let i = 0; i < n - 1; i++) {
1191
+ length += points[i].distanceTo(points[i + 1]);
1192
+ }
1193
+ }
1194
+ const calculateNormal = (prev, point, next) => {
1195
+ const dirA = point.subtract(prev).normalize();
1196
+ const dirB = point.subtract(next).normalize();
1197
+ const dot = dirB.dot(dirA);
1198
+ if (dot < -1 + epsilon) {
1199
+ return { normal: dirA.perpendicular(), miters: 1 };
1200
+ } else {
1201
+ let miter = dirB.add(dirA).normalize();
1202
+ if (dirA.cross(dirB) < 0) miter = miter.multiply(-1);
1203
+ let miterLength = 1 / Math.sqrt((1 - dot) / 2);
1204
+ return { normal: miter, miters: Math.min(miterLength, maxMiterLength) };
1205
+ }
1206
+ };
1207
+ if (closed) {
1208
+ for (let i = 0; i < n - 1; i++) {
1209
+ const prev = i === 0 ? points[n - 2] : points[i - 1];
1210
+ const point = points[i];
1211
+ const next = points[i + 1];
1212
+ normals.push(calculateNormal(prev, point, next));
1213
+ }
1214
+ normals.push(normals[0]);
1215
+ } else {
1216
+ for (let i = 0; i < n; i++) {
1217
+ if (i === 0) {
1218
+ const direction = points[1].subtract(points[0]).normalize();
1219
+ normals.push({ normal: direction.perpendicular(), miters: 1 });
1220
+ } else if (i === n - 1) {
1221
+ const direction = points[i].subtract(points[i - 1]).normalize();
1222
+ normals.push({ normal: direction.perpendicular(), miters: 1 });
1223
+ } else {
1224
+ normals.push(calculateNormal(points[i - 1], points[i], points[i + 1]));
1225
+ }
1226
+ }
1227
+ }
1228
+ return { normals, length };
1229
+ };
1230
+ const getLineGeometry = (options) => {
1231
+ const points = options.points;
1232
+ if (points.length < 2) return { vertices: [], uv: [] };
1233
+ const { normals, length } = getLineNormal(points, options.closed);
1234
+ const lineWidth = (options.width || 1) / 2;
1235
+ const vertices = [];
1236
+ const uv = [];
1237
+ const roundCap = options.roundCap || false;
1238
+ const textureMode = options.textureMode || LineTextureMode.STRETCH;
1239
+ let currentLength = 0;
1240
+ const textureWidth = options.texture?.width || 1;
1241
+ for (let i = 0; i < points.length - 1; i++) {
1242
+ const point = points[i];
1243
+ const normal = normals[i].normal;
1244
+ const miters = normals[i].miters;
1245
+ const top = point.add(normal.multiply(miters * lineWidth));
1246
+ const bottom = point.subtract(normal.multiply(miters * lineWidth));
1247
+ const nextPoint = points[i + 1];
1248
+ const nextNormal = normals[i + 1].normal;
1249
+ const nextMiter = normals[i + 1].miters;
1250
+ const nextTop = nextPoint.add(nextNormal.multiply(nextMiter * lineWidth));
1251
+ const nextBottom = nextPoint.subtract(nextNormal.multiply(nextMiter * lineWidth));
1252
+ const segmentLength = point.distanceTo(nextPoint);
1253
+ let u1 = 0, u2 = 0;
1254
+ if (textureMode === LineTextureMode.STRETCH) {
1255
+ u1 = currentLength / length;
1256
+ u2 = (currentLength + segmentLength) / length;
1257
+ } else {
1258
+ u1 = currentLength / textureWidth;
1259
+ u2 = u1 + segmentLength / textureWidth;
1260
+ }
1261
+ const topUv = new Vec2(u1, 0);
1262
+ const bottomUv = new Vec2(u1, 1);
1263
+ const nextTopUv = new Vec2(u2, 0);
1264
+ const nextBottomUv = new Vec2(u2, 1);
1265
+ vertices.push(top);
1266
+ uv.push(topUv);
1267
+ vertices.push(bottom);
1268
+ uv.push(bottomUv);
1269
+ vertices.push(nextTop);
1270
+ uv.push(nextTopUv);
1271
+ vertices.push(nextTop);
1272
+ uv.push(nextTopUv);
1273
+ vertices.push(nextBottom);
1274
+ uv.push(nextBottomUv);
1275
+ vertices.push(bottom);
1276
+ uv.push(bottomUv);
1277
+ currentLength += segmentLength;
1278
+ }
1279
+ if (roundCap && !options.closed) {
1280
+ const startPoint = points[0];
1281
+ const startNormal = normals[0].normal;
1282
+ const startVertices = addSemicircle(startPoint, startNormal, lineWidth, true);
1283
+ vertices.push(...startVertices);
1284
+ const endPoint = points[points.length - 1];
1285
+ const endNormal = normals[points.length - 1].normal;
1286
+ const endVertices = addSemicircle(endPoint, endNormal, lineWidth, false);
1287
+ vertices.push(...endVertices);
1288
+ }
1289
+ return { vertices, uv };
1290
+ };
1291
+ const fragString$1 = "precision mediump float;varying vec2 vRegion;varying vec4 vColor;uniform sampler2D uTexture;uniform int uUseTexture;void main(void){vec4 color;if(uUseTexture>0){color=texture2D(uTexture,vRegion)*vColor;}else{color=vColor;}gl_FragColor=color;}";
1292
+ const vertString$1 = "precision mediump float;attribute vec2 aPosition;attribute vec4 aColor;attribute vec2 aRegion;varying vec4 vColor;uniform mat4 uProjectionMatrix;uniform vec4 uColor;varying vec2 vRegion;void main(void){gl_Position=uProjectionMatrix*vec4(aPosition,0.0,1.0);vColor=aColor;vRegion=aRegion;}";
1293
+ const getContext = (canvas) => {
1294
+ const options = { stencil: true };
1295
+ const gl = canvas.getContext("webgl2", options) || canvas.getContext("webgl", options);
1296
+ if (!gl) {
1297
+ throw new Error("Unable to initialize WebGL. Your browser may not support it.");
1298
+ }
1299
+ return gl;
1300
+ };
1301
+ const compileShader = (gl, source, type) => {
1302
+ const shader = gl.createShader(type);
1303
+ if (!shader) {
1304
+ throw new Error("Unable to create webgl shader");
1305
+ }
1306
+ gl.shaderSource(shader, source);
1307
+ gl.compileShader(shader);
1308
+ const compileStatus = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
1309
+ if (!compileStatus) {
1310
+ const errorLog = gl.getShaderInfoLog(shader);
1311
+ console.error("Shader compilation failed:", errorLog);
1312
+ throw new Error("Unable to compile shader: " + errorLog + source);
1313
+ }
1314
+ return shader;
1315
+ };
1316
+ const createShaderProgram = (gl, vsSource, fsSource) => {
1317
+ var program = gl.createProgram(), vShader = compileShader(gl, vsSource, 35633), fShader = compileShader(gl, fsSource, 35632);
1318
+ if (!program) {
1319
+ throw new Error("Unable to create program shader");
1320
+ }
1321
+ gl.attachShader(program, vShader);
1322
+ gl.attachShader(program, fShader);
1323
+ gl.linkProgram(program);
1324
+ const linkStatus = gl.getProgramParameter(program, gl.LINK_STATUS);
1325
+ if (!linkStatus) {
1326
+ const errorLog = gl.getProgramInfoLog(program);
1327
+ throw new Error("Unable to link shader program: " + errorLog);
1328
+ }
1329
+ return program;
1330
+ };
1331
+ function createTexture(gl, source, antialias, withSize = false, flipY = false, wrapMode = "clamp") {
1332
+ const texture = gl.createTexture();
1333
+ if (!texture) {
1334
+ throw new Error("unable to create texture");
1335
+ }
1336
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1337
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, antialias ? gl.LINEAR : gl.NEAREST);
1338
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, antialias ? gl.LINEAR : gl.NEAREST);
1339
+ let wrapParam;
1340
+ switch (wrapMode) {
1341
+ case "repeat":
1342
+ wrapParam = gl.REPEAT;
1343
+ break;
1344
+ case "mirror":
1345
+ wrapParam = gl.MIRRORED_REPEAT;
1346
+ break;
1347
+ case "clamp":
1348
+ default:
1349
+ wrapParam = gl.CLAMP_TO_EDGE;
1350
+ break;
1351
+ }
1352
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapParam);
1353
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapParam);
1354
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
1355
+ if (withSize) {
1356
+ source = source;
1357
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, source.width, source.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
1358
+ } else {
1359
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
1360
+ }
1361
+ return texture;
1362
+ }
1363
+ function generateFragShader(fs, max) {
1364
+ if (fs.includes("%TEXTURE_NUM%")) fs = fs.replace("%TEXTURE_NUM%", max.toString());
1365
+ if (fs.includes("%GET_COLOR%")) {
1366
+ let code = "";
1367
+ for (let index = 0; index < max; index++) {
1368
+ if (index == 0) {
1369
+ code += `if(vTextureId == ${index}.0)`;
1370
+ } else if (index == max - 1) {
1371
+ code += `else`;
1372
+ } else {
1373
+ code += `else if(vTextureId == ${index}.0)`;
1374
+ }
1375
+ code += `{color = texture2D(uTextures[${index}], vRegion);}`;
1376
+ }
1377
+ fs = fs.replace("%GET_COLOR%", code);
1378
+ }
1379
+ return fs;
1380
+ }
1381
+ const FLOAT = 5126;
1382
+ const UNSIGNED_BYTE = 5121;
1383
+ const fragString = "precision mediump float;uniform sampler2D uTextures[%TEXTURE_NUM%];varying vec2 vRegion;varying float vTextureId;varying vec4 vColor;void main(void){vec4 color;%GET_COLOR%gl_FragColor=color*vColor;}";
1384
+ const vertString = "precision mediump float;attribute vec2 aPosition;attribute vec2 aRegion;attribute float aTextureId;attribute vec4 aColor;uniform mat4 uProjectionMatrix;varying vec2 vRegion;varying float vTextureId;varying vec4 vColor;void main(void){vRegion=aRegion;vTextureId=aTextureId;vColor=aColor;gl_Position=uProjectionMatrix*vec4(aPosition,0.0,1.0);}";
1385
+ const SPRITE_BYTES_PER_VERTEX = 4 * 2 + 4 * 2 + 4 + 4;
1386
+ const stride = SPRITE_BYTES_PER_VERTEX;
1387
+ const spriteAttributes = [
1388
+ { name: "aPosition", size: 2, type: FLOAT, stride },
1389
+ { name: "aRegion", size: 2, type: FLOAT, stride, offset: 2 * Float32Array.BYTES_PER_ELEMENT },
1390
+ { name: "aTextureId", size: 1, type: FLOAT, stride, offset: 4 * Float32Array.BYTES_PER_ELEMENT },
1391
+ { name: "aColor", size: 4, type: UNSIGNED_BYTE, stride, offset: 5 * Float32Array.BYTES_PER_ELEMENT, normalized: true }
1392
+ ];
1393
+ const GRAPHIC_BYTES_PER_VERTEX = 2 * 4 + 4 + 2 * 4;
1394
+ const graphicAttributes = [
1395
+ { name: "aPosition", size: 2, type: FLOAT, stride: GRAPHIC_BYTES_PER_VERTEX },
1396
+ { name: "aColor", size: 4, type: UNSIGNED_BYTE, stride: GRAPHIC_BYTES_PER_VERTEX, offset: 2 * Float32Array.BYTES_PER_ELEMENT, normalized: true },
1397
+ { name: "aRegion", size: 2, type: FLOAT, stride: GRAPHIC_BYTES_PER_VERTEX, offset: 3 * Float32Array.BYTES_PER_ELEMENT }
1398
+ ];
1399
+ class GLShader {
1400
+ constructor(rapid, vs, fs, attributes, textureUnitNum = 0) {
1401
+ this.attributeLoc = {};
1402
+ this.uniformLoc = {};
1403
+ this.textureUnitNum = 0;
1404
+ this.attributes = [];
1405
+ const processedFragmentShaderSource = generateFragShader(fs, rapid.maxTextureUnits - textureUnitNum);
1406
+ this.program = createShaderProgram(rapid.gl, vs, processedFragmentShaderSource);
1407
+ this.gl = rapid.gl;
1408
+ this.textureUnitNum = textureUnitNum;
1409
+ this.parseShader(vs);
1410
+ this.parseShader(processedFragmentShaderSource);
1411
+ if (attributes) {
1412
+ this.setAttributes(attributes);
1413
+ }
1414
+ }
1415
+ /**
1416
+ * Set the uniform of this shader
1417
+ * @param uniforms
1418
+ * @param usedTextureUnit How many texture units have been used
1419
+ */
1420
+ setUniforms(uniform, region) {
1421
+ const gl = this.gl;
1422
+ for (const uniformName of uniform.getUnifromNames()) {
1423
+ const loc = this.getUniform(uniformName);
1424
+ uniform.bind(gl, uniformName, loc, region);
1425
+ }
1426
+ }
1427
+ getUniform(name) {
1428
+ return this.uniformLoc[name];
1429
+ }
1430
+ /**
1431
+ * use this shader
1432
+ */
1433
+ use() {
1434
+ this.gl.useProgram(this.program);
1435
+ }
1436
+ parseShader(shader) {
1437
+ const gl = this.gl;
1438
+ const attributeMatches = shader.match(/attribute\s+\w+\s+(\w+)/g);
1439
+ if (attributeMatches) {
1440
+ for (const match of attributeMatches) {
1441
+ const name = match.split(" ")[2];
1442
+ const loc = gl.getAttribLocation(this.program, name);
1443
+ if (loc != -1) this.attributeLoc[name] = loc;
1444
+ }
1445
+ }
1446
+ const uniformMatches = shader.match(/uniform\s+\w+\s+(\w+)/g);
1447
+ if (uniformMatches) {
1448
+ for (const match of uniformMatches) {
1449
+ const name = match.split(" ")[2];
1450
+ this.uniformLoc[name] = gl.getUniformLocation(this.program, name);
1451
+ }
1452
+ }
1453
+ }
1454
+ /**
1455
+ * Set vertex attribute in glsl shader
1456
+ * @param element
1457
+ */
1458
+ setAttribute(element) {
1459
+ const loc = this.attributeLoc[element.name];
1460
+ if (typeof loc != "undefined") {
1461
+ const gl = this.gl;
1462
+ gl.vertexAttribPointer(
1463
+ loc,
1464
+ element.size,
1465
+ element.type,
1466
+ element.normalized || false,
1467
+ element.stride,
1468
+ element.offset || 0
1469
+ );
1470
+ gl.enableVertexAttribArray(loc);
1471
+ }
1472
+ }
1473
+ /**
1474
+ * Set vertex attributes in glsl shader
1475
+ * @param elements
1476
+ */
1477
+ setAttributes(elements) {
1478
+ this.attributes = elements;
1479
+ for (const element of elements) {
1480
+ this.setAttribute(element);
1481
+ }
1482
+ }
1483
+ updateAttributes() {
1484
+ this.setAttributes(this.attributes);
1485
+ }
1486
+ static createCostumShader(rapid, vs, fs, type, textureUnitNum = 0) {
1487
+ let baseFs = {
1488
+ [ShaderType.SPRITE]: fragString,
1489
+ [ShaderType.GRAPHIC]: fragString$1
1490
+ }[type];
1491
+ let baseVs = {
1492
+ [ShaderType.SPRITE]: vertString,
1493
+ [ShaderType.GRAPHIC]: vertString$1
1494
+ }[type];
1495
+ const attribute = {
1496
+ [ShaderType.SPRITE]: spriteAttributes,
1497
+ [ShaderType.GRAPHIC]: graphicAttributes
1498
+ }[type];
1499
+ baseFs = baseFs.replace("void main(void) {", fs + "\nvoid main(void) {");
1500
+ baseVs = baseVs.replace("void main(void) {", vs + "\nvoid main(void) {");
1501
+ baseFs = baseFs.replace("// fragment", "fragment(color);");
1502
+ baseVs = baseVs.replace(
1503
+ /\/\/ vertex s[\s\S]*?\/\/ vertex e/,
1504
+ `vec2 position = aPosition;
1505
+ vertex(position, vRegion);
1506
+ gl_Position = uProjectionMatrix * vec4(position, 0.0, 1.0);`
1507
+ );
1508
+ return new GLShader(rapid, baseVs, baseFs, attribute, textureUnitNum);
1509
+ }
1510
+ }
1511
+ class RenderRegion {
1512
+ constructor(rapid) {
1513
+ this.usedTextures = [];
1514
+ this.shaders = /* @__PURE__ */ new Map();
1515
+ this.isCostumShader = false;
1516
+ this.freeTextureUnitNum = 0;
1517
+ this.rapid = rapid;
1518
+ this.gl = rapid.gl;
1519
+ this.webglArrayBuffer = new WebglBufferArray(rapid.gl, ArrayType.Float32, rapid.gl.ARRAY_BUFFER, rapid.gl.STREAM_DRAW);
1520
+ this.maxTextureUnits = rapid.maxTextureUnits;
1521
+ }
1522
+ getTextureUnitList() {
1523
+ return Array.from(
1524
+ { length: this.maxTextureUnits },
1525
+ (_, index) => index
1526
+ );
1527
+ }
1528
+ // setTextureUnits(usedTexture: number) {
1529
+ // return this.MAX_TEXTURE_UNIT_ARRAY.slice(usedTexture, -1)
1530
+ // }
1531
+ addVertex(x, y, ..._) {
1532
+ const [tx, ty] = this.rapid.matrixStack.apply(x, y);
1533
+ this.webglArrayBuffer.pushFloat32(tx);
1534
+ this.webglArrayBuffer.pushFloat32(ty);
1535
+ }
1536
+ /**
1537
+ * 使用纹理,必须在渲染操作之前
1538
+ * @param texture
1539
+ * @returns
1540
+ */
1541
+ useTexture(texture) {
1542
+ const textureUnit = this.usedTextures.indexOf(texture);
1543
+ if (textureUnit == -1) {
1544
+ this.usedTextures.push(texture);
1545
+ this.freeTextureUnitNum = this.maxTextureUnits - this.usedTextures.length;
1546
+ return [this.usedTextures.length - 1, true];
1547
+ }
1548
+ return [textureUnit, false];
1549
+ }
1550
+ enterRegion(customShader) {
1551
+ this.currentShader = customShader ?? this.getShader("default");
1552
+ this.currentShader.use();
1553
+ this.initializeForNextRender();
1554
+ this.webglArrayBuffer.bindBuffer();
1555
+ this.currentShader.updateAttributes();
1556
+ this.updateProjection();
1557
+ this.isCostumShader = Boolean(customShader);
1558
+ }
1559
+ updateProjection() {
1560
+ this.gl.uniformMatrix4fv(
1561
+ this.currentShader.uniformLoc["uProjectionMatrix"],
1562
+ false,
1563
+ this.rapid.projection
1564
+ );
1565
+ }
1566
+ isUnifromChanged(newCurrentUniform) {
1567
+ if (!newCurrentUniform) return false;
1568
+ if (this.costumUnifrom != newCurrentUniform) {
1569
+ return true;
1570
+ } else if (newCurrentUniform?.isDirty) {
1571
+ return true;
1572
+ }
1573
+ return false;
1574
+ }
1575
+ setCurrentUniform(newCurrentUniform) {
1576
+ newCurrentUniform.clearDirty();
1577
+ this.costumUnifrom = newCurrentUniform;
1578
+ }
1579
+ exitRegion() {
1580
+ }
1581
+ initDefaultShader(vs, fs, attributes) {
1582
+ this.setShader("default", vs, fs, attributes);
1583
+ }
1584
+ setShader(name, vs, fs, attributes) {
1585
+ this.webglArrayBuffer.bindBuffer();
1586
+ this.shaders.set(name, new GLShader(this.rapid, vs, fs, attributes));
1587
+ if (name === "default") {
1588
+ this.defaultShader = this.shaders.get(name);
1589
+ }
1590
+ }
1591
+ getShader(name) {
1592
+ return this.shaders.get(name);
1593
+ }
1594
+ render() {
1595
+ this.executeRender();
1596
+ this.initializeForNextRender();
1597
+ }
1598
+ executeRender() {
1599
+ const gl = this.gl;
1600
+ for (let unit = 0; unit < this.usedTextures.length; unit++) {
1601
+ gl.activeTexture(gl.TEXTURE0 + unit);
1602
+ gl.bindTexture(gl.TEXTURE_2D, this.usedTextures[unit]);
1603
+ }
1604
+ this.webglArrayBuffer.bufferData();
1605
+ }
1606
+ initializeForNextRender() {
1607
+ this.webglArrayBuffer.clear();
1608
+ this.usedTextures.length = 0;
1609
+ this.isCostumShader = false;
1610
+ this.freeTextureUnitNum = this.maxTextureUnits;
1611
+ }
1612
+ hasPendingContent() {
1613
+ return false;
1614
+ }
1615
+ isShaderChanged(shader) {
1616
+ return (shader || this.defaultShader) != this.currentShader;
1617
+ }
1618
+ }
1619
+ const FLOAT32_PER_VERTEX = 3;
1620
+ class GraphicRegion extends RenderRegion {
1621
+ constructor(rapid) {
1622
+ super(rapid);
1623
+ this.vertex = 0;
1624
+ this.offset = Vec2.ZERO;
1625
+ this.drawType = rapid.gl.TRIANGLE_FAN;
1626
+ this.setShader("default", vertString$1, fragString$1, graphicAttributes);
1627
+ }
1628
+ startRender(offsetX, offsetY, texture, uniforms) {
1629
+ uniforms && this.currentShader?.setUniforms(uniforms, this);
1630
+ this.offset = new Vec2(offsetX, offsetY);
1631
+ this.vertex = 0;
1632
+ this.webglArrayBuffer.clear();
1633
+ if (texture && texture.base) {
1634
+ this.texture = this.useTexture(texture.base.texture)[0];
1635
+ }
1636
+ }
1637
+ addVertex(x, y, u, v, color) {
1638
+ this.webglArrayBuffer.resize(FLOAT32_PER_VERTEX);
1639
+ super.addVertex(x + this.offset.x, y + this.offset.y);
1640
+ this.webglArrayBuffer.pushUint32(color);
1641
+ this.webglArrayBuffer.pushFloat32(u);
1642
+ this.webglArrayBuffer.pushFloat32(v);
1643
+ this.vertex += 1;
1644
+ }
1645
+ executeRender() {
1646
+ super.executeRender();
1647
+ const gl = this.gl;
1648
+ gl.uniform1i(this.currentShader.uniformLoc["uUseTexture"], typeof this.texture == "undefined" ? 0 : 1);
1649
+ if (this.texture) {
1650
+ gl.uniform1i(this.currentShader.uniformLoc["uTexture"], this.texture);
1651
+ }
1652
+ gl.drawArrays(this.drawType, 0, this.vertex);
1653
+ this.drawType = this.rapid.gl.TRIANGLE_FAN;
1654
+ this.vertex = 0;
1655
+ this.texture = void 0;
1656
+ }
1657
+ }
1658
+ const INDEX_PER_SPRITE = 6;
1659
+ const VERTEX_PER_SPRITE = 4;
1660
+ const SPRITE_ELMENT_PER_VERTEX = 5;
1661
+ const FLOAT32_PER_SPRITE = VERTEX_PER_SPRITE * SPRITE_ELMENT_PER_VERTEX;
1662
+ const MAX_BATCH = Math.floor(2 ** 16 / VERTEX_PER_SPRITE);
1663
+ class SpriteElementArray extends WebglElementBufferArray {
1664
+ constructor(gl, max) {
1665
+ super(gl, INDEX_PER_SPRITE, VERTEX_PER_SPRITE, max);
1666
+ }
1667
+ addObject(vertex) {
1668
+ super.addObject();
1669
+ this.pushUint16(vertex);
1670
+ this.pushUint16(vertex + 1);
1671
+ this.pushUint16(vertex + 2);
1672
+ this.pushUint16(vertex);
1673
+ this.pushUint16(vertex + 3);
1674
+ this.pushUint16(vertex + 2);
1675
+ }
1676
+ }
1677
+ class SpriteRegion extends RenderRegion {
1678
+ constructor(rapid) {
1679
+ const gl = rapid.gl;
1680
+ super(rapid);
1681
+ this.batchSprite = 0;
1682
+ this.spriteTextureUnits = [];
1683
+ this.spriteTextureUnitIndexOffset = 0;
1684
+ this.setShader("default", vertString, fragString, spriteAttributes);
1685
+ this.indexBuffer = new SpriteElementArray(gl, MAX_BATCH);
1686
+ }
1687
+ addVertex(x, y, u, v, textureUnit, color) {
1688
+ super.addVertex(x, y);
1689
+ this.webglArrayBuffer.pushFloat32(u);
1690
+ this.webglArrayBuffer.pushFloat32(v);
1691
+ this.webglArrayBuffer.pushFloat32(textureUnit);
1692
+ this.webglArrayBuffer.pushUint32(color);
1693
+ }
1694
+ renderSprite(texture, width, height, u0, v0, u1, v1, offsetX, offsetY, color, uniforms, flipX, flipY, additionalTexturenit = 0) {
1695
+ if (1 + additionalTexturenit > this.freeTextureUnitNum || this.batchSprite >= MAX_BATCH || this.isUnifromChanged(uniforms) || this.rapid.projectionDirty) {
1696
+ this.render();
1697
+ if (uniforms && this.isUnifromChanged(uniforms)) {
1698
+ this.currentShader.setUniforms(uniforms, this);
1699
+ this.setCurrentUniform(uniforms);
1700
+ }
1701
+ if (this.rapid.projectionDirty) this.updateProjection();
1702
+ }
1703
+ this.batchSprite++;
1704
+ this.webglArrayBuffer.resize(FLOAT32_PER_SPRITE);
1705
+ const [textureUnit, isNew] = this.useTexture(texture);
1706
+ if (isNew) {
1707
+ this.spriteTextureUnits.push(textureUnit);
1708
+ this.spriteTextureUnitIndexOffset = this.spriteTextureUnits[0];
1709
+ }
1710
+ const textureIndex = textureUnit - this.spriteTextureUnitIndexOffset;
1711
+ const uA = flipX ? u1 : u0;
1712
+ const uB = flipX ? u0 : u1;
1713
+ const vA = flipY ? v1 : v0;
1714
+ const vB = flipY ? v0 : v1;
1715
+ const x1 = offsetX;
1716
+ const x2 = offsetX + width;
1717
+ const y1 = offsetY;
1718
+ const y2 = offsetY + height;
1719
+ this.addVertex(x1, y1, uA, vA, textureIndex, color);
1720
+ this.addVertex(x2, y1, uB, vA, textureIndex, color);
1721
+ this.addVertex(x2, y2, uB, vB, textureIndex, color);
1722
+ this.addVertex(x1, y2, uA, vB, textureIndex, color);
1723
+ }
1724
+ executeRender() {
1725
+ super.executeRender();
1726
+ if (this.batchSprite <= 0) return;
1727
+ const gl = this.gl;
1728
+ if (this.spriteTextureUnits.length > 0) {
1729
+ this.gl.uniform1iv(
1730
+ this.currentShader.uniformLoc["uTextures"],
1731
+ this.spriteTextureUnits
1732
+ );
1733
+ }
1734
+ gl.drawElements(gl.TRIANGLES, this.batchSprite * INDEX_PER_SPRITE, gl.UNSIGNED_SHORT, 0);
1735
+ }
1736
+ enterRegion(customShader) {
1737
+ super.enterRegion(customShader);
1738
+ this.indexBuffer.bindBuffer();
1739
+ }
1740
+ initializeForNextRender() {
1741
+ super.initializeForNextRender();
1742
+ this.batchSprite = 0;
1743
+ this.spriteTextureUnits.length = 0;
1744
+ }
1745
+ hasPendingContent() {
1746
+ return this.batchSprite > 0;
1747
+ }
1748
+ }
1749
+ class TextureCache {
1750
+ constructor(render, antialias) {
1751
+ this.cache = /* @__PURE__ */ new Map();
1752
+ this.render = render;
1753
+ this.antialias = antialias;
1754
+ }
1755
+ /**
1756
+ * create texture from url
1757
+ * Equivalent to {@link Texture.fromUrl} method
1758
+ * @param url
1759
+ * @param antialias
1760
+ * @returns
1761
+ */
1762
+ async textureFromUrl(url, antialias = this.antialias, wrapMode = TextureWrapMode.CLAMP) {
1763
+ let base = this.cache.get(url);
1764
+ if (!base) {
1765
+ const image = await this.loadImage(url);
1766
+ base = BaseTexture.fromImageSource(this.render, image, antialias, wrapMode);
1767
+ base.cacheKey = url;
1768
+ this.cache.set(url, base);
1769
+ }
1770
+ return new Texture(base);
1771
+ }
1772
+ /**
1773
+ * Create a new `Texture` instance from a FrameBufferObject.
1774
+ * @param fbo - The FrameBufferObject to create the texture from.
1775
+ * @returns A new `Texture` instance created from the specified FrameBufferObject.
1776
+ */
1777
+ textureFromFrameBufferObject(fbo) {
1778
+ return new Texture(fbo);
1779
+ }
1780
+ /**
1781
+ * Create a new `Texture` instance from an image source.
1782
+ * @param source - The image source to create the texture from.
1783
+ * @param antialias - Whether to enable antialiasing.
1784
+ * @returns A new `Texture` instance created from the specified image source.
1785
+ */
1786
+ textureFromSource(source, antialias = this.antialias, wrapMode = TextureWrapMode.CLAMP) {
1787
+ let base = this.cache.get(source);
1788
+ if (!base) {
1789
+ base = BaseTexture.fromImageSource(this.render, source, antialias, wrapMode);
1790
+ base.cacheKey = source;
1791
+ this.cache.set(source, base);
1792
+ }
1793
+ return new Texture(base);
1794
+ }
1795
+ /**
1796
+ * Load an image from the specified URL.
1797
+ * @param url - The URL of the image to load.
1798
+ * @returns A promise that resolves to the loaded HTMLImageElement.
1799
+ */
1800
+ async loadImage(url) {
1801
+ return new Promise((resolve) => {
1802
+ const image = new Image();
1803
+ image.onload = () => {
1804
+ resolve(image);
1805
+ };
1806
+ image.src = url;
1807
+ });
1808
+ }
1809
+ /**
1810
+ * Create a new `Text` instance.
1811
+ * @param options - The options for rendering the text, such as font, size, color, etc.
1812
+ * @returns A new `Text` instance.
1813
+ */
1814
+ createText(options) {
1815
+ return new Text(this.render, options);
1816
+ }
1817
+ /**
1818
+ * Destroy the texture
1819
+ * @param texture
1820
+ */
1821
+ destroy(texture) {
1822
+ const baseTexture = texture instanceof Texture ? texture.base : texture;
1823
+ if (baseTexture) {
1824
+ baseTexture.destroy(this.render.gl);
1825
+ this.removeCache(baseTexture);
1826
+ }
1827
+ }
1828
+ /**
1829
+ * Create a new FrameBufferObject instance
1830
+ * @param width - Width of the framebuffer
1831
+ * @param height - Height of the framebuffer
1832
+ * @param antialias - Whether to enable antialiasing
1833
+ * @returns A new FrameBufferObject instance
1834
+ */
1835
+ createFrameBufferObject(width, height, antialias = this.antialias) {
1836
+ return new FrameBufferObject(this.render, width, height, antialias);
1837
+ }
1838
+ removeCache(baseTexture) {
1839
+ if (baseTexture.cacheKey) {
1840
+ this.cache.delete(baseTexture.cacheKey);
1841
+ }
1842
+ }
1843
+ }
1844
+ class BaseTexture {
1845
+ constructor(texture, width, height, wrapMode = TextureWrapMode.CLAMP) {
1846
+ this.cacheKey = null;
1847
+ this.texture = texture;
1848
+ this.width = width;
1849
+ this.height = height;
1850
+ this.wrapMode = wrapMode;
1851
+ }
1852
+ static fromImageSource(r, image, antialias = false, wrapMode = TextureWrapMode.CLAMP) {
1853
+ return new BaseTexture(
1854
+ createTexture(r.gl, image, antialias, false, false, wrapMode),
1855
+ image.width,
1856
+ image.height
1857
+ );
1858
+ }
1859
+ /**
1860
+ * Destroy the texture
1861
+ * @param gl
1862
+ * @ignore
1863
+ */
1864
+ destroy(gl) {
1865
+ gl.deleteTexture(this.texture);
1866
+ }
1867
+ }
1868
+ class Texture {
1869
+ /**
1870
+ * Creates a new `Texture` instance with the specified base texture reference.
1871
+ * @param base - The {@link BaseTexture} to be used by the texture.
1872
+ */
1873
+ constructor(base) {
1874
+ this.scale = 1;
1875
+ this.setBaseTexture(base);
1876
+ }
1877
+ /**
1878
+ * Set or change BaseTexture
1879
+ * @param base
1880
+ * @param scale
1881
+ */
1882
+ setBaseTexture(base) {
1883
+ if (base) {
1884
+ this.base = base;
1885
+ this.setClipRegion(0, 0, base.width, base.height);
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Sets the region of the texture to be used for rendering.
1890
+ * @param x - The x-coordinate of the top-left corner of the region.
1891
+ * @param y - The y-coordinate of the top-left corner of the region.
1892
+ * @param w - The width of the region.
1893
+ * @param h - The height of the region.
1894
+ */
1895
+ setClipRegion(x, y, w, h) {
1896
+ if (!this.base) return;
1897
+ this.clipX = x / this.base.width;
1898
+ this.clipY = y / this.base.height;
1899
+ this.clipW = this.clipX + w / this.base.width;
1900
+ this.clipH = this.clipY + h / this.base.height;
1901
+ this.width = w * this.scale;
1902
+ this.height = h * this.scale;
1903
+ return this;
1904
+ }
1905
+ /**
1906
+ * Creates a new `Texture` instance from the specified image source.
1907
+ * @param rapid - The Rapid instance to use.
1908
+ * @param image - The image source to create the texture from.
1909
+ * @param antialias - Whether to enable antialiasing for the texture. Default is `false`.
1910
+ * @returns A new `Texture` instance created from the image source.
1911
+ */
1912
+ static fromImageSource(rapid, image, antialias = false) {
1913
+ return new Texture(
1914
+ BaseTexture.fromImageSource(rapid, image, antialias)
1915
+ );
1916
+ }
1917
+ static fromFrameBufferObject(fbo) {
1918
+ return new Texture(fbo);
1919
+ }
1920
+ /**
1921
+ * Converts the current texture into a spritesheet.
1922
+ * @param rapid - The Rapid instance to use.
1923
+ * @param spriteWidth - The width of each sprite in the spritesheet.
1924
+ * @param spriteHeight - The height of each sprite in the spritesheet.
1925
+ * @returns An array of `Texture` instances representing the sprites in the spritesheet.
1926
+ */
1927
+ createSpritesheet(spriteWidth, spriteHeight) {
1928
+ if (!this.base) return [];
1929
+ const sprites = [];
1930
+ const columns = Math.floor(this.base.width / spriteWidth);
1931
+ const rows = Math.floor(this.base.height / spriteHeight);
1932
+ for (let y = 0; y < rows; y++) {
1933
+ for (let x = 0; x < columns; x++) {
1934
+ const sprite = this.clone();
1935
+ sprite.setClipRegion(
1936
+ x * spriteWidth,
1937
+ y * spriteHeight,
1938
+ spriteWidth,
1939
+ spriteHeight
1940
+ );
1941
+ sprites.push(sprite);
1942
+ }
1943
+ }
1944
+ return sprites;
1945
+ }
1946
+ /**
1947
+ * Clone the current texture
1948
+ * @returns A new `Texture` instance with the same base texture reference.
1949
+ */
1950
+ clone() {
1951
+ return new Texture(this.base);
1952
+ }
1953
+ }
1954
+ const SCALEFACTOR = 2.5;
1955
+ class Text extends Texture {
1956
+ constructor(rapid, options) {
1957
+ super();
1958
+ this.scale = 1 / SCALEFACTOR;
1959
+ this.rapid = rapid;
1960
+ this.options = options;
1961
+ this.text = options.text || " ";
1962
+ this.updateTextImage();
1963
+ }
1964
+ updateTextImage() {
1965
+ if (this.base) {
1966
+ this.base.destroy(this.rapid.gl);
1967
+ }
1968
+ const canvas = this.createTextCanvas();
1969
+ this.setBaseTexture(BaseTexture.fromImageSource(this.rapid, canvas, true));
1970
+ }
1971
+ createTextCanvas() {
1972
+ const canvas = document.createElement("canvas");
1973
+ const context = canvas.getContext("2d");
1974
+ if (!context) throw new Error("Failed to get 2D canvas context");
1975
+ const fontSize = this.options.fontSize || 16;
1976
+ const font = `${fontSize}px ${this.options.fontFamily || "Arial"}`;
1977
+ const lines = this.text.split("\n");
1978
+ context.font = font;
1979
+ let maxWidth = 0;
1980
+ for (const line of lines) {
1981
+ const metrics = context.measureText(line);
1982
+ maxWidth = Math.max(maxWidth, metrics.width);
1983
+ }
1984
+ const totalHeight = fontSize * lines.length;
1985
+ canvas.width = Math.ceil(maxWidth) * SCALEFACTOR;
1986
+ canvas.height = Math.ceil(totalHeight) * SCALEFACTOR;
1987
+ context.scale(SCALEFACTOR, SCALEFACTOR);
1988
+ context.font = font;
1989
+ context.fillStyle = this.options.color ? this.options.color.toHexString() : "#000";
1990
+ context.textAlign = this.options.textAlign || "left";
1991
+ context.textBaseline = this.options.textBaseline || "top";
1992
+ let yOffset = 0;
1993
+ for (const line of lines) {
1994
+ context.fillText(line, 0, yOffset);
1995
+ yOffset += fontSize;
1996
+ }
1997
+ return canvas;
1998
+ }
1999
+ /**
2000
+ * Updates the displayed text. Re-renders the texture if the text has changed.
2001
+ * @param text - The new text to display.
2002
+ */
2003
+ setText(text) {
2004
+ if (this.text === text) return;
2005
+ this.text = text || " ";
2006
+ this.updateTextImage();
2007
+ }
2008
+ }
2009
+ class FrameBufferObject extends BaseTexture {
2010
+ /**
2011
+ * Creates a new FrameBufferObject instance
2012
+ * @param render - The Rapid instance to use
2013
+ * @param width - Width of the framebuffer
2014
+ * @param height - Height of the framebuffer
2015
+ * @param antialias - Whether to enable antialiasing
2016
+ */
2017
+ constructor(render, width, height, antialias = false) {
2018
+ const gl = render.gl;
2019
+ const texture = createTexture(gl, { width, height }, antialias, true, false);
2020
+ const framebuffer = gl.createFramebuffer();
2021
+ if (!framebuffer) {
2022
+ gl.deleteTexture(texture);
2023
+ throw new Error("Failed to create WebGL framebuffer");
2024
+ }
2025
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
2026
+ gl.framebufferTexture2D(
2027
+ gl.FRAMEBUFFER,
2028
+ gl.COLOR_ATTACHMENT0,
2029
+ gl.TEXTURE_2D,
2030
+ texture,
2031
+ 0
2032
+ );
2033
+ const stencilBuffer = gl.createRenderbuffer();
2034
+ if (!stencilBuffer) {
2035
+ gl.deleteFramebuffer(framebuffer);
2036
+ gl.deleteTexture(texture);
2037
+ throw new Error("Failed to create depth-stencil renderbuffer");
2038
+ }
2039
+ gl.bindRenderbuffer(gl.RENDERBUFFER, stencilBuffer);
2040
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, width, height);
2041
+ gl.framebufferRenderbuffer(
2042
+ gl.FRAMEBUFFER,
2043
+ gl.STENCIL_ATTACHMENT,
2044
+ gl.RENDERBUFFER,
2045
+ stencilBuffer
2046
+ );
2047
+ super(texture, width, height);
2048
+ this.gl = gl;
2049
+ this.framebuffer = framebuffer;
2050
+ this.stencilBuffer = stencilBuffer;
2051
+ gl.bindTexture(gl.TEXTURE_2D, null);
2052
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2053
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
2054
+ }
2055
+ /**
2056
+ * Bind the framebuffer for rendering
2057
+ * @ignore
2058
+ */
2059
+ bind(bgColor) {
2060
+ const gl = this.gl;
2061
+ gl.bindTexture(gl.TEXTURE_2D, null);
2062
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
2063
+ gl.clearColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a);
2064
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
2065
+ }
2066
+ /**
2067
+ * Unbind the framebuffer and restore default framebuffer
2068
+ * @ignore
2069
+ */
2070
+ unbind() {
2071
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
2072
+ this.gl.bindTexture(this.gl.TEXTURE_2D, null);
2073
+ }
2074
+ /**
2075
+ * Resize the framebuffer
2076
+ * @param width - New width
2077
+ * @param height - New height
2078
+ */
2079
+ resize(width, height) {
2080
+ if (this.width === width && this.height === height) {
2081
+ return;
2082
+ }
2083
+ this.width = width;
2084
+ this.height = height;
2085
+ const gl = this.gl;
2086
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
2087
+ gl.texImage2D(
2088
+ gl.TEXTURE_2D,
2089
+ 0,
2090
+ gl.RGBA,
2091
+ width,
2092
+ height,
2093
+ 0,
2094
+ gl.RGBA,
2095
+ gl.UNSIGNED_BYTE,
2096
+ null
2097
+ );
2098
+ gl.bindTexture(gl.TEXTURE_2D, null);
2099
+ gl.bindRenderbuffer(gl.RENDERBUFFER, this.stencilBuffer);
2100
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, width, height);
2101
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
2102
+ }
2103
+ /**
2104
+ * Override destroy method to clean up framebuffer resources
2105
+ * @param gl - WebGL context
2106
+ */
2107
+ destroy(gl) {
2108
+ gl.deleteFramebuffer(this.framebuffer);
2109
+ gl.deleteRenderbuffer(this.stencilBuffer);
2110
+ super.destroy(gl);
2111
+ }
2112
+ }
2113
+ function getDefaultExportFromCjs(x) {
2114
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
2115
+ }
2116
+ var eventemitter3 = { exports: {} };
2117
+ var hasRequiredEventemitter3;
2118
+ function requireEventemitter3() {
2119
+ if (hasRequiredEventemitter3) return eventemitter3.exports;
2120
+ hasRequiredEventemitter3 = 1;
2121
+ (function(module) {
2122
+ var has = Object.prototype.hasOwnProperty, prefix = "~";
2123
+ function Events() {
2124
+ }
2125
+ if (Object.create) {
2126
+ Events.prototype = /* @__PURE__ */ Object.create(null);
2127
+ if (!new Events().__proto__) prefix = false;
2128
+ }
2129
+ function EE(fn, context, once) {
2130
+ this.fn = fn;
2131
+ this.context = context;
2132
+ this.once = once || false;
2133
+ }
2134
+ function addListener(emitter, event, fn, context, once) {
2135
+ if (typeof fn !== "function") {
2136
+ throw new TypeError("The listener must be a function");
2137
+ }
2138
+ var listener = new EE(fn, context || emitter, once), evt = prefix ? prefix + event : event;
2139
+ if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
2140
+ else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
2141
+ else emitter._events[evt] = [emitter._events[evt], listener];
2142
+ return emitter;
2143
+ }
2144
+ function clearEvent(emitter, evt) {
2145
+ if (--emitter._eventsCount === 0) emitter._events = new Events();
2146
+ else delete emitter._events[evt];
2147
+ }
2148
+ function EventEmitter2() {
2149
+ this._events = new Events();
2150
+ this._eventsCount = 0;
2151
+ }
2152
+ EventEmitter2.prototype.eventNames = function eventNames() {
2153
+ var names = [], events, name;
2154
+ if (this._eventsCount === 0) return names;
2155
+ for (name in events = this._events) {
2156
+ if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
2157
+ }
2158
+ if (Object.getOwnPropertySymbols) {
2159
+ return names.concat(Object.getOwnPropertySymbols(events));
2160
+ }
2161
+ return names;
2162
+ };
2163
+ EventEmitter2.prototype.listeners = function listeners(event) {
2164
+ var evt = prefix ? prefix + event : event, handlers = this._events[evt];
2165
+ if (!handlers) return [];
2166
+ if (handlers.fn) return [handlers.fn];
2167
+ for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
2168
+ ee[i] = handlers[i].fn;
2169
+ }
2170
+ return ee;
2171
+ };
2172
+ EventEmitter2.prototype.listenerCount = function listenerCount(event) {
2173
+ var evt = prefix ? prefix + event : event, listeners = this._events[evt];
2174
+ if (!listeners) return 0;
2175
+ if (listeners.fn) return 1;
2176
+ return listeners.length;
2177
+ };
2178
+ EventEmitter2.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
2179
+ var evt = prefix ? prefix + event : event;
2180
+ if (!this._events[evt]) return false;
2181
+ var listeners = this._events[evt], len = arguments.length, args, i;
2182
+ if (listeners.fn) {
2183
+ if (listeners.once) this.removeListener(event, listeners.fn, void 0, true);
2184
+ switch (len) {
2185
+ case 1:
2186
+ return listeners.fn.call(listeners.context), true;
2187
+ case 2:
2188
+ return listeners.fn.call(listeners.context, a1), true;
2189
+ case 3:
2190
+ return listeners.fn.call(listeners.context, a1, a2), true;
2191
+ case 4:
2192
+ return listeners.fn.call(listeners.context, a1, a2, a3), true;
2193
+ case 5:
2194
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
2195
+ case 6:
2196
+ return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
2197
+ }
2198
+ for (i = 1, args = new Array(len - 1); i < len; i++) {
2199
+ args[i - 1] = arguments[i];
2200
+ }
2201
+ listeners.fn.apply(listeners.context, args);
2202
+ } else {
2203
+ var length = listeners.length, j;
2204
+ for (i = 0; i < length; i++) {
2205
+ if (listeners[i].once) this.removeListener(event, listeners[i].fn, void 0, true);
2206
+ switch (len) {
2207
+ case 1:
2208
+ listeners[i].fn.call(listeners[i].context);
2209
+ break;
2210
+ case 2:
2211
+ listeners[i].fn.call(listeners[i].context, a1);
2212
+ break;
2213
+ case 3:
2214
+ listeners[i].fn.call(listeners[i].context, a1, a2);
2215
+ break;
2216
+ case 4:
2217
+ listeners[i].fn.call(listeners[i].context, a1, a2, a3);
2218
+ break;
2219
+ default:
2220
+ if (!args) for (j = 1, args = new Array(len - 1); j < len; j++) {
2221
+ args[j - 1] = arguments[j];
2222
+ }
2223
+ listeners[i].fn.apply(listeners[i].context, args);
2224
+ }
2225
+ }
2226
+ }
2227
+ return true;
2228
+ };
2229
+ EventEmitter2.prototype.on = function on(event, fn, context) {
2230
+ return addListener(this, event, fn, context, false);
2231
+ };
2232
+ EventEmitter2.prototype.once = function once(event, fn, context) {
2233
+ return addListener(this, event, fn, context, true);
2234
+ };
2235
+ EventEmitter2.prototype.removeListener = function removeListener(event, fn, context, once) {
2236
+ var evt = prefix ? prefix + event : event;
2237
+ if (!this._events[evt]) return this;
2238
+ if (!fn) {
2239
+ clearEvent(this, evt);
2240
+ return this;
2241
+ }
2242
+ var listeners = this._events[evt];
2243
+ if (listeners.fn) {
2244
+ if (listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context)) {
2245
+ clearEvent(this, evt);
2246
+ }
2247
+ } else {
2248
+ for (var i = 0, events = [], length = listeners.length; i < length; i++) {
2249
+ if (listeners[i].fn !== fn || once && !listeners[i].once || context && listeners[i].context !== context) {
2250
+ events.push(listeners[i]);
2251
+ }
2252
+ }
2253
+ if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
2254
+ else clearEvent(this, evt);
2255
+ }
2256
+ return this;
2257
+ };
2258
+ EventEmitter2.prototype.removeAllListeners = function removeAllListeners(event) {
2259
+ var evt;
2260
+ if (event) {
2261
+ evt = prefix ? prefix + event : event;
2262
+ if (this._events[evt]) clearEvent(this, evt);
2263
+ } else {
2264
+ this._events = new Events();
2265
+ this._eventsCount = 0;
2266
+ }
2267
+ return this;
2268
+ };
2269
+ EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
2270
+ EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
2271
+ EventEmitter2.prefixed = prefix;
2272
+ EventEmitter2.EventEmitter = EventEmitter2;
2273
+ {
2274
+ module.exports = EventEmitter2;
2275
+ }
2276
+ })(eventemitter3);
2277
+ return eventemitter3.exports;
2278
+ }
2279
+ var eventemitter3Exports = requireEventemitter3();
2280
+ const EventEmitter = /* @__PURE__ */ getDefaultExportFromCjs(eventemitter3Exports);
2281
+ class AssetsLoader extends EventEmitter {
2282
+ /**
2283
+ * Creates an instance of AssetsLoader.
2284
+ * Initializes the asset storage and counters.
2285
+ */
2286
+ constructor(game) {
2287
+ super();
2288
+ this.totalAsset = 0;
2289
+ this.loadedAssets = 0;
2290
+ this.game = game;
2291
+ this.assets = {
2292
+ json: {},
2293
+ audio: {},
2294
+ images: {}
2295
+ };
2296
+ }
2297
+ /**
2298
+ * Loads a JSON file asynchronously.
2299
+ * @param name - The unique identifier for the JSON asset.
2300
+ * @param url - The URL of the JSON file.
2301
+ */
2302
+ loadJson(name, url) {
2303
+ this.totalAsset++;
2304
+ fetch(url).then((response) => {
2305
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
2306
+ return response.json();
2307
+ }).then((data) => {
2308
+ this.assets.json[name] = data;
2309
+ this.assetLoaded();
2310
+ }).catch((error) => this.handleError(name, url, error));
2311
+ }
2312
+ /**
2313
+ * Loads an audio file asynchronously using the AudioManager.
2314
+ * The AudioManager will handle fetching and caching.
2315
+ * @param name - The unique identifier for the audio asset.
2316
+ * @param url - The URL of the audio file.
2317
+ */
2318
+ async loadAudio(name, url) {
2319
+ this.totalAsset++;
2320
+ try {
2321
+ const audioPlayer = await this.game.audio.audioFromUrl(url);
2322
+ this.assets.audio[name] = audioPlayer;
2323
+ this.assetLoaded();
2324
+ } catch (error) {
2325
+ this.handleError(name, url, error);
2326
+ this.assetLoaded();
2327
+ }
2328
+ }
2329
+ /**
2330
+ * Loads an image file asynchronously using the TextureManager.
2331
+ * @param name - The unique identifier for the image asset.
2332
+ * @param url - The URL of the image file.
2333
+ */
2334
+ async loadImage(name, url) {
2335
+ this.totalAsset++;
2336
+ try {
2337
+ this.assets.images[name] = await this.game.render.texture.textureFromUrl(url);
2338
+ this.assetLoaded();
2339
+ } catch (error) {
2340
+ this.handleError(name, url, error);
2341
+ this.assetLoaded();
2342
+ }
2343
+ }
2344
+ /**
2345
+ * Loads multiple assets from a list.
2346
+ * @param assetList - Array of assets to load.
2347
+ */
2348
+ loadAssets(assetList) {
2349
+ if (assetList.length === 0) {
2350
+ this.emit("progress", 1, 0, 0);
2351
+ this.emit("complete", this.assets);
2352
+ return;
2353
+ }
2354
+ assetList.forEach((asset) => {
2355
+ switch (asset.type) {
2356
+ case "json":
2357
+ this.loadJson(asset.name, asset.url);
2358
+ break;
2359
+ case "audio":
2360
+ this.loadAudio(asset.name, asset.url);
2361
+ break;
2362
+ case "image":
2363
+ this.loadImage(asset.name, asset.url);
2364
+ break;
2365
+ default:
2366
+ console.warn(`Unknown asset type: ${asset.type}`);
2367
+ }
2368
+ });
2369
+ }
2370
+ /**
2371
+ * Handles the completion of an asset load.
2372
+ * Updates progress and triggers completion event if all assets are loaded.
2373
+ */
2374
+ assetLoaded() {
2375
+ this.loadedAssets++;
2376
+ const progress = this.totalAsset > 0 ? this.loadedAssets / this.totalAsset : 1;
2377
+ this.emit("progress", progress, this.loadedAssets, this.totalAsset);
2378
+ if (this.loadedAssets >= this.totalAsset) {
2379
+ this.emit("complete", this.assets);
2380
+ }
2381
+ }
2382
+ /**
2383
+ * Handles errors during asset loading.
2384
+ * @param name - The name of the asset.
2385
+ * @param url - The URL of the asset.
2386
+ * @param error - The error object or message.
2387
+ */
2388
+ handleError(name, url, error) {
2389
+ this.emit("error", { name, url, error });
2390
+ console.error(`Failed to load asset '${name}' from ${url}:`, error);
2391
+ }
2392
+ /**
2393
+ * Retrieves a loaded asset by type and name.
2394
+ * @param type - The type of asset ('json', 'audio', or 'images').
2395
+ * @param name - The name of the asset.
2396
+ * @returns The loaded asset or undefined if not found.
2397
+ */
2398
+ get(type, name) {
2399
+ return this.assets[type]?.[name];
2400
+ }
2401
+ /**
2402
+ * Retrieves a loaded JSON asset by name.
2403
+ * @param name - The name of the JSON asset.
2404
+ * @returns The loaded JSON data or undefined if not found.
2405
+ */
2406
+ getJSON(name) {
2407
+ return this.assets.json[name];
2408
+ }
2409
+ /**
2410
+ * Retrieves a loaded audio asset by name.
2411
+ * @param name - The name of the audio asset.
2412
+ * @returns The loaded AudioPlayer instance or undefined if not found.
2413
+ */
2414
+ getAudio(name) {
2415
+ return this.assets.audio[name];
2416
+ }
2417
+ /**
2418
+ * Retrieves a loaded texture (image) asset by name.
2419
+ * @param name - The name of the texture asset.
2420
+ * @returns The loaded texture or undefined if not found.
2421
+ */
2422
+ getTexture(name) {
2423
+ return this.assets.images[name];
2424
+ }
2425
+ }
2426
+ class AudioPlayer extends EventEmitter {
2427
+ constructor(audioContext, audioBuffer) {
2428
+ super();
2429
+ this.sourceNode = null;
2430
+ this._volume = 1;
2431
+ this._loop = false;
2432
+ this._isPlaying = false;
2433
+ this.audioContext = audioContext;
2434
+ this.audioBuffer = audioBuffer;
2435
+ this.gainNode = this.audioContext.createGain();
2436
+ this.gainNode.connect(this.audioContext.destination);
2437
+ }
2438
+ /**
2439
+ * Plays the audio. If already playing, it will stop the current playback and start over.
2440
+ */
2441
+ play() {
2442
+ if (!this.audioContext || !this.audioBuffer || !this.gainNode) {
2443
+ console.warn("Cannot play audio on a destroyed AudioPlayer.");
2444
+ return;
2445
+ }
2446
+ if (this._isPlaying) {
2447
+ this.stop();
2448
+ }
2449
+ this.sourceNode = this.audioContext.createBufferSource();
2450
+ this.sourceNode.buffer = this.audioBuffer;
2451
+ this.sourceNode.loop = this._loop;
2452
+ this.sourceNode.connect(this.gainNode);
2453
+ this.sourceNode.onended = () => {
2454
+ if (!this._isPlaying) return;
2455
+ this._isPlaying = false;
2456
+ this.sourceNode = null;
2457
+ this.emit("ended");
2458
+ };
2459
+ this.sourceNode.start(0);
2460
+ this._isPlaying = true;
2461
+ }
2462
+ /**
2463
+ * Stops the audio playback immediately.
2464
+ */
2465
+ stop() {
2466
+ if (this.sourceNode) {
2467
+ this.sourceNode.stop();
2468
+ }
2469
+ this._isPlaying = false;
2470
+ }
2471
+ /**
2472
+ * Gets the volume of the audio, from 0.0 to 1.0.
2473
+ */
2474
+ get volume() {
2475
+ return this._volume;
2476
+ }
2477
+ /**
2478
+ * Sets the volume of the audio.
2479
+ * @param value - The volume, from 0.0 (silent) to 1.0 (full).
2480
+ */
2481
+ set volume(value) {
2482
+ this._volume = Math.max(0, Math.min(1, value));
2483
+ if (this.gainNode && this.audioContext) {
2484
+ this.gainNode.gain.setTargetAtTime(this._volume, this.audioContext.currentTime, 0.01);
2485
+ }
2486
+ }
2487
+ /**
2488
+ * Gets whether the audio will loop.
2489
+ */
2490
+ get loop() {
2491
+ return this._loop;
2492
+ }
2493
+ /**
2494
+ * Sets whether the audio should loop.
2495
+ * Can be changed while the audio is playing.
2496
+ */
2497
+ set loop(value) {
2498
+ this._loop = value;
2499
+ if (this.sourceNode) {
2500
+ this.sourceNode.loop = value;
2501
+ }
2502
+ }
2503
+ /**
2504
+ * Gets whether the audio is currently playing.
2505
+ */
2506
+ get isPlaying() {
2507
+ return this._isPlaying;
2508
+ }
2509
+ /**
2510
+ * Stops playback, disconnects audio nodes, and removes all event listeners.
2511
+ * This makes the AudioPlayer instance unusable.
2512
+ */
2513
+ destroy() {
2514
+ this.stop();
2515
+ if (this.gainNode) {
2516
+ this.gainNode.disconnect();
2517
+ }
2518
+ this.removeAllListeners();
2519
+ this.audioBuffer = null;
2520
+ this.audioContext = null;
2521
+ this.gainNode = null;
2522
+ this.sourceNode = null;
2523
+ }
2524
+ }
2525
+ class AudioManager {
2526
+ constructor() {
2527
+ this.cache = /* @__PURE__ */ new Map();
2528
+ try {
2529
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
2530
+ } catch (e) {
2531
+ console.error("Web Audio API is not supported in this browser.");
2532
+ this.audioContext = null;
2533
+ }
2534
+ }
2535
+ /**
2536
+ * Creates an AudioPlayer from a URL.
2537
+ * It fetches, decodes, and caches the audio data. If the URL is already cached,
2538
+ * it skips the network request and uses the cached data.
2539
+ *
2540
+ * @param url - The URL of the audio file.
2541
+ * @returns A Promise that resolves to a new AudioPlayer instance.
2542
+ */
2543
+ async audioFromUrl(url) {
2544
+ if (!this.audioContext) {
2545
+ throw new Error("AudioContext is not available. Cannot process audio.");
2546
+ }
2547
+ let cachedBuffer = this.cache.get(url);
2548
+ if (!cachedBuffer) {
2549
+ const response = await fetch(url);
2550
+ if (!response.ok) {
2551
+ throw new Error(`Failed to fetch audio from ${url}: ${response.statusText}`);
2552
+ }
2553
+ const arrayBuffer = await response.arrayBuffer();
2554
+ try {
2555
+ cachedBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
2556
+ } catch (error) {
2557
+ throw new Error(`Failed to decode audio from ${url}: ${error}`);
2558
+ }
2559
+ this.cache.set(url, cachedBuffer);
2560
+ }
2561
+ return new AudioPlayer(this.audioContext, cachedBuffer);
2562
+ }
2563
+ /**
2564
+ * Destroys the AudioManager, clears the audio cache, and closes the AudioContext.
2565
+ * This releases all associated audio resources. After calling this, the AudioManager
2566
+ * and any AudioPlayers created by it will be unusable.
2567
+ */
2568
+ destroy() {
2569
+ this.cache.clear();
2570
+ if (this.audioContext && this.audioContext.state !== "closed") {
2571
+ this.audioContext.close().catch((err) => {
2572
+ console.error("Error closing AudioContext:", err);
2573
+ });
2574
+ }
2575
+ this.audioContext = null;
2576
+ }
2577
+ }
2578
+ class InputManager {
2579
+ /**
2580
+ * Creates an instance of InputManager.
2581
+ * @param rapid - The Rapid instance for rendering and coordinate conversion.
2582
+ */
2583
+ constructor(rapid) {
2584
+ this.mousePosition = Vec2.ZERO;
2585
+ this.keysDown = /* @__PURE__ */ new Set();
2586
+ this.keysDownLastFrame = /* @__PURE__ */ new Set();
2587
+ this.buttonsDown = /* @__PURE__ */ new Set();
2588
+ this.buttonsDownLastFrame = /* @__PURE__ */ new Set();
2589
+ this.rapid = rapid;
2590
+ this.canvas = rapid.canvas;
2591
+ this.attachEventListeners();
2592
+ }
2593
+ /**
2594
+ * Attaches all necessary event listeners for keyboard and mouse input.
2595
+ * @private
2596
+ */
2597
+ attachEventListeners() {
2598
+ const rect = this.canvas.getBoundingClientRect();
2599
+ this.canvas.addEventListener("mousemove", this.handleMouseMove = (event) => {
2600
+ this.mousePosition = this.rapid.cssToGameCoords(event.clientX - rect.left, event.clientY - rect.top);
2601
+ });
2602
+ window.addEventListener("keydown", this.handleKeyDown = (event) => {
2603
+ this.keysDown.add(event.code);
2604
+ });
2605
+ window.addEventListener("keyup", this.handleKeyUp = (event) => {
2606
+ this.keysDown.delete(event.code);
2607
+ });
2608
+ this.canvas.addEventListener("mousedown", this.handleMouseDown = (event) => {
2609
+ this.buttonsDown.add(event.button);
2610
+ });
2611
+ this.canvas.addEventListener("mouseup", this.handleMouseUp = (event) => {
2612
+ this.buttonsDown.delete(event.button);
2613
+ });
2614
+ }
2615
+ /**
2616
+ * Updates the state of keys and buttons for the next frame.
2617
+ */
2618
+ updateNextFrame() {
2619
+ this.keysDownLastFrame = new Set(this.keysDown);
2620
+ this.buttonsDownLastFrame = new Set(this.buttonsDown);
2621
+ }
2622
+ /**
2623
+ * Checks if a key is currently pressed (continuous detection).
2624
+ * @param key - The key code (e.g., "KeyW", "Space").
2625
+ * @returns True if the key is pressed, false otherwise.
2626
+ */
2627
+ isKeyDown(key) {
2628
+ return this.keysDown.has(key);
2629
+ }
2630
+ /**
2631
+ * Checks if a key is currently released.
2632
+ * @param key - The key code (e.g., "KeyW", "Space").
2633
+ * @returns True if the key is released, false otherwise.
2634
+ */
2635
+ isKeyUp(key) {
2636
+ return !this.keysDown.has(key);
2637
+ }
2638
+ /**
2639
+ * Checks if a key was pressed in the current frame (single trigger).
2640
+ * @param key - The key code (e.g., "KeyW", "Space").
2641
+ * @returns True if the key was just pressed, false otherwise.
2642
+ */
2643
+ wasKeyPressed(key) {
2644
+ return this.keysDown.has(key) && !this.keysDownLastFrame.has(key);
2645
+ }
2646
+ /**
2647
+ * Checks if a key was released in the current frame (single trigger).
2648
+ * @param key - The key code (e.g., "KeyW", "Space").
2649
+ * @returns True if the key was just released, false otherwise.
2650
+ */
2651
+ wasKeyReleased(key) {
2652
+ return !this.keysDown.has(key) && this.keysDownLastFrame.has(key);
2653
+ }
2654
+ /**
2655
+ * Checks if a mouse button is currently pressed (continuous detection).
2656
+ * @param button - The mouse button number (0: left, 1: middle, 2: right).
2657
+ * @returns True if the button is pressed, false otherwise.
2658
+ */
2659
+ isButtonDown(button) {
2660
+ return this.buttonsDown.has(button);
2661
+ }
2662
+ /**
2663
+ * Checks if a mouse button is currently released.
2664
+ * @param button - The mouse button number (0: left, 1: middle, 2: right).
2665
+ * @returns True if the button is released, false otherwise.
2666
+ */
2667
+ isButtonUp(button) {
2668
+ return !this.buttonsDown.has(button);
2669
+ }
2670
+ /**
2671
+ * Checks if a mouse button was pressed in the current frame (single trigger).
2672
+ * @param button - The mouse button number (0: left, 1: middle, 2: right).
2673
+ * @returns True if the button was just pressed, false otherwise.
2674
+ */
2675
+ wasButtonPressed(button) {
2676
+ return this.buttonsDown.has(button) && !this.buttonsDownLastFrame.has(button);
2677
+ }
2678
+ /**
2679
+ * Checks if a mouse button was released in the current frame (single trigger).
2680
+ * @param button - The mouse button number (0: left, 1: middle, 2: right).
2681
+ * @returns True if the button was just released, false otherwise.
2682
+ */
2683
+ wasButtonReleased(button) {
2684
+ return !this.buttonsDown.has(button) && this.buttonsDownLastFrame.has(button);
2685
+ }
2686
+ /**
2687
+ * Converts the mouse position to local coordinates relative to an entity.
2688
+ * @param entity - The entity to convert coordinates for.
2689
+ * @returns The mouse position in the entity's local coordinate system.
2690
+ */
2691
+ getMouseLocal(entity) {
2692
+ return entity.transform.globalToLocal(this.mousePosition);
2693
+ }
2694
+ getAxis(negativeAction, positiveAction) {
2695
+ let value = 0;
2696
+ if (this.isKeyDown(positiveAction)) {
2697
+ value += 1;
2698
+ }
2699
+ if (this.isKeyDown(negativeAction)) {
2700
+ value -= 1;
2701
+ }
2702
+ return value;
2703
+ }
2704
+ getVector(negativeX, positiveX, negativeY, positiveY) {
2705
+ const x = this.getAxis(negativeX, positiveX);
2706
+ const y = this.getAxis(negativeY, positiveY);
2707
+ const vector = new Vec2(x, y);
2708
+ return vector.normalize();
2709
+ }
2710
+ /**
2711
+ * Removes all event listeners and cleans up resources.
2712
+ */
2713
+ destroy() {
2714
+ this.canvas.removeEventListener("mousemove", this.handleMouseMove);
2715
+ window.removeEventListener("keydown", this.handleKeyDown);
2716
+ window.removeEventListener("keyup", this.handleKeyUp);
2717
+ this.canvas.removeEventListener("mousedown", this.handleMouseDown);
2718
+ this.canvas.removeEventListener("mouseup", this.handleMouseUp);
2719
+ this.keysDown.clear();
2720
+ this.keysDownLastFrame.clear();
2721
+ this.buttonsDown.clear();
2722
+ this.buttonsDownLastFrame.clear();
2723
+ }
2724
+ }
2725
+ class Tween extends EventEmitter {
2726
+ /**
2727
+ * Creates an instance of Tween.
2728
+ * @param target - The object whose property will be animated.
2729
+ * @param property - The name of the property to animate.
2730
+ * @param to - The target value of the property.
2731
+ * @param duration - The duration of the animation in seconds.
2732
+ * @param easing - The easing function to use for the animation.
2733
+ */
2734
+ constructor(target, property, to, duration, easing) {
2735
+ super();
2736
+ this.elapsed = 0;
2737
+ this.isRunning = false;
2738
+ this.isFinished = false;
2739
+ this.target = target;
2740
+ this.property = property;
2741
+ this.to = to;
2742
+ this.duration = duration;
2743
+ this.easing = easing;
2744
+ }
2745
+ /**
2746
+ * Starts the tween animation.
2747
+ * It captures the initial state of the property.
2748
+ */
2749
+ start() {
2750
+ const initialValue = this.target[this.property];
2751
+ if (typeof initialValue === "number") {
2752
+ this.from = initialValue;
2753
+ } else if (typeof initialValue.clone === "function") {
2754
+ this.from = initialValue.clone();
2755
+ } else {
2756
+ console.error("Tween target property is not a 'number' or does not have a 'clone' method.");
2757
+ this.isFinished = true;
2758
+ return;
2759
+ }
2760
+ this.elapsed = 0;
2761
+ this.isRunning = true;
2762
+ this.isFinished = false;
2763
+ }
2764
+ /**
2765
+ * Updates the tween's state. This should be called every frame.
2766
+ * @param deltaTime - The time elapsed since the last frame, in seconds.
2767
+ */
2768
+ update(deltaTime) {
2769
+ if (!this.isRunning || this.isFinished) {
2770
+ return;
2771
+ }
2772
+ this.elapsed += deltaTime;
2773
+ const progress = Math.min(this.elapsed / this.duration, 1);
2774
+ const easedProgress = this.easing(progress);
2775
+ let currentValue;
2776
+ if (typeof this.from === "number" && typeof this.to === "number") {
2777
+ currentValue = this.from + (this.to - this.from) * easedProgress;
2778
+ } else {
2779
+ const from = this.from;
2780
+ const to = this.to;
2781
+ const range = to.subtract(from);
2782
+ const step = range.multiply(easedProgress);
2783
+ currentValue = from.add(step);
2784
+ }
2785
+ this.target[this.property] = currentValue;
2786
+ if (progress >= 1) {
2787
+ this.isFinished = true;
2788
+ this.isRunning = false;
2789
+ this.target[this.property] = this.to;
2790
+ this.emit("complete");
2791
+ }
2792
+ }
2793
+ }
2794
+ const isPlainObject = (obj) => {
2795
+ if (typeof obj !== "object" || obj === null) return false;
2796
+ return Object.getPrototypeOf(obj) === Object.prototype;
2797
+ };
2798
+ const _Easing = class _Easing {
2799
+ };
2800
+ _Easing.Linear = (t) => t;
2801
+ _Easing.EaseInQuad = (t) => t * t;
2802
+ _Easing.EaseOutQuad = (t) => t * (2 - t);
2803
+ _Easing.EaseInOutQuad = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
2804
+ _Easing.EaseInCubic = (t) => t * t * t;
2805
+ _Easing.EaseOutCubic = (t) => --t * t * t + 1;
2806
+ _Easing.EaseInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
2807
+ _Easing.EaseInQuart = (t) => t * t * t * t;
2808
+ _Easing.EaseOutQuart = (t) => 1 - --t * t * t * t;
2809
+ _Easing.EaseInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
2810
+ let Easing = _Easing;
2811
+ class Timer extends EventEmitter {
2812
+ /**
2813
+ * Creates an instance of a Timer.
2814
+ * @param duration - The duration of the timer in seconds.
2815
+ * @param onComplete - The callback function to execute when the timer completes a cycle.
2816
+ * @param repeat - Whether the timer should restart automatically after completion. Defaults to false.
2817
+ */
2818
+ constructor(duration, repeat = false) {
2819
+ super();
2820
+ this.elapsedTime = 0;
2821
+ this.isRunning = false;
2822
+ this.isFinished = false;
2823
+ this.duration = duration;
2824
+ this.repeat = repeat;
2825
+ }
2826
+ /**
2827
+ * Starts or resumes the timer.
2828
+ */
2829
+ start() {
2830
+ this.isRunning = true;
2831
+ }
2832
+ /**
2833
+ * Stops (pauses) the timer. It can be resumed with start().
2834
+ */
2835
+ stop() {
2836
+ this.isRunning = false;
2837
+ }
2838
+ /**
2839
+ * Updates the timer's state. This is called by the Game loop every frame.
2840
+ * @param deltaTime - The time elapsed since the last frame, in seconds.
2841
+ * @internal
2842
+ */
2843
+ update(deltaTime) {
2844
+ if (!this.isRunning || this.isFinished) {
2845
+ return;
2846
+ }
2847
+ this.elapsedTime += deltaTime;
2848
+ if (this.elapsedTime >= this.duration) {
2849
+ this.emit("timeout");
2850
+ if (this.repeat) {
2851
+ this.elapsedTime -= this.duration;
2852
+ } else {
2853
+ this.isFinished = true;
2854
+ this.isRunning = false;
2855
+ }
2856
+ }
2857
+ }
2858
+ /**
2859
+ * Immediately stops the timer and marks it as finished, preventing any further execution.
2860
+ * This is used for cleanup to avoid memory leaks from stale callbacks.
2861
+ */
2862
+ destroy() {
2863
+ this.isFinished = true;
2864
+ this.isRunning = false;
2865
+ }
2866
+ }
2867
+ class Entity {
2868
+ /**
2869
+ * Creates an entity with optional transform properties.
2870
+ * @param game - The game instance this entity belongs to.
2871
+ * @param options - Configuration options for position, scale, rotation, and tags.
2872
+ */
2873
+ constructor(game, options = {}) {
2874
+ this.parent = null;
2875
+ this.globalZindex = 0;
2876
+ this.localZindex = 0;
2877
+ this.transform = new MatrixStack();
2878
+ this.children = [];
2879
+ this.transform.pushIdentity();
2880
+ this.game = game;
2881
+ this.rapid = game.render;
2882
+ this.tags = options.tags ?? [];
2883
+ if (options.position) {
2884
+ this.position = options.position;
2885
+ } else {
2886
+ this.position = new Vec2(options.x ?? 0, options.y ?? 0);
2887
+ }
2888
+ if (typeof options.scale === "number") {
2889
+ this.scale = new Vec2(options.scale, options.scale);
2890
+ } else if (options.scale) {
2891
+ this.scale = options.scale;
2892
+ } else {
2893
+ this.scale = new Vec2(1, 1);
2894
+ }
2895
+ this.rotation = options.rotation ?? 0;
2896
+ }
2897
+ get x() {
2898
+ return this.position.x;
2899
+ }
2900
+ get y() {
2901
+ return this.position.y;
2902
+ }
2903
+ set x(nx) {
2904
+ this.position.x = nx;
2905
+ }
2906
+ set y(ny) {
2907
+ this.position.y = ny;
2908
+ }
2909
+ getScene() {
2910
+ return this.game.getMainScene();
2911
+ }
2912
+ /**
2913
+ * Gets the parent transform or the renderer's matrix stack if no parent exists.
2914
+ * @returns The transform matrix stack.
2915
+ */
2916
+ getParentTransform() {
2917
+ return this.parent ? this.parent.transform : this.rapid.matrixStack;
2918
+ }
2919
+ /**
2920
+ * Updates the entity and its children.
2921
+ * @param deltaTime - Time elapsed since the last update in seconds.
2922
+ */
2923
+ update(deltaTime) {
2924
+ this.onUpdate(deltaTime);
2925
+ for (const child of this.children) {
2926
+ child.update(deltaTime);
2927
+ }
2928
+ }
2929
+ /**
2930
+ * Hook for custom update logic.
2931
+ * @param deltaTime - Time elapsed since the last update in seconds.
2932
+ */
2933
+ onUpdate(deltaTime) {
2934
+ }
2935
+ /**
2936
+ * Collects entities that need rendering.
2937
+ * @param queue - The array to collect renderable entities.
2938
+ * @ignore
2939
+ */
2940
+ collectRenderables(queue) {
2941
+ this.updateTransform();
2942
+ if (this.onRender !== Entity.prototype.onRender) {
2943
+ queue.push(this);
2944
+ }
2945
+ for (const child of this.children) {
2946
+ child.collectRenderables(queue);
2947
+ }
2948
+ }
2949
+ /**
2950
+ * Prepares the entity's transform before rendering.
2951
+ * @ignore
2952
+ */
2953
+ beforeOnRender() {
2954
+ const transform = this.transform;
2955
+ this.rapid.matrixStack.setTransform(transform.getTransform());
2956
+ }
2957
+ /**
2958
+ * Hook for custom rendering logic.
2959
+ * @param render - The rendering engine instance.
2960
+ */
2961
+ onRender(render) {
2962
+ }
2963
+ /**
2964
+ * Updates the entity's transform, optionally updating parent transforms.
2965
+ * @param deep - If true, recursively updates parent transforms.
2966
+ */
2967
+ updateTransform(deep = false) {
2968
+ if (deep && this.parent) {
2969
+ this.parent.updateTransform(deep);
2970
+ }
2971
+ const transform = this.transform;
2972
+ const parentTransform = this.getParentTransform();
2973
+ transform.setTransform(parentTransform.getTransform());
2974
+ transform.translate(this.position);
2975
+ transform.rotate(this.rotation);
2976
+ transform.scale(this.scale);
2977
+ }
2978
+ /**
2979
+ * Adds a child entity to this entity.
2980
+ * @param child - The entity to add as a child.
2981
+ */
2982
+ addChild(child) {
2983
+ if (child.parent) {
2984
+ child.parent.removeChild(child);
2985
+ }
2986
+ child.parent = this;
2987
+ this.children.push(child);
2988
+ }
2989
+ /**
2990
+ * Removes a child entity from this entity.
2991
+ * @param child - The entity to remove.
2992
+ */
2993
+ removeChild(child) {
2994
+ const index = this.children.indexOf(child);
2995
+ if (index > -1) {
2996
+ child.parent = null;
2997
+ this.children.splice(index, 1);
2998
+ }
2999
+ }
3000
+ /**
3001
+ * Finds descendant entities matching a predicate.
3002
+ * @param predicate - Function to test each entity.
3003
+ * @param onlyFirst - If true, returns only the first match; otherwise, returns all matches.
3004
+ * @returns A single entity, an array of entities, or null if no matches are found.
3005
+ */
3006
+ findDescendant(predicate, onlyFirst = true) {
3007
+ const results = [];
3008
+ for (const child of this.children) {
3009
+ if (predicate(child)) {
3010
+ if (onlyFirst) {
3011
+ return child;
3012
+ }
3013
+ results.push(child);
3014
+ }
3015
+ const childFind = child.findDescendant(predicate, onlyFirst);
3016
+ if (onlyFirst) {
3017
+ if (childFind) {
3018
+ return childFind;
3019
+ }
3020
+ } else {
3021
+ results.push(...childFind);
3022
+ }
3023
+ }
3024
+ return onlyFirst ? null : results;
3025
+ }
3026
+ /**
3027
+ * Finds descendant entities with all specified tags.
3028
+ * @param tags - Array of tags to match.
3029
+ * @param onlyFirst - If true, returns only the first match; otherwise, returns all matches.
3030
+ * @returns A single entity, an array of entities, or null if no matches are found.
3031
+ */
3032
+ findDescendantByTag(tags, onlyFirst = false) {
3033
+ const predicate = (entity) => {
3034
+ if (tags.length === 0) return false;
3035
+ return tags.every((tag) => entity.tags.includes(tag));
3036
+ };
3037
+ return this.findDescendant(predicate, onlyFirst);
3038
+ }
3039
+ /**
3040
+ * Disposes of the entity and its children.
3041
+ */
3042
+ dispose() {
3043
+ this.postDispose();
3044
+ if (this.parent) {
3045
+ this.parent.removeChild(this);
3046
+ }
3047
+ }
3048
+ /**
3049
+ * Hook for custom cleanup logic before disposal.
3050
+ */
3051
+ postDispose() {
3052
+ this.children.forEach((child) => {
3053
+ child.dispose();
3054
+ });
3055
+ }
3056
+ getMouseLocalPosition() {
3057
+ return this.game.input.getMouseLocal(this);
3058
+ }
3059
+ getMouseGlobalPosition() {
3060
+ return this.game.input.mousePosition;
3061
+ }
3062
+ }
3063
+ class CanvasLayer extends Entity {
3064
+ constructor(game, options) {
3065
+ super(game, options);
3066
+ }
3067
+ updateTransform() {
3068
+ this.transform.identity();
3069
+ }
3070
+ }
3071
+ class Camera extends Entity {
3072
+ constructor(game, options = {}) {
3073
+ super(game, options);
3074
+ this.enable = false;
3075
+ this.center = false;
3076
+ this.positionSmoothingSpeed = 0;
3077
+ this.rotationSmoothingSpeed = 0;
3078
+ this.center = options.center ?? true;
3079
+ this.positionSmoothingSpeed = options.positionSmoothingSpeed ?? 0;
3080
+ this.rotationSmoothingSpeed = options.rotationSmoothingSpeed ?? 0;
3081
+ this._currentRenderPosition = this.position.clone();
3082
+ this._currentRenderRotation = this.rotation;
3083
+ this.setEnable(options.enable ?? false);
3084
+ }
3085
+ /**
3086
+ * 设置此摄像机是否为当前场景的主摄像机。
3087
+ * @param isEnable
3088
+ */
3089
+ setEnable(isEnable) {
3090
+ this.enable = isEnable;
3091
+ if (isEnable) {
3092
+ this.game.setMainCamera(this);
3093
+ } else if (this.game.mainCamera === this) {
3094
+ this.game.setMainCamera(null);
3095
+ }
3096
+ }
3097
+ /**
3098
+ * 每帧更新,用于平滑摄像机的【局部】变换属性。
3099
+ * @param deltaTime
3100
+ */
3101
+ onUpdate(deltaTime) {
3102
+ if (this.positionSmoothingSpeed > 0) {
3103
+ const factor = 1 - Math.exp(-this.positionSmoothingSpeed * deltaTime);
3104
+ this._currentRenderPosition.lerp(this.position, factor);
3105
+ } else {
3106
+ this._currentRenderPosition.copy(this.position);
3107
+ }
3108
+ if (this.rotationSmoothingSpeed > 0) {
3109
+ const factor = 1 - Math.exp(-this.rotationSmoothingSpeed * deltaTime);
3110
+ let diff = this.rotation - this._currentRenderRotation;
3111
+ while (diff < -Math.PI) diff += Math.PI * 2;
3112
+ while (diff > Math.PI) diff -= Math.PI * 2;
3113
+ this._currentRenderRotation += diff * factor;
3114
+ } else {
3115
+ this._currentRenderRotation = this.rotation;
3116
+ }
3117
+ }
3118
+ /**
3119
+ * 根据摄像机的【全局】变换计算最终的视图矩阵。
3120
+ * 这个方法现在正确地处理了父子关系。
3121
+ */
3122
+ updateTransform() {
3123
+ const parentTransform = this.getParentTransform();
3124
+ const globalTransform = this.transform;
3125
+ globalTransform.setTransform(parentTransform.getTransform());
3126
+ globalTransform.translate(this._currentRenderPosition);
3127
+ globalTransform.rotate(this._currentRenderRotation);
3128
+ globalTransform.scale(this.scale);
3129
+ const viewMatrix = globalTransform.getInverse();
3130
+ if (this.center) {
3131
+ const centerX = this.rapid.logicWidth / 2;
3132
+ const centerY = this.rapid.logicHeight / 2;
3133
+ viewMatrix[4] = viewMatrix[0] * centerX + viewMatrix[2] * centerY + viewMatrix[4];
3134
+ viewMatrix[5] = viewMatrix[1] * centerX + viewMatrix[3] * centerY + viewMatrix[5];
3135
+ }
3136
+ this.transform.setTransform(viewMatrix);
3137
+ }
3138
+ }
3139
+ class Scene extends Entity {
3140
+ /**
3141
+ * Initializes the scene.
3142
+ */
3143
+ create() {
3144
+ }
3145
+ }
3146
+ class Game {
3147
+ /**
3148
+ * Creates a new game instance.
3149
+ * @param options - Configuration options for the game.
3150
+ */
3151
+ constructor(options) {
3152
+ this.mainScene = null;
3153
+ this.isRunning = false;
3154
+ this.lastTime = 0;
3155
+ this.tweens = [];
3156
+ this.timers = [];
3157
+ this.mainCamera = null;
3158
+ this.renderQueue = [];
3159
+ this.worldTransform = new MatrixStack();
3160
+ this.render = new Rapid(options);
3161
+ this.input = new InputManager(this.render);
3162
+ this.asset = new AssetsLoader(this);
3163
+ this.audio = new AudioManager();
3164
+ this.texture = this.render.texture;
3165
+ }
3166
+ getMainScene() {
3167
+ return this.mainScene;
3168
+ }
3169
+ setMainCamera(camera) {
3170
+ this.mainCamera = camera;
3171
+ }
3172
+ /**
3173
+ * Switches to a new scene, disposing of the current one.
3174
+ * @param newScene - The new scene to switch to.
3175
+ */
3176
+ switchScene(newScene) {
3177
+ if (this.mainScene) {
3178
+ this.mainScene.dispose();
3179
+ }
3180
+ this.mainScene = newScene;
3181
+ newScene.create();
3182
+ }
3183
+ /**
3184
+ * Starts the game loop.
3185
+ */
3186
+ start() {
3187
+ if (!this.isRunning) {
3188
+ this.isRunning = true;
3189
+ this.lastTime = performance.now();
3190
+ this.gameLoop();
3191
+ }
3192
+ }
3193
+ /**
3194
+ * Stops the game loop.
3195
+ */
3196
+ stop() {
3197
+ this.isRunning = false;
3198
+ }
3199
+ /**
3200
+ * Adds an entity to the render queue.
3201
+ * @param entity - The entity to add.
3202
+ */
3203
+ addEntityRenderQueue(entity) {
3204
+ this.renderQueue.push(entity);
3205
+ }
3206
+ /**
3207
+ * Runs the game loop, updating and rendering the scene.
3208
+ * @private
3209
+ */
3210
+ gameLoop() {
3211
+ if (!this.isRunning) return;
3212
+ const currentTime = performance.now();
3213
+ const deltaTime = (currentTime - this.lastTime) / 1e3;
3214
+ this.lastTime = currentTime;
3215
+ this.render.startRender();
3216
+ if (this.mainScene) {
3217
+ if (this.mainCamera && this.mainCamera.enable) {
3218
+ this.render.matrixStack.setTransform(this.mainCamera.transform.getTransform());
3219
+ }
3220
+ this.worldTransform.setTransform(this.render.matrixStack.getTransform());
3221
+ this.mainScene.update(deltaTime);
3222
+ this.mainScene.collectRenderables(this.renderQueue);
3223
+ this.renderQueue.sort((a, b) => {
3224
+ const isBrother = a.parent && a.parent === b.parent;
3225
+ const global = a.globalZindex - b.globalZindex;
3226
+ if (global !== 0) {
3227
+ return global;
3228
+ }
3229
+ if (isBrother) {
3230
+ return a.localZindex - b.localZindex;
3231
+ }
3232
+ return 0;
3233
+ });
3234
+ this.renderQueue.forEach((entity) => {
3235
+ entity.beforeOnRender();
3236
+ entity.onRender(this.render);
3237
+ });
3238
+ this.renderQueue.length = 0;
3239
+ }
3240
+ this.render.endRender();
3241
+ this.input.updateNextFrame();
3242
+ this.updateTweens(deltaTime);
3243
+ this.updateTimers(deltaTime);
3244
+ requestAnimationFrame(this.gameLoop.bind(this));
3245
+ }
3246
+ destroy() {
3247
+ this.audio.destroy();
3248
+ this.input.destroy();
3249
+ }
3250
+ /**
3251
+ * Creates and starts a new timer that will be automatically updated by the game loop.
3252
+ * @param duration - The duration in seconds before the timer triggers.
3253
+ * @param onComplete - The function to call when the timer finishes.
3254
+ * @param repeat - If true, the timer will restart after completing. Defaults to false.
3255
+ * @returns The created Timer instance, allowing you to `stop()` it manually if needed.
3256
+ * @example
3257
+ * // Log a message after 2.5 seconds
3258
+ * game.createTimer(2.5, () => {
3259
+ * console.log('Timer finished!');
3260
+ * });
3261
+ *
3262
+ * // Spawn an enemy every 5 seconds
3263
+ * const enemySpawner = game.createTimer(5, () => {
3264
+ * scene.spawnEnemy();
3265
+ * }, true);
3266
+ */
3267
+ createTimer(duration, repeat = false) {
3268
+ const timer = new Timer(duration, repeat);
3269
+ this.timers.push(timer);
3270
+ timer.start();
3271
+ return timer;
3272
+ }
3273
+ /**
3274
+ * Updates all active timers and removes completed ones.
3275
+ * @param deltaTime - The time elapsed since the last frame.
3276
+ * @private
3277
+ */
3278
+ updateTimers(deltaTime) {
3279
+ for (const timer of this.timers) {
3280
+ timer.update(deltaTime);
3281
+ }
3282
+ this.timers = this.timers.filter((timer) => !timer.isFinished);
3283
+ }
3284
+ /**
3285
+ * Creates and starts a new tween animation.
3286
+ * The tween will be automatically updated by the game loop.
3287
+ * @template T - The type of the value being animated (e.g., Vec2, Color, number).
3288
+ * @param target - The object to animate.
3289
+ * @param property - The name of the property on the target to animate.
3290
+ * @param to - The final value of the property.
3291
+ * @param duration - The duration of the animation in seconds.
3292
+ * @param easing - The easing function to use. Defaults to Linear.
3293
+ * @returns The created Tween instance, allowing you to chain calls like .onComplete().
3294
+ * @example
3295
+ * // Tween a sprite's position over 2 seconds
3296
+ * game.createTween(mySprite, 'position', new Vec2(500, 300), 2, Easing.EaseOutQuad);
3297
+ *
3298
+ * // Tween a shape's color to red and do something on completion
3299
+ * game.createTween(myShape, 'color', Color.Red, 1.5)
3300
+ * .onComplete(() => {
3301
+ * console.log('Color tween finished!');
3302
+ * });
3303
+ */
3304
+ createTween(target, property, to, duration, easing = Easing.Linear) {
3305
+ const tween = new Tween(target, property, to, duration, easing);
3306
+ this.tweens.push(tween);
3307
+ tween.start();
3308
+ return tween;
3309
+ }
3310
+ /**
3311
+ * Updates all active tweens and removes completed ones.
3312
+ * @param deltaTime - The time elapsed since the last frame.
3313
+ * @private
3314
+ */
3315
+ updateTweens(deltaTime) {
3316
+ for (const tween of this.tweens) {
3317
+ tween.update(deltaTime);
3318
+ }
3319
+ this.tweens = this.tweens.filter((tween) => !tween.isFinished);
3320
+ }
3321
+ }
3322
+ class Sprite extends Entity {
3323
+ /**
3324
+ * Creates a new Sprite entity.
3325
+ * @param game - The game instance.
3326
+ * @param options - Configuration for the sprite's transform and initial texture.
3327
+ */
3328
+ constructor(game, options = {}) {
3329
+ super(game, options);
3330
+ this.animations = /* @__PURE__ */ new Map();
3331
+ this.currentAnimation = null;
3332
+ this.currentFrame = 0;
3333
+ this.frameTimer = 0;
3334
+ this.isPlaying = false;
3335
+ this.texture = options.texture ?? null;
3336
+ this.flipX = options.flipX ?? false;
3337
+ this.flipY = options.flipY ?? false;
3338
+ this.color = options.color ?? Color.White;
3339
+ this.offset = options.offset ?? Vec2.ZERO;
3340
+ if (options.animations) {
3341
+ this.setAnimations(options.animations);
3342
+ }
3343
+ }
3344
+ setAnimations(animations) {
3345
+ this.animations = new Map(Object.entries(animations));
3346
+ }
3347
+ /**
3348
+ * Defines a new animation sequence.
3349
+ * @param name - A unique name for the animation (e.g., "walk", "jump").
3350
+ * @param frames - An array of Textures to use as frames.
3351
+ * @param fps - The playback speed in frames per second.
3352
+ * @param loop - Whether the animation should repeat.
3353
+ */
3354
+ addAnimation(name, frames, fps = 10, loop = true) {
3355
+ this.animations.set(name, { name, frames, fps, loop });
3356
+ }
3357
+ /**
3358
+ * Plays an animation that has been previously defined with `addAnimation`.
3359
+ * @param name - The name of the animation to play.
3360
+ * @param forceRestart - If true, the animation will restart from the first frame even if it's already playing.
3361
+ */
3362
+ play(name, forceRestart = false) {
3363
+ const animation = this.animations.get(name);
3364
+ if (!animation) {
3365
+ console.warn(`Animation "${name}" not found.`);
3366
+ return;
3367
+ }
3368
+ if (this.currentAnimation === animation && !forceRestart) {
3369
+ if (!this.isPlaying) this.isPlaying = true;
3370
+ return;
3371
+ }
3372
+ this.currentAnimation = animation;
3373
+ this.currentFrame = 0;
3374
+ this.frameTimer = 0;
3375
+ this.isPlaying = true;
3376
+ this.texture = this.currentAnimation.frames[this.currentFrame];
3377
+ }
3378
+ /**
3379
+ * Stops the currently playing animation.
3380
+ * The sprite will remain on the current frame.
3381
+ */
3382
+ stop() {
3383
+ this.isPlaying = false;
3384
+ }
3385
+ /**
3386
+ * Updates the animation frame based on the elapsed time.
3387
+ * @param deltaTime - Time in seconds since the last frame.
3388
+ * @override
3389
+ */
3390
+ onUpdate(deltaTime) {
3391
+ if (!this.isPlaying || !this.currentAnimation) {
3392
+ return;
3393
+ }
3394
+ const frameDuration = 1 / this.currentAnimation.fps;
3395
+ this.frameTimer += deltaTime;
3396
+ while (this.frameTimer >= frameDuration) {
3397
+ this.frameTimer -= frameDuration;
3398
+ this.currentFrame++;
3399
+ if (this.currentFrame >= this.currentAnimation.frames.length) {
3400
+ if (this.currentAnimation.loop) {
3401
+ this.currentFrame = 0;
3402
+ } else {
3403
+ this.currentFrame = this.currentAnimation.frames.length - 1;
3404
+ this.isPlaying = false;
3405
+ break;
3406
+ }
3407
+ }
3408
+ }
3409
+ this.texture = this.currentAnimation.frames[this.currentFrame];
3410
+ }
3411
+ /**
3412
+ * Renders the sprite's current texture to the screen.
3413
+ * @param render - The Rapid rendering instance.
3414
+ * @override
3415
+ */
3416
+ onRender(render) {
3417
+ if (this.texture) {
3418
+ render.renderSprite({
3419
+ texture: this.texture,
3420
+ flipX: this.flipX,
3421
+ flipY: this.flipY,
3422
+ offset: this.offset,
3423
+ color: this.color
3424
+ });
3425
+ }
3426
+ }
3427
+ }
3428
+ class Label extends Entity {
3429
+ constructor(game, options) {
3430
+ super(game, options);
3431
+ this.text = new Text(game.render, options);
3432
+ }
3433
+ onRender(render) {
3434
+ render.renderSprite({
3435
+ texture: this.text
3436
+ });
3437
+ }
3438
+ /**
3439
+ * Updates the displayed text. Re-renders the texture if the text has changed.
3440
+ * @param text - The new text to display.
3441
+ */
3442
+ setText(text) {
3443
+ this.text.setText(text);
3444
+ }
3445
+ }
3446
+ class TileSet {
3447
+ /**
3448
+ * Creates a new TileSet instance.
3449
+ * @param width - The width of each tile in pixels
3450
+ * @param height - The height of each tile in pixels
3451
+ */
3452
+ constructor(width, height) {
3453
+ this.textures = /* @__PURE__ */ new Map();
3454
+ this.width = width;
3455
+ this.height = height;
3456
+ }
3457
+ /**
3458
+ * Registers a new tile with the given ID and options.
3459
+ * @param id - The unique identifier for the tile
3460
+ * @param options - The tile options or texture to register
3461
+ */
3462
+ setTile(id, options) {
3463
+ if (options instanceof Texture) {
3464
+ options = {
3465
+ texture: options
3466
+ };
3467
+ }
3468
+ this.textures.set(id, options);
3469
+ }
3470
+ /**
3471
+ * Retrieves the registered tile options for the given ID.
3472
+ * @param id - The unique identifier of the tile to retrieve
3473
+ * @returns The registered tile options, or undefined if not found
3474
+ */
3475
+ getTile(id) {
3476
+ return this.textures.get(id);
3477
+ }
3478
+ }
3479
+ class TileMapRender {
3480
+ /**
3481
+ * Creates a new TileMapRender instance.
3482
+ * @param rapid - The Rapid rendering instance to use.
3483
+ */
3484
+ constructor(rapid) {
3485
+ this.rapid = rapid;
3486
+ }
3487
+ /**
3488
+ * Calculates the error offset values for tile rendering.
3489
+ * @param options - Layer rendering options containing error values.
3490
+ * @returns Object containing x and y error offsets.
3491
+ * @private
3492
+ */
3493
+ getOffset(options) {
3494
+ let errorX = (options.errorX ?? 2) + 1;
3495
+ let errorY = (options.errorY ?? 2) + 1;
3496
+ if (typeof options.error === "number") {
3497
+ const error = (options.error ?? 2) + 1;
3498
+ errorX = error;
3499
+ errorY = error;
3500
+ } else if (options.error) {
3501
+ errorX = options.error.x + 1;
3502
+ errorY = options.error.y + 1;
3503
+ }
3504
+ return { errorX, errorY };
3505
+ }
3506
+ /**
3507
+ * Calculates tile rendering data based on the viewport and tileset.
3508
+ * @param tileSet - The tileset to use for rendering.
3509
+ * @param options - Layer rendering options.
3510
+ * @returns Object containing calculated tile rendering data.
3511
+ * @private
3512
+ */
3513
+ getTileData(tileSet, options) {
3514
+ const shape = options.shape ?? TilemapShape.SQUARE;
3515
+ const width = tileSet.width;
3516
+ const height = shape === TilemapShape.ISOMETRIC ? tileSet.height / 2 : tileSet.height;
3517
+ const matrix = this.rapid.matrixStack;
3518
+ const { errorX, errorY } = this.getOffset(options);
3519
+ const screenTopLeft = new Vec2(0, 0);
3520
+ const screenTopRight = new Vec2(this.rapid.width, 0);
3521
+ const screenBottomLeft = new Vec2(0, this.rapid.height);
3522
+ const screenBottomRight = new Vec2(this.rapid.width, this.rapid.height);
3523
+ const localTopLeft = matrix.globalToLocal(screenTopLeft);
3524
+ const localTopRight = matrix.globalToLocal(screenTopRight);
3525
+ const localBottomLeft = matrix.globalToLocal(screenBottomLeft);
3526
+ const localBottomRight = matrix.globalToLocal(screenBottomRight);
3527
+ const minX = Math.min(localTopLeft.x, localTopRight.x, localBottomLeft.x, localBottomRight.x);
3528
+ const maxX = Math.max(localTopLeft.x, localTopRight.x, localBottomLeft.x, localBottomRight.x);
3529
+ const minY = Math.min(localTopLeft.y, localTopRight.y, localBottomLeft.y, localBottomRight.y);
3530
+ const maxY = Math.max(localTopLeft.y, localTopRight.y, localBottomLeft.y, localBottomRight.y);
3531
+ const startTile = new Vec2(
3532
+ Math.floor(minX / width) - errorX,
3533
+ Math.floor(minY / height) - errorY
3534
+ );
3535
+ const endTile = new Vec2(
3536
+ Math.ceil(maxX / width) + errorX,
3537
+ Math.ceil(maxY / height) + errorY
3538
+ );
3539
+ const viewportWidth = endTile.x - startTile.x;
3540
+ const viewportHeight = endTile.y - startTile.y;
3541
+ return {
3542
+ startTile,
3543
+ viewportWidth,
3544
+ viewportHeight
3545
+ };
3546
+ }
3547
+ /**
3548
+ * Renders the tilemap layer based on the provided data and options.
3549
+ * This method collects all visible tiles and y-sortable objects into a single queue,
3550
+ * sorts them once by their y-coordinate, and then renders them in the correct order.
3551
+ *
3552
+ * @param data - A 2D array representing the tilemap data.
3553
+ * @param options - The rendering options for the tilemap layer.
3554
+ */
3555
+ renderLayer(data, options) {
3556
+ this.rapid.matrixStack.applyTransform(options);
3557
+ const tileSet = options.tileSet;
3558
+ const {
3559
+ startTile,
3560
+ viewportWidth,
3561
+ viewportHeight
3562
+ } = this.getTileData(tileSet, options);
3563
+ const renderQueue = [];
3564
+ if (options.ySortCallback) {
3565
+ renderQueue.push(...options.ySortCallback);
3566
+ }
3567
+ for (let y = 0; y < viewportHeight; y++) {
3568
+ const mapY = y + startTile.y;
3569
+ if (mapY < 0 || mapY >= data.length) {
3570
+ continue;
3571
+ }
3572
+ for (let x = 0; x < viewportWidth; x++) {
3573
+ const mapX = x + startTile.x;
3574
+ if (mapX < 0 || mapX >= data[mapY].length) {
3575
+ continue;
3576
+ }
3577
+ const tileId = data[mapY][mapX];
3578
+ const tile = tileSet.getTile(tileId);
3579
+ if (!tile) {
3580
+ continue;
3581
+ }
3582
+ const localPos = this.mapToLocal(new Vec2(mapX, mapY), options);
3583
+ const ySort = localPos.y + (tile.ySortOffset ?? 0);
3584
+ const edata = options.eachTile ? options.eachTile(tileId, mapX, mapY) || {} : {};
3585
+ renderQueue.push({
3586
+ ySort,
3587
+ renderSprite: {
3588
+ ...tile,
3589
+ x: localPos.x + (tile.x || 0),
3590
+ y: localPos.y + (tile.y || 0),
3591
+ ...edata
3592
+ }
3593
+ });
3594
+ }
3595
+ }
3596
+ const enableYSort = options.ySortCallback && options.ySortCallback.length > 0;
3597
+ if (enableYSort) {
3598
+ renderQueue.sort((a, b) => a.ySort - b.ySort);
3599
+ }
3600
+ for (const item of renderQueue) {
3601
+ if (item.render) {
3602
+ item.render();
3603
+ } else if (item.entity) {
3604
+ item.entity.onRender(this.rapid);
3605
+ } else if (item.renderSprite) {
3606
+ this.rapid.renderSprite(item.renderSprite);
3607
+ }
3608
+ }
3609
+ this.rapid.matrixStack.applyTransformAfter(options);
3610
+ }
3611
+ /**
3612
+ * Converts local coordinates to map coordinates.
3613
+ * @param local - The local coordinates.
3614
+ * @param options - The rendering options.
3615
+ * @returns The map coordinates.
3616
+ */
3617
+ localToMap(local, options) {
3618
+ const tileSet = options.tileSet;
3619
+ if (options.shape === TilemapShape.ISOMETRIC) {
3620
+ let outputX = 0, outputY = 0;
3621
+ const H = tileSet.height / 2;
3622
+ const W = tileSet.width / 2;
3623
+ let mapY = Math.floor(local.y / H);
3624
+ const isYEven = mapY % 2 === 0;
3625
+ let mapX = Math.floor(local.x / W);
3626
+ const isXEven = mapX % 2 === 0;
3627
+ const xRatio = local.x % W / W;
3628
+ const yRatio = local.y % H / H;
3629
+ const up2down = yRatio < xRatio;
3630
+ const down2up = yRatio < 1 - xRatio;
3631
+ if (!isYEven) mapY -= 1;
3632
+ if (up2down && (!isXEven && isYEven)) {
3633
+ mapY -= 1;
3634
+ } else if (!up2down && (isXEven && !isYEven)) {
3635
+ mapY += 1;
3636
+ mapX -= 2;
3637
+ } else if (down2up && (isXEven && isYEven)) {
3638
+ mapX -= 2;
3639
+ mapY -= 1;
3640
+ } else if (!down2up && (!isXEven && !isYEven)) {
3641
+ mapY += 1;
3642
+ }
3643
+ outputX = mapX;
3644
+ outputY = mapY;
3645
+ outputX = Math.floor(mapX / 2);
3646
+ return new Vec2(outputX, outputY);
3647
+ } else {
3648
+ return new Vec2(
3649
+ Math.floor(local.x / tileSet.width),
3650
+ Math.floor(local.y / tileSet.height)
3651
+ );
3652
+ }
3653
+ }
3654
+ /**
3655
+ * Converts map coordinates to local coordinates.
3656
+ * @param map - The map coordinates.
3657
+ * @param options - The rendering options.
3658
+ * @returns The local coordinates.
3659
+ */
3660
+ mapToLocal(map, options) {
3661
+ const tileSet = options.tileSet;
3662
+ if (options.shape === TilemapShape.ISOMETRIC) {
3663
+ let pos = new Vec2(
3664
+ map.x * tileSet.width,
3665
+ map.y * tileSet.height / 2
3666
+ );
3667
+ if (map.y % 2 !== 0) {
3668
+ pos.x += tileSet.width / 2;
3669
+ }
3670
+ return pos;
3671
+ } else {
3672
+ return new Vec2(
3673
+ map.x * tileSet.width,
3674
+ map.y * tileSet.height
3675
+ );
3676
+ }
3677
+ }
3678
+ }
3679
+ const _Tilemap = class _Tilemap extends Entity {
3680
+ /**
3681
+ * Creates a tilemap entity.
3682
+ * @param game - The game instance this tilemap belongs to.
3683
+ * @param options - Configuration options for the tilemap.
3684
+ */
3685
+ constructor(game, options) {
3686
+ super(game, options);
3687
+ this.error = new Vec2(_Tilemap.DEFAULT_ERROR);
3688
+ this.data = [[]];
3689
+ this.ySortCallback = [];
3690
+ if (options.error) {
3691
+ if (typeof options.error === "number") {
3692
+ this.error = new Vec2(options.error);
3693
+ } else {
3694
+ this.error = options.error;
3695
+ }
3696
+ } else {
3697
+ this.error = new Vec2(options.errorX ?? _Tilemap.DEFAULT_ERROR, options.errorY ?? _Tilemap.DEFAULT_ERROR);
3698
+ }
3699
+ this.tileSet = options.tileSet;
3700
+ this.shape = options.shape ?? TilemapShape.SQUARE;
3701
+ this.eachTile = options.eachTile;
3702
+ this.enableYsort = options.enableYsort ?? false;
3703
+ }
3704
+ /**
3705
+ * Collects renderable entities, excluding children unless they override onRender.
3706
+ * @param queue - The array to collect renderable entities.
3707
+ */
3708
+ collectRenderables(queue) {
3709
+ if (this.enableYsort) {
3710
+ if (this.onRender !== Entity.prototype.onRender) {
3711
+ queue.push(this);
3712
+ }
3713
+ } else {
3714
+ super.collectRenderables(queue);
3715
+ }
3716
+ }
3717
+ /**
3718
+ * Sets a tile at the specified map coordinates.
3719
+ * @param x - The X coordinate (column) on the map.
3720
+ * @param y - The Y coordinate (row) on the map.
3721
+ * @param tileId - The tile ID to set (number or string).
3722
+ * @returns True if the tile was set successfully, false if coordinates are out of bounds.
3723
+ */
3724
+ setTile(x, y, tileId) {
3725
+ if (y < 0 || y >= this.data.length || x < 0 || x >= this.data[y].length) {
3726
+ console.warn(`Tilemap.setTile: Coordinates (${x}, ${y}) are out of bounds.`);
3727
+ return false;
3728
+ }
3729
+ this.data[y][x] = tileId;
3730
+ return true;
3731
+ }
3732
+ /**
3733
+ * Gets the tile ID at the specified map coordinates.
3734
+ * @param x - The X coordinate (column) on the map.
3735
+ * @param y - The Y coordinate (row) on the map.
3736
+ * @returns The tile ID (number or string) or undefined if coordinates are out of bounds.
3737
+ */
3738
+ getTile(x, y) {
3739
+ if (y < 0 || y >= this.data.length || x < 0 || x >= this.data[y].length) {
3740
+ return void 0;
3741
+ }
3742
+ return this.data[y][x];
3743
+ }
3744
+ /**
3745
+ * Removes a tile at the specified map coordinates (sets it to EMPTY_TILE).
3746
+ * @param x - The X coordinate (column) on the map.
3747
+ * @param y - The Y coordinate (row) on the map.
3748
+ * @returns True if the tile was removed successfully, false if coordinates are out of bounds.
3749
+ */
3750
+ removeTile(x, y) {
3751
+ return this.setTile(x, y, _Tilemap.EMPTY_TILE);
3752
+ }
3753
+ /**
3754
+ * Fills a rectangular area with a specified tile ID.
3755
+ * @param tileId - The tile ID to use for filling.
3756
+ * @param startX - The starting X coordinate.
3757
+ * @param startY - The starting Y coordinate.
3758
+ * @param width - The width of the fill area.
3759
+ * @param height - The height of the fill area.
3760
+ */
3761
+ fill(tileId, startX, startY, width, height) {
3762
+ const endX = startX + width;
3763
+ const endY = startY + height;
3764
+ for (let y = startY; y < endY; y++) {
3765
+ for (let x = startX; x < endX; x++) {
3766
+ this.setTile(x, y, tileId);
3767
+ }
3768
+ }
3769
+ }
3770
+ /**
3771
+ * Replaces the entire tilemap data and updates dimensions.
3772
+ * @param newData - The new 2D array of tile data.
3773
+ */
3774
+ setData(newData) {
3775
+ this.data = newData;
3776
+ }
3777
+ /**
3778
+ * Converts local coordinates to map coordinates.
3779
+ * @param local - The local coordinates to convert.
3780
+ */
3781
+ localToMap(local) {
3782
+ this.rapid.tileMap.localToMap(local, { tileSet: this.tileSet });
3783
+ }
3784
+ /**
3785
+ * Converts map coordinates to local coordinates.
3786
+ * @param local - The map coordinates to convert.
3787
+ */
3788
+ mapToLocal(local) {
3789
+ this.rapid.tileMap.mapToLocal(local, { tileSet: this.tileSet });
3790
+ }
3791
+ /**
3792
+ * Renders the tilemap layer.
3793
+ * @param render - The rendering engine instance.
3794
+ */
3795
+ onRender(render) {
3796
+ const ySortCallback = [...this.ySortCallback];
3797
+ if (this.enableYsort) {
3798
+ this.children.forEach((child) => {
3799
+ ySortCallback.push({
3800
+ ySort: child.localZindex,
3801
+ entity: child
3802
+ });
3803
+ });
3804
+ }
3805
+ render.renderTileMapLayer(this.data, {
3806
+ error: this.error,
3807
+ tileSet: this.tileSet,
3808
+ eachTile: this.eachTile,
3809
+ ySortCallback
3810
+ });
3811
+ }
3812
+ };
3813
+ _Tilemap.DEFAULT_ERROR = 0;
3814
+ _Tilemap.EMPTY_TILE = -1;
3815
+ let Tilemap = _Tilemap;
3816
+ class Rapid {
3817
+ /**
3818
+ * Constructs a new `Rapid` instance with the given options.
3819
+ * @param options - Options for initializing the `Rapid` instance.
3820
+ */
3821
+ constructor(options) {
3822
+ this.projectionDirty = true;
3823
+ this.matrixStack = new MatrixStack();
3824
+ this.tileMap = new TileMapRender(this);
3825
+ this.light = new LightManager(this);
3826
+ this.defaultColor = new Color(255, 255, 255, 255);
3827
+ this.regions = /* @__PURE__ */ new Map();
3828
+ this.currentMaskType = [];
3829
+ this.currentTransform = [];
3830
+ this.currentFBO = [];
3831
+ this.lastTime = 0;
3832
+ const gl = getContext(options.canvas);
3833
+ this.gl = gl;
3834
+ this.canvas = options.canvas;
3835
+ this.texture = new TextureCache(this, options.antialias ?? false);
3836
+ this.maxTextureUnits = gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS);
3837
+ this.width = options.width || this.canvas.width;
3838
+ this.height = options.height || this.canvas.height;
3839
+ this.scaleEnable = options.scaleEnable ?? false;
3840
+ this.scaleRadio = options.scaleRadio || ScaleRadio.KEEP;
3841
+ this.devicePixelRatio = options.devicePixelRatio ?? window.devicePixelRatio ?? 1;
3842
+ this.backgroundColor = options.backgroundColor || new Color(255, 255, 255, 255);
3843
+ this.registerBuildInRegion();
3844
+ this.initWebgl(gl);
3845
+ this.updateDisplaySize(this.width, this.height);
3846
+ this.projectionDirty = false;
3847
+ this.canvas.style.display = "block";
3848
+ }
3849
+ /**
3850
+ * @param displayWdith css px size
3851
+ * @param displayHeight css px size
3852
+ */
3853
+ updateDisplaySize(displayWdith, displayHeight) {
3854
+ const designW = this.width;
3855
+ const designH = this.height;
3856
+ let logicW;
3857
+ let logicH;
3858
+ let physicsW;
3859
+ let physicsH;
3860
+ if (this.scaleEnable) {
3861
+ const designAspect = designW / designH;
3862
+ const windowAspect = displayWdith / displayHeight;
3863
+ switch (this.scaleRadio) {
3864
+ case ScaleRadio.IGNORE:
3865
+ logicW = designW;
3866
+ logicH = designH;
3867
+ physicsW = displayWdith * this.devicePixelRatio;
3868
+ physicsH = displayHeight * this.devicePixelRatio;
3869
+ break;
3870
+ case ScaleRadio.KEEP:
3871
+ logicW = designW;
3872
+ logicH = designH;
3873
+ let targetW;
3874
+ let targetH;
3875
+ if (windowAspect > designAspect) {
3876
+ targetH = displayHeight;
3877
+ targetW = targetH * designAspect;
3878
+ } else {
3879
+ targetW = displayWdith;
3880
+ targetH = targetW / designAspect;
3881
+ }
3882
+ physicsW = targetW * this.devicePixelRatio;
3883
+ physicsH = targetH * this.devicePixelRatio;
3884
+ break;
3885
+ case ScaleRadio.EXPAND:
3886
+ if (windowAspect > designAspect) {
3887
+ logicH = designH;
3888
+ logicW = designH * windowAspect;
3889
+ } else {
3890
+ logicW = designW;
3891
+ logicH = designW / windowAspect;
3892
+ }
3893
+ physicsW = displayWdith * this.devicePixelRatio;
3894
+ physicsH = displayHeight * this.devicePixelRatio;
3895
+ break;
3896
+ case ScaleRadio.KEEP_W:
3897
+ logicW = designW;
3898
+ logicH = designW / windowAspect;
3899
+ physicsW = displayWdith * this.devicePixelRatio;
3900
+ physicsH = displayHeight * this.devicePixelRatio;
3901
+ break;
3902
+ case ScaleRadio.KEEP_H:
3903
+ logicH = designH;
3904
+ logicW = designH * windowAspect;
3905
+ physicsW = displayWdith * this.devicePixelRatio;
3906
+ physicsH = displayHeight * this.devicePixelRatio;
3907
+ break;
3908
+ default:
3909
+ logicW = designW;
3910
+ logicH = designH;
3911
+ physicsW = displayWdith * this.devicePixelRatio;
3912
+ physicsH = displayHeight * this.devicePixelRatio;
3913
+ break;
3914
+ }
3915
+ } else {
3916
+ logicW = designW;
3917
+ logicH = designH;
3918
+ physicsW = designW * this.devicePixelRatio;
3919
+ physicsH = designH * this.devicePixelRatio;
3920
+ }
3921
+ this.logicWidth = logicW;
3922
+ this.logicHeight = logicH;
3923
+ this.physicsWidth = physicsW;
3924
+ this.physicsHeight = physicsH;
3925
+ this.resizeSize(logicW, logicH, physicsW, physicsH, true);
3926
+ }
3927
+ resizeSize(logicW, logicH, physicsW, physicsH, updateCanvas = false) {
3928
+ this.updateProjection(0, logicW, logicH, 0);
3929
+ this.gl.viewport(0, 0, physicsW, physicsH);
3930
+ this.gl.scissor(0, 0, physicsW, physicsH);
3931
+ if (updateCanvas) {
3932
+ this.canvas.width = physicsW;
3933
+ this.canvas.height = physicsH;
3934
+ this.canvas.style.width = physicsW / this.devicePixelRatio + "px";
3935
+ this.canvas.style.height = physicsH / this.devicePixelRatio + "px";
3936
+ }
3937
+ }
3938
+ updateProjection(left, right, bottom, top) {
3939
+ this.projection = this.createOrthMatrix(left, right, bottom, top);
3940
+ this.projectionDirty = true;
3941
+ }
3942
+ /**
3943
+ * Initializes WebGL context settings.
3944
+ * @param gl - The WebGL context.
3945
+ */
3946
+ initWebgl(gl) {
3947
+ gl.enable(gl.BLEND);
3948
+ gl.disable(gl.DEPTH_TEST);
3949
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
3950
+ gl.enable(gl.STENCIL_TEST);
3951
+ gl.enable(gl.SCISSOR_TEST);
3952
+ }
3953
+ /**
3954
+ * @ignore
3955
+ */
3956
+ clearTextureUnit() {
3957
+ for (let i = 0; i < this.maxTextureUnits; i++) {
3958
+ this.gl.activeTexture(this.gl.TEXTURE0 + i);
3959
+ this.gl.bindTexture(this.gl.TEXTURE_2D, null);
3960
+ }
3961
+ }
3962
+ /**
3963
+ * Registers built-in regions such as sprite and graphic regions.
3964
+ */
3965
+ registerBuildInRegion() {
3966
+ this.registerRegion("sprite", SpriteRegion);
3967
+ this.registerRegion("graphic", GraphicRegion);
3968
+ }
3969
+ /**
3970
+ * Registers a custom render region with a specified name.
3971
+ * @param name - The name of the region.
3972
+ * @param regionClass - The class of the region to register.
3973
+ */
3974
+ registerRegion(name, regionClass) {
3975
+ this.regions.set(name, new regionClass(this));
3976
+ }
3977
+ quitCurrentRegion() {
3978
+ if (this.currentRegion && this.currentRegion.hasPendingContent()) {
3979
+ this.currentRegion.render();
3980
+ this.currentRegion.exitRegion();
3981
+ }
3982
+ }
3983
+ /**
3984
+ * Sets the current render region by name and optionally a custom shader.
3985
+ * @param regionName - The name of the region to set as current.
3986
+ * @param customShader - An optional custom shader to use with the region.
3987
+ * @param hasUnifrom - have costum unifrom
3988
+ */
3989
+ setRegion(regionName, customShader) {
3990
+ if (
3991
+ // isRegionChanged
3992
+ regionName != this.currentRegionName || // isShaderChanged
3993
+ this.currentRegion && this.currentRegion.isShaderChanged(customShader)
3994
+ ) {
3995
+ const region = this.regions.get(regionName);
3996
+ this.quitCurrentRegion();
3997
+ this.currentRegion = region;
3998
+ this.currentRegionName = regionName;
3999
+ region.enterRegion(customShader);
4000
+ }
4001
+ }
4002
+ /**
4003
+ * Saves the current matrix state to the stack.
4004
+ */
4005
+ save() {
4006
+ this.matrixStack.pushMat();
4007
+ }
4008
+ /**
4009
+ * Restores the matrix state from the stack.
4010
+ */
4011
+ restore() {
4012
+ this.matrixStack.popMat();
4013
+ }
4014
+ /**
4015
+ * Executes a callback function within a saved and restored matrix state scope.
4016
+ * @param cb - The callback function to execute within the saved and restored matrix state scope.
4017
+ */
4018
+ withTransform(cb) {
4019
+ this.save();
4020
+ cb();
4021
+ this.restore();
4022
+ }
4023
+ /**
4024
+ * Starts the rendering process, resetting the matrix stack and clearing the current region.
4025
+ * @param clear - Whether to clear the matrix stack. Defaults to true.
4026
+ */
4027
+ startRender(clear = true) {
4028
+ this.clear();
4029
+ clear && this.matrixStack.clear();
4030
+ this.matrixStack.pushIdentity();
4031
+ this.currentRegion = void 0;
4032
+ this.currentRegionName = void 0;
4033
+ const now = performance.now();
4034
+ const dt = this.lastTime ? (now - this.lastTime) / 1e3 : 0;
4035
+ this.lastTime = now;
4036
+ return dt;
4037
+ }
4038
+ /**
4039
+ * Ends the rendering process by rendering the current region.
4040
+ */
4041
+ endRender() {
4042
+ this.currentRegion?.render();
4043
+ this.projectionDirty = false;
4044
+ }
4045
+ /**
4046
+ * Render
4047
+ * @param cb - The function to render.
4048
+ */
4049
+ render(cb) {
4050
+ const dt = this.startRender();
4051
+ cb(dt);
4052
+ this.endRender();
4053
+ }
4054
+ /**
4055
+ * Render a tile map layer.
4056
+ * @param data - The map data to render.
4057
+ * @param options - The options for rendering the tile map layer.
4058
+ */
4059
+ renderTileMapLayer(data, options) {
4060
+ this.tileMap.renderLayer(data, options instanceof TileSet ? { tileSet: options } : options);
4061
+ }
4062
+ /**
4063
+ * Renders a sprite with the specified options.
4064
+ *
4065
+ * @param options - The rendering options for the sprite, including texture, position, color, and shader.
4066
+ */
4067
+ renderSprite(options) {
4068
+ const texture = options.texture;
4069
+ if (!texture || !texture.base) return;
4070
+ const { offsetX, offsetY } = this.startDraw(options, texture.width, texture.height);
4071
+ this.setRegion("sprite", options.shader);
4072
+ this.currentRegion.renderSprite(
4073
+ texture.base.texture,
4074
+ texture.width,
4075
+ texture.height,
4076
+ texture.clipX,
4077
+ texture.clipY,
4078
+ texture.clipW,
4079
+ texture.clipH,
4080
+ offsetX,
4081
+ offsetY,
4082
+ (options.color || this.defaultColor).uint32,
4083
+ options.uniforms,
4084
+ options.flipX,
4085
+ options.flipY
4086
+ );
4087
+ this.afterDraw();
4088
+ }
4089
+ /**
4090
+ * Renders a texture directly without additional options.
4091
+ * This is a convenience method that calls renderSprite with just the texture.
4092
+ *
4093
+ * @param texture - The texture to render at the current transformation position.
4094
+ */
4095
+ renderTexture(texture) {
4096
+ if (!texture.base) return;
4097
+ this.renderSprite({ texture });
4098
+ }
4099
+ /**
4100
+ * Renders a line with the specified options.
4101
+ *
4102
+ * @param options - The options for rendering the line, including points, color, width, and join/cap types.
4103
+ */
4104
+ renderLine(options) {
4105
+ const linePoints = options.closed ? [...options.points, options.points[0]] : options.points;
4106
+ const { vertices, uv } = getLineGeometry({ ...options, points: linePoints });
4107
+ this.renderGraphic({ ...options, drawType: this.gl.TRIANGLES, points: vertices, uv });
4108
+ }
4109
+ /**
4110
+ * Renders graphics based on the provided options.
4111
+ *
4112
+ * @param options - The options for rendering the graphic, including points, color, texture, and draw type.
4113
+ */
4114
+ renderGraphic(options) {
4115
+ this.startGraphicDraw(options);
4116
+ options.points.forEach((vec, index) => {
4117
+ const color = Array.isArray(options.color) ? options.color[index] : options.color;
4118
+ const uv = options.uv?.[index];
4119
+ this.addGraphicVertex(vec.x, vec.y, uv, color);
4120
+ });
4121
+ this.endGraphicDraw();
4122
+ }
4123
+ /**
4124
+ * Starts the graphic drawing process.
4125
+ *
4126
+ * @param options - The options for the graphic drawing, including shader, texture, and draw type.
4127
+ */
4128
+ startGraphicDraw(options) {
4129
+ const { offsetX, offsetY } = this.startDraw(options);
4130
+ this.setRegion("graphic", options.shader);
4131
+ const currentRegion = this.currentRegion;
4132
+ currentRegion.startRender(offsetX, offsetY, options.texture, options.uniforms);
4133
+ if (options.drawType) {
4134
+ currentRegion.drawType = options.drawType;
4135
+ }
4136
+ }
4137
+ /**
4138
+ * Adds a vertex to the current graphic being drawn.
4139
+ *
4140
+ * @param offsetX - The X coordinate of the vertex.
4141
+ * @param offsetY - The Y coordinate of the vertex.
4142
+ * @param uv - The texture UV coordinates for the vertex.
4143
+ * @param color - The color of the vertex. Defaults to the renderer's default color.
4144
+ */
4145
+ addGraphicVertex(offsetX, offsetY, uv, color) {
4146
+ const currentRegion = this.currentRegion;
4147
+ currentRegion.addVertex(offsetX, offsetY, uv?.x, uv?.y, (color || this.defaultColor).uint32);
4148
+ }
4149
+ /**
4150
+ * Completes the graphic drawing process and renders the result.
4151
+ */
4152
+ endGraphicDraw() {
4153
+ const currentRegion = this.currentRegion;
4154
+ currentRegion.render();
4155
+ this.afterDraw();
4156
+ }
4157
+ startDraw(options, width = 0, height = 0) {
4158
+ this.currentTransform.push(options);
4159
+ return this.matrixStack.applyTransform(options, width, height);
4160
+ }
4161
+ afterDraw() {
4162
+ if (this.currentTransform.length > 0) {
4163
+ this.matrixStack.applyTransformAfter(this.currentTransform.pop());
4164
+ }
4165
+ }
4166
+ /**
4167
+ * Renders a rectangle with the specified options.
4168
+ *
4169
+ * @param options - The options for rendering the rectangle, including width, height, position, and color.
4170
+ */
4171
+ renderRect(options) {
4172
+ const { width, height } = options;
4173
+ const points = [
4174
+ new Vec2(0, 0),
4175
+ new Vec2(width, 0),
4176
+ new Vec2(width, height),
4177
+ new Vec2(0, height)
4178
+ ];
4179
+ this.renderGraphic({ ...options, points, drawType: this.gl.TRIANGLE_FAN });
4180
+ }
4181
+ /**
4182
+ * Renders a circle with the specified options.
4183
+ *
4184
+ * @param options - The options for rendering the circle, including radius, position, color, and segment count.
4185
+ */
4186
+ renderCircle(options) {
4187
+ const segments = options.segments || 32;
4188
+ const radius = options.radius;
4189
+ const color = options.color || this.defaultColor;
4190
+ const points = [];
4191
+ for (let i = 0; i <= segments; i++) {
4192
+ const angle = i / segments * Math.PI * 2;
4193
+ const x = Math.cos(angle) * radius;
4194
+ const y = Math.sin(angle) * radius;
4195
+ points.push(new Vec2(x, y));
4196
+ }
4197
+ this.renderGraphic({ ...options, points, color, drawType: this.gl.TRIANGLE_FAN });
4198
+ }
4199
+ /**
4200
+ * Clears the canvas with the background color.
4201
+ * @param bgColor - The background color to clear the canvas with.
4202
+ */
4203
+ clear(bgColor) {
4204
+ const gl = this.gl;
4205
+ const c = bgColor || this.backgroundColor;
4206
+ gl.clearColor(c.r / 255, c.g / 255, c.b / 255, c.a / 255);
4207
+ gl.clear(gl.COLOR_BUFFER_BIT);
4208
+ this.clearMask();
4209
+ }
4210
+ /**
4211
+ * Creates an orthogonal projection matrix.
4212
+ * @param left - The left bound of the projection.
4213
+ * @param right - The right bound of the projection.
4214
+ * @param bottom - The bottom bound of the projection.
4215
+ * @param top - The top bound of the projection.
4216
+ * @returns The orthogonal projection matrix as a `Float32Array`.
4217
+ */
4218
+ createOrthMatrix(left, right, bottom, top) {
4219
+ return new Float32Array([
4220
+ 2 / (right - left),
4221
+ 0,
4222
+ 0,
4223
+ 0,
4224
+ 0,
4225
+ 2 / (top - bottom),
4226
+ 0,
4227
+ 0,
4228
+ 0,
4229
+ 0,
4230
+ -1,
4231
+ 0,
4232
+ -(right + left) / (right - left),
4233
+ -(top + bottom) / (top - bottom),
4234
+ 0,
4235
+ 1
4236
+ ]);
4237
+ }
4238
+ /**
4239
+ * Draw a mask. Automatically calls startDrawMask.
4240
+ * @param type - The type of mask to draw.
4241
+ * @param cb - The callback function to execute.
4242
+ */
4243
+ drawMask(type = MaskType.Include, cb) {
4244
+ this.startDrawMask(type);
4245
+ cb();
4246
+ this.endDrawMask();
4247
+ }
4248
+ /**
4249
+ * Start drawing a mask using the stencil buffer.
4250
+ * This method configures the WebGL context to begin defining a mask area.
4251
+ */
4252
+ startDrawMask(type = MaskType.Include) {
4253
+ const gl = this.gl;
4254
+ this.currentMaskType.push(type);
4255
+ this.setMaskType(type, true);
4256
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
4257
+ gl.colorMask(false, false, false, false);
4258
+ }
4259
+ /**
4260
+ * End the mask drawing process.
4261
+ * This method configures the WebGL context to use the defined mask for subsequent rendering.
4262
+ */
4263
+ endDrawMask() {
4264
+ const gl = this.gl;
4265
+ this.quitCurrentRegion();
4266
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
4267
+ gl.colorMask(true, true, true, true);
4268
+ this.setMaskType(this.currentMaskType.pop() ?? MaskType.Include, false);
4269
+ }
4270
+ /**
4271
+ * Set the mask type for rendering
4272
+ * @param type - The mask type to apply
4273
+ * @param start - Whether this is the start of mask drawing
4274
+ */
4275
+ setMaskType(type, start = false) {
4276
+ const gl = this.gl;
4277
+ this.quitCurrentRegion();
4278
+ if (start) {
4279
+ this.clearMask();
4280
+ gl.stencilFunc(gl.ALWAYS, 1, 255);
4281
+ } else {
4282
+ switch (type) {
4283
+ case MaskType.Include:
4284
+ gl.stencilFunc(gl.EQUAL, 1, 255);
4285
+ break;
4286
+ case MaskType.Exclude:
4287
+ gl.stencilFunc(gl.NOTEQUAL, 1, 255);
4288
+ break;
4289
+ }
4290
+ }
4291
+ }
4292
+ /**
4293
+ * Clear the current mask by clearing the stencil buffer.
4294
+ * This effectively removes any previously defined mask.
4295
+ */
4296
+ clearMask() {
4297
+ const gl = this.gl;
4298
+ this.quitCurrentRegion();
4299
+ gl.clearStencil(0);
4300
+ gl.clear(gl.STENCIL_BUFFER_BIT);
4301
+ gl.stencilFunc(gl.ALWAYS, 1, 255);
4302
+ }
4303
+ /**
4304
+ * Creates a custom shader.
4305
+ * @param vs - Vertex shader code.
4306
+ * @param fs - Fragment shader code.
4307
+ * @param type - Shader type.
4308
+ * @returns The created shader object.
4309
+ */
4310
+ createCostumShader(vs, fs, type, textureUnitNum = 0) {
4311
+ return GLShader.createCostumShader(this, vs, fs, type, textureUnitNum);
4312
+ }
4313
+ /**
4314
+ * Starts rendering to a Frame Buffer Object (FBO)
4315
+ * Sets up the FBO for rendering by binding it, adjusting viewport size and projection
4316
+ * @param fbo - The Frame Buffer Object to render to
4317
+ */
4318
+ startFBO(fbo, color = this.defaultColor) {
4319
+ this.quitCurrentRegion();
4320
+ fbo.bind(color);
4321
+ this.clearTextureUnit();
4322
+ this.resizeSize(fbo.width, fbo.height, fbo.width, fbo.height);
4323
+ this.updateProjection(0, fbo.width, 0, fbo.height);
4324
+ this.save();
4325
+ this.matrixStack.identity();
4326
+ this.currentFBO.push(fbo);
4327
+ }
4328
+ /**
4329
+ * Ends rendering to a Frame Buffer Object
4330
+ * Restores the default framebuffer and original viewport settings
4331
+ * @param fbo - The Frame Buffer Object to unbind
4332
+ */
4333
+ endFBO() {
4334
+ if (this.currentFBO.length > 0) {
4335
+ const fbo = this.currentFBO.pop();
4336
+ this.quitCurrentRegion();
4337
+ fbo.unbind();
4338
+ this.clearTextureUnit();
4339
+ this.resizeSize(
4340
+ this.logicWidth,
4341
+ this.logicHeight,
4342
+ this.physicsWidth,
4343
+ this.physicsHeight
4344
+ );
4345
+ this.restore();
4346
+ }
4347
+ }
4348
+ /**
4349
+ * Convenience method to render to a Frame Buffer Object
4350
+ * Handles starting and ending the FBO rendering automatically
4351
+ * @param fbo - The Frame Buffer Object to render to
4352
+ * @param cb - Callback function containing render commands to execute on the FBO
4353
+ */
4354
+ drawToFBO(fbo, cb) {
4355
+ this.startFBO(fbo);
4356
+ cb();
4357
+ this.endFBO();
4358
+ }
4359
+ /**
4360
+ * Set the blend mode for rendering
4361
+ * @param mode - The blend mode to apply
4362
+ */
4363
+ setBlendMode(mode) {
4364
+ switch (mode) {
4365
+ case BlendMode.Additive:
4366
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
4367
+ break;
4368
+ case BlendMode.Subtractive:
4369
+ this.gl.blendFunc(this.gl.ZERO, this.gl.ONE_MINUS_SRC_COLOR);
4370
+ break;
4371
+ case BlendMode.Mix:
4372
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
4373
+ break;
4374
+ }
4375
+ }
4376
+ /**
4377
+ * Render light shadow
4378
+ * @param occlusion - The occlusion polygon
4379
+ * @param lightSource - The light source position
4380
+ */
4381
+ drawLightShadowMask(options) {
4382
+ this.startDrawMask(options.type || MaskType.Exclude);
4383
+ const shadowPolygon = this.light.createLightShadowMaskPolygon(options.occlusion, options.lightSource, options.baseProjectionLength);
4384
+ shadowPolygon.forEach((polygon) => {
4385
+ this.renderGraphic({
4386
+ points: polygon,
4387
+ color: Color.Black
4388
+ });
4389
+ });
4390
+ this.endDrawMask();
4391
+ }
4392
+ cssToGameCoords(x, y) {
4393
+ const css = x instanceof Vec2 ? x : new Vec2(x, y);
4394
+ const rect = this.canvas.getBoundingClientRect();
4395
+ const normalizedX = css.x / rect.width;
4396
+ const normalizedY = css.y / rect.height;
4397
+ const gameX = normalizedX * this.logicWidth;
4398
+ const gameY = normalizedY * this.logicHeight;
4399
+ return new Vec2(gameX, gameY);
4400
+ }
4401
+ gameToCssCoords(x, y) {
4402
+ const gameCoords = x instanceof Vec2 ? x : new Vec2(x, y ?? 0);
4403
+ const rect = this.canvas.getBoundingClientRect();
4404
+ const normalizedX = gameCoords.x / this.logicWidth;
4405
+ const normalizedY = gameCoords.y / this.logicHeight;
4406
+ const cssXRelativeToCanvas = normalizedX * rect.width;
4407
+ const cssYRelativeToCanvas = normalizedY * rect.height;
4408
+ const cssX = cssXRelativeToCanvas;
4409
+ const cssY = cssYRelativeToCanvas;
4410
+ return new Vec2(cssX, cssY);
4411
+ }
4412
+ }
4413
+ class Uniform {
4414
+ constructor(data) {
4415
+ this.isDirty = false;
4416
+ this.data = data;
4417
+ }
4418
+ setUniform(key, data) {
4419
+ if (this.data[key] != data) {
4420
+ this.isDirty = true;
4421
+ }
4422
+ this.data[key] = data;
4423
+ }
4424
+ /**
4425
+ * @ignore
4426
+ */
4427
+ clearDirty() {
4428
+ this.isDirty = false;
4429
+ }
4430
+ getUnifromNames() {
4431
+ return Object.keys(this.data);
4432
+ }
4433
+ bind(gl, uniformName, loc, region) {
4434
+ if (!loc) return;
4435
+ const value = this.data[uniformName];
4436
+ if (typeof value === "number") {
4437
+ gl.uniform1f(loc, value);
4438
+ } else if (Array.isArray(value)) {
4439
+ switch (value.length) {
4440
+ case 1:
4441
+ if (Number.isInteger(value[0])) {
4442
+ gl.uniform1i(loc, value[0]);
4443
+ } else {
4444
+ gl.uniform1f(loc, value[0]);
4445
+ }
4446
+ break;
4447
+ case 2:
4448
+ if (Number.isInteger(value[0])) {
4449
+ gl.uniform2iv(loc, value);
4450
+ } else {
4451
+ gl.uniform2fv(loc, value);
4452
+ }
4453
+ break;
4454
+ case 3:
4455
+ if (Number.isInteger(value[0])) {
4456
+ gl.uniform3iv(loc, value);
4457
+ } else {
4458
+ gl.uniform3fv(loc, value);
4459
+ }
4460
+ break;
4461
+ case 4:
4462
+ if (Number.isInteger(value[0])) {
4463
+ gl.uniform4iv(loc, value);
4464
+ } else {
4465
+ gl.uniform4fv(loc, value);
4466
+ }
4467
+ break;
4468
+ case 9:
4469
+ gl.uniformMatrix3fv(loc, false, value);
4470
+ break;
4471
+ case 16:
4472
+ gl.uniformMatrix4fv(loc, false, value);
4473
+ break;
4474
+ default:
4475
+ console.error(`Unsupported uniform array length for ${uniformName}:`, value.length);
4476
+ break;
4477
+ }
4478
+ } else if (typeof value === "boolean") {
4479
+ gl.uniform1i(loc, value ? 1 : 0);
4480
+ } else if (value.base?.texture) {
4481
+ const usedTextureUnit = region.useTexture(value.base.texture)[0];
4482
+ gl.uniform1i(loc, usedTextureUnit);
4483
+ } else {
4484
+ console.error(`Unsupported uniform type for ${uniformName}:`, typeof value);
4485
+ }
4486
+ }
4487
+ }
4488
+ const DEFAULT_EMIT_RATE = 10;
4489
+ const DEFAULT_EMIT_TIME = 0;
4490
+ const DEFAULT_LOCAL_SPACE = true;
4491
+ class Particle {
4492
+ /**
4493
+ * Creates a new particle instance
4494
+ * @param rapid - The Rapid renderer instance
4495
+ * @param options - Particle configuration options
4496
+ */
4497
+ constructor(rapid, options) {
4498
+ this.life = 0;
4499
+ this.datas = {};
4500
+ this.rapid = rapid;
4501
+ this.options = options;
4502
+ if (options.texture instanceof Texture) {
4503
+ this.texture = options.texture;
4504
+ } else if (options.texture instanceof Array && options.texture[0] instanceof Array) {
4505
+ this.texture = Random.pickWeight(options.texture);
4506
+ } else if (options.texture instanceof Array) {
4507
+ this.texture = Random.pick(options.texture);
4508
+ }
4509
+ this.maxLife = Random.scalarOrRange(options.life, 1);
4510
+ this.datas = {
4511
+ speed: this.processAttribute(options.animation.speed, 0),
4512
+ rotation: this.processAttribute(options.animation.rotation, 0),
4513
+ scale: this.processAttribute(options.animation.scale, 1),
4514
+ color: this.processAttribute(options.animation.color, Color.White),
4515
+ velocity: this.processAttribute(options.animation.velocity, Vec2.ZERO),
4516
+ acceleration: this.processAttribute(options.animation.acceleration, Vec2.ZERO)
4517
+ };
4518
+ this.position = Vec2.ZERO;
4519
+ this.initializePosition();
4520
+ }
4521
+ processAttribute(attribute, defaultData) {
4522
+ if (!attribute) return { value: defaultData };
4523
+ if (isPlainObject(attribute)) {
4524
+ const attr = attribute;
4525
+ const start = Random.scalarOrRange(attr.start, defaultData);
4526
+ const end = Random.scalarOrRange(attr.end || start, defaultData);
4527
+ return {
4528
+ delta: attr.delta ?? this.getDelta(start, end, this.maxLife),
4529
+ value: start,
4530
+ damping: attr.damping
4531
+ };
4532
+ } else {
4533
+ return this.processAttribute({ start: attribute }, defaultData);
4534
+ }
4535
+ }
4536
+ updateDamping(deltaTime) {
4537
+ for (const att of Object.values(this.datas)) {
4538
+ if (att.damping) {
4539
+ const value = att.value;
4540
+ const timeBasedDamping = Math.pow(att.damping, deltaTime);
4541
+ if (typeof value === "number") {
4542
+ att.value *= timeBasedDamping;
4543
+ } else if (value.multiply) {
4544
+ att.value = value.multiply(timeBasedDamping);
4545
+ }
4546
+ }
4547
+ }
4548
+ }
4549
+ updateDelta(deltaTime) {
4550
+ const datas = this.datas;
4551
+ for (const att of Object.values(datas)) {
4552
+ if (att.delta) {
4553
+ const value = att.value;
4554
+ if (typeof value === "number") {
4555
+ att.value += deltaTime * att.delta;
4556
+ } else if (value.add) {
4557
+ att.value = value.add(att.delta.multiply(deltaTime));
4558
+ }
4559
+ }
4560
+ }
4561
+ datas.color.value.clamp();
4562
+ datas.velocity.value = datas.velocity.value.add(datas.acceleration.value.multiply(deltaTime));
4563
+ const direction = Vec2.fromAngle(datas.rotation.value);
4564
+ const speedOffset = direction.multiply(datas.speed.value * deltaTime);
4565
+ this.position = this.position.add(speedOffset).add(datas.velocity.value.multiply(deltaTime));
4566
+ }
4567
+ getDelta(start, end, lifeTime) {
4568
+ if (lifeTime === 0) return start;
4569
+ if (typeof start === "number" && typeof end === "number") {
4570
+ return (end - start) / lifeTime;
4571
+ } else if (start instanceof Vec2 && end instanceof Vec2) {
4572
+ return end.subtract(start).divide(lifeTime);
4573
+ } else if (start instanceof Color && end instanceof Color) {
4574
+ return end.subtract(start).divide(lifeTime);
4575
+ }
4576
+ return start;
4577
+ }
4578
+ /**
4579
+ * Updates particle state
4580
+ * @param deltaTime - Time in seconds since last update
4581
+ * @returns Whether the particle is still active
4582
+ */
4583
+ update(deltaTime) {
4584
+ this.life += deltaTime;
4585
+ if (this.life >= this.maxLife) {
4586
+ return false;
4587
+ }
4588
+ this.updateDamping(deltaTime);
4589
+ this.updateDelta(deltaTime);
4590
+ return true;
4591
+ }
4592
+ /**
4593
+ * Renders the particle at a given position.
4594
+ */
4595
+ render() {
4596
+ const renderPosition = this.getPosition();
4597
+ this.rapid.renderSprite({
4598
+ ...this.options,
4599
+ position: renderPosition,
4600
+ scale: this.datas.scale.value,
4601
+ rotation: this.datas.rotation.value,
4602
+ color: this.datas.color.value,
4603
+ texture: this.texture
4604
+ });
4605
+ }
4606
+ /**
4607
+ * Gets the particle's current position, relative to the emitter if in local space.
4608
+ */
4609
+ getPosition() {
4610
+ return this.position;
4611
+ }
4612
+ initializePosition() {
4613
+ switch (this.options.emitShape) {
4614
+ case ParticleShape.POINT:
4615
+ this.position = Vec2.ZERO;
4616
+ break;
4617
+ case ParticleShape.CIRCLE:
4618
+ const angle = Math.random() * Math.PI * 2;
4619
+ const radius = (this.options.emitRadius || 0) * Math.sqrt(Math.random());
4620
+ this.position = new Vec2(
4621
+ Math.cos(angle) * radius,
4622
+ Math.sin(angle) * radius
4623
+ );
4624
+ break;
4625
+ case ParticleShape.RECT:
4626
+ this.position = new Vec2(
4627
+ (Math.random() - 0.5) * (this.options.emitRect?.width || 0),
4628
+ (Math.random() - 0.5) * (this.options.emitRect?.height || 0)
4629
+ );
4630
+ break;
4631
+ }
4632
+ if (!this.options.localSpace && this.options.position) {
4633
+ this.position = this.position.add(this.options.position);
4634
+ }
4635
+ }
4636
+ }
4637
+ class ParticleEmitter extends Entity {
4638
+ /**
4639
+ * Creates a new particle emitter
4640
+ * @param rapid - The Rapid renderer instance
4641
+ * @param options - Emitter configuration options
4642
+ */
4643
+ constructor(game, options) {
4644
+ super(game, options);
4645
+ this.particles = [];
4646
+ this.emitting = false;
4647
+ this.emitTimer = 0;
4648
+ this.emitRate = DEFAULT_EMIT_RATE;
4649
+ this.emitTime = DEFAULT_EMIT_TIME;
4650
+ this.emitTimeCounter = 0;
4651
+ this.localSpace = DEFAULT_LOCAL_SPACE;
4652
+ this.options = options;
4653
+ this.emitRate = options.emitRate ?? DEFAULT_EMIT_RATE;
4654
+ this.emitTime = options.emitTime ?? DEFAULT_EMIT_TIME;
4655
+ this.localSpace = options.localSpace ?? DEFAULT_LOCAL_SPACE;
4656
+ }
4657
+ /**
4658
+ * Gets the transform options
4659
+ */
4660
+ getTransform() {
4661
+ return this.options;
4662
+ }
4663
+ /**
4664
+ * Sets particle emission rate.
4665
+ * In continuous mode (`emitTime`=0), this is particles per second.
4666
+ * In burst mode (`emitTime`>0), this is particles per burst.
4667
+ * @param rate - The emission rate.
4668
+ */
4669
+ setEmitRate(rate) {
4670
+ this.emitRate = rate;
4671
+ }
4672
+ /**
4673
+ * Sets time interval between emissions. Set to 0 for continuous emission.
4674
+ * @param time - Time interval in seconds
4675
+ */
4676
+ setEmitTime(time) {
4677
+ this.emitTime = time;
4678
+ }
4679
+ /**
4680
+ * Starts emitting particles
4681
+ */
4682
+ start() {
4683
+ this.emitting = true;
4684
+ this.emitTimer = 0;
4685
+ this.emitTimeCounter = 0;
4686
+ }
4687
+ /**
4688
+ * Stops emitting new particles but allows existing ones to complete their lifecycle
4689
+ */
4690
+ stop() {
4691
+ this.emitting = false;
4692
+ }
4693
+ /**
4694
+ * Clears all particles and resets the emitter
4695
+ */
4696
+ clear() {
4697
+ this.particles = [];
4698
+ this.emitTimer = 0;
4699
+ this.emitTimeCounter = 0;
4700
+ }
4701
+ /**
4702
+ * Emits a specified number of particles immediately.
4703
+ * @param count - Number of particles to emit
4704
+ */
4705
+ emit(count) {
4706
+ const actualCount = Math.floor(Math.min(count, (this.options.maxParticles || Infinity) - this.particles.length));
4707
+ for (let i = 0; i < actualCount; i++) {
4708
+ const position = this.localSpace ? this.position : this.transform.getGlobalPosition();
4709
+ const particle = new Particle(this.rapid, { ...this.options, position, localSpace: this.localSpace });
4710
+ this.particles.unshift(particle);
4711
+ }
4712
+ }
4713
+ /**
4714
+ * Updates particle emitter state
4715
+ * @param deltaTime - Time in seconds since last update
4716
+ */
4717
+ onUpdate(deltaTime) {
4718
+ if (this.emitting && this.emitRate > 0) {
4719
+ if (this.emitTime > 0) {
4720
+ this.emitTimeCounter += deltaTime;
4721
+ while (this.emitTimeCounter >= this.emitTime) {
4722
+ this.emit(this.emitRate);
4723
+ this.emitTimeCounter -= this.emitTime;
4724
+ }
4725
+ } else {
4726
+ this.emitTimer += deltaTime;
4727
+ const timePerParticle = 1 / this.emitRate;
4728
+ while (this.emitTimer >= timePerParticle) {
4729
+ this.emit(1);
4730
+ this.emitTimer -= timePerParticle;
4731
+ }
4732
+ }
4733
+ }
4734
+ for (let i = this.particles.length - 1; i >= 0; i--) {
4735
+ if (!this.particles[i].update(deltaTime)) {
4736
+ this.particles.splice(i, 1);
4737
+ }
4738
+ }
4739
+ }
4740
+ /**
4741
+ * Renders all particles
4742
+ */
4743
+ onRender() {
4744
+ this.rapid.save();
4745
+ if (!this.localSpace) {
4746
+ const worldTransform = this.game.worldTransform;
4747
+ this.rapid.matrixStack.setTransform(worldTransform.getTransform());
4748
+ }
4749
+ for (const particle of this.particles) {
4750
+ particle.render();
4751
+ }
4752
+ this.rapid.restore();
4753
+ }
4754
+ /**
4755
+ * Gets current particle count
4756
+ */
4757
+ getParticleCount() {
4758
+ return this.particles.length;
4759
+ }
4760
+ /**
4761
+ * Checks if the particle emitter is active (has particles or is emitting)
4762
+ */
4763
+ isActive() {
4764
+ return this.emitting || this.particles.length > 0;
4765
+ }
4766
+ /**
4767
+ * Triggers a single burst of particles, regardless of whether the emitter is running.
4768
+ * The number of particles is determined by `emitRate`.
4769
+ */
4770
+ oneShot() {
4771
+ this.emit(this.emitRate);
4772
+ }
4773
+ }
4774
+ export {
4775
+ ArrayType,
4776
+ BaseTexture,
4777
+ BlendMode,
4778
+ Camera,
4779
+ CanvasLayer,
4780
+ Color,
4781
+ DynamicArrayBuffer,
4782
+ Entity,
4783
+ FrameBufferObject,
4784
+ GLShader,
4785
+ Game,
4786
+ InputManager,
4787
+ Label,
4788
+ LineTextureMode,
4789
+ MaskType,
4790
+ MathUtils,
4791
+ MatrixStack,
4792
+ ParticleEmitter,
4793
+ ParticleShape,
4794
+ Random,
4795
+ Rapid,
4796
+ SCALEFACTOR,
4797
+ ScaleRadio,
4798
+ Scene,
4799
+ ShaderType,
4800
+ Sprite,
4801
+ Text,
4802
+ Texture,
4803
+ TextureCache,
4804
+ TextureWrapMode,
4805
+ TileMapRender,
4806
+ TileSet,
4807
+ TilemapShape,
4808
+ Uniform,
4809
+ Vec2,
4810
+ WebglBufferArray,
4811
+ WebglElementBufferArray,
4812
+ graphicAttributes,
4813
+ spriteAttributes
4814
+ };