littlejsengine 1.18.29 → 1.20.0
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/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/dist/littlejs.esm.min.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
// LittleJS Engine - MIT License - Copyright 2021 Frank Force
|
|
2
2
|
// https://github.com/KilledByAPixel/LittleJS
|
|
3
3
|
|
|
4
|
-
"use strict";const engineName="LittleJS";const engineVersion="1.18.29";const frameRate=60;const timeDelta=1/frameRate;let engineObjects=[];let engineObjectsCollide=[];let frame=0;let time=0;let timeReal=0;let paused=false;function getPaused(){return paused}function setPaused(isPaused=true){paused=isPaused}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0;let engineUpdateInternal;let showEngineVersion=true;const pluginList=[];class EnginePlugin{constructor(update,render,glContextLost,glContextRestored){this.update=update;this.render=render;this.glContextLost=glContextLost;this.glContextRestored=glContextRestored}}function engineAddPlugin(update,render,glContextLost,glContextRestored){ASSERT(!pluginList.find(p=>p.update===update&&p.render===render&&p.glContextLost===glContextLost&&p.glContextRestored===glContextRestored));const plugin=new EnginePlugin(update,render,glContextLost,glContextRestored);pluginList.push(plugin)}async function engineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources=[],rootElement){showEngineVersion&&console.log(`${engineName} Engine v${engineVersion}`);ASSERT(!mainContext,"engine already initialized");if(mainContext)return;ASSERT(isArray(imageSources),"pass in images as array");if(!document.body)document.documentElement.appendChild(document.createElement("body"));rootElement||=document.body;gameInit||=()=>{};gameUpdate||=()=>{};gameUpdatePost||=()=>{};gameRender||=()=>{};gameRenderPost||=()=>{};function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!tilesPixelated;glPreRender()}function engineUpdate(frameTimeMS=0){let frameTimeDeltaMS=frameTimeMS-frameTimeLastMS;if(!frameTimeLastMS)frameTimeDeltaMS=0;frameTimeLastMS=frameTimeMS;if(debug||debugWatermark)averageFPS=lerp(averageFPS,1e3/(frameTimeDeltaMS||1),.05);const debugSpeedUp=debug&&keyIsDown("Equal");const debugSpeedDown=debug&&keyIsDown("Minus");const debugScale=debugSpeedUp?10:debugSpeedDown?.1:1;timeReal+=frameTimeDeltaMS*debugScale/1e3;const combinedScale=timeScale*debugScale;frameTimeDeltaMS*=combinedScale;frameTimeBufferMS+=paused?0:frameTimeDeltaMS;if(combinedScale<=1)frameTimeBufferMS=min(frameTimeBufferMS,50);let wasUpdated=false;if(paused){wasUpdated=true;updateCanvas();inputUpdate();pluginList.forEach(plugin=>plugin.update?.());for(const o of engineObjects)o.parent||o.updateTransforms();debugUpdate();gameUpdatePost();inputUpdatePost();if(debugVideoCaptureIsActive())renderFrame()}else{let deltaSmooth=0;if(frameTimeBufferMS<0&&frameTimeBufferMS>-9){deltaSmooth=frameTimeBufferMS;frameTimeBufferMS=0}for(;frameTimeBufferMS>=0;frameTimeBufferMS-=1e3/frameRate){time=frame++/frameRate;wasUpdated=true;updateCanvas();inputUpdate();gameUpdate();pluginList.forEach(plugin=>plugin.update?.());engineObjectsUpdate();debugUpdate();gameUpdatePost();inputUpdatePost();if(debugVideoCaptureIsActive())renderFrame()}frameTimeBufferMS+=deltaSmooth}if(!debugVideoCaptureIsActive())renderFrame();if(!engineManualStep)requestAnimationFrame(engineUpdate);function renderFrame(){if(headlessMode)return;if(!wasUpdated)updateCanvas();enginePreRender();gameRender();engineObjects.sort((a,b)=>a.renderOrder-b.renderOrder);for(const o of engineObjects)o.destroyed||o.render();gameRenderPost();pluginList.forEach(plugin=>plugin.render?.());inputRender();debugRender();glFlush();debugRenderPost();drawCount=0;primitiveCount=0}}engineUpdateInternal=engineUpdate;function updateCanvas(){if(headlessMode)return;if(canvasFixedSize.x){mainCanvasSize=canvasFixedSize.copy();const innerAspect=innerWidth/innerHeight;const fixedAspect=canvasFixedSize.x/canvasFixedSize.y;const w=innerAspect<fixedAspect?"100%":"";const h=innerAspect<fixedAspect?"":"100%";mainCanvas.style.width=w;mainCanvas.style.height=h;if(glCanvas){glCanvas.style.width=w;glCanvas.style.height=h}}else{const dpr=canvasPixelRatio??(devicePixelRatio||1);const viewWidth=innerWidth*dpr|0;const viewHeight=innerHeight*dpr|0;mainCanvasSize.x=min(viewWidth,canvasMaxSize.x);mainCanvasSize.y=min(viewHeight,canvasMaxSize.y);const innerAspect=viewWidth/viewHeight;ASSERT(canvasMinAspect<=canvasMaxAspect);if(canvasMaxAspect&&innerAspect>canvasMaxAspect){const w=mainCanvasSize.y*canvasMaxAspect|0;mainCanvasSize.x=min(w,canvasMaxSize.x)}else if(innerAspect<canvasMinAspect){const h=mainCanvasSize.x/canvasMinAspect|0;mainCanvasSize.y=min(h,canvasMaxSize.y)}const cssW=(mainCanvasSize.x/dpr|0)+"px";const cssH=(mainCanvasSize.y/dpr|0)+"px";mainCanvas.style.width=cssW;mainCanvas.style.height=cssH;if(glCanvas){glCanvas.style.width=cssW;glCanvas.style.height=cssH}}mainCanvas.width=mainCanvasSize.x;mainCanvas.height=mainCanvasSize.y;if(canvasClearColor.a>0&&!glEnable){mainContext.fillStyle=canvasClearColor.toString();mainContext.fillRect(0,0,mainCanvasSize.x,mainCanvasSize.y);mainContext.fillStyle=BLACK.toString()}mainContext.lineJoin="round";mainContext.lineCap="round"}if(headlessMode)return startEngine();glInit(rootElement);const styleRoot="margin:0;"+"overflow:hidden;"+"background:#000;"+"user-select:none;"+"-webkit-user-select:none;"+"touch-action:none;"+"-webkit-touch-callout:none";rootElement.style.cssText=styleRoot;mainCanvas=rootElement.appendChild(document.createElement("canvas"));drawContext=mainContext=mainCanvas.getContext("2d");inputInit();audioInit();debugInit();const styleCanvas="position:absolute;"+"top:50%;left:50%;transform:translate(-50%,-50%)";mainCanvas.style.cssText=styleCanvas;if(glCanvas)glCanvas.style.cssText=styleCanvas;setCanvasPixelated(canvasPixelated);updateCanvas();glPreRender();workCanvas=new OffscreenCanvas(64,64);workContext=workCanvas.getContext("2d");workReadCanvas=new OffscreenCanvas(64,64);workReadContext=workReadCanvas.getContext("2d",{willReadFrequently:true});const promises=imageSources.map((src,i)=>loadTexture(i,src));if(!imageSources.length)promises.push(loadTexture(0));promises.push(imageFontInit());if(showSplashScreen){promises.push(new Promise(resolve=>{let t=0;updateSplash();function updateSplash(){inputClear();drawEngineLogo(t+=.01);t>1?resolve():setTimeout(updateSplash,16)}}))}await Promise.all(promises);return startEngine();async function startEngine(){await gameInit();engineManualStep||engineUpdate()}}const engineStepMaxFrames=36e3;function engineStep(frames=1){ASSERT(engineManualStep,"engineStep requires setEngineManualStep(true) before engineInit");ASSERT(engineUpdateInternal,"engineStep requires engineInit to complete");if(!engineManualStep||!engineUpdateInternal)return;ASSERT(Number.isInteger(frames)&&frames>=0&&frames<=engineStepMaxFrames,"engineStep requires a whole frame count from 0 to "+engineStepMaxFrames);frames=min(frames,engineStepMaxFrames);for(let i=frames;i>0;--i)engineUpdateInternal(frameTimeLastMS+1e3/frameRate)}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(o=>o.collideSolidObjects);for(const o of engineObjects)if(!o.parent&&!o.destroyed)o.updatePhysics();function updateChildObject(o){if(o.destroyed)return;o.update();for(const child of o.children)updateChildObject(child)}for(const o of engineObjects){if(o.parent||o.destroyed)continue;o.update();for(const child of o.children)updateChildObject(child);o.updateTransforms()}engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsDestroy(immediate=true){for(const o of engineObjects)o.parent||o.destroy(immediate);engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsCollect(pos,size,objects=engineObjects){const collectedObjects=[];if(!pos){for(const o of objects)collectedObjects.push(o)}else if(size instanceof Vector2){for(const o of objects)o.isOverlapping(pos,size)&&collectedObjects.push(o)}else{const sizeSquared=size*size;for(const o of objects)pos.distanceSquared(o.pos)<sizeSquared&&collectedObjects.push(o)}return collectedObjects}function engineObjectsCallback(pos,size,callbackFunction,objects=engineObjects){engineObjectsCollect(pos,size,objects).forEach(o=>callbackFunction(o))}function engineObjectsRaycast(start,end,objects=engineObjects){const hitObjects=[];for(const o of objects){if(o.collideRaycast&&isIntersecting(start,end,o.pos,o.size)){debugRaycast&&debugRect(o.pos,o.size,"#f00");hitObjects.push(o)}}debugRaycast&&debugLine(start,end,hitObjects.length?"#f00":"#00f",.02);return hitObjects}let debugWatermark=0;let debugKey="";const debug=0;const debugOverlay=0;const debugPhysics=0;const debugParticles=0;const debugRaycast=0;const debugGamepads=0;const debugSound=0;const debugPointSize=.5;function ASSERT(){}function LOG(){}function debugInit(){}function debugUpdate(){}function debugRender(){}function debugRenderPost(){}function debugRect(){}function debugPoly(){}function debugCircle(){}function debugPoint(){}function debugLine(){}function debugOverlap(){}function debugText(){}function debugClear(){}function debugScreenshot(){}function debugShowErrors(){}function debugVideoCaptureIsActive(){return false}function debugVideoCaptureStart(){}function debugVideoCaptureStop(){}function debugVideoCaptureUpdate(){}function debugProtectConstant(o){return o}const PI=Math.PI;const abs=Math.abs;const floor=Math.floor;const ceil=Math.ceil;const round=Math.round;const min=Math.min;const max=Math.max;const sign=x=>Math.sign(x);const hypot=(...values)=>Math.hypot(...values);const log2=x=>Math.log2(x);const sin=Math.sin;const cos=Math.cos;const tan=Math.tan;const atan2=Math.atan2;function mod(dividend,divisor=1){return(dividend%divisor+divisor)%divisor}function clamp(value,min=0,max=1){return value<min?min:value>max?max:value}function percent(value,valueA,valueB){return(valueB-=valueA)?clamp((value-valueA)/valueB):0}function lerp(valueA,valueB,percent){return valueA+clamp(percent)*(valueB-valueA)}function percentLerp(value,percentA,percentB,lerpA,lerpB){return lerp(lerpA,lerpB,percent(value,percentA,percentB))}function distanceWrap(valueA,valueB,wrapSize=1){ASSERT(wrapSize>0,"distanceWrap wrapSize must be > 0");const d=(valueA-valueB)%wrapSize;return d*2%wrapSize-d}function lerpWrap(valueA,valueB,percent,wrapSize=1){return valueA+clamp(percent)*distanceWrap(valueB,valueA,wrapSize)}function distanceAngle(angleA,angleB){return distanceWrap(angleA,angleB,2*PI)}function lerpAngle(angleA,angleB,percent){return lerpWrap(angleA,angleB,percent,2*PI)}function smoothStep(percent){return percent*percent*(3-2*percent)}function isPowerOfTwo(value){return value>0&&!(value&value-1)}function nearestPowerOfTwo(value){return 2**ceil(log2(value))}function isOverlapping(posA,sizeA,posB,sizeB=vec2()){const dx=(posA.x-posB.x)*2;const dy=(posA.y-posB.y)*2;const sx=sizeA.x+sizeB.x;const sy=sizeA.y+sizeB.y;return abs(dx)<sx&&abs(dy)<sy}function isIntersecting(start,end,pos,size){const boxMin=pos.subtract(size.scale(.5));const boxMax=boxMin.add(size);const delta=end.subtract(start);const a=start.subtract(boxMin);const b=start.subtract(boxMax);const p=[-delta.x,delta.x,-delta.y,delta.y];const q=[a.x,-b.x,a.y,-b.y];let tMin=0,tMax=1;for(let i=4;i--;){if(p[i]){const t=q[i]/p[i];if(p[i]<0){if(t>tMax)return false;tMin=max(t,tMin)}else{if(t<tMin)return false;tMax=min(t,tMax)}}else if(q[i]<0)return false}return true}function oscillate(frequency=1,amplitude=1,t=time,offset=0,type=0){const phase=mod(offset+t*frequency,1);let value;if(type===1)value=2*abs(2*phase-1)-1;else if(type===2)value=phase<.5?-1:1;else if(type===3)value=2*phase-1;else value=-cos(phase*2*PI);return amplitude/2*(value+1)}function isNumber(n){return typeof n==="number"&&!isNaN(n)}function isStringLike(s){return s!=null&&typeof s?.toString()==="string"}function isArray(a){return Array.isArray(a)}function lineTest(posStart,posEnd,testFunction,normal){ASSERT(isVector2(posStart),"posStart must be a vec2");ASSERT(isVector2(posEnd),"posEnd must be a vec2");ASSERT(typeof testFunction==="function","testFunction must be a function");ASSERT(!normal||isVector2(normal),"normal must be a vec2");const dx=posEnd.x-posStart.x;const dy=posEnd.y-posStart.y;const totalLength=(dx*dx+dy*dy)**.5;if(!totalLength)return;const pos=posStart.floor();const dirX=dx/totalLength;const dirY=dy/totalLength;const stepX=sign(dirX);const stepY=sign(dirY);const tDeltaX=dirX?abs(1/dirX):Infinity;const tDeltaY=dirY?abs(1/dirY):Infinity;const nextGridX=stepX>0?pos.x+1:pos.x;const nextGridY=stepY>0?pos.y+1:pos.y;const tMaxX=dirX?(nextGridX-posStart.x)/dirX:Infinity;const tMaxY=dirY?(nextGridY-posStart.y)/dirY:Infinity;let t=0,tX=tMaxX,tY=tMaxY,wasX=tDeltaX<tDeltaY;while(t<totalLength){if(testFunction(pos)){const hitPos=vec2(posStart.x+dirX*t,posStart.y+dirY*t);const e=1e-9;const hitPosFloor=hitPos.floor();if(hitPosFloor.x<pos.x)hitPos.x=pos.x;else if(hitPosFloor.x>pos.x)hitPos.x=pos.x+1-e;if(hitPosFloor.y<pos.y)hitPos.y=pos.y;else if(hitPosFloor.y>pos.y)hitPos.y=pos.y+1-e;if(normal)wasX?normal.set(-stepX,0):normal.set(0,-stepY);return hitPos}if(wasX=tX<tY){pos.x+=stepX;t=tX;tX+=tDeltaX}else{pos.y+=stepY;t=tY;tY+=tDeltaY}}}function rand(valueA=1,valueB=0){return valueB+Math.random()*(valueA-valueB)}function randInt(valueA,valueB=0){return floor(rand(valueA,valueB))}function randBool(chance=.5){return rand()<chance}function randSign(){return randInt(2)*2-1}function randVec2(length=1){return(new Vector2).setAngle(rand(2*PI),length)}function randInCircle(radius=1,minRadius=0){if(radius<=0)return new Vector2;const ratio=clamp(minRadius/radius);return randVec2(radius*rand(ratio*ratio,1)**.5)}function randColor(colorA=new Color,colorB=new Color(0,0,0,1),linear=false){return linear?colorA.lerp(colorB,rand()):new Color(rand(colorA.r,colorB.r),rand(colorA.g,colorB.g),rand(colorA.b,colorB.b),rand(colorA.a,colorB.a))}class RandomGenerator{constructor(seed=123456789){ASSERT(seed!==0,"RandomGenerator seed must be non-zero (xorshift is fixed at 0)");this.seed=seed}float(valueA=1,valueB=0){this.seed^=this.seed<<13;this.seed^=this.seed>>>17;this.seed^=this.seed<<5;return valueB+(valueA-valueB)*((this.seed>>>0)/2**32)}int(valueA,valueB=0){return floor(this.float(valueA,valueB))}bool(chance=.5){return this.float()<chance}sign(){return this.float()>.5?1:-1}floatSign(valueA=1,valueB=0){const lo=min(valueA,valueB);const hi=max(valueA,valueB);const d=hi-lo;const e=this.float(d*2);return e<d?lo+e:d-lo-e}angle(){return this.float(-PI,PI)}vec2(valueA=1,valueB=0){return vec2(this.float(valueA,valueB),this.float(valueA,valueB))}randColor(colorA=new Color,colorB=new Color(0,0,0,1),linear=false){return linear?colorA.lerp(colorB,this.float()):new Color(this.float(colorA.r,colorB.r),this.float(colorA.g,colorB.g),this.float(colorA.b,colorB.b),this.float(colorA.a,colorB.a))}mutateColor(color,amount=.05,alphaAmount=0){ASSERT_NUMBER_VALID(amount);ASSERT_NUMBER_VALID(alphaAmount);return new Color(color.r+this.float(amount,-amount),color.g+this.float(amount,-amount),color.b+this.float(amount,-amount),color.a+this.float(alphaAmount,-alphaAmount)).clamp()}}function vec2(x=0,y){return new Vector2(x,y??x)}function isVector2(v){return v instanceof Vector2&&v.isValid()}function ASSERT_VECTOR2_VALID(v){ASSERT(isVector2(v),"Vector2 is invalid.",v)}function ASSERT_NUMBER_VALID(n){ASSERT(isNumber(n),"Number is invalid.",n)}function ASSERT_VECTOR2_NORMAL(v){ASSERT_VECTOR2_VALID(v);ASSERT(abs(v.lengthSquared()-1)<.01,"Vector2 is not normal.",v)}class Vector2{constructor(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid(),"Constructed Vector2 is invalid.",this)}set(x=0,y=0){this.x=x;this.y=y;ASSERT_VECTOR2_VALID(this);return this}setFrom(v){return this.set(v.x,v.y)}copy(){return new Vector2(this.x,this.y)}add(v){return new Vector2(this.x+v.x,this.y+v.y)}subtract(v){return new Vector2(this.x-v.x,this.y-v.y)}multiply(v){return new Vector2(this.x*v.x,this.y*v.y)}divide(v){return new Vector2(this.x/v.x,this.y/v.y)}scale(s){return new Vector2(this.x*s,this.y*s)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(v){return this.distanceSquared(v)**.5}distanceSquared(v){return(this.x-v.x)**2+(this.y-v.y)**2}normalize(length=1){const l=this.length();return l?this.scale(length/l):new Vector2(0,length)}clampLength(length=1){const l=this.length();return l>length?this.scale(length/l):this.copy()}dot(v){return this.x*v.x+this.y*v.y}cross(v){return this.x*v.y-this.y*v.x}reflect(normal,restitution=1){return this.subtract(normal.scale((1+restitution)*this.dot(normal)))}angle(){return atan2(this.x,this.y)}setAngle(angle=0,length=1){ASSERT_NUMBER_VALID(angle);ASSERT_NUMBER_VALID(length);this.x=length*sin(angle);this.y=length*cos(angle);return this}rotate(angle){ASSERT_NUMBER_VALID(angle);const c=cos(-angle),s=sin(-angle);return new Vector2(this.x*c-this.y*s,this.x*s+this.y*c)}setDirection(direction,length=1){ASSERT_NUMBER_VALID(direction);ASSERT_NUMBER_VALID(length);direction=mod(direction,4);ASSERT(direction===0||direction===1||direction===2||direction===3,"Vector2.setDirection() direction must be an integer between 0 and 3.");this.x=direction%2?direction-1?-length:length:0;this.y=direction%2?0:direction?-length:length;return this}direction(){return abs(this.x)>abs(this.y)?this.x<0?3:1:this.y<0?2:0}abs(){return new Vector2(abs(this.x),abs(this.y))}floor(){return new Vector2(floor(this.x),floor(this.y))}snap(grid){ASSERT_NUMBER_VALID(grid);return new Vector2(floor(this.x*grid)/grid,floor(this.y*grid)/grid)}mod(divisor=1){return new Vector2(mod(this.x,divisor),mod(this.y,divisor))}area(){return abs(this.x*this.y)}lerp(v,percent){ASSERT_VECTOR2_VALID(v);ASSERT_NUMBER_VALID(percent);const p=clamp(percent);return new Vector2(v.x*p+this.x*(1-p),v.y*p+this.y*(1-p))}arrayCheck(arraySize){return this.x>=0&&this.y>=0&&this.x<arraySize.x&&this.y<arraySize.y}toString(digits=3){ASSERT_NUMBER_VALID(digits);if(this.isValid())return`(${(this.x<0?"":" ")+this.x.toFixed(digits)},${(this.y<0?"":" ")+this.y.toFixed(digits)} )`;else return`(${this.x}, ${this.y})`}isValid(){return isNumber(this.x)&&isNumber(this.y)}}function rgb(r,g,b,a){return new Color(r,g,b,a)}function hsl(h,s,l,a){return(new Color).setHSLA(h,s,l,a)}function isColor(c){return c instanceof Color&&c.isValid()}function ASSERT_COLOR_VALID(c){ASSERT(isColor(c),"Color is invalid.",c)}class Color{constructor(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;ASSERT(this.isValid(),"Constructed Color is invalid.",this)}set(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;ASSERT_COLOR_VALID(this);return this}setFrom(c){return this.set(c.r,c.g,c.b,c.a)}setAlpha(a=1){this.a=a;ASSERT_COLOR_VALID(this);return this}copy(){return new Color(this.r,this.g,this.b,this.a)}withAlpha(a=1){return new Color(this.r,this.g,this.b,a)}add(c){return new Color(this.r+c.r,this.g+c.g,this.b+c.b,this.a+c.a)}subtract(c){return new Color(this.r-c.r,this.g-c.g,this.b-c.b,this.a-c.a)}multiply(c){return new Color(this.r*c.r,this.g*c.g,this.b*c.b,this.a*c.a)}divide(c){return new Color(this.r/c.r,this.g/c.g,this.b/c.b,this.a/c.a)}scale(scale,alphaScale=scale){return new Color(this.r*scale,this.g*scale,this.b*scale,this.a*alphaScale)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(c,percent){ASSERT_COLOR_VALID(c);ASSERT_NUMBER_VALID(percent);const p=clamp(percent);return new Color(c.r*p+this.r*(1-p),c.g*p+this.g*(1-p),c.b*p+this.b*(1-p),c.a*p+this.a*(1-p))}setHSLA(h=0,s=0,l=1,a=1){h=mod(h,1);s=clamp(s);l=clamp(l);const q=l<.5?l*(1+s):l+s-l*s,p=2*l-q,f=(p,q,t)=>(t=mod(t,1))*6<1?p+(q-p)*6*t:t*2<1?q:t*3<2?p+(q-p)*(4-t*6):p;this.r=f(p,q,h+1/3);this.g=f(p,q,h);this.b=f(p,q,h-1/3);this.a=a;ASSERT_COLOR_VALID(this);return this}HSLA(){const r=clamp(this.r);const g=clamp(this.g);const b=clamp(this.b);const a=clamp(this.a);const maxC=max(r,g,b);const minC=min(r,g,b);const l=(maxC+minC)/2;let h=0,s=0;if(maxC!==minC){let d=maxC-minC;s=l>.5?d/(2-maxC-minC):d/(maxC+minC);if(r===maxC)h=(g-b)/d+(g<b?6:0);else if(g===maxC)h=(b-r)/d+2;else if(b===maxC)h=(r-g)/d+4}return[h/6,s,l,a]}mutate(amount=.05,alphaAmount=0){ASSERT_NUMBER_VALID(amount);ASSERT_NUMBER_VALID(alphaAmount);return new Color(this.r+rand(amount,-amount),this.g+rand(amount,-amount),this.b+rand(amount,-amount),this.a+rand(alphaAmount,-alphaAmount)).clamp()}toString(useAlpha=true){if(debug&&!this.isValid())return"#000";const toHex=c=>((c=clamp(c)*255|0)<16?"0":"")+c.toString(16);return"#"+toHex(this.r)+toHex(this.g)+toHex(this.b)+(useAlpha?toHex(this.a):"")}setHex(hex){ASSERT(isStringLike(hex),"Color hex code must be a string");ASSERT(hex[0]==="#","Color hex code must start with #");ASSERT([4,5,7,9].includes(hex.length),"Invalid hex");if(hex.length<6){const fromHex=c=>clamp(parseInt(hex[c],16)/15);this.r=fromHex(1);this.g=fromHex(2);this.b=fromHex(3);this.a=hex.length===5?fromHex(4):1}else{const fromHex=c=>clamp(parseInt(hex.slice(c,c+2),16)/255);this.r=fromHex(1);this.g=fromHex(3);this.b=fromHex(5);this.a=hex.length===9?fromHex(7):1}ASSERT_COLOR_VALID(this);return this}rgbaInt(){const r=clamp(this.r)*255|0;const g=clamp(this.g)*255<<8;const b=clamp(this.b)*255<<16;const a=clamp(this.a)*255<<24;return r+g+b+a}isValid(){return isNumber(this.r)&&isNumber(this.g)&&isNumber(this.b)&&isNumber(this.a)}}const WHITE=debugProtectConstant(rgb());const CLEAR_WHITE=debugProtectConstant(rgb(1,1,1,0));const BLACK=debugProtectConstant(rgb(0,0,0));const CLEAR_BLACK=debugProtectConstant(rgb(0,0,0,0));const GRAY=debugProtectConstant(rgb(.5,.5,.5));const RED=debugProtectConstant(rgb(1,0,0));const ORANGE=debugProtectConstant(rgb(1,.5,0));const YELLOW=debugProtectConstant(rgb(1,1,0));const GREEN=debugProtectConstant(rgb(0,1,0));const CYAN=debugProtectConstant(rgb(0,1,1));const BLUE=debugProtectConstant(rgb(0,0,1));const PURPLE=debugProtectConstant(rgb(.5,0,1));const MAGENTA=debugProtectConstant(rgb(1,0,1));class Timer{constructor(timeLeft,useRealTime=false){ASSERT(timeLeft===undefined||isNumber(timeLeft),"Constructed Timer is invalid.",timeLeft);this.useRealTime=useRealTime;const globalTime=this.getGlobalTime();this.time=timeLeft===undefined?undefined:globalTime+timeLeft;this.setTime=timeLeft}set(timeLeft=0){ASSERT(isNumber(timeLeft),"Timer is invalid.",timeLeft);const globalTime=this.getGlobalTime();this.time=globalTime+timeLeft;this.setTime=timeLeft}setUseRealTime(useRealTime=true){ASSERT(!this.isSet(),"Cannot change global time setting while timer is set.");this.useRealTime=useRealTime}unset(){this.time=undefined}isSet(){return this.time!==undefined}active(){return this.getGlobalTime()<this.time}elapsed(){return this.getGlobalTime()>=this.time}get(){return this.isSet()?this.getGlobalTime()-this.time:0}getPercent(){if(!this.isSet())return 0;if(!this.setTime)return 1;return 1-percent(this.time-this.getGlobalTime(),0,this.setTime)}getSetTime(){return this.isSet()?this.setTime:0}getGlobalTime(){return this.useRealTime?timeReal:time}toString(){return this.isSet()?abs(this.get())+" seconds "+(this.get()<0?"before":"after"):"unset"}valueOf(){return this.get()}}function formatTime(t){const signStr=t<0?"-":"";t=abs(t)|0;return signStr+(t/60|0)+":"+(t%60<10?"0":"")+t%60}async function fetchJSON(url){const response=await fetch(url);if(!response.ok)throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);return response.json()}function saveText(text,filename="text",type="text/plain"){saveDataURL(URL.createObjectURL(new Blob([text],{type:type})),filename)}function saveCanvas(canvas,filename="screenshot",type="image/png"){if(canvas instanceof OffscreenCanvas){const saveCanvas=document.createElement("canvas");saveCanvas.width=canvas.width;saveCanvas.height=canvas.height;saveCanvas.getContext("2d").drawImage(canvas,0,0);saveDataURL(saveCanvas.toDataURL(type),filename)}else saveDataURL(canvas.toDataURL(type),filename)}function saveDataURL(url,filename="download",revokeTime){ASSERT(isStringLike(url),"saveDataURL requires url string");ASSERT(isStringLike(filename),"saveDataURL requires filename string");const link=document.createElement("a");link.download=filename;link.href=url;link.click();if(revokeTime!==undefined)setTimeout(()=>URL.revokeObjectURL(url),revokeTime)}function shareURL(title,url,callback){ASSERT(isStringLike(title),"shareURL requires title string");ASSERT(isStringLike(url),"shareURL requires url string");navigator.share?.({title:title,url:url}).then(()=>callback?.())}function readSaveData(saveName,defaultSaveData){ASSERT(isStringLike(saveName),"readSaveData requires saveName string");ASSERT(defaultSaveData===undefined||typeof defaultSaveData==="object"&&defaultSaveData!==null,"readSaveData: default must be an object - the result is "+"{...default, ...loaded}, so a scalar default yields {}. "+"Use readSaveData(key, {best:0}).best");let loadedData={};try{const data=localStorage[saveName];if(data){try{loadedData=JSON.parse(data)}catch{LOG("readSaveData: corrupt JSON for",saveName,"— using defaults")}}}catch{LOG("readSaveData: localStorage unavailable — using defaults")}return{...defaultSaveData,...loadedData}}function writeSaveData(saveName,saveData){ASSERT(isStringLike(saveName),"writeSaveData requires saveName string");try{localStorage[saveName]=JSON.stringify(saveData)}catch{LOG("writeSaveData: failed to write",saveName)}}function noiseHash(i){let h=(i|0)^2654435769;h=Math.imul(h^h>>>16,2246822507);h=Math.imul(h^h>>>13,3266489909);h^=h>>>16;return(h>>>0)/2**32}function noise1D(x){const i=floor(x);return lerp(noiseHash(i),noiseHash(i+1),smoothStep(x-i))}function noise2D(x,y){const ix=floor(x),iy=floor(y);const fx=smoothStep(x-ix),fy=smoothStep(y-iy);const h=(a,b)=>noiseHash(a+b*374761393);return lerp(lerp(h(ix,iy),h(ix+1,iy),fx),lerp(h(ix,iy+1),h(ix+1,iy+1),fx),fy)}let cameraPos=vec2();let cameraAngle=0;let cameraScale=32;let timeScale=1;let canvasColorTiles=true;let canvasClearColor=CLEAR_BLACK;let canvasMaxSize=vec2(1920,1080);let canvasMinAspect=0;let canvasMaxAspect=0;let canvasFixedSize=vec2();let canvasPixelated=false;let tilesPixelated=true;let canvasPixelRatio=1;let fontDefault="arial";let showSplashScreen=false;let headlessMode=false;let engineManualStep=false;let glEnable=true;let glCircleSides=32;let tileDefaultSize=vec2(16);let tileDefaultPadding=0;let tileDefaultBleed=0;let enablePhysicsSolver=true;let objectDefaultMass=1;let objectDefaultDamping=1;let objectDefaultAngleDamping=1;let objectDefaultRestitution=0;let objectDefaultFriction=.8;let objectMaxSpeed=1;let gravity=vec2();let particleEmitRateScale=1;let gamepadsEnable=true;let gamepadDirectionEmulateStick=true;let gamepadAxisFilterEnable=true;let inputWASDEmulateDirection=true;let touchInputEnable=true;let touchGamepadEnable=false;let touchGamepadPassthrough=false;let touchGamepadCenterButtonSize=0;let touchGamepadButtonCount=4;let touchGamepadLeftStick=true;let touchGamepadLeftButtonCount=0;let touchGamepadRightStick=false;let touchGamepadAnalog=true;let touchGamepadFloating=false;let touchGamepadSize=100;let touchGamepadAlpha=.3;let touchGamepadDisplayTime=3;let touchGamepadVibration=0;let vibrateEnable=true;let soundEnable=true;let soundVolume=.3;let soundDefaultRange=40;let soundDefaultTaper=.7;function setCameraPos(pos){cameraPos=pos.copy()}function setCameraAngle(angle){cameraAngle=angle}function setCameraScale(scale){cameraScale=scale}function setTimeScale(scale){timeScale=scale}function setCanvasColorTiles(colorTiles){canvasColorTiles=colorTiles}function setCanvasClearColor(color){canvasClearColor=color.copy()}function setCanvasMaxSize(size){canvasMaxSize=size.copy()}function setCanvasMinAspect(aspect){canvasMinAspect=aspect}function setCanvasMaxAspect(aspect){canvasMaxAspect=aspect}function setCanvasFixedSize(size){canvasFixedSize=size.copy()}function setCanvasPixelated(pixelated){canvasPixelated=pixelated;if(mainCanvas)mainCanvas.style.imageRendering=pixelated?"pixelated":"";if(glCanvas)glCanvas.style.imageRendering=pixelated?"pixelated":""}function setTilesPixelated(pixelated){tilesPixelated=pixelated}function setCanvasPixelRatio(pixelRatio){canvasPixelRatio=pixelRatio}function setFontDefault(font){fontDefault=font}function setShowSplashScreen(show){showSplashScreen=show}function setHeadlessMode(headless){headlessMode=headless}function setEngineManualStep(enable=true){engineManualStep=enable}function setGLEnable(enable){if(enable&&!glCanBeEnabled){console.warn("Can not enable WebGL if it was disabled on start.");return}glEnable=enable;if(glCanvas)glCanvas.style.display=enable?"":"none"}function setGLCircleSides(sides){glCircleSides=sides}function setTileDefaultSize(size){tileDefaultSize=size.copy()}function setTileDefaultPadding(padding){tileDefaultPadding=padding}function setTileDefaultBleed(bleed){tileDefaultBleed=bleed}function setEnablePhysicsSolver(enable){enablePhysicsSolver=enable}function setObjectDefaultMass(mass){objectDefaultMass=mass}function setObjectDefaultDamping(damp){objectDefaultDamping=damp}function setObjectDefaultAngleDamping(damp){objectDefaultAngleDamping=damp}function setObjectDefaultRestitution(restitution){objectDefaultRestitution=restitution}function setObjectDefaultFriction(friction){objectDefaultFriction=friction}function setObjectMaxSpeed(speed){objectMaxSpeed=speed}function setGravity(newGravity){gravity=newGravity.copy()}function setParticleEmitRateScale(scale){particleEmitRateScale=scale}function setGamepadsEnable(enable){gamepadsEnable=enable}function setGamepadDirectionEmulateStick(enable){gamepadDirectionEmulateStick=enable}function setGamepadAxisFilterEnable(enable){gamepadAxisFilterEnable=enable}function setInputWASDEmulateDirection(enable){inputWASDEmulateDirection=enable}function setTouchInputEnable(enable){touchInputEnable=enable}function setTouchGamepadEnable(enable){touchGamepadEnable=enable}function setTouchGamepadPassthrough(passthrough){touchGamepadPassthrough=passthrough}function setTouchGamepadCenterButtonSize(size){touchGamepadCenterButtonSize=size}function setTouchGamepadButtonCount(count){touchGamepadButtonCount=count;if(count>0)touchGamepadRightStick=false}function setTouchGamepadLeftStick(enable){touchGamepadLeftStick=enable;if(enable)touchGamepadLeftButtonCount=0}function setTouchGamepadLeftButtonCount(count){touchGamepadLeftButtonCount=count;if(count>0)touchGamepadLeftStick=false}function setTouchGamepadRightStick(rightStick){touchGamepadRightStick=rightStick;if(rightStick)touchGamepadButtonCount=0}function setTouchGamepadAnalog(analog){touchGamepadAnalog=analog}function setTouchGamepadFloating(floating){touchGamepadFloating=floating}function setTouchGamepadSize(size){touchGamepadSize=size}function setTouchGamepadAlpha(alpha){touchGamepadAlpha=alpha}function setTouchGamepadDisplayTime(time){touchGamepadDisplayTime=time}function setTouchGamepadVibration(ms){touchGamepadVibration=ms}function setVibrateEnable(enable){vibrateEnable=enable}function setSoundEnable(enable){soundEnable=enable}function setSoundVolume(volume){soundVolume=volume;if(soundEnable&&!headlessMode&&audioMasterGain)audioMasterGain.gain.value=volume}function setSoundDefaultRange(range){soundDefaultRange=range}function setSoundDefaultTaper(taper){soundDefaultTaper=taper}function setDebugWatermark(show){debugWatermark=show}function setDebugKey(key){debugKey=key}class EngineObject{constructor(pos=vec2(),size=vec2(1),tileInfo,angle=0,color=WHITE,renderOrder=0){ASSERT(isVector2(pos),"object pos must be a vec2");ASSERT(isVector2(size),"object size must be a vec2");ASSERT(!tileInfo||tileInfo instanceof TileInfo,"object tileInfo should be a TileInfo or undefined");ASSERT(typeof angle==="number"&&isFinite(angle),"object angle should be a number");ASSERT(isColor(color),"object color should be a valid rgba color");ASSERT(typeof renderOrder==="number","object renderOrder should be a number");this.pos=pos.copy();this.size=size.copy();this.drawSize=undefined;this.tileInfo=tileInfo;this.angle=angle;this.color=color.copy();this.additiveColor=undefined;this.mirror=false;this.destroyed=false;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.restitution=objectDefaultRestitution;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=renderOrder;this.velocity=vec2();this.angleVelocity=0;this.spawnTime=time;this.children=[];this.clampSpeed=true;this.groundObject=undefined;this.parent=undefined;this.localPos=vec2();this.localAngle=0;this.collideTiles=false;this.collideSolidObjects=false;this.isSolid=false;this.collideRaycast=false;engineObjects.push(this)}updateTransforms(){const parent=this.parent;if(parent){const mirror=parent.getMirrorSign();const lp=this.localPos,pp=parent.pos;const lx=lp.x*mirror,ly=lp.y,pa=parent.angle;if(pa){const c=cos(-pa),s=sin(-pa);this.pos.set(lx*c-ly*s+pp.x,lx*s+ly*c+pp.y)}else this.pos.set(lx+pp.x,ly+pp.y);this.angle=mirror*this.localAngle+pa}for(const child of this.children)child.updateTransforms()}updatePhysics(){ASSERT(!this.parent);if(this.destroyed)return;if(this.clampSpeed){this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed);this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed)}const oldPos=this.pos.copy();this.velocity.x*=this.damping;this.velocity.y*=this.damping;if(this.mass){this.velocity.x+=gravity.x*this.gravityScale;this.velocity.y+=gravity.y*this.gravityScale}this.pos.x+=this.velocity.x;this.pos.y+=this.velocity.y;this.angle+=this.angleVelocity*=this.angleDamping;ASSERT(this.angleDamping>=0&&this.angleDamping<=1);ASSERT(this.damping>=0&&this.damping<=1);if(!enablePhysicsSolver||!this.mass)return;const wasFalling=this.velocity.y<0&&gravity.y<0||this.velocity.y>0&&gravity.y>0;if(this.groundObject){const friction=max(this.friction,this.groundObject.friction);const groundSpeed=this.groundObject.velocity.x;this.velocity.x=groundSpeed+(this.velocity.x-groundSpeed)*friction;this.groundObject=undefined}if(this.collideSolidObjects){const epsilon=.001;for(const o of engineObjectsCollide){if(o.destroyed||o.parent||o===this)continue;if(!this.isSolid&&!o.isSolid)continue;if(!this.isOverlappingObject(o))continue;const collide1=this.collideWithObject(o);const collide2=o.collideWithObject(this);if(!collide1||!collide2)continue;if(isOverlapping(oldPos,this.size,o.pos,o.size)){const deltaPos=oldPos.subtract(o.pos);const length=deltaPos.length();const pushAwayAccel=.001;const velocity=length<.001?vec2(0,1):deltaPos.scale(pushAwayAccel/length);this.velocity=this.velocity.add(velocity);if(o.mass)o.velocity=o.velocity.subtract(velocity);debugPhysics&&debugOverlap(this.pos,this.size,o.pos,o.size,"#f00");continue}const sizeBoth=this.size.add(o.size);const smallStepUp=(oldPos.y-o.pos.y)*2>sizeBoth.y+gravity.y;const isBlockedX=abs(oldPos.y-o.pos.y)*2<sizeBoth.y;const isBlockedY=abs(oldPos.x-o.pos.x)*2<sizeBoth.x;const restitution=max(this.restitution,o.restitution);if(smallStepUp||isBlockedY||!isBlockedX){this.pos.y=o.pos.y+(sizeBoth.y/2+epsilon)*sign(oldPos.y-o.pos.y);if(o.groundObject&&wasFalling||!o.mass){if(wasFalling)this.groundObject=o;this.velocity.y*=-restitution}else if(o.mass){const inelastic=(this.mass*this.velocity.y+o.mass*o.velocity.y)/(this.mass+o.mass);const elastic0=this.velocity.y*(this.mass-o.mass)/(this.mass+o.mass)+o.velocity.y*2*o.mass/(this.mass+o.mass);const elastic1=o.velocity.y*(o.mass-this.mass)/(this.mass+o.mass)+this.velocity.y*2*this.mass/(this.mass+o.mass);this.velocity.y=lerp(inelastic,elastic0,restitution);o.velocity.y=lerp(inelastic,elastic1,restitution)}}if(!smallStepUp&&isBlockedX){this.pos.x=o.pos.x+(sizeBoth.x/2+epsilon)*sign(oldPos.x-o.pos.x);if(o.mass){const inelastic=(this.mass*this.velocity.x+o.mass*o.velocity.x)/(this.mass+o.mass);const elastic0=this.velocity.x*(this.mass-o.mass)/(this.mass+o.mass)+o.velocity.x*2*o.mass/(this.mass+o.mass);const elastic1=o.velocity.x*(o.mass-this.mass)/(this.mass+o.mass)+this.velocity.x*2*this.mass/(this.mass+o.mass);this.velocity.x=lerp(inelastic,elastic0,restitution);o.velocity.x=lerp(inelastic,elastic1,restitution)}else this.velocity.x*=-restitution}debugPhysics&&debugOverlap(this.pos,this.size,o.pos,o.size,"#f0f")}}if(this.collideTiles){const hitLayer=tileCollisionTest(this.pos,this.size,this);if(hitLayer){if(!tileCollisionTest(oldPos,this.size,this)){const isBlockedX=tileCollisionTest(vec2(this.pos.x,oldPos.y),this.size,this);const isBlockedY=tileCollisionTest(vec2(oldPos.x,this.pos.y),this.size,this);const restitution=max(this.restitution,hitLayer.restitution);if(isBlockedX){const epsilon=.001;const maxMove=.1;const gravitySign=gravity.y>0?-1:1;const y=gravitySign>0?floor(oldPos.y-this.size.y/2+1)+this.size.y/2+epsilon:ceil(oldPos.y+this.size.y/2-1)-this.size.y/2-epsilon;const delta=abs(y-this.pos.y);if(delta<maxMove)if(!tileCollisionTest(vec2(this.pos.x,y),this.size,this)){this.pos.y=y;debugPhysics&&debugRect(this.pos,this.size,"#ff0");return}this.pos.x=oldPos.x;this.velocity.x*=-restitution}if(isBlockedY||!isBlockedX){if(wasFalling){const epsilon=1e-4;const offset=this.size.y/2+epsilon;this.pos.y=gravity.y<0?floor(oldPos.y-this.size.y/2)+offset:ceil(oldPos.y+this.size.y/2)-offset;this.groundObject=hitLayer}else{this.pos.y=oldPos.y;this.groundObject=undefined}this.velocity.y*=-restitution}debugPhysics&&debugRect(this.pos,this.size,"#f00")}}}}update(){}render(){drawTile(this.pos,this.drawSize||this.size,this.tileInfo,this.color,this.angle,this.mirror,this.additiveColor)}renderLight(){}destroy(immediate=false){if(this.destroyed)return;this.destroyed=true;this.parent?.removeChild(this);for(const child of this.children){child.parent=undefined;child.destroy(immediate)}}localToWorld(pos){return this.pos.add(pos.rotate(this.angle))}worldToLocal(pos){return pos.subtract(this.pos).rotate(-this.angle)}localToWorldVector(vec){return vec.rotate(this.angle)}worldToLocalVector(vec){return vec.rotate(-this.angle)}collideWithTile(tileData,pos){return tileData>0}collideWithObject(object){return true}getUp(scale=1){return vec2().setAngle(this.angle,scale)}getRight(scale=1){return vec2().setAngle(this.angle+PI/2,scale)}getAliveTime(){return time-this.spawnTime}getSpeed(){return this.velocity.length()}applyAcceleration(acceleration){if(this.mass)this.velocity=this.velocity.add(acceleration)}applyAngularAcceleration(acceleration){if(this.mass)this.angleVelocity+=acceleration}applyForce(force){if(this.mass)this.applyAcceleration(force.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(child,localPos=vec2(),localAngle=0){ASSERT(!this.destroyed,"cannot add child to destroyed object");if(this.destroyed)return child;ASSERT(!child.parent&&!this.children.includes(child));ASSERT(child instanceof EngineObject,"child must be an EngineObject");ASSERT(child!==this,"cannot add self as child");this.children.push(child);child.parent=this;child.localPos=localPos.copy();child.localAngle=localAngle;child.updateTransforms();return child}removeChild(child){ASSERT(child.parent===this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=undefined}isOverlappingObject(object){return this.isOverlapping(object.pos,object.size)}isOverlapping(pos,size=vec2()){return isOverlapping(this.pos,this.size,pos,size)}setCollision(collideSolidObjects=true,isSolid=true,collideTiles=true,collideRaycast=true){ASSERT(collideSolidObjects||!isSolid,"solid objects must be set to collide");this.collideSolidObjects=collideSolidObjects;this.isSolid=isSolid;this.collideTiles=collideTiles;this.collideRaycast=collideRaycast}toString(){let text="type = "+this.constructor.name;if(this.pos.x||this.pos.y)text+="\npos = "+this.pos;if(this.velocity.x||this.velocity.y)text+="\nvelocity = "+this.velocity;if(this.size.x||this.size.y)text+="\nsize = "+this.size;if(this.angle)text+="\nangle = "+this.angle.toFixed(3);if(this.color)text+="\ncolor = "+this.color;return text}renderDebugInfo(){if(!debug)return;const hasPhysics=this.collideTiles||this.collideSolidObjects||this.isSolid;if(!hasPhysics&&!this.parent)return;const size=vec2(max(this.size.x,.2),max(this.size.y,.2));const color=rgb(this.collideTiles?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,.5);debugRect(this.pos,size,color,0,this.angle,hasPhysics);if(this.parent)debugRect(this.pos,size.scale(.8),rgb(1,1,1,.5),0,this.angle);this.parent&&debugLine(this.pos,this.parent.pos,rgb(1,1,1,.5),.5)}}let mainCanvas;let mainContext;let drawContext;let workCanvas;let workContext;let workReadCanvas;let workReadContext;let backgroundCanvas;let mainCanvasSize=vec2();let textureInfos=[];let drawCount;let primitiveCount;function isWhite(c){return c.r>=1&&c.g>=1&&c.b>=1}function isBlack(c){return c.r<=0&&c.g<=0&&c.b<=0&&c.a<=0}function tile(index=0,size=tileDefaultSize,texture=0,padding=tileDefaultPadding,bleed=tileDefaultBleed){ASSERT(isVector2(index)||typeof index==="number","index must be a vec2 or number");ASSERT(isVector2(size)||typeof size==="number","size must be a vec2 or number");ASSERT(isNumber(texture)||texture instanceof TextureInfo,"texture must be a number or TextureInfo");ASSERT(isNumber(padding),"padding must be a number");if(headlessMode)return new TileInfo;if(typeof size==="number"){ASSERT(size>0);size=new Vector2(size,size)}const textureInfo=typeof texture==="number"?textureInfos[texture]:texture;ASSERT(textureInfo instanceof TextureInfo,"tile texture is not loaded");ASSERT(textureInfo.size.x>0,"tile texture is not loaded");const sizePaddedX=size.x+padding*2;const sizePaddedY=size.y+padding*2;let x,y;if(typeof index==="number"){const cols=textureInfo.size.x/sizePaddedX|0;x=index%cols;y=index/cols|0}else{x=index.x;y=index.y}const pos=new Vector2(x*sizePaddedX+padding,y*sizePaddedY+padding);return new TileInfo(pos,size,textureInfo,padding,bleed)}class TileInfo{constructor(pos=vec2(),size=tileDefaultSize,textureInfo=textureInfos[0],padding=tileDefaultPadding,bleed=tileDefaultBleed,columns=0){this.pos=pos.copy();this.size=size.copy();this.padding=padding;this.textureInfo=textureInfo;this.bleed=bleed;this.columns=columns}offset(offset){return new TileInfo(this.pos.add(offset),this.size,this.textureInfo,this.padding,this.bleed,this.columns)}frame(frame){ASSERT(typeof frame==="number");const w=this.size.x+this.padding*2;const h=this.size.y+this.padding*2;const x=(this.columns?frame%this.columns:frame)*w;const y=(this.columns?frame/this.columns|0:0)*h;ASSERT(this.pos.x+x+this.size.x<=this.textureInfo.size.x,"frame extends beyond texture width!");ASSERT(this.pos.y+y+this.size.y<=this.textureInfo.size.y,"frame extends beyond texture height!");return this.offset(new Vector2(x,y))}setColumns(columns=0){ASSERT(isNumber(columns)&&columns>=0,"columns must be a number >= 0");this.columns=columns;return this}index(index){return tile(index,this.size,this.textureInfo,this.padding,this.bleed).setColumns(this.columns)}setFullImage(textureInfo=this.textureInfo){this.textureInfo=textureInfo;this.pos=new Vector2;this.size=textureInfo.size.copy();this.bleed=this.padding=this.columns=0;return this}}class TextureInfo{constructor(image,useWebGL=true,wrap=false){this.image=image;this.size=image?vec2(image.width,image.height):vec2();this.sizeInverse=image?vec2(1/image.width,1/image.height):vec2();this.glTexture=undefined;this.wrap=wrap;useWebGL&&this.createWebGLTexture()}createWebGLTexture(){glRegisterTextureInfo(this)}destroyWebGLTexture(){glUnregisterTextureInfo(this)}hasWebGL(){return!!this.glTexture}setWrap(wrap=true){this.wrap=wrap;glSetTextureWrap(this.glTexture,wrap)}}function drawTile(pos,size=vec2(1),tileInfo,color=WHITE,angle=0,mirror,additiveColor,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isColor(color),"color is invalid");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!additiveColor||isColor(additiveColor),"additiveColor must be a color");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");const textureInfo=tileInfo?.textureInfo;const bleed=tileInfo?.bleed??0;if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");if(screenSpace)[pos,size,angle]=screenToWorldTransform(pos,size,angle);if(textureInfo){const sizeInverse=textureInfo.sizeInverse;const x=tileInfo.pos.x*sizeInverse.x;const y=tileInfo.pos.y*sizeInverse.y;const w=tileInfo.size.x*sizeInverse.x;const h=tileInfo.size.y*sizeInverse.y;glSetTexture(textureInfo.glTexture);if(bleed){const bleedX=sizeInverse.x*bleed;const bleedY=sizeInverse.y*bleed;glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x+bleedX,y+bleedY,x-bleedX+w,y-bleedY+h,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt())}else{glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x,y,x+w,y+h,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt())}}else{const combined=additiveColor?color.add(additiveColor):color;glDrawUntextured(pos.x,pos.y,size.x,size.y,angle,combined.rgbaInt())}}else{++drawCount;++primitiveCount;drawCanvas2D(pos,size,angle,mirror,context=>{if(textureInfo){context.scale(1,-1);const x=tileInfo.pos.x,y=tileInfo.pos.y;const w=tileInfo.size.x,h=tileInfo.size.y;drawImageColor(context,textureInfo.image,x,y,w,h,-.5,-.5,1,1,color,additiveColor,bleed)}else{const c=additiveColor?color.add(additiveColor):color;context.fillStyle=c.toString();context.fillRect(-.5,-.5,1,1)}},screenSpace,context)}}function drawRect(pos,size,color,angle,useWebGL,screenSpace,context){drawTile(pos,size,undefined,color,angle,false,undefined,useWebGL,screenSpace,context)}function drawRectGradient(pos,size,colorTop=WHITE,colorBottom=CLEAR_WHITE,angle=0,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isColor(colorTop)&&isColor(colorBottom),"color is invalid");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");if(screenSpace){pos=screenToWorld(pos);size=size.scale(1/cameraScale);angle+=cameraAngle}const points=[],colors=[];const halfSizeX=size.x/2,halfSizeY=size.y/2;const colorTopInt=colorTop.rgbaInt();const colorBottomInt=colorBottom.rgbaInt();const c=cos(-angle),s=sin(-angle);for(let i=4;i--;){const x=i&1?halfSizeX:-halfSizeX;const y=i&2?halfSizeY:-halfSizeY;const rx=x*c-y*s;const ry=x*s+y*c;const color=i&2?colorTopInt:colorBottomInt;points.push(vec2(pos.x+rx,pos.y+ry));colors.push(color)}glDrawColoredPoints(points,colors)}else{++drawCount;++primitiveCount;drawCanvas2D(pos,size,angle,false,context=>{const gradient=context.createLinearGradient(0,.5,0,-.5);gradient.addColorStop(0,colorTop.toString());gradient.addColorStop(1,colorBottom.toString());context.fillStyle=gradient;context.fillRect(-.5,-.5,1,1)},screenSpace,context)}}function drawTextureWrapped(pos,size,wrapCount,texture=0,color=WHITE,angle=0,additiveColor,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isVector2(wrapCount),"wrapCount must be a vec2");ASSERT(isColor(color),"color is invalid");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!additiveColor||isColor(additiveColor),"additiveColor must be a color");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");ASSERT(!(texture instanceof TileInfo),"pass a TextureInfo or texture index, not a TileInfo — use tileInfo.textureInfo");if(headlessMode)return;const textureInfo=typeof texture==="number"?textureInfos[texture]:texture;ASSERT(textureInfo instanceof TextureInfo,"texture not loaded");ASSERT(textureInfo.size.x>0,"texture not loaded");ASSERT(textureInfo.wrap,"drawTextureWrapped requires a wrap-enabled texture; call textureInfo.setWrap(true) first");if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");if(screenSpace)[pos,size,angle]=screenToWorldTransform(pos,size,angle);glSetTexture(textureInfo.glTexture);glDraw(pos.x,pos.y,size.x,size.y,angle,0,0,wrapCount.x,wrapCount.y,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt());return}++drawCount;++primitiveCount;if(!screenSpace){pos=worldToScreen(pos);size=size.scale(cameraScale);angle-=cameraAngle}const noTint=!canvasColorTiles||(additiveColor?isWhite(color.add(additiveColor))&&additiveColor.a<=0:isWhite(color));const alphaBaked=!noTint&&additiveColor&&!isBlack(additiveColor);const source=noTint?textureInfo.image:bakeTintedImage(textureInfo.image,color,additiveColor);context=context||drawContext;context.save();context.translate(pos.x+.5,pos.y+.5);context.rotate(angle);context.globalAlpha=alphaBaked?1:color.a;const pattern=context.createPattern(source,"repeat");const m=(new DOMMatrix).translate(-size.x/2,-size.y/2).scale(size.x/(wrapCount.x*source.width),size.y/(wrapCount.y*source.height));pattern.setTransform(m);context.fillStyle=pattern;context.fillRect(-size.x/2,-size.y/2,size.x,size.y);context.globalAlpha=1;context.restore()}function drawLineList(points,width=.1,color=WHITE,wrap=false,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=false,context){ASSERT(isArray(points),"points must be an array");ASSERT(isNumber(width),"width must be a number");ASSERT(isColor(color),"color is invalid");ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");let size=vec2(1);if(screenSpace)[pos,size,angle]=screenToWorldTransform(pos,size,angle);glDrawOutlineTransform(points,color.rgbaInt(),width,pos.x,pos.y,size.x,size.y,angle,wrap)}else{++drawCount;++primitiveCount;drawCanvas2D(pos,vec2(1),angle,false,context=>{context.strokeStyle=color.toString();context.lineWidth=width;context.beginPath();for(let i=0;i<points.length;++i){const point=points[i];context.lineTo(point.x,point.y)}wrap&&context.closePath();context.stroke()},screenSpace,context)}}function drawLine(posA,posB,width=.1,color=WHITE,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=false,context){const halfDelta=vec2((posB.x-posA.x)/2,(posB.y-posA.y)/2);const size=vec2(width,halfDelta.length()*2);pos=pos.add(posA.add(halfDelta));if(screenSpace)halfDelta.y*=-1;angle+=halfDelta.angle();drawRect(pos,size,color,angle,useWebGL,screenSpace,context)}function drawRegularPoly(pos,size=vec2(1),sides=3,color=WHITE,lineWidth=0,lineColor=BLACK,angle=0,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(size),"size must be a vec2");ASSERT(isNumber(sides),"sides must be a number");const points=[];const sizeX=size.x/2,sizeY=size.y/2;for(let i=sides;i--;){const a=i/sides*PI*2;points.push(vec2(sin(a)*sizeX,cos(a)*sizeY))}drawPoly(points,color,lineWidth,lineColor,pos,angle,useWebGL,screenSpace,context)}function drawPoly(points,color=WHITE,lineWidth=0,lineColor=BLACK,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=false,context=undefined){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isArray(points),"points must be an array");ASSERT(isColor(color)&&isColor(lineColor),"color is invalid");ASSERT(isNumber(lineWidth),"lineWidth must be a number");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");let size=vec2(1);if(screenSpace)[pos,size,angle]=screenToWorldTransform(pos,size,angle);glDrawPointsTransform(points,color.rgbaInt(),pos.x,pos.y,size.x,size.y,angle);if(lineWidth>0)glDrawOutlineTransform(points,lineColor.rgbaInt(),lineWidth,pos.x,pos.y,size.x,size.y,angle)}else{drawCanvas2D(pos,vec2(1),angle,false,context=>{context.fillStyle=color.toString();context.beginPath();for(const point of points)context.lineTo(point.x,point.y);context.closePath();context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}},screenSpace,context)}}function drawEllipse(pos,size=vec2(1),color=WHITE,angle=0,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isColor(color)&&isColor(lineColor),"color is invalid");ASSERT(isNumber(angle),"angle must be a number");ASSERT(isNumber(lineWidth),"lineWidth must be a number");ASSERT(lineWidth>=0,"lineWidth must be a positive value or 0");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");lineWidth=clamp(lineWidth,0,min(size.x,size.y));if(useWebGL&&glEnable){const sides=glCircleSides;drawRegularPoly(pos,size,sides,color,lineWidth,lineColor,angle,useWebGL,screenSpace,context)}else{drawCanvas2D(pos,vec2(1),angle,false,context=>{context.fillStyle=color.toString();context.beginPath();context.ellipse(0,0,size.x/2,size.y/2,0,0,9);context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}},screenSpace,context)}}function drawCircle(pos,size=1,color=WHITE,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=false,context){ASSERT(isNumber(size),"size must be a number");drawEllipse(pos,vec2(size),color,0,lineWidth,lineColor,useWebGL,screenSpace,context)}let drawEllipseGradientOffset=0;function drawEllipseGradient(pos,size=vec2(1),colorInner=WHITE,colorOuter=CLEAR_WHITE,angle=0,useWebGL=glEnable,screenSpace=false,context){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isColor(colorInner)&&isColor(colorOuter),"color is invalid");ASSERT(isNumber(angle),"angle must be a number");ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(headlessMode)return;if(useWebGL&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");if(screenSpace){pos=screenToWorld(pos);size=size.scale(1/cameraScale);angle+=cameraAngle}const sides=glCircleSides;const radiusX=size.x/2,radiusY=size.y/2;const innerInt=colorInner.rgbaInt();const outerInt=colorOuter.rgbaInt();const offset=drawEllipseGradientOffset++;const c=cos(-angle),s=sin(-angle);const rim=a=>{const lx=sin(a)*radiusX,ly=cos(a)*radiusY;return vec2(pos.x+lx*c-ly*s,pos.y+lx*s+ly*c)};const startA=offset%sides/sides*PI*2;const points=[rim(startA)];const colors=[outerInt];for(let i=sides;i--;){const a=(i+offset)%sides/sides*PI*2;points.push(pos);colors.push(innerInt);points.push(rim(a));colors.push(outerInt)}glDrawColoredPoints(points,colors)}else{++drawCount;++primitiveCount;drawCanvas2D(pos,size,angle,false,context=>{const gradient=context.createRadialGradient(0,0,0,0,0,.5);gradient.addColorStop(0,colorInner.toString());gradient.addColorStop(1,colorOuter.toString());context.fillStyle=gradient;context.beginPath();context.ellipse(0,0,.5,.5,0,0,9);context.fill()},screenSpace,context)}}function drawCircleGradient(pos,size=1,colorInner=WHITE,colorOuter=CLEAR_WHITE,useWebGL=glEnable,screenSpace=false,context){ASSERT(isNumber(size),"size must be a number");drawEllipseGradient(pos,vec2(size),colorInner,colorOuter,0,useWebGL,screenSpace,context)}function drawCanvas2D(pos,size,angle=0,mirror=false,drawFunction,screenSpace=false,context=drawContext){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isNumber(angle),"angle must be a number");ASSERT(typeof drawFunction==="function","drawFunction must be a function");if(!screenSpace){pos=worldToScreen(pos);size=size.scale(cameraScale);angle-=cameraAngle}context.save();context.translate(pos.x+.5,pos.y+.5);context.rotate(angle);context.scale(mirror?-size.x:size.x,-size.y);drawFunction(context);context.restore()}function drawText(text,pos,size=1,color=WHITE,lineWidth=0,lineColor=BLACK,textAlign="center",font=fontDefault,fontStyle="",maxWidth,angle=0,context=drawContext){pos=worldToScreen(pos);size*=cameraScale;lineWidth*=cameraScale;angle-=cameraAngle;angle*=-1;drawTextScreen(text,pos,size,color,lineWidth,lineColor,textAlign,font,fontStyle,maxWidth,angle,context)}function drawTextScreen(text,pos,size,color=WHITE,lineWidth=0,lineColor=BLACK,textAlign="center",font=fontDefault,fontStyle="",maxWidth,angle=0,context=drawContext){ASSERT(isStringLike(text),"text must be a string");ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isNumber(size),"size must be a number");ASSERT(isColor(color),"color must be a color");ASSERT(isNumber(lineWidth),"lineWidth must be a number");ASSERT(isColor(lineColor),"lineColor must be a color");ASSERT(["left","center","right"].includes(textAlign),"align must be left, center, or right");ASSERT(isStringLike(font),"font must be a string");ASSERT(isStringLike(fontStyle),"fontStyle must be a string");ASSERT(isNumber(angle),"angle must be a number");const lines=(text+"").split("\n");const posY=pos.y-(lines.length-1)*size/2;context.save();context.fillStyle=color.toString();context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.textAlign=textAlign;context.font=fontStyle+" "+size+"px "+font;context.textBaseline="middle";context.translate(pos.x,posY);context.rotate(-angle);let yOffset=0;lines.forEach(line=>{lineWidth&&context.strokeText(line,0,yOffset,maxWidth);context.fillText(line,0,yOffset,maxWidth);yOffset+=size});context.restore()}async function loadTexture(textureIndex,src){ASSERT(isNumber(textureIndex),"textureIndex must be a number");ASSERT(!textureInfos[textureIndex],"textureIndex is already loaded!");ASSERT(!src||isStringLike(src),"image src must be a string");const image=new Image;if(src){await new Promise(resolve=>{image.onerror=image.onload=resolve;image.crossOrigin="anonymous";image.src=src})}textureInfos[textureIndex]=new TextureInfo(image)}function screenToWorld(screenPos){ASSERT(isVector2(screenPos),"screenPos must be a vec2");let x=(screenPos.x-mainCanvasSize.x/2+.5)/cameraScale;let y=(screenPos.y-mainCanvasSize.y/2+.5)/-cameraScale;if(cameraAngle){const c=cos(-cameraAngle),s=sin(-cameraAngle);const xr=x*c-y*s,yr=x*s+y*c;x=xr;y=yr}return new Vector2(x+cameraPos.x,y+cameraPos.y)}function worldToScreen(worldPos){ASSERT(isVector2(worldPos),"worldPos must be a vec2");let x=worldPos.x-cameraPos.x;let y=worldPos.y-cameraPos.y;if(cameraAngle){const c=cos(cameraAngle),s=sin(cameraAngle);const xr=x*c-y*s,yr=x*s+y*c;x=xr;y=yr}return new Vector2(x*cameraScale+mainCanvasSize.x/2-.5,y*-cameraScale+mainCanvasSize.y/2-.5)}function screenToWorldDelta(screenDelta){ASSERT(isVector2(screenDelta),"screenDelta must be a vec2");let x=screenDelta.x/cameraScale;let y=screenDelta.y/-cameraScale;if(cameraAngle){const c=cos(-cameraAngle),s=sin(-cameraAngle);const xr=x*c-y*s,yr=x*s+y*c;x=xr;y=yr}return new Vector2(x,y)}function worldToScreenDelta(worldDelta){ASSERT(isVector2(worldDelta),"worldDelta must be a vec2");let x=worldDelta.x;let y=worldDelta.y;if(cameraAngle){const c=cos(cameraAngle),s=sin(cameraAngle);const xr=x*c-y*s,yr=x*s+y*c;x=xr;y=yr}return new Vector2(x*cameraScale,y*-cameraScale)}function screenToWorldTransform(screenPos,screenSize,screenAngle=0){ASSERT(isVector2(screenPos),"screenPos must be a vec2");ASSERT(isVector2(screenSize),"screenSize must be a vec2");ASSERT(isNumber(screenAngle),"screenAngle must be a number");return[screenToWorld(screenPos),screenSize.scale(1/cameraScale),screenAngle+cameraAngle]}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function cameraFit(center,size,worldMargin,screenInset){ASSERT(isVector2(center),"center must be a vec2");ASSERT(isVector2(size),"size must be a vec2");const margin=padSides(worldMargin);const inset=padSides(screenInset);const worldW=size.x+margin.left+margin.right;const worldH=size.y+margin.top+margin.bottom;const viewW=mainCanvasSize.x-inset.left-inset.right;const viewH=mainCanvasSize.y-inset.top-inset.bottom;if(!(worldW>0&&worldH>0&&viewW>0&&viewH>0))return cameraScale;cameraScale=min(viewW/worldW,viewH/worldH);const marginVector=vec2(margin.right-margin.left,margin.top-margin.bottom).scale(.5);const insetVector=vec2(inset.right-inset.left,inset.top-inset.bottom).scale(.5/cameraScale);cameraPos=center.add(marginVector).add(insetVector);return cameraScale;function padSides(p){if(p===undefined||isNumber(p))p=vec2(p);if(isVector2(p))return{top:p.y,right:p.x,bottom:p.y,left:p.x};return{top:p.top||0,right:p.right||0,bottom:p.bottom||0,left:p.left||0}}}function isOnScreen(pos,size=0){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size)||isNumber(size),"size must be a vec2 or number");if(!cameraScale)return false;let x=pos.x-cameraPos.x;let y=pos.y-cameraPos.y;if(cameraAngle){const c=cos(cameraAngle),s=sin(cameraAngle);const xr=x*c-y*s,yr=x*s+y*c;x=xr;y=yr}x*=cameraScale*2;y*=-cameraScale*2;if(size instanceof Vector2)size=size.length();size*=cameraScale;const w=mainCanvasSize.x,h=mainCanvasSize.y;return x+size>-w&&x-size<w&&y+size>-h&&y-size<h}function setAdditiveBlendMode(additive=true){glAdditive=additive;drawContext.globalCompositeOperation=additive?"lighter":"source-over"}function setBackgroundCanvas(canvas){backgroundCanvas=canvas}function combineCanvases(){const w=mainCanvasSize.x,h=mainCanvasSize.y;workCanvas.width=w;workCanvas.height=h;workContext.fillStyle="#000";workContext.fillRect(0,0,w,h);if(backgroundCanvas)workContext.drawImage(backgroundCanvas,0,0,w,h);glCopyToContext(workContext);workContext.drawImage(mainCanvas,0,0);mainContext.drawImage(workCanvas,0,0)}function bakeTintedImage(image,color,additiveColor){const w=image.width|0,h=image.height|0;workReadCanvas.width=w;workReadCanvas.height=h;workReadContext.drawImage(image,0,0);const imageData=workReadContext.getImageData(0,0,w,h);const data=imageData.data;if(additiveColor&&!isBlack(additiveColor)){const colorMultiply=[color.r,color.g,color.b,color.a];const colorAdd=[additiveColor.r*255,additiveColor.g*255,additiveColor.b*255,additiveColor.a*255];for(let i=0;i<data.length;++i)data[i]=data[i]*colorMultiply[i&3]+colorAdd[i&3]|0}else{for(let i=0;i<data.length;i+=4){data[i]*=color.r;data[i+1]*=color.g;data[i+2]*=color.b}}workReadContext.putImageData(imageData,0,0);return workReadCanvas}function drawImageColor(context,image,sx,sy,sWidth,sHeight,dx,dy,dWidth,dHeight,color,additiveColor,bleed=0){const sx2=bleed;const sy2=bleed;sWidth=max(1,sWidth|0);sHeight=max(1,sHeight|0);const sWidth2=sWidth-2*bleed;const sHeight2=sHeight-2*bleed;if(!canvasColorTiles||(additiveColor?isWhite(color.add(additiveColor))&&additiveColor.a<=0:isWhite(color))){context.globalAlpha=color.a;context.drawImage(image,sx+sx2,sy+sy2,sWidth2,sHeight2,dx,dy,dWidth,dHeight);context.globalAlpha=1}else{workReadCanvas.width=sWidth;workReadCanvas.height=sHeight;workReadContext.drawImage(image,sx|0,sy|0,sWidth,sHeight,0,0,sWidth,sHeight);const imageData=workReadContext.getImageData(0,0,sWidth,sHeight);const data=imageData.data;if(additiveColor&&!isBlack(additiveColor)){const colorMultiply=[color.r,color.g,color.b,color.a];const colorAdd=[additiveColor.r*255,additiveColor.g*255,additiveColor.b*255,additiveColor.a*255];for(let i=0;i<data.length;++i)data[i]=data[i]*colorMultiply[i&3]+colorAdd[i&3]|0;workReadContext.putImageData(imageData,0,0);context.drawImage(workReadCanvas,sx2,sy2,sWidth2,sHeight2,dx,dy,dWidth,dHeight)}else{for(let i=0;i<data.length;i+=4){data[i]*=color.r;data[i+1]*=color.g;data[i+2]*=color.b}workReadContext.putImageData(imageData,0,0);context.globalAlpha=color.a;context.drawImage(workReadCanvas,sx2,sy2,sWidth2,sHeight2,dx,dy,dWidth,dHeight);context.globalAlpha=1}}}function isFullscreen(){return!!document.fullscreenElement}function toggleFullscreen(){const rootElement=mainCanvas.parentElement;if(isFullscreen()){if(document.exitFullscreen)document.exitFullscreen()}else if(rootElement.requestFullscreen)rootElement.requestFullscreen()}function setCursor(cursorStyle="auto"){const rootElement=mainCanvas.parentElement;rootElement.style.cursor=cursorStyle}let engineImageFont;class ImageFont{constructor(tileInfo){ASSERT(!!tileInfo,"tileInfo is required for ImageFont");this.tileInfo=tileInfo.frame(0)}drawText(text,pos,size=1,center,color,useWebGL,context){ASSERT(isVector2(size)||typeof size==="number","size must be a vec2 or number");if(typeof size==="number"){ASSERT(size>0);size*=cameraScale;size=new Vector2(size,size)}else size=size.scale(cameraScale);this.drawTextScreen(text,worldToScreen(pos),size,center,color,useWebGL,context)}drawTextScreen(text,pos,size,center=true,color=WHITE,useWebGL=glEnable,context){ASSERT(isStringLike(text),"text must be a string");ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size)||typeof size==="number","size must be a vec2 or number");ASSERT(isColor(color),"color must be a color");size=typeof size==="number"?new Vector2(size,size):size;const drawPos=new Vector2;const tileInfo=this.tileInfo;const padding=tileInfo.padding;const sizePaddedX=tileInfo.size.x+padding*2;const sizePaddedY=tileInfo.size.y+padding*2;const cols=tileInfo.textureInfo.size.x/sizePaddedX|0;(text+"").split("\n").forEach((line,j)=>{const centerOffset=center?(line.length-1)*size.x/2:0;for(let i=line.length;i--;){const charCode=line.charCodeAt(i);const index=charCode<32||charCode>127?95:charCode-32;const x=index%cols;const y=index/cols|0;tileInfo.pos.x=x*sizePaddedX+padding;tileInfo.pos.y=y*sizePaddedY+padding;drawPos.x=ceil(pos.x+i*size.x-centerOffset-size.x/2)+size.x/2-.5;drawPos.y=ceil(pos.y+j*size.y-size.y/2)+size.y/2-.5;drawTile(drawPos,size,tileInfo,color,0,false,undefined,useWebGL,true,context)}})}}async function imageFontInit(){const image=new Image;await new Promise(resolve=>{image.onerror=image.onload=resolve;image.crossOrigin="anonymous";image.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg=="});const tilePos=vec2(),tileSize=vec2(8),padding=1,bleed=0;const textureInfo=new TextureInfo(image);const tileInfo=new TileInfo(tilePos,tileSize,textureInfo,padding,bleed);engineImageFont=new ImageFont(tileInfo)}let mousePos=vec2();let mousePosScreen=vec2();let mouseDelta=vec2();let mouseDeltaScreen=vec2();let mouseWheel=0;let mouseInWindow=true;let isUsingGamepad=false;let lastInputDevice="mouse";let inputMouseMoveThreshold=6;let inputPreventDefault=true;let gamepadPrimary=0;const isTouchDevice=!headlessMode&&window.ontouchstart!==undefined;function setInputPreventDefault(preventDefault=true){inputPreventDefault=preventDefault}function setInputMouseMoveThreshold(threshold){inputMouseMoveThreshold=threshold}function usingMouseInput(){return lastInputDevice==="mouse"}function usingKeyboardInput(){return lastInputDevice==="keyboard"}function usingGamepadInput(){return lastInputDevice==="gamepad"}function inputClearKey(key,device=0,clearDown=true,clearPressed=true,clearReleased=true){if(!inputData[device])return;inputData[device][key]&=~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0))}function inputClear(){inputData.length=0;inputData[0]=[];touchGamepadButtons.length=0;touchGamepadSticks.length=0;touchGamepadStickPointerId.length=0;gamepadStickData.length=0;gamepadDpadData.length=0;gamepadAxisCentered.length=0}function keyIsDown(key,device=0){ASSERT(isStringLike(key),"key must be a number or string");ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return!!(inputData[device]?.[key]&1)}function keyWasPressed(key,device=0){ASSERT(isStringLike(key),"key must be a number or string");ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return!!(inputData[device]?.[key]&2)}function keyWasReleased(key,device=0){ASSERT(isStringLike(key),"key must be a number or string");ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return!!(inputData[device]?.[key]&4)}function keyDirection(up="ArrowUp",down="ArrowDown",left="ArrowLeft",right="ArrowRight"){ASSERT(isStringLike(up),"up key must be a string");ASSERT(isStringLike(down),"down key must be a string");ASSERT(isStringLike(left),"left key must be a string");ASSERT(isStringLike(right),"right key must be a string");const k=key=>keyIsDown(key)?1:0;return vec2(k(right)-k(left),k(up)-k(down))}function mouseIsDown(button){ASSERT(isNumber(button),"mouse button must be a number");return keyIsDown(button)}function mouseWasPressed(button){ASSERT(isNumber(button),"mouse button must be a number");return keyWasPressed(button)}function mouseWasReleased(button){ASSERT(isNumber(button),"mouse button must be a number");return keyWasReleased(button)}function gamepadIsDown(button,gamepad=gamepadPrimary){ASSERT(isNumber(button),"button must be a number");ASSERT(isNumber(gamepad),"gamepad must be a number");return keyIsDown(button,gamepad+1)}function gamepadWasPressed(button,gamepad=gamepadPrimary){ASSERT(isNumber(button),"button must be a number");ASSERT(isNumber(gamepad),"gamepad must be a number");return keyWasPressed(button,gamepad+1)}function gamepadWasReleased(button,gamepad=gamepadPrimary){ASSERT(isNumber(button),"button must be a number");ASSERT(isNumber(gamepad),"gamepad must be a number");return keyWasReleased(button,gamepad+1)}function gamepadStick(stick,gamepad=gamepadPrimary){ASSERT(isNumber(stick),"stick must be a number");ASSERT(isNumber(gamepad),"gamepad must be a number");return gamepadStickData[gamepad]?.[stick]??vec2()}function gamepadDpad(gamepad=gamepadPrimary){ASSERT(isNumber(gamepad),"gamepad must be a number");return gamepadDpadData[gamepad]??vec2()}function gamepadConnected(gamepad=gamepadPrimary){ASSERT(isNumber(gamepad),"gamepad must be a number");return!!inputData[gamepad+1]}function gamepadStickCount(gamepad=gamepadPrimary){ASSERT(isNumber(gamepad),"gamepad must be a number");return gamepadStickData[gamepad]?.length??0}function gamepadVibrate(gamepad=gamepadPrimary,duration=200,strongMagnitude=1,weakMagnitude=1,startDelay=0){ASSERT(isNumber(gamepad),"gamepad must be a number");if(!vibrateEnable||headlessMode)return;const pad=navigator?.getGamepads?.()[gamepad];pad?.vibrationActuator?.playEffect?.("dual-rumble",{duration:duration,strongMagnitude:strongMagnitude,weakMagnitude:weakMagnitude,startDelay:startDelay})}function gamepadVibrateStop(gamepad=gamepadPrimary){ASSERT(isNumber(gamepad),"gamepad must be a number");if(!vibrateEnable||headlessMode)return;const pad=navigator?.getGamepads?.()[gamepad];pad?.vibrationActuator?.reset?.()}function vibrate(pattern=100){ASSERT(isNumber(pattern)||isArray(pattern),"pattern must be a number or array");vibrateEnable&&!headlessMode&&navigator?.vibrate?.(pattern)}function vibrateStop(){vibrate(0)}function pointerLockRequest(){!isTouchDevice&&mainCanvas.requestPointerLock?.()}function pointerLockExit(){document.exitPointerLock?.()}function pointerLockIsActive(){return document.pointerLockElement===mainCanvas}const inputData=[[]];const gamepadStickData=[],gamepadDpadData=[],gamepadHadInput=[];const gamepadAxisCentered=[];const gamepadAxisCenteredFrames=15;const touchGamepadTimer=new Timer,touchGamepadButtons=[],touchGamepadSticks=[];const touchGamepadStickAnchors=[],touchGamepadStickPointerId=[];const touchGamepadPointerRole=new Map;let touchGamepadOverlay,touchGamepadStage,touchGamepadSvg,touchGamepadSvgEls;let touchGamepadSideZones=[],touchGamepadZoneC;let touchGamepadNeedRelayout=true,touchGamepadLastLayout;function inputInit(){if(headlessMode)return;document.addEventListener("keydown",onKeyDown);document.addEventListener("keyup",onKeyUp);document.addEventListener("mousedown",onMouseDown);document.addEventListener("mouseup",onMouseUp);document.addEventListener("mousemove",onMouseMove);document.addEventListener("mouseleave",onMouseLeave);document.addEventListener("wheel",onMouseWheel,{passive:false});document.addEventListener("contextmenu",onContextMenu);document.addEventListener("blur",onBlur);if(isTouchDevice&&touchInputEnable)touchInputInit();function onKeyDown(e){if(!e.repeat){inputData[0][e.code]=3;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=3}if(!inputPreventDefault||!e.cancelable||!document.hasFocus())return;if(e.ctrlKey||e.metaKey||e.altKey)return;if(isTextInput(e.target)||isTextInput(document.activeElement))return;const printable=typeof e.key==="string"&&e.key.length===1;const preventDefaultKeys=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space","Tab","Backspace"];if(preventDefaultKeys.includes(e.code)||printable)e.preventDefault();function isTextInput(element){const tag=element?.tagName;const editable=element?.isContentEditable;return editable||["INPUT","TEXTAREA","SELECT"].includes(tag)}}function onKeyUp(e){inputData[0][e.code]=inputData[0][e.code]&2|4;if(inputWASDEmulateDirection){const remap=remapKey(e.code);inputData[0][remap]=inputData[0][remap]&2|4}}function remapKey(k){return inputWASDEmulateDirection?k==="KeyW"?"ArrowUp":k==="KeyS"?"ArrowDown":k==="KeyA"?"ArrowLeft":k==="KeyD"?"ArrowRight":k:k}function onMouseDown(e){if(isTouchDevice&&touchInputEnable)return;if(soundEnable&&!headlessMode&&audioContext&&!audioIsRunning())audioContext.resume();inputData[0][e.button]=3;const mousePosScreenLast=mousePosScreen;mousePosScreen=mouseEventToScreen(vec2(e.x,e.y));mouseDeltaScreen=mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));if(inputPreventDefault&&e.cancelable&&document.hasFocus())e.preventDefault()}function onMouseUp(e){if(isTouchDevice&&touchInputEnable)return;inputData[0][e.button]=inputData[0][e.button]&2|4}function onMouseMove(e){mouseInWindow=true;const mousePosScreenLast=mousePosScreen;mousePosScreen=mouseEventToScreen(vec2(e.x,e.y));const movement=pointerLockIsActive()?vec2(e.movementX,e.movementY):mousePosScreen.subtract(mousePosScreenLast);mouseDeltaScreen=mouseDeltaScreen.add(movement)}function onMouseLeave(){mouseInWindow=false}function onMouseWheel(e){if(!e.ctrlKey)mouseWheel+=sign(e.deltaY);if(inputPreventDefault&&e.cancelable&&document.hasFocus())e.preventDefault()}function onContextMenu(e){e.preventDefault()}function onBlur(){inputClear();touchGamepadPointerRole.clear();touchGamepadButtons.length=0;touchGamepadSticks.length=0;touchGamepadStickPointerId.length=0}function touchInputInit(){document.addEventListener("touchstart",e=>handleTouch(e),{passive:false});document.addEventListener("touchmove",e=>handleTouch(e),{passive:false});document.addEventListener("touchend",e=>handleTouch(e),{passive:false});let wasTouching,touchIdentifier;function handleTouch(e){if(!touchInputEnable)return;if(soundEnable&&!headlessMode&&audioContext&&!audioIsRunning())audioContext.resume();if(!touchGamepadEnable||touchGamepadPassthrough){const isGamepadTouch=t=>touchGamepadSideZones.includes(t.target)||t.target===touchGamepadZoneC;const gameTouches=[];for(const t of e.touches)if(!isGamepadTouch(t))gameTouches.push(t);const touching=gameTouches.length;const button=0;if(touching){const pos=vec2(gameTouches[0].clientX,gameTouches[0].clientY);const mousePosScreenLast=mousePosScreen;mousePosScreen=mouseEventToScreen(pos);if(wasTouching&&gameTouches[0].identifier===touchIdentifier)mouseDeltaScreen=mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));else if(!wasTouching)inputData[0][button]=3;touchIdentifier=gameTouches[0].identifier}else if(wasTouching)inputData[0][button]=inputData[0][button]&2|4;wasTouching=touching}if(inputPreventDefault&&e.cancelable&&document.hasFocus())e.preventDefault();return true}}function mouseEventToScreen(mousePos){const rect=mainCanvas.getBoundingClientRect();const px=percent(mousePos.x,rect.left,rect.right);const py=percent(mousePos.y,rect.top,rect.bottom);return vec2(px*mainCanvas.width,py*mainCanvas.height)}}function inputUpdate(){if(headlessMode)return;if(!(touchInputEnable&&isTouchDevice)&&!document.hasFocus())inputClear();mousePos=screenToWorld(mousePosScreen);mouseDelta=screenToWorldDelta(mouseDeltaScreen);touchGamepadInit();gamepadsUpdate();updateLastInputDevice();function updateLastInputDevice(){const mouseActive=mouseIsDown(0)||mouseIsDown(1)||mouseIsDown(2)||mouseDeltaScreen.length()>inputMouseMoveThreshold;let gamepadActive=false;for(let s=gamepadStickCount();s--&&!gamepadActive;)gamepadActive=gamepadStick(s).lengthSquared()>.2;for(let b=17;b--&&!gamepadActive;)gamepadActive=gamepadIsDown(b);let keyboardActive=false;for(const k in inputData[0])if(isNaN(+k)&&inputData[0][k]&1){keyboardActive=true;break}if(gamepadActive)lastInputDevice="gamepad";else if(mouseActive)lastInputDevice="mouse";else if(keyboardActive)lastInputDevice="keyboard";isUsingGamepad=lastInputDevice==="gamepad"}function gamepadsUpdate(){const deadZoneMin=.3,deadZoneMax=.8;const applyDeadZones=v=>{const deadZone=v=>v>deadZoneMin?percent(v,deadZoneMin,deadZoneMax):v<-deadZoneMin?-percent(-v,deadZoneMin,deadZoneMax):0;return vec2(deadZone(v.x),deadZone(-v.y)).clampLength()};if(touchGamepadEnable&&isTouchDevice){ASSERT(!touchGamepadLeftStick||!touchGamepadLeftButtonCount,"set touchGamepadLeftStick or touchGamepadLeftButtonCount, not both");ASSERT(!touchGamepadRightStick||!touchGamepadButtonCount,"set touchGamepadRightStick or touchGamepadButtonCount, not both");if(!touchGamepadTimer.isSet())return;gamepadPrimary=0;const sticks=gamepadStickData[0]??(gamepadStickData[0]=[]);const dpad=gamepadDpadData[0]??(gamepadDpadData[0]=vec2());sticks.length=0;dpad.set();for(let side=0;side<2;side++){if(!touchGamepadSideStick(side))continue;const out=touchGamepadStickOut(side);sticks[out]=vec2();const touchStick=touchGamepadSticks[side]??vec2();if(touchGamepadAnalog)sticks[out]=applyDeadZones(touchStick);else if(touchStick.lengthSquared()>.3){const x=clamp(round(touchStick.x),-1,1);const y=clamp(round(touchStick.y),-1,1);sticks[out]=vec2(x,-y).clampLength();if(!out)dpad.set(x,-y)}}const data=inputData[1]??(inputData[1]=[]);for(let i=12;i--;){const wasDown=gamepadIsDown(i,0);data[i]=touchGamepadButtons[i]?wasDown?1:3:wasDown?4:0;if(touchGamepadVibration&&data[i]===3&&(i===9||touchGamepadIsFaceButton(i)))vibrate(touchGamepadVibration)}return}try{if(!gamepadsEnable||!navigator?.getGamepads)return}catch(e){return}if(!debug&&!document.hasFocus())return;const maxGamepads=8;const gamepads=navigator.getGamepads();const gamepadCount=min(maxGamepads,gamepads.length);for(let i=0;i<gamepadCount;++i){const gamepad=gamepads[i];if(!gamepad){inputData[i+1]=undefined;gamepadStickData[i]=undefined;gamepadDpadData[i]=undefined;gamepadHadInput[i]=undefined;gamepadAxisCentered[i]=undefined;continue}const data=inputData[i+1]??(inputData[i+1]=[]);const sticks=gamepadStickData[i]??(gamepadStickData[i]=[]);const dpad=gamepadDpadData[i]??(gamepadDpadData[i]=vec2());const isStandard=gamepad.mapping==="standard";const centered=gamepadAxisCentered[i]??(gamepadAxisCentered[i]=[]);const readAxis=j=>{const v=gamepad.axes[j];if(isStandard&&j<4)return v;if(!gamepadAxisFilterEnable)return v;const frames=centered[j]|0;if(frames>gamepadAxisCenteredFrames)return v;centered[j]=abs(v)<deadZoneMin?frames+1:0;return 0};for(let j=0;j<gamepad.axes.length-1;j+=2)sticks[j>>1]=applyDeadZones(vec2(readAxis(j),readAxis(j+1)));let hadInput=false;for(let j=gamepad.buttons.length;j--;){const button=gamepad.buttons[j];const wasDown=gamepadIsDown(j,i);data[j]=button.pressed?wasDown?1:3:wasDown?4:0;if(button.pressed&&(!button.value||button.value>.9))hadInput=true}if(hadInput){gamepadHadInput[i]=true;if(!gamepadHadInput[gamepadPrimary])gamepadPrimary=i}if(gamepad.mapping==="standard"){dpad.set((gamepadIsDown(15,i)&&1)-(gamepadIsDown(14,i)&&1),(gamepadIsDown(12,i)&&1)-(gamepadIsDown(13,i)&&1))}if(gamepadDirectionEmulateStick&&(dpad.x||dpad.y))sticks[0]=dpad.clampLength()}touchGamepadEnable&&isUsingGamepad&&touchGamepadTimer.unset()}}function inputUpdatePost(){if(headlessMode)return;for(const deviceInputData of inputData)for(const i in deviceInputData)deviceInputData[i]&=1;mouseWheel=0;mouseDelta=vec2();mouseDeltaScreen=vec2()}function inputRender(){touchGamepadRender()}const touchGamepadSvgNS="http://www.w3.org/2000/svg";function touchGamepadInit(){if(touchGamepadOverlay||!touchGamepadEnable||!isTouchDevice||headlessMode||!document.body)return;const overlay=touchGamepadOverlay=document.createElement("div");overlay.style.cssText="position:fixed;inset:0;z-index:50;pointer-events:none;opacity:0;"+"touch-action:none;user-select:none;-webkit-user-select:none;"+"-webkit-touch-callout:none;transition:opacity .2s;box-sizing:border-box;"+"padding:env(safe-area-inset-top) env(safe-area-inset-right) "+"env(safe-area-inset-bottom) env(safe-area-inset-left)";const stage=touchGamepadStage=document.createElement("div");stage.style.cssText="position:relative;width:100%;height:100%;pointer-events:none";overlay.appendChild(stage);const svg=touchGamepadSvg=document.createElementNS(touchGamepadSvgNS,"svg");svg.style.cssText="position:absolute;inset:0;width:100%;height:100%;"+"pointer-events:none;overflow:visible;fill:none;stroke:#fff;stroke-width:3";stage.appendChild(svg);const makeZone=()=>{const z=document.createElement("div");z.style.cssText="position:absolute;pointer-events:auto;touch-action:none";z.addEventListener("pointerdown",e=>touchGamepadPointerDown(e,z));z.addEventListener("pointermove",e=>touchGamepadPointerMove(e));z.addEventListener("pointerup",e=>touchGamepadPointerUp(e));z.addEventListener("pointercancel",e=>touchGamepadPointerUp(e));stage.appendChild(z);return z};touchGamepadSideZones[0]=makeZone();touchGamepadSideZones[1]=makeZone();touchGamepadZoneC=makeZone();addEventListener("resize",()=>touchGamepadNeedRelayout=true);document.body.appendChild(overlay);touchGamepadNeedRelayout=true}function touchGamepadStageRect(){return touchGamepadStage.getBoundingClientRect()}function touchGamepadSideStick(side){return side?touchGamepadRightStick:touchGamepadLeftStick}function touchGamepadSideButtonCount(side){return side?touchGamepadButtonCount:touchGamepadLeftButtonCount}function touchGamepadSideButtonBase(side){return side?0:4}function touchGamepadStickOut(side){return side&&touchGamepadLeftStick?1:0}function touchGamepadSideHasControl(side){return touchGamepadSideStick(side)||touchGamepadSideButtonCount(side)>0}function touchGamepadIsFaceButton(i){for(let side=0;side<2;side++){const base=touchGamepadSideButtonBase(side);if(!touchGamepadSideStick(side)&&i>=base&&i<base+touchGamepadSideButtonCount(side))return true}return false}function touchGamepadSideCenter(side,W,H){if(touchGamepadFloating&&touchGamepadSideStick(side)&&touchGamepadStickAnchors[side])return touchGamepadStickAnchors[side];let y=H-touchGamepadSize;const count=touchGamepadSideButtonCount(side);if(!touchGamepadSideStick(side)&&(count===2||count===3))y-=touchGamepadSize/4;return vec2(side?W-touchGamepadSize:touchGamepadSize,y)}function touchGamepadRelayout(){if(!touchGamepadOverlay)return;const r=touchGamepadStageRect();const W=r.width,H=r.height,S=touchGamepadSize;const setZone=(z,css)=>z.style.cssText="position:absolute;pointer-events:auto;touch-action:none;"+css;if(paused){for(const zone of touchGamepadSideZones)zone.style.display="none";if(touchGamepadCenterButtonSize){setZone(touchGamepadZoneC,"inset:0");touchGamepadZoneC.style.display=""}else touchGamepadZoneC.style.display="none"}else{for(let side=0;side<2;side++){const zone=touchGamepadSideZones[side],edge=side?"right":"left";zone.style.display=touchGamepadSideHasControl(side)?"":"none";if(touchGamepadFloating){const width=touchGamepadSideHasControl(side?0:1)?"50%":"100%";setZone(zone,`${edge}:0;bottom:0;width:${width};height:60%`)}else setZone(zone,`${edge}:0;bottom:0;width:${3*S}px;height:${3*S}px`)}touchGamepadZoneC.style.display=touchGamepadCenterButtonSize?"":"none";const c=touchGamepadCenterButtonSize;setZone(touchGamepadZoneC,`left:50%;top:50%;width:${2*c}px;height:${2*c}px;transform:translate(-50%,-50%)`)}touchGamepadBuildSvg(W,H);touchGamepadNeedRelayout=false}function touchGamepadBuildSvg(W,H){const svg=touchGamepadSvg;while(svg.firstChild)svg.removeChild(svg.firstChild);const els=touchGamepadSvgEls={face:[],thumb:[]};const S=touchGamepadSize;const circle=(cx,cy,rr,fill)=>{const c=document.createElementNS(touchGamepadSvgNS,"circle");c.setAttribute("cx",cx);c.setAttribute("cy",cy);c.setAttribute("r",rr);if(fill)c.setAttribute("fill",fill);svg.appendChild(c);return c};const cross=ctr=>{const a=S*.18,b=S*.5,x=ctr.x,y=ctr.y;const p=document.createElementNS(touchGamepadSvgNS,"path");p.setAttribute("d",`M ${x-a} ${y-b} H ${x+a} V ${y-a} H ${x+b} V ${y+a} H ${x+a} `+`V ${y+b} H ${x-a} V ${y+a} H ${x-b} V ${y-a} H ${x-a} Z`);svg.appendChild(p)};for(let side=0;side<2;side++){const count=touchGamepadSideButtonCount(side);const base=touchGamepadSideButtonBase(side);const ctr=touchGamepadSideCenter(side,W,H);if(touchGamepadSideStick(side)){if(touchGamepadAnalog)circle(ctr.x,ctr.y,S/2);else cross(ctr);els.thumb[side]=circle(ctr.x,ctr.y,S/4,"#fff")}else if(count===1)els.face[base]=circle(ctr.x,ctr.y,S/2,"#000");else for(let i=0;i<count;i++){const j=mod(i-1,4);let button=count>2?j:min(j,count-1);button=button===3?2:button===2?3:button;const offset=vec2().setDirection(j,S/2);if(count===2)offset.x*=-1;if(!side)offset.x*=-1;const pos=ctr.add(offset);els.face[base+button]=circle(pos.x,pos.y,S/4,"#000")}}if(debug&&debugGamepads)touchGamepadBuildDebug(W,H)}function touchGamepadBuildDebug(W,H){const S=touchGamepadSize,svg=touchGamepadSvg;const shape=(tag,attrs,stroke)=>{const el=document.createElementNS(touchGamepadSvgNS,tag);for(const k in attrs)el.setAttribute(k,attrs[k]);el.setAttribute("stroke",stroke);el.setAttribute("stroke-width",2);el.setAttribute("fill","none");svg.appendChild(el)};const ring=(c,rr,stroke)=>shape("circle",{cx:c.x,cy:c.y,r:rr},stroke);shape("line",{x1:W/2,y1:0,x2:W/2,y2:H},"#0f0");for(let side=0;side<2;side++){if(touchGamepadSideStick(side)){if(touchGamepadFloating){const top=H*.4,full=!touchGamepadSideHasControl(side?0:1);const x=full?0:side?W/2:0;shape("rect",{x:x,y:top,width:full?W:W/2,height:H-top},"#0ff")}else ring(touchGamepadSideCenter(side,W,H),2*S,"#0ff")}else if(touchGamepadSideButtonCount(side)>=1)ring(touchGamepadSideCenter(side,W,H),S,"#0ff")}if(touchGamepadCenterButtonSize){ring(vec2(W/2,H/2),touchGamepadCenterButtonSize,"#ff0");for(let side=0;side<2;side++)if(touchGamepadSideHasControl(side))ring(touchGamepadSideCenter(side,W,H),2*S,"#f0f")}}function touchGamepadRender(){if(!touchGamepadOverlay||headlessMode)return;if(!touchGamepadEnable||!isTouchDevice){if(touchGamepadOverlay.style.display!=="none"){touchGamepadOverlay.style.display="none";touchGamepadPointerRole.clear();touchGamepadButtons.length=0;touchGamepadSticks.length=0;touchGamepadStickPointerId.length=0}return}touchGamepadOverlay.style.display="";const dbg=debug&&debugGamepads;const layout=[touchGamepadButtonCount,touchGamepadLeftButtonCount,touchGamepadLeftStick,touchGamepadRightStick,touchGamepadAnalog,touchGamepadSize,touchGamepadFloating,touchGamepadCenterButtonSize,paused,dbg].join();if(layout!==touchGamepadLastLayout){touchGamepadLastLayout=layout;touchGamepadNeedRelayout=true}if(touchGamepadNeedRelayout)touchGamepadRelayout();const fade=touchGamepadDisplayTime?percent(touchGamepadTimer.get(),touchGamepadDisplayTime+1,touchGamepadDisplayTime):1;const visible=dbg||touchGamepadTimer.isSet()&&fade>0&&!paused;touchGamepadOverlay.style.opacity=!visible?0:dbg?1:fade*touchGamepadAlpha;if(!visible)return;const r=touchGamepadStageRect();const W=r.width,H=r.height,S=touchGamepadSize;const els=touchGamepadSvgEls;if(!els)return;for(let side=0;side<2;side++)if(touchGamepadSideStick(side)&&els.thumb[side]){const ctr=touchGamepadSideCenter(side,W,H);const t=ctr.add((touchGamepadSticks[side]??vec2()).scale(S/2));els.thumb[side].setAttribute("cx",t.x);els.thumb[side].setAttribute("cy",t.y)}for(let i=0;i<els.face.length;i++)if(els.face[i])els.face[i].setAttribute("fill",touchGamepadButtons[i]?"#fff":"#000")}function touchGamepadEventPos(e){const r=touchGamepadStageRect();return vec2(e.clientX-r.left,e.clientY-r.top)}function touchGamepadApplyStick(side,p){const delta=p.subtract(touchGamepadStickAnchors[side]);touchGamepadSticks[side]=delta.scale(2/touchGamepadSize).clampLength();touchGamepadButtons[touchGamepadStickOut(side)?11:10]=1}function touchGamepadFaceButtonAt(side,p,W,H){const count=touchGamepadSideButtonCount(side);const base=touchGamepadSideButtonBase(side);const bc=touchGamepadSideCenter(side,W,H);if(bc.distance(p)>=touchGamepadSize)return-1;if(count===1)return base;const d=bc.subtract(p);if(!side)d.x*=-1;let button=count===2?d.x<d.y?1:0:mod(d.direction()+2,4);button=button===3?2:button===2?3:button;return button<count?base+button:-1}function touchGamepadControlAt(p,W,H){const S=touchGamepadSize;const leftHalf=p.x<W/2;const floatTop=H*.4;for(let side=0;side<2;side++){const onHalf=side?!leftHalf:leftHalf;if(touchGamepadSideStick(side)){const otherControl=touchGamepadSideHasControl(side?0:1);const grab=touchGamepadFloating?(!otherControl||onHalf)&&p.y>floatTop:onHalf&&touchGamepadSideCenter(side,W,H).distance(p)<2*S;if(grab)return{role:"stick",side:side}}else if(touchGamepadSideButtonCount(side)>=1){const btn=touchGamepadFaceButtonAt(side,p,W,H);if(btn>=0)return{role:"face",btn:btn}}}if(touchGamepadCenterButtonSize){for(let side=0;side<2;side++)if(touchGamepadSideHasControl(side)&&touchGamepadSideCenter(side,W,H).distance(p)<2*S)return;if(vec2(W/2,H/2).distance(p)<touchGamepadCenterButtonSize)return{role:"start"}}}function touchGamepadPointerDown(e,zone){if(!touchGamepadEnable)return;e.preventDefault();zone.setPointerCapture(e.pointerId);touchGamepadTimer.set();if(soundEnable&&!headlessMode&&audioContext&&!audioIsRunning())audioContext.resume();if(paused){if(touchGamepadCenterButtonSize){touchGamepadButtons[9]=1;touchGamepadPointerRole.set(e.pointerId,"start")}return}const r=touchGamepadStageRect();const W=r.width,H=r.height;const p=vec2(e.clientX-r.left,e.clientY-r.top);const hit=touchGamepadControlAt(p,W,H);if(!hit)return;if(hit.role==="stick"){const side=hit.side;touchGamepadStickAnchors[side]=touchGamepadFloating?p:touchGamepadSideCenter(side,W,H);touchGamepadStickPointerId[side]=e.pointerId;touchGamepadPointerRole.set(e.pointerId,"stick"+side);touchGamepadNeedRelayout=true;touchGamepadApplyStick(side,p)}else if(hit.role==="face"){touchGamepadButtons[hit.btn]=1;touchGamepadPointerRole.set(e.pointerId,"face"+hit.btn)}else{touchGamepadButtons[9]=1;touchGamepadPointerRole.set(e.pointerId,"start")}}function touchGamepadPointerMove(e){const role=touchGamepadPointerRole.get(e.pointerId);if(!role)return;e.preventDefault();const p=touchGamepadEventPos(e);if(role==="stick0"||role==="stick1")touchGamepadApplyStick(role==="stick1"?1:0,p)}function touchGamepadPointerUp(e){const role=touchGamepadPointerRole.get(e.pointerId);if(!role)return;touchGamepadPointerRole.delete(e.pointerId);if(role==="stick0"||role==="stick1"){const side=role==="stick1"?1:0;touchGamepadStickPointerId[side]=undefined;touchGamepadSticks[side]=vec2();delete touchGamepadButtons[touchGamepadStickOut(side)?11:10]}else if(role==="start")delete touchGamepadButtons[9];else delete touchGamepadButtons[+role.slice(4)];touchGamepadTimer.set()}let audioContext=new AudioContext;let audioMasterGain;const audioDefaultSampleRate=44100;function audioIsRunning(){return audioContext.state==="running"}function audioInit(){if(!soundEnable||headlessMode)return;audioMasterGain=audioContext.createGain();audioMasterGain.connect(audioContext.destination);audioMasterGain.gain.value=soundVolume}class Sound{constructor(asset,randomness,range=soundDefaultRange,taper=soundDefaultTaper,onloadCallback){if(!soundEnable||headlessMode)return;ASSERT(!asset||isArray(asset)||isStringLike(asset),"asset must be a file name or zzfx array");ASSERT(randomness===undefined||isNumber(randomness),"randomness must be a number");ASSERT(randomness===undefined||randomness>=0&&randomness<=1,"randomness must be between 0 and 1");ASSERT(isNumber(range),"range must be a number");ASSERT(isNumber(taper),"taper must be a number");this.range=range;this.taper=taper;this.randomness=randomness??0;this.sampleRate=audioDefaultSampleRate;this.sampleLength=0;this.sampleBuffer=undefined;this._sampleChannels=undefined;this.loadedPercent=0;this.onloadCallback=onloadCallback;if(isArray(asset)){const zzfxSound=asset.slice();const defaultRandomness=randomness??.05;const randomnessIndex=1;this.randomness=zzfxSound[randomnessIndex]??defaultRandomness;zzfxSound[randomnessIndex]=0;this.sampleChannels=[zzfxG(...zzfxSound)];this.buildSampleBuffer();this.loadedPercent=1;onloadCallback?.(this)}else if(typeof asset==="string"){const filename=asset;this.loadSound(filename).catch(e=>LOG("Sound load failed for",filename,"-",e.message))}}get sampleChannels(){const buffer=this.sampleBuffer;if(!this._sampleChannels&&buffer){const channels=[];for(let i=0;i<buffer.numberOfChannels;i++)channels.push(buffer.getChannelData(i).slice());this._sampleChannels=channels}return this._sampleChannels}set sampleChannels(sampleChannels){this._sampleChannels=sampleChannels;this.sampleBuffer=undefined;this.sampleLength=sampleChannels?.[0]?.length||0}buildSampleBuffer(){if(this.sampleBuffer||!this._sampleChannels||headlessMode)return;this.sampleBuffer=createAudioBuffer(this._sampleChannels,this.sampleRate);this._sampleChannels=undefined}play(pos,volume=1,pitch=1,randomnessScale=1,loop=false,paused=false){ASSERT(!pos||isVector2(pos),"pos must be a vec2");ASSERT(isNumber(volume),"volume must be a number");ASSERT(isNumber(pitch),"pitch must be a number");ASSERT(isNumber(randomnessScale),"randomnessScale must be a number");if(!soundEnable||headlessMode)return;if(!this.sampleBuffer&&!this._sampleChannels)return;let pan;if(pos){const range=this.range;if(range){const lengthSquared=cameraPos.distanceSquared(pos);if(lengthSquared>range*range)return;volume*=percent(lengthSquared**.5,range,range*this.taper)}pan=worldToScreen(pos).x*2/mainCanvas.width-1}const rate=pitch+pitch*this.randomness*randomnessScale*rand(-1,1);const instance=new SoundInstance(this,volume,rate,pan,loop,paused);if(debug&&debugSound&&pos){debugCircle(pos,.5,"#0ff",.5,true);if(this.range){debugCircle(pos,2*this.range,"#0ff",.5);debugCircle(pos,2*this.range*this.taper,"#0ff",.5)}debugText("vol "+volume.toFixed(2)+" pitch "+rate.toFixed(2),pos,.5,"#0ff",.5)}return instance}playMusic(volume=1,loop=true,paused=false){return this.play(undefined,volume,1,0,loop,paused)}playNote(semitoneOffset=0,pos,volume){ASSERT(isNumber(semitoneOffset),"semitoneOffset must be a number");const pitch=getNoteFrequency(semitoneOffset,1);return this.play(pos,volume,pitch,0)}getDuration(){return this.sampleLength/this.sampleRate||0}isLoaded(){return this.loadedPercent===1}async loadSound(filename){const response=await fetch(filename);if(!response.ok)throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);const arrayBuffer=await response.arrayBuffer();const audioBuffer=await audioContext.decodeAudioData(arrayBuffer);this.sampleRate=audioBuffer.sampleRate;this.sampleLength=audioBuffer.length;this.sampleBuffer=audioBuffer;this.loadedPercent=1;this.onloadCallback?.(this)}}class SoundInstance{constructor(sound,volume=1,rate=1,pan=0,loop=false,paused=false){ASSERT(sound instanceof Sound,"SoundInstance requires a valid Sound object");ASSERT(volume>=0,"Sound volume must be positive or zero");ASSERT(rate>=0,"Sound rate must be positive or zero");ASSERT(isNumber(pan),"Sound pan must be a number");this.sound=sound;this.volume=volume;this.rate=rate;this.pan=pan;this.loop=loop;this.pausedTime=0;this.startTime=undefined;this.gainNode=undefined;this.source=undefined;this.onendedCallback=source=>{if(source===this.source)this.source=undefined};if(!paused)this.start()}start(offset=0){ASSERT(offset>=0,"Sound start offset must be positive or zero");if(this.isPlaying())this.stop();this.gainNode=audioContext.createGain();this.sound.buildSampleBuffer();this.source=this.sound.sampleBuffer?playAudioBuffer(this.sound.sampleBuffer,this.volume,this.rate,this.pan,this.loop,this.gainNode,offset,this.onendedCallback):playSamples(this.sound.sampleChannels,this.volume,this.rate,this.pan,this.loop,this.sound.sampleRate,this.gainNode,offset,this.onendedCallback);if(this.source){this.startTime=audioContext.currentTime-offset;this.pausedTime=undefined}else{this.startTime=undefined;this.pausedTime=0}}setVolume(volume){ASSERT(volume>=0,"Sound volume must be positive or zero");this.volume=volume;if(this.gainNode)this.gainNode.gain.value=volume}stop(fadeTime=0){ASSERT(fadeTime>=0,"Sound fade time must be positive or zero");if(this.isPlaying()){if(fadeTime){const startFade=audioContext.currentTime;const endFade=startFade+fadeTime;this.gainNode.gain.cancelScheduledValues(startFade);this.gainNode.gain.setValueAtTime(this.volume,startFade);this.gainNode.gain.linearRampToValueAtTime(0,endFade);this.source.stop(endFade)}else this.source.stop()}this.pausedTime=0;this.source=undefined;this.startTime=undefined}pause(){if(this.isPaused())return;this.pausedTime=this.getCurrentTime();this.source.stop();this.source=undefined;this.startTime=undefined}resume(){if(!this.isPaused())return;this.start(this.pausedTime)}isPlaying(){return!!this.source}isPaused(){return!this.isPlaying()}getCurrentTime(){if(!this.isPlaying())return this.pausedTime;const duration=this.getDuration();return duration?mod(audioContext.currentTime-this.startTime,duration):0}getDuration(){return this.rate?this.sound.getDuration()/this.rate:0}getSource(){return this.source}}function speak(text,volume=1,rate=1,pitch=1,language=""){ASSERT(typeof volume!=="string","speak() signature changed: language is now the last parameter, after pitch");if(!soundEnable||headlessMode)return;if(typeof speechSynthesis==="undefined")return;const utterance=new SpeechSynthesisUtterance(text);utterance.lang=language;utterance.volume=volume*soundVolume;utterance.rate=rate;utterance.pitch=pitch;speechSynthesis.speak(utterance);return utterance}function speakStop(){if(typeof speechSynthesis!=="undefined")speechSynthesis.cancel()}function getNoteFrequency(semitoneOffset,rootFrequency=220){return rootFrequency*2**(semitoneOffset/12)}function playSamples(sampleChannels,volume=1,rate=1,pan=0,loop=false,sampleRate=audioDefaultSampleRate,gainNode,offset=0,onended){if(!soundEnable||headlessMode)return;if(!audioIsRunning()){audioContext.resume();return}const buffer=createAudioBuffer(sampleChannels,sampleRate);return playAudioBuffer(buffer,volume,rate,pan,loop,gainNode,offset,onended)}function createAudioBuffer(sampleChannels,sampleRate=audioDefaultSampleRate){const channelCount=sampleChannels.length;const sampleLength=sampleChannels[0].length;const buffer=audioContext.createBuffer(channelCount,sampleLength,sampleRate);sampleChannels.forEach((c,i)=>buffer.getChannelData(i).set(c));return buffer}function playAudioBuffer(buffer,volume=1,rate=1,pan=0,loop=false,gainNode,offset=0,onended){if(!soundEnable||headlessMode)return;if(!audioIsRunning()){audioContext.resume();return}const source=audioContext.createBufferSource();source.buffer=buffer;source.playbackRate.value=rate;source.loop=loop;gainNode=gainNode||audioContext.createGain();gainNode.gain.value=volume;gainNode.connect(audioMasterGain);const pannerNode=new StereoPannerNode(audioContext,{pan:clamp(pan,-1,1)});source.connect(pannerNode).connect(gainNode);source.addEventListener("ended",()=>{gainNode.disconnect();pannerNode.disconnect();if(onended)onended(source)});const startOffset=offset*rate;source.start(0,startOffset);if(debug&&debugSound)LOG("sound","vol",volume.toFixed(2),"rate",rate.toFixed(2),"pan",pan.toFixed(2),loop?"loop":"");return source}function zzfx(...zzfxSound){return playSamples([zzfxG(...zzfxSound)])}function zzfxG(volume=1,randomness=.05,frequency=220,attack=0,sustain=0,release=.1,shape=0,shapeCurve=1,slide=0,deltaSlide=0,pitchJump=0,pitchJumpTime=0,repeatTime=0,noise=0,modulation=0,bitCrush=0,delay=0,sustainVolume=1,decay=0,tremolo=0,filter=0){let sampleRate=audioDefaultSampleRate,PI2=PI*2,startSlide=slide*=500*PI2/sampleRate/sampleRate,startFrequency=frequency*=(1+rand(randomness,-randomness))*PI2/sampleRate,modOffset=0,repeat=0,crush=0,jump=1,length,b=[],t=0,i=0,s=0,f,quality=2,w=PI2*abs(filter)*2/sampleRate,cosw=cos(w),alpha=sin(w)/2/quality,a0=1+alpha,a1=-2*cosw/a0,a2=(1-alpha)/a0,b0=(1+sign(filter)*cosw)/2/a0,b1=-(sign(filter)+cosw)/a0,b2=b0,x2=0,x1=0,y2=0,y1=0;const minAttack=9;attack=attack*sampleRate||minAttack;decay*=sampleRate;sustain*=sampleRate;release*=sampleRate;delay*=sampleRate;deltaSlide*=500*PI2/sampleRate**3;modulation*=PI2/sampleRate;pitchJump*=PI2/sampleRate;pitchJumpTime*=sampleRate;repeatTime=repeatTime*sampleRate|0;for(length=attack+decay+sustain+release+delay|0;i<length;b[i++]=s*volume){if(!(++crush%(bitCrush*100|0))){s=shape?shape>1?shape>2?shape>3?shape>4?t/PI2%1<shapeCurve/2?1:-1:sin(t**3):max(min(tan(t),1),-1):1-(2*t/PI2%2+2)%2:1-4*abs(round(t/PI2)-t/PI2):sin(t);s=(repeatTime?1-tremolo+tremolo*sin(PI2*i/repeatTime):1)*(shape>4?s:sign(s)*abs(s)**shapeCurve)*(i<attack?i/attack:i<attack+decay?1-(i-attack)/decay*(1-sustainVolume):i<attack+decay+sustain?sustainVolume:i<length-delay?(length-i-delay)/release*sustainVolume:0);s=delay?s/2+(delay>i?0:(i<length-delay?1:(length-i)/delay)*b[i-delay|0]/2/volume):s;if(filter)s=y1=b2*x2+b1*(x2=x1)+b0*(x1=s)-a2*y2-a1*(y2=y1)}f=(frequency+=slide+=deltaSlide)*cos(modulation*modOffset++);t+=f+f*noise*sin(i**5);if(jump&&++jump>pitchJumpTime){frequency+=pitchJump;startFrequency+=pitchJump;jump=0}if(repeatTime&&!(++repeat%repeatTime)){frequency=startFrequency;slide=startSlide;jump||=1}}return b}const tileCollisionLayers=[];function tileCollisionGetData(pos,solidOnly=true){for(const layer of tileCollisionLayers)if(!solidOnly||layer.isSolid){const layerPos=pos.subtract(layer.pos);if(layerPos.arrayCheck(layer.size)){const data=layer.getCollisionData(layerPos);if(data)return data}}return 0}function tileCollisionTest(pos,size=vec2(),callbackObject,solidOnly=true){for(const layer of tileCollisionLayers){if(!solidOnly||layer.isSolid)if(layer.collisionTest(pos,size,callbackObject))return layer}}function tileCollisionRaycast(posStart,posEnd,callbackObject,normal,solidOnly=true){let closestHit,closestDistSq,closestNormal;const scratchNormal=normal&&vec2();for(const layer of tileCollisionLayers){if(!solidOnly||layer.isSolid){const hitPos=layer.collisionRaycast(posStart,posEnd,callbackObject,scratchNormal);if(hitPos){const d=posStart.distanceSquared(hitPos);if(closestHit===undefined||d<closestDistSq){closestHit=hitPos;closestDistSq=d;if(normal)closestNormal=scratchNormal.copy()}}}}if(closestHit&&normal)normal.setFrom(closestNormal);return closestHit}function tileLayersLoad(tileMapData,tileInfo=tile(),renderOrder=0,collisionLayer,draw=true){if(!tileMapData){const s=50;tileMapData={};tileMapData.height=tileMapData.width=s;tileMapData.layers=[{}];tileMapData.layers[0].data=new Array(s*s).fill(0)}ASSERT(tileMapData.width&&tileMapData.height);ASSERT(tileMapData.layers&&tileMapData.layers.length);const tileLayers=[];const levelSize=vec2(tileMapData.width,tileMapData.height);const layerCount=tileMapData.layers.length;for(let layerIndex=layerCount;layerIndex--;){const dataLayer=tileMapData.layers[layerIndex];ASSERT(dataLayer.data&&dataLayer.data.length);ASSERT(levelSize.area()===dataLayer.data.length);const layerRenderOrder=renderOrder-(layerCount-1-layerIndex);const tileLayer=new TileCollisionLayer(vec2(),levelSize,tileInfo,layerRenderOrder);tileLayers[layerIndex]=tileLayer;const layerColor=dataLayer.tintcolor?(new Color).setHex(dataLayer.tintcolor):dataLayer.color||WHITE;ASSERT(isColor(layerColor),"layer color is not a color");for(let x=levelSize.x;x--;)for(let y=levelSize.y;y--;){const pos=vec2(x,levelSize.y-1-y);const data=dataLayer.data[x+y*levelSize.x];if(data){const layerData=new TileLayerData(data-1,0,false,layerColor);tileLayer.setData(pos,layerData);if(layerIndex===collisionLayer)tileLayer.setCollisionData(pos,1)}}if(draw)tileLayer.redraw()}return tileLayers}class TileLayerData{constructor(tile,direction=0,mirror=false,color=new Color){this.tile=tile;this.direction=direction;this.mirror=mirror;this.color=color.copy()}clear(){this.tile=this.direction=0;this.mirror=false;this.color=new Color}}class CanvasLayer extends EngineObject{constructor(pos,size,angle=0,renderOrder=0,canvasSize=vec2(512),useWebGL=true){ASSERT(isVector2(canvasSize),"canvasSize must be a Vector2");super(pos,size,undefined,angle,WHITE,renderOrder);this.canvas=headlessMode?undefined:new OffscreenCanvas(canvasSize.x,canvasSize.y);this.context=this.canvas?.getContext("2d");this.textureInfo=new TextureInfo(this.canvas,useWebGL);this.mass=0}destroy(){if(this.destroyed)return;this.textureInfo.destroyWebGLTexture();super.destroy()}render(){this.draw(this.pos,this.size,this.color,this.angle,this.mirror,this.additiveColor)}draw(pos,size,color=WHITE,angle=0,mirror=false,additiveColor,screenSpace=false,context){const tileInfo=(new TileInfo).setFullImage(this.textureInfo);const useWebGL=this.hasWebGL();drawTile(pos,size,tileInfo,color,angle,mirror,additiveColor,useWebGL,screenSpace,context)}updateWebGL(){this.textureInfo.createWebGLTexture()}hasWebGL(){return glEnable&&this.textureInfo.hasWebGL()}}class TileLayer extends CanvasLayer{constructor(pos,size,tileInfo=tile(),renderOrder=0,useWebGL=true){const canvasSize=tileInfo?size.multiply(tileInfo.size):size;super(pos,size,0,renderOrder,canvasSize,useWebGL);this.tileInfo=undefined;this.data=[];this.isUsingWebGL=false;if(headlessMode){this.render=()=>{};this.redraw=()=>{};this.redrawStart=()=>{};this.redrawEnd=()=>{};this.drawTileData=()=>{};this.redrawTileData=()=>{};this.drawLayerTile=()=>{};this.drawLayerRect=()=>{};this.drawTile=()=>{};this.drawRect=()=>{};this.clearLayerRect=()=>{};return}if(tileInfo){this.tileInfo=tileInfo.frame(0);this.tileInfo.bleed=0}for(let j=this.size.area();j--;)this.data.push(new TileLayerData)}setData(layerPos,data,redraw=false){layerPos=layerPos.floor();ASSERT(isVector2(layerPos),"layerPos must be a Vector2");ASSERT(data instanceof TileLayerData,"data must be a TileLayerData");if(!layerPos.arrayCheck(this.size))return;this.data[(layerPos.y|0)*this.size.x+(layerPos.x|0)]=data;if(!redraw)return;const isRedraw=drawContext===this.context;isRedraw?this.drawTileData(layerPos):this.redrawTileData(layerPos)}clearData(layerPos,redraw=false){this.setData(layerPos,new TileLayerData,redraw)}getData(layerPos){ASSERT(isVector2(layerPos),"layerPos must be a Vector2");return layerPos.arrayCheck(this.size)?this.data[(layerPos.y|0)*this.size.x+(layerPos.x|0)]:undefined}update(){if(!glEnable&&this.isUsingWebGL){this.isUsingWebGL=false;this.redraw()}}render(){ASSERT(drawContext!==this.context,"must call redrawEnd() after drawing tiles!");const size=this.drawSize||this.size;const pos=this.pos.add(size.scale(.5));this.draw(pos,size,this.color,this.angle,this.mirror,this.additiveColor)}onRedraw(){}redraw(){this.redrawStart(true);for(let x=this.size.x;x--;)for(let y=this.size.y;y--;)this.drawTileData(vec2(x,y),false);this.isUsingWebGL&&glFlush();this.onRedraw();this.redrawEnd()}redrawStart(clear=false){if(!this.context)return;ASSERT(drawContext!==this.context);this.savedRenderSettings=[drawContext,mainCanvasSize,cameraPos,cameraScale,canvasClearColor];drawContext=this.context;const tileSize=this.tileInfo?.size??vec2(1);mainCanvasSize=this.size.multiply(tileSize);canvasClearColor=CLEAR_BLACK;cameraPos=this.size.multiply(tileSize).scale(.5);cameraScale=1;this.isUsingWebGL=this.hasWebGL();if(this.isUsingWebGL)glSetRenderTarget(this.textureInfo.glTexture,clear);else{this.context.imageSmoothingEnabled=!tilesPixelated;if(clear){this.canvas.width=mainCanvasSize.x;this.canvas.height=mainCanvasSize.y}}}redrawEnd(){if(!this.context)return;ASSERT(drawContext===this.context);if(this.isUsingWebGL)glSetRenderTarget();[drawContext,mainCanvasSize,cameraPos,cameraScale,canvasClearColor]=this.savedRenderSettings}drawTileData(layerPos,clear=true){if(!this.context)return;ASSERT(drawContext===this.context,"must call redrawStart() before drawing tiles");const drawSize=this.tileInfo?.size??vec2(1);const drawPos=layerPos.multiply(drawSize);clear&&this.clearLayerRect(drawPos,drawSize);const d=this.getData(layerPos);if(!d||!d.tile)return;const tileInfo=this.tileInfo&&this.tileInfo.index(d.tile);this.drawLayerTile(drawPos,drawSize,tileInfo,d.color,d.direction*PI/2,d.mirror)}redrawTileData(layerPos,clear=true){if(!this.context)return;ASSERT(drawContext!==this.context,"redrawStart() should not be active when calling redrawTileData(), instead use drawTileData()");this.redrawStart();this.drawTileData(layerPos,clear);this.redrawEnd()}drawLayerTile(pos,size=vec2(1),tileInfo,color=WHITE,angle=0,mirror,additiveColor){const drawPos=pos.add(size.scale(.5));drawTile(drawPos,size,tileInfo,color,angle,mirror,additiveColor,this.isUsingWebGL)}drawLayerRect(pos,size,color,angle=0){this.drawLayerTile(pos,size,undefined,color,angle)}drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle=0,mirror=false){pos=pos.subtract(this.pos).multiply(this.tileInfo.size);size=size.multiply(this.tileInfo.size);pos.y=this.canvas.height-pos.y;const oldMainCanvasSize=mainCanvasSize;mainCanvasSize=vec2(this.canvas.width,this.canvas.height);const useWebGL=this.hasWebGL();useWebGL&&glSetRenderTarget(this.textureInfo.glTexture);const drawContext=useWebGL?undefined:this.context;drawTile(pos,size,tileInfo,color,angle,mirror,undefined,useWebGL,true,drawContext);useWebGL&&glSetRenderTarget();mainCanvasSize=oldMainCanvasSize}drawRect(pos,size,color,angle){this.drawTile(pos,size,undefined,color,angle)}clearLayerRect(pos,size){ASSERT(drawContext===this.context,"must call redrawStart() before clearing tiles");const x=pos.x,y=this.canvas.height-pos.y-size.y;const useWebGL=this.hasWebGL();if(useWebGL)glClearRect(x,y,size.x,size.y);else this.context.clearRect(x,y,size.x,size.y)}}class TileCollisionLayer extends TileLayer{constructor(pos,size,tileInfo=tile(),renderOrder=0,useWebGL=true){super(pos,size.floor(),tileInfo,renderOrder,useWebGL);this.collisionData=[];this.initCollision(this.size);tileCollisionLayers.push(this);this.isSolid=true}destroy(){if(this.destroyed)return;const index=tileCollisionLayers.indexOf(this);ASSERT(index>=0,"tile collision layer not found in array");index>=0&&tileCollisionLayers.splice(index,1);super.destroy()}initCollision(size){ASSERT(isVector2(size),"size must be a Vector2");this.size=size.floor();this.collisionData=[];this.collisionData.length=size.area();this.collisionData.fill(0)}setCollisionData(layerPos,data=1){ASSERT(isVector2(layerPos),"layerPos must be a Vector2");const i=(layerPos.y|0)*this.size.x+(layerPos.x|0);layerPos.arrayCheck(this.size)&&(this.collisionData[i]=data)}clearCollisionData(layerPos){this.setCollisionData(layerPos,0)}getCollisionData(layerPos){ASSERT(isVector2(layerPos),"layerPos must be a Vector2");const i=(layerPos.y|0)*this.size.x+(layerPos.x|0);return layerPos.arrayCheck(this.size)?this.collisionData[i]:0}collisionTest(pos,size=new Vector2,callbackObject){ASSERT(isVector2(pos)&&isVector2(size),"pos and size must be Vector2s");ASSERT(!callbackObject||typeof callbackObject==="function"||callbackObject instanceof EngineObject,"callbackObject must be a function or EngineObject");const collisionTest=callbackObject?typeof callbackObject==="function"?(tileData,pos)=>callbackObject(tileData,pos):(tileData,pos)=>callbackObject.collideWithTile(tileData,pos):()=>true;const posX=pos.x-this.pos.x;const posY=pos.y-this.pos.y;if(posX+size.x/2<0||posX-size.x/2>this.size.x)return false;if(posY+size.y/2<0||posY-size.y/2>this.size.y)return false;const minX=max(posX-size.x/2|0,0);const minY=max(posY-size.y/2|0,0);const maxX=min(max(posX+size.x/2,minX+1),this.size.x);const maxY=min(max(posY+size.y/2,minY+1),this.size.y);const hitPos=new Vector2;for(let y=minY;y<maxY;++y)for(let x=minX;x<maxX;++x){const tileData=this.collisionData[y*this.size.x+x];if(tileData&&collisionTest(tileData,hitPos.set(x+this.pos.x,y+this.pos.y)))return true}return false}collisionRaycast(posStart,posEnd,callbackObject,normal){ASSERT(isVector2(posStart)&&isVector2(posEnd),"positions must be Vector2s");ASSERT(!callbackObject||typeof callbackObject==="function"||callbackObject instanceof EngineObject,"callbackObject must be a function or EngineObject");const collisionTest=callbackObject?typeof callbackObject==="function"?(tileData,pos)=>callbackObject(tileData,pos):(tileData,pos)=>callbackObject.collideWithTile(tileData,pos):tileData=>tileData>0;const testFunction=pos=>{const tileData=this.getCollisionData(localPos.set(pos.x-this.pos.x,pos.y-this.pos.y));return tileData&&collisionTest(tileData,pos)};const localPos=new Vector2;const hitPos=lineTest(posStart,posEnd,testFunction,normal);if(debugRaycast&&hitPos){const tilePos=hitPos.floor().add(vec2(.5));debugRect(tilePos,vec2(1),"#f008");debugLine(posStart,posEnd,"#00f",.02);debugLine(posStart,hitPos,"#f00",.02);debugPoint(hitPos,"#0f0");normal&&debugLine(hitPos,hitPos.add(normal),"#ff0",.02)}return hitPos}}class ParticleEmitter extends EngineObject{constructor(pos,angle,emitSize=0,emitTime=0,emitRate=100,emitConeAngle=PI,tileInfo,colorStartA=WHITE,colorStartB=WHITE,colorEndA=CLEAR_WHITE,colorEndB=CLEAR_WHITE,particleTime=.5,sizeStart=.1,sizeEnd=1,speed=.1,angleSpeed=.05,damping=1,angleDamping=1,gravityScale=0,particleConeAngle=PI,fadeRate=.1,randomness=.2,collideTiles=false,additive=false,randomColorLinear=true,renderOrder=additive?1e9:0,localSpace=false){super(pos,vec2(),tileInfo,angle,undefined,renderOrder);this.emitCircle=typeof emitSize==="number";this.emitSize=typeof emitSize==="number"?vec2(emitSize):emitSize.copy();this.emitTime=emitTime;this.emitRate=emitRate;this.emitConeAngle=emitConeAngle;this.colorStartA=colorStartA.copy();this.colorStartB=colorStartB.copy();this.colorEndA=colorEndA.copy();this.colorEndB=colorEndB.copy();this.randomColorLinear=randomColorLinear;this.particleTime=particleTime;this.sizeStart=sizeStart;this.sizeEnd=sizeEnd;this.speed=speed;this.angleSpeed=angleSpeed;this.damping=damping;this.angleDamping=angleDamping;this.gravityScale=gravityScale;this.particleConeAngle=particleConeAngle;this.fadeRate=fadeRate;this.randomness=randomness;this.collideTiles=collideTiles;this.additive=additive;this.localSpace=localSpace;this.trailScale=0;this.particleCreateCallback=undefined;this.particleDestroyCallback=undefined;this.particleCollideCallback=undefined;this.velocityInheritance=0;this.emitTimeBuffer=0;this.particles=[];this.previousAngle=this.angle;this.previousPos=this.pos.copy()}update(){ASSERT(this.angleDamping>=0&&this.angleDamping<=1);ASSERT(this.damping>=0&&this.damping<=1);if(this.velocityInheritance){const p=this.velocityInheritance;this.velocity.x=p*(this.pos.x-this.previousPos.x);this.velocity.y=p*(this.pos.y-this.previousPos.y);this.angleVelocity=p*(this.angle-this.previousAngle);this.previousAngle=this.angle;this.previousPos.x=this.pos.x;this.previousPos.y=this.pos.y}if(this.isActive()){if(this.emitRate&&particleEmitRateScale){const rate=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;this.emitTimeBuffer>0;this.emitTimeBuffer-=rate)this.emitParticle()}}else if(this.particles.length===0)this.destroy(true);const particles=this.particles;let alive=0;for(let i=0;i<particles.length;++i){const p=particles[i];p.update();if(!p.destroyed)particles[alive++]=p}particles.length=alive;if(debugParticles){if(this.emitCircle)debugCircle(this.pos,this.emitSize.x/2,"#0f0");else debugRect(this.pos,this.emitSize,"#0f0",0,this.angle)}}emitParticle(){let pos=this.emitCircle?randInCircle(this.emitSize.x/2):vec2(rand(-.5,.5),rand(-.5,.5)).multiply(this.emitSize).rotate(this.angle);let angle=rand(this.particleConeAngle,-this.particleConeAngle);if(!this.localSpace){pos.x+=this.pos.x;pos.y+=this.pos.y;angle+=this.angle}const randomness=this.randomness;const randomizeScale=v=>v+v*rand(randomness,-randomness);const particleTime=randomizeScale(this.particleTime);const sizeStart=randomizeScale(this.sizeStart);const sizeEnd=randomizeScale(this.sizeEnd);const speed=randomizeScale(this.speed);const angleSpeed=randomizeScale(this.angleSpeed)*randSign();const coneAngle=rand(this.emitConeAngle,-this.emitConeAngle);const colorStart=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear);const colorEnd=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear);const velocityAngle=this.localSpace?coneAngle:this.angle+coneAngle;const velocity=vec2(speed*sin(velocityAngle),speed*cos(velocityAngle));let angleVelocity=angleSpeed;if(!this.localSpace&&this.velocityInheritance>0){velocity.x+=this.velocity.x;velocity.y+=this.velocity.y;angleVelocity+=this.angleVelocity}const particle=new Particle(this,pos,angle,colorStart,colorEnd,particleTime,sizeStart,sizeEnd,velocity,angleVelocity);this.particles.push(particle);this.particleCreateCallback?.(particle);return particle}updatePhysics(){}render(){for(const particle of this.particles)particle.render()}isActive(){return!this.emitTime||this.getAliveTime()<this.emitTime}destroy(immediate=false){if(this.destroyed)return;super.destroy(immediate);if(!immediate&&this.particles.length>0){this.destroyed=false;this.emitTime=-1}}}const particleDrawPos=new Vector2;class Particle{constructor(emitter,pos,angle,colorStart,colorEnd,lifeTime,sizeStart,sizeEnd,velocity=vec2(),angleVelocity=0){this.emitter=emitter;this.pos=pos;this.angle=angle;this.size=vec2(sizeStart);this.color=colorStart.copy();this.colorStart=colorStart;this.colorEnd=colorEnd;this.lifeTime=lifeTime;this.sizeStart=sizeStart;this.sizeEnd=sizeEnd;this.velocity=velocity;this.angleVelocity=angleVelocity;this.spawnTime=time;this.mirror=randBool();this.groundObject=undefined;this.destroyed=false;this.tileInfo=emitter.tileInfo}update(){const emitter=this.emitter;const damping=emitter.damping;const angleDamping=emitter.angleDamping;const restitution=emitter.restitution;const friction=emitter.friction;const gravityScale=emitter.gravityScale;const collideTiles=emitter.collideTiles;const collideCallback=emitter.particleCollideCallback;if(this.lifeTime>0&&time-this.spawnTime>this.lifeTime){this.destroy();return}const oldPos=this.pos.copy();this.velocity.x*=damping;this.velocity.y*=damping;this.pos.x+=this.velocity.x+=gravity.x*gravityScale;this.pos.y+=this.velocity.y+=gravity.y*gravityScale;this.angle+=this.angleVelocity*=angleDamping;if(!enablePhysicsSolver||!collideTiles)return;const length2=this.velocity.lengthSquared();if(length2>objectMaxSpeed*objectMaxSpeed){const s=objectMaxSpeed/length2**.5;this.velocity.x*=s;this.velocity.y*=s}this.groundObject=undefined;const testCollision=collideCallback?pos=>{const data=tileCollisionGetData(pos);return data&&collideCallback(this,data,pos)}:pos=>tileCollisionGetData(pos)>0;if(testCollision(this.pos)){const hitLayer=tileCollisionTest(this.pos);if(!testCollision(oldPos)){const isBlockedX=testCollision(vec2(this.pos.x,oldPos.y));const isBlockedY=testCollision(vec2(oldPos.x,this.pos.y));const hitRestitution=hitLayer?max(restitution,hitLayer.restitution):restitution;const hitFriction=hitLayer?max(friction,hitLayer.friction):friction;if(isBlockedX){this.pos.x=oldPos.x;this.velocity.x*=-hitRestitution;this.velocity.y*=hitFriction}if(isBlockedY||!isBlockedX){const wasFalling=this.velocity.y<0&&gravity.y<0||this.velocity.y>0&&gravity.y>0;if(wasFalling)this.groundObject=hitLayer;this.pos.y=oldPos.y;this.velocity.y*=-hitRestitution;this.velocity.x*=hitFriction}debugPhysics&&debugRect(this.pos,this.size,"#f00")}}}destroy(){const destroyCallback=this.emitter.particleDestroyCallback;const c=this.colorEnd;this.color.set(c.r,c.g,c.b,c.a);this.size.set(this.sizeEnd,this.sizeEnd);this.destroyed=true;destroyCallback?.(this)}render(){const emitter=this.emitter;const localSpace=emitter.localSpace;const additive=emitter.additive;const trailScale=emitter.trailScale;const fadeRate=emitter.fadeRate/2;const p1=this.lifeTime>0?min((time-this.spawnTime)/this.lifeTime,1):1,p2=1-p1;const radius=p2*this.sizeStart+p1*this.sizeEnd;const size=vec2(radius);const alphaFade=p1<fadeRate?p1/fadeRate:p1>1-fadeRate?(1-p1)/fadeRate:1;this.color.r=p2*this.colorStart.r+p1*this.colorEnd.r;this.color.g=p2*this.colorStart.g+p1*this.colorEnd.g;this.color.b=p2*this.colorStart.b+p1*this.colorEnd.b;this.color.a=(p2*this.colorStart.a+p1*this.colorEnd.a)*alphaFade;const pos=particleDrawPos.set(this.pos.x,this.pos.y);let angle=this.angle;if(localSpace){const a=emitter.angle;const c=cos(-a),s=sin(-a);pos.set(emitter.pos.x+pos.x*c-pos.y*s,emitter.pos.y+pos.x*s+pos.y*c);angle+=a}additive&&setAdditiveBlendMode();if(trailScale){const velocity=localSpace?this.velocity.rotate(emitter.angle):this.velocity;const speed=velocity.length();if(speed){const trailLength=speed*trailScale;size.y=max(size.x,trailLength);angle=atan2(velocity.x,velocity.y);drawTile(pos,size,this.tileInfo,this.color,angle,this.mirror)}}else drawTile(pos,size,this.tileInfo,this.color,angle,this.mirror);additive&&setAdditiveBlendMode(false);debugParticles&&debugRect(pos,size,"#f005",0,angle)}}let glCanvas;let glContext;let glAntialias=true;let glShader,glPolyShader,glPolyMode,glAdditive,glBatchAdditive,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glBatchCount,glTextureInfos,glInstancedVAO,glPolyVAO,glFramebuffer,glRenderTarget,glCanBeEnabled=true;const gl_ARRAY_BUFFER_SIZE=5e5;const gl_INDICES_PER_INSTANCE=11;const gl_INSTANCE_BYTE_STRIDE=gl_INDICES_PER_INSTANCE*4;const gl_MAX_INSTANCES=gl_ARRAY_BUFFER_SIZE/gl_INSTANCE_BYTE_STRIDE|0;const gl_INDICES_PER_POLY_VERTEX=3;const gl_POLY_VERTEX_BYTE_STRIDE=gl_INDICES_PER_POLY_VERTEX*4;const gl_MAX_POLY_VERTEXES=gl_ARRAY_BUFFER_SIZE/gl_POLY_VERTEX_BYTE_STRIDE|0;function glInit(rootElement){glTextureInfos=new Set;if(!glEnable||headlessMode){glCanBeEnabled=false;return}glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl2",{antialias:glAntialias});if(!glContext){console.warn("WebGL2 not supported, falling back to 2D canvas rendering!");glCanvas=glContext=undefined;glEnable=false;glCanBeEnabled=false;return}rootElement.appendChild(glCanvas);initWebGL();glCanvas.addEventListener("webglcontextlost",e=>{glEnable=false;glCanvas.style.display="none";e.preventDefault();LOG("WebGL context lost! Switching to Canvas2d rendering.");for(const info of glTextureInfos)info.glTexture=undefined;glActiveTexture=undefined;glBatchCount=0;glPolyMode=false;pluginList.forEach(plugin=>plugin.glContextLost?.())});glCanvas.addEventListener("webglcontextrestored",()=>{glEnable=true;glCanvas.style.display="";LOG("WebGL context restored, reinitializing...");initWebGL();for(const info of glTextureInfos)info.glTexture=glCreateTexture(info.image,info.wrap);pluginList.forEach(plugin=>plugin.glContextRestored?.())});function initWebGL(){glShader=glCreateProgram("#version 300 es\n"+"precision highp float;"+"uniform mat4 m;"+"in vec2 g;"+"in vec4 p,u,c,a;"+"in float r;"+"out vec2 v;"+"out vec4 d,e;"+"void main(){"+"vec2 s=(g-.5)*p.zw;"+"gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);"+"v=mix(u.xw,u.zy,g);"+"d=c;e=a;"+"}","#version 300 es\n"+"precision highp float;"+"uniform sampler2D s;"+"in vec2 v;"+"in vec4 d,e;"+"out vec4 c;"+"void main(){"+"c=texture(s,v)*d+e;"+"}");glPolyShader=glCreateProgram("#version 300 es\n"+"precision highp float;"+"uniform mat4 m;"+"in vec2 p;"+"in vec4 c;"+"out vec4 d;"+"void main(){"+"gl_Position=m*vec4(p,1,1);"+"d=c;"+"}","#version 300 es\n"+"precision highp float;"+"in vec4 d;"+"out vec4 c;"+"void main(){"+"c=d;"+"}");const glInstanceData=new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);glPositionData=new Float32Array(glInstanceData);glColorData=new Uint32Array(glInstanceData);glArrayBuffer=glContext.createBuffer();glGeometryBuffer=glContext.createBuffer();glFramebuffer=glContext.createFramebuffer();glBatchCount=0;const geometry=new Float32Array([0,0,1,0,0,1,1,1]);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,geometry,glContext.STATIC_DRAW);let offset,shader,stride;const initVertexAttrib=(name,type,typeSize,size,divisor=0)=>{const location=glContext.getAttribLocation(shader,name);const normalize=typeSize===1;const fixedStride=typeSize&&stride;glContext.enableVertexAttribArray(location);glContext.vertexAttribPointer(location,size,type,normalize,fixedStride,offset);glContext.vertexAttribDivisor(location,divisor);offset+=size*typeSize};glInstancedVAO=glContext.createVertexArray();glContext.bindVertexArray(glInstancedVAO);offset=0,shader=glShader,stride=gl_INSTANCE_BYTE_STRIDE;glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);initVertexAttrib("g",glContext.FLOAT,0,2);glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,gl_ARRAY_BUFFER_SIZE,glContext.DYNAMIC_DRAW);initVertexAttrib("p",glContext.FLOAT,4,4,1);initVertexAttrib("u",glContext.FLOAT,4,4,1);initVertexAttrib("c",glContext.UNSIGNED_BYTE,1,4,1);initVertexAttrib("a",glContext.UNSIGNED_BYTE,1,4,1);initVertexAttrib("r",glContext.FLOAT,4,1,1);glPolyVAO=glContext.createVertexArray();glContext.bindVertexArray(glPolyVAO);offset=0,shader=glPolyShader,stride=gl_POLY_VERTEX_BYTE_STRIDE;initVertexAttrib("p",glContext.FLOAT,4,2);initVertexAttrib("c",glContext.UNSIGNED_BYTE,1,4)}}function glSetInstancedMode(force=false){if(!force&&!glPolyMode)return;glFlush();glPolyMode=false;glContext.useProgram(glShader);glContext.bindVertexArray(glInstancedVAO)}function glSetPolyMode(){if(glPolyMode)return;glFlush();glPolyMode=true;glContext.useProgram(glPolyShader);glContext.bindVertexArray(glPolyVAO)}function glPreRender(clear=true){if(!glEnable||!glContext)return;ASSERT(!glBatchCount,"glPreRender called with unflushed batch.");if(!glRenderTarget){glCanvas.width=mainCanvasSize.x;glCanvas.height=mainCanvasSize.y}glContext.viewport(0,0,mainCanvasSize.x,mainCanvasSize.y);clear&&glClearCanvas();const s=vec2(2*cameraScale).divide(mainCanvasSize);if(glRenderTarget)s.y=-s.y;const rotatedCam=cameraPos.rotate(-cameraAngle);const p=vec2(-1).subtract(rotatedCam.multiply(s));const ca=cos(cameraAngle);const sa=sin(cameraAngle);const transform=[s.x*ca,s.y*sa,0,0,-s.x*sa,s.y*ca,0,0,1,1,1,0,p.x,p.y,0,1];const initUniform=(program,uniform,value)=>{glContext.useProgram(program);const location=glContext.getUniformLocation(program,uniform);glContext.uniformMatrix4fv(location,false,value)};initUniform(glPolyShader,"m",transform);initUniform(glShader,"m",transform);glContext.activeTexture(glContext.TEXTURE0);if(textureInfos[0]){glActiveTexture=textureInfos[0].glTexture;glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)}glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer);glAdditive=glBatchAdditive=false;glSetInstancedMode(true)}function glClearCanvas(){if(!glContext)return;const color=canvasClearColor;glContext.clearColor(color.r,color.g,color.b,color.a);glContext.clear(glContext.COLOR_BUFFER_BIT)}function glSetTexture(texture){if(!glContext||texture===glActiveTexture)return;glFlush();glActiveTexture=texture;glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)}function glSetTextureWrap(texture,wrap=true){if(!glContext||!texture)return;const isCurrent=texture===glActiveTexture;if(isCurrent)glFlush();else glContext.bindTexture(glContext.TEXTURE_2D,texture);const wrapMode=wrap?glContext.REPEAT:glContext.CLAMP_TO_EDGE;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,wrapMode);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,wrapMode);if(!isCurrent&&glActiveTexture)glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)}function glCompileShader(source,type){if(!glContext)return;const shader=glContext.createShader(type);glContext.shaderSource(shader,source);glContext.compileShader(shader);if(debug&&!glContext.getShaderParameter(shader,glContext.COMPILE_STATUS))throw glContext.getShaderInfoLog(shader);return shader}function glCreateProgram(vsSource,fsSource){if(!glContext)return;const program=glContext.createProgram();glContext.attachShader(program,glCompileShader(vsSource,glContext.VERTEX_SHADER));glContext.attachShader(program,glCompileShader(fsSource,glContext.FRAGMENT_SHADER));glContext.linkProgram(program);if(debug&&!glContext.getProgramParameter(program,glContext.LINK_STATUS))throw glContext.getProgramInfoLog(program);return program}function glCreateTexture(image,wrap=false){if(!glContext)return;const texture=glContext.createTexture();let mipMap=false;if(image?.width){glSetTextureData(texture,image);glContext.bindTexture(glContext.TEXTURE_2D,texture);mipMap=!tilesPixelated&&isPowerOfTwo(image.width)&&isPowerOfTwo(image.height)}else{const whitePixel=new Uint8Array([255,255,255,255]);glContext.bindTexture(glContext.TEXTURE_2D,texture);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,1,1,0,glContext.RGBA,glContext.UNSIGNED_BYTE,whitePixel)}const magFilter=tilesPixelated?glContext.NEAREST:glContext.LINEAR;const minFilter=mipMap?glContext.LINEAR_MIPMAP_LINEAR:magFilter;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,magFilter);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,minFilter);const wrapMode=wrap?glContext.REPEAT:glContext.CLAMP_TO_EDGE;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,wrapMode);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,wrapMode);if(mipMap)glContext.generateMipmap(glContext.TEXTURE_2D);glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture);return texture}function glDeleteTexture(texture){if(!glContext)return;glContext.deleteTexture(texture)}function glSetTextureData(texture,image){if(!glContext)return;ASSERT(image?.width>0,"Invalid image data.");glContext.bindTexture(glContext.TEXTURE_2D,texture);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,image);if(!tilesPixelated&&isPowerOfTwo(image.width)&&isPowerOfTwo(image.height))glContext.generateMipmap(glContext.TEXTURE_2D);glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)}function glRegisterTextureInfo(textureInfo){if(headlessMode)return;glTextureInfos.add(textureInfo);if(!glContext)return;if(textureInfo.glTexture)glSetTextureData(textureInfo.glTexture,textureInfo.image);else textureInfo.glTexture=glCreateTexture(textureInfo.image,textureInfo.wrap)}function glUnregisterTextureInfo(textureInfo){if(headlessMode)return;glTextureInfos.delete(textureInfo);const glTexture=textureInfo.glTexture;textureInfo.glTexture=undefined;glDeleteTexture(glTexture)}function glFlush(){if(glEnable&&glContext&&glBatchCount){const destBlend=glBatchAdditive?glContext.ONE:glContext.ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(glContext.SRC_ALPHA,destBlend,glContext.ONE,destBlend);glContext.enable(glContext.BLEND);const byteLength=glBatchCount*(glPolyMode?gl_INDICES_PER_POLY_VERTEX:gl_INDICES_PER_INSTANCE);glContext.bufferSubData(glContext.ARRAY_BUFFER,0,glPositionData,0,byteLength);if(glPolyMode)glContext.drawArrays(glContext.TRIANGLE_STRIP,0,glBatchCount);else glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP,0,4,glBatchCount);++drawCount;primitiveCount+=glBatchCount;glBatchCount=0}glBatchAdditive=glAdditive}function glCopyToContext(context){if(!glEnable||!glContext)return;glFlush();context.drawImage(glCanvas,0,0)}function glSetAntialias(antialias=true){ASSERT(!glCanvas,"must be called before engineInit");glAntialias=antialias}function glDraw(x,y,sizeX,sizeY,angle=0,uv0X=0,uv0Y=0,uv1X=1,uv1Y=1,rgba=-1,rgbaAdditive=0){if(glBatchCount>=gl_MAX_INSTANCES||glBatchAdditive!==glAdditive)glFlush();glSetInstancedMode();let offset=glBatchCount++*gl_INDICES_PER_INSTANCE;glPositionData[offset++]=x;glPositionData[offset++]=y;glPositionData[offset++]=sizeX;glPositionData[offset++]=sizeY;glPositionData[offset++]=uv0X;glPositionData[offset++]=uv0Y;glPositionData[offset++]=uv1X;glPositionData[offset++]=uv1Y;glColorData[offset++]=rgba;glColorData[offset++]=rgbaAdditive;glPositionData[offset++]=angle}function glDrawUntextured(x,y,sizeX,sizeY,angle,rgba){glDraw(x,y,sizeX,sizeY,angle,0,0,0,0,0,rgba)}function glDrawPointsTransform(points,rgba,x,y,sx,sy,angle,tristrip=true){const pointsOut=[];const sa=sin(-angle);const ca=cos(-angle);for(const p of points){const px=p.x*sx;const py=p.y*sy;pointsOut.push(vec2(x+ca*px-sa*py,y+sa*px+ca*py))}const drawPoints=tristrip?glPolyStrip(pointsOut):pointsOut;glDrawPoints(drawPoints,rgba)}function glDrawOutlineTransform(points,rgba,lineWidth,x,y,sx,sy,angle,wrap=true){const outlinePoints=glMakeOutline(points,lineWidth,wrap);glDrawPointsTransform(outlinePoints,rgba,x,y,sx,sy,angle,false)}function glDrawPoints(points,rgba){if(!glEnable||points.length<3)return;const vertCount=points.length+2;if(glBatchCount+vertCount>=gl_MAX_POLY_VERTEXES||glBatchAdditive!==glAdditive)glFlush();ASSERT(vertCount<gl_MAX_POLY_VERTEXES,"poly exceeds max batch size");if(vertCount>=gl_MAX_POLY_VERTEXES)return;glSetPolyMode();let offset=glBatchCount*gl_INDICES_PER_POLY_VERTEX;for(let i=vertCount;i--;){const j=clamp(i-1,0,vertCount-3);const point=points[j];glPositionData[offset++]=point.x;glPositionData[offset++]=point.y;glColorData[offset++]=rgba}glBatchCount+=vertCount}function glDrawColoredPoints(points,pointColors){if(!glEnable||points.length<3)return;const vertCount=points.length+2;if(glBatchCount+vertCount>=gl_MAX_POLY_VERTEXES||glBatchAdditive!==glAdditive)glFlush();ASSERT(vertCount<gl_MAX_POLY_VERTEXES,"poly exceeds max batch size");if(vertCount>=gl_MAX_POLY_VERTEXES)return;glSetPolyMode();let offset=glBatchCount*gl_INDICES_PER_POLY_VERTEX;for(let i=vertCount;i--;){const j=clamp(i-1,0,vertCount-3);const point=points[j];const color=pointColors[j];glPositionData[offset++]=point.x;glPositionData[offset++]=point.y;glColorData[offset++]=color}glBatchCount+=vertCount}function glSetRenderTarget(texture,clear=false){if(texture){glRenderTarget=texture;glContext.bindFramebuffer(glContext.FRAMEBUFFER,glFramebuffer);glContext.framebufferTexture2D(glContext.FRAMEBUFFER,glContext.COLOR_ATTACHMENT0,glContext.TEXTURE_2D,texture,0);glPreRender(clear)}else{glFlush();glRenderTarget=undefined;glContext.bindFramebuffer(glContext.FRAMEBUFFER,null);glContext.viewport(0,0,mainCanvasSize.x,mainCanvasSize.y)}}function glClearRect(x,y,width,height){if(!glEnable)return;glContext.enable(glContext.SCISSOR_TEST);glContext.scissor(x,y,width,height);glContext.clearColor(0,0,0,0);glContext.clear(glContext.COLOR_BUFFER_BIT);glContext.disable(glContext.SCISSOR_TEST)}function glMakeOutline(points,width,wrap=true){if(points.length<2)return[];const halfWidth=width/2;const strip=[];const n=points.length;const e=1e-6;const miterLimit=10;for(let i=0;i<n;i++){const prev=points[wrap?(i-1+n)%n:max(i-1,0)];const curr=points[i];const next=points[wrap?(i+1)%n:min(i+1,n-1)];const dx1=curr.x-prev.x;const dy1=curr.y-prev.y;const len1=(dx1*dx1+dy1*dy1)**.5;const dx2=next.x-curr.x;const dy2=next.y-curr.y;const len2=(dx2*dx2+dy2*dy2)**.5;if(len1<e&&len2<e)continue;const nx1=len1>e?-dy1/len1:0;const ny1=len1>e?dx1/len1:0;const nx2=len2>e?-dy2/len2:0;const ny2=len2>e?dx2/len2:0;let nx=nx1+nx2;let ny=ny1+ny2;const nlen=(nx*nx+ny*ny)**.5;if(nlen<e){nx=nx1;ny=ny1}else{nx/=nlen;ny/=nlen;const dot=nx1*nx+ny1*ny;if(dot>e){const miterLength=min(1/dot,miterLimit);nx*=miterLength;ny*=miterLength}}const inner=vec2(curr.x-nx*halfWidth,curr.y-ny*halfWidth);const outer=vec2(curr.x+nx*halfWidth,curr.y+ny*halfWidth);strip.push(inner);strip.push(outer)}if(strip.length>1&&wrap){strip.push(strip[0]);strip.push(strip[1])}return strip}function glPolyStrip(points){if(points.length<3)return[];const cross=(a,b,c)=>(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);const signedArea=poly=>{let area=0;for(let i=poly.length;i--;){const j=(i+1)%poly.length;area+=poly[i].cross(poly[j])}return area};if(signedArea(points)<0)points=points.slice().reverse();const e=1e-9;const pointInTriangle=(p,a,b,c)=>{const c1=cross(a,b,p);const c2=cross(b,c,p);const c3=cross(c,a,p);const negative=(c1<-e?1:0)+(c2<-e?1:0)+(c3<-e?1:0);const positive=(c1>e?1:0)+(c2>e?1:0)+(c3>e?1:0);return!(negative&&positive)};const indices=[];for(let i=0;i<points.length;++i)indices[i]=i;const triangles=[];let attempts=0;const maxAttempts=points.length**2+100;while(indices.length>3&&attempts++<maxAttempts){let foundEar=false;for(let i=0;i<indices.length;i++){const i0=indices[(i+indices.length-1)%indices.length];const i1=indices[i];const i2=indices[(i+1)%indices.length];const a=points[i0],b=points[i1],c=points[i2];if(cross(a,b,c)<e)continue;let hasInside=false;for(let j=0;j<indices.length;j++){const k=indices[j];if(k===i0||k===i1||k===i2)continue;const p=points[k];hasInside=pointInTriangle(p,a,b,c);if(hasInside)break}if(hasInside)continue;triangles.push([i0,i1,i2]);indices.splice(i,1);foundEar=true;break}if(!foundEar){let worstIndex=-1,worstValue=Infinity;for(let i=0;i<indices.length;i++){const i0=indices[(i+indices.length-1)%indices.length];const i1=indices[i];const i2=indices[(i+1)%indices.length];const value=abs(cross(points[i0],points[i1],points[i2]));if(value<worstValue){worstValue=value;worstIndex=i}}if(worstIndex<0)break;const i0=indices[(worstIndex+indices.length-1)%indices.length];const i1=indices[worstIndex];const i2=indices[(worstIndex+1)%indices.length];triangles.push([i0,i1,i2]);indices.splice(worstIndex,1)}}if(indices.length===3)triangles.push([indices[0],indices[1],indices[2]]);if(!triangles.length)return[];const strip=[];let[a0,b0,c0]=triangles[0];strip.push(points[a0],points[b0],points[c0]);for(let i=1;i<triangles.length;i++){const[a,b,c]=triangles[i];strip.push(points[c0],points[a]);strip.push(points[a],points[b],points[c]);c0=c}return strip}function drawEngineLogo(t){const blackAndWhite=0;const showName=1;const x=mainContext;const dpr=canvasPixelRatio??(devicePixelRatio||1);const w=mainCanvas.width=innerWidth*dpr;const h=mainCanvas.height=innerHeight*dpr;{const p3=percent(t,1,.8);const p4=percent(t,0,.5);const g=x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());g.addColorStop(1,hsl(0,0,0,p3).toString());x.save();x.fillStyle=g;x.fillRect(0,0,w,h)}const gradient=(X1,Y1,X2,Y2,C,S=1)=>{if(C>=0){if(blackAndWhite)x.fillStyle="#fff";else{const g=x.fillStyle=x.createLinearGradient(X1,Y1,X2,Y2);g.addColorStop(0,color(C,2));g.addColorStop(1,color(C,1))}}else x.fillStyle="#000";C>=-1?(x.fill(),S&&x.stroke()):x.stroke()};const circle=(X,Y,R,A=0,B=2*PI,C,S)=>{x.beginPath();x.arc(X,Y,R,p*A,p*B);gradient(X,Y-R,X,Y+R,C,S)};const rect=(X,Y,W,H,C)=>{x.beginPath();x.rect(X,Y,W,H*p);gradient(X,Y+H,X+W,Y,C)};const poly=(points,C,Y,H)=>{x.beginPath();for(const p of points)x.lineTo(p.x,p.y);x.closePath();gradient(0,Y,0,Y+H,C)};const color=(c,l)=>l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%)`:"#000";const alpha=oscillate(1,1,t);const p=percent(alpha,.1,.5);const size=min(6,min(w,h)/99);x.translate(w/2,h/2);x.scale(size,size);x.translate(-40,-35);p<1&&x.setLineDash([99*p,99]);x.lineJoin=x.lineCap="round";x.lineWidth=.1+p*1.9;if(showName){const Y=54;const s="LittleJS";x.font="900 15.5px arial";x.lineWidth=.1+p*3.9;x.textAlign="center";x.textBaseline="top";rect(11,Y+1,59,8*p,-1);x.beginPath();let w2=0;for(let i=0;i<s.length;++i)w2+=x.measureText(s[i]).width;for(let j=2;j--;)for(let i=0,X=40-w2/2;i<s.length;++i){const w=x.measureText(s[i]).width,X2=X+w/2;gradient(X2,Y,X2+2,Y+13,i>5?1:0);x[j?"strokeText":"fillText"](s[i],X2,Y+.5,17*p);X+=w}x.lineWidth=.1+p*1.9;rect(3,Y,73,0)}rect(7,15,26,-7,0);rect(25,15,8,25,-1);rect(10,40,15,-25,1);rect(14,21,7,9,2);rect(38,20,6,-6,2);rect(49,20,10,-6,0);const stackPoints=[vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];poly(stackPoints,2,8,6*p);rect(44,8,20,-7,0);for(let i=5;i--;)circle(59-i*6*p,30,10,0,2*PI,1,0);circle(59,30,4,0,7,2);rect(35,20,24,0);circle(59,30,10);circle(47,30,10,PI/2,PI*3/2);circle(35,30,10,PI/2,PI*3/2);rect(7,40,13,7,-1);rect(17,40,43,14,-1);for(let i=3;i--;)for(let j=2;j--;)circle(17+15*i,47,j?7:1,0,2*PI,2);for(let i=2;i--;){let w=6,s=7,o=53+w*p*i;const points=[vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];poly(points,0,40,14)}x.restore()}let debugMedals=false;let medalDisplayTime=5;let medalDisplaySlideTime=.5;let medalDisplaySize=vec2(640,80);let medalsPreventUnlock=false;const medals={};let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(saveName){medalsSaveName=saveName;if(!debugMedals){let saved={};try{saved=JSON.parse(localStorage[saveName]||"{}")}catch(e){saved={}}medalsForEach(medal=>{medal.unlocked=!!(saved[medal.id]&&saved[medal.id].unlocked)});medalsSave()}engineAddPlugin(undefined,medalsRender);function medalsRender(){if(!medalsDisplayQueue.length)return;const medal=medalsDisplayQueue[0];const time=timeReal-medalsDisplayTimeLast;if(!medalsDisplayTimeLast)medalsDisplayTimeLast=timeReal;else if(time>medalDisplayTime){medalsDisplayTimeLast=0;medalsDisplayQueue.shift()}else{const slideOffTime=medalDisplayTime-medalDisplaySlideTime;const hidePercent=time<medalDisplaySlideTime?1-time/medalDisplaySlideTime:time>slideOffTime?(time-slideOffTime)/medalDisplaySlideTime:0;medal.render(hidePercent)}}}function medalsForEach(callback){Object.values(medals).forEach(medal=>callback(medal))}function medalsReset(){medalsForEach(medal=>medal.unlocked=false);medalsSave()}function medalsSave(){if(!medalsSaveName)return;const data={};medalsForEach(medal=>{const entry={name:medal.name,description:medal.description,icon:medal.icon,unlocked:medal.unlocked};if(medal.image)entry.src=medal.image.src;data[medal.id]=entry});localStorage[medalsSaveName]=JSON.stringify(data)}class Medal{constructor(id,name,description="",icon="🏆",src){ASSERT(id>=0&&!medals[id]);this.id=id;this.name=name;this.description=description;this.icon=icon;this.unlocked=false;this.image=undefined;if(src)(this.image=new Image).src=src;medals[id]=this}unlock(){if(medalsPreventUnlock||this.unlocked)return;ASSERT(medalsSaveName,"save name must be set");this.unlocked=true;medalsSave();medalsDisplayQueue.push(this)}render(hidePercent=0){const context=mainContext;const width=min(medalDisplaySize.x,mainCanvas.width);const height=medalDisplaySize.y;const x=mainCanvas.width-width;const y=-height*hidePercent;const backgroundColor=hsl(0,0,.9);context.save();context.beginPath();context.fillStyle=backgroundColor.toString();context.strokeStyle=BLACK.toString();context.lineWidth=3;context.rect(x,y,width,height);context.fill();context.stroke();context.clip();const gap=vec2(.1,.05).scale(height);const medalDisplayIconSize=height-2*gap.x;this.renderIcon(vec2(x+gap.x+medalDisplayIconSize/2,y+height/2),medalDisplayIconSize);const nameSize=height*.5;const descriptionSize=height*.3;const pos=vec2(x+medalDisplayIconSize+2*gap.x,y+gap.y*2+nameSize/2);const textWidth=width-medalDisplayIconSize-3*gap.x;drawTextScreen(this.name,pos,nameSize,BLACK,0,undefined,"left",undefined,undefined,textWidth);pos.y=y+height-gap.y*2-descriptionSize/2;drawTextScreen(this.description,pos,descriptionSize,BLACK,0,undefined,"left",undefined,undefined,textWidth);context.restore()}renderIcon(pos,size){if(this.image)mainContext.drawImage(this.image,pos.x-size/2,pos.y-size/2,size,size);else drawTextScreen(this.icon,pos,size*.7,BLACK)}}function setMedalDisplayTime(time){medalDisplayTime=time}function setMedalDisplaySlideTime(time){medalDisplaySlideTime=time}function setMedalDisplaySize(size){medalDisplaySize=size.copy()}function setMedalsPreventUnlock(preventUnlock){medalsPreventUnlock=preventUnlock}let newgrounds;class NewgroundsMedal extends Medal{constructor(id,name,description,icon,src){super(id,name,description,icon,src)}unlock(){super.unlock();newgrounds&&newgrounds.unlockMedal(this.id)}}class NewgroundsPlugin{constructor(app_id,cipher,cryptoJS){ASSERT(!newgrounds,"there can only be one newgrounds object");ASSERT(!cipher||cryptoJS,"must provide cryptojs if there is a cipher");newgrounds=this;this.app_id=app_id;this.cipher=cipher;this.cryptoJS=cryptoJS;this.host=location?location.hostname:"";const url=new URL(location.href);this.session_id=url.searchParams.get("ngio_session_id");if(!this.session_id)return;const medalsResult=this.call("Medal.getList");if(!medalsResult||!medalsResult.result||medalsResult.result.error){debugMedals&&LOG("Newgrounds session unavailable; skipping plugin init");this.medals=[];this.scoreboards=[];return}this.medals=medalsResult.result.data?.["medals"]||[];debugMedals&&LOG(this.medals);for(const newgroundsMedal of this.medals){const medal=medals[newgroundsMedal["id"]];if(medal){medal.image=new Image;medal.image.src=newgroundsMedal["icon"];medal.name=newgroundsMedal["name"];medal.description=newgroundsMedal["description"];medal.unlocked=newgroundsMedal["unlocked"];medal.difficulty=newgroundsMedal["difficulty"];medal.value=newgroundsMedal["value"];if(medal.value)medal.description=medal.description+` (${medal.value})`}}const scoreboardResult=this.call("ScoreBoard.getBoards");this.scoreboards=scoreboardResult?.result?.data?.scoreboards||[];debugMedals&&LOG(this.scoreboards);const keepAliveMS=60*1e3;setInterval(()=>this.call("Gateway.ping",0,true),keepAliveMS)}unlockMedal(id){return this.call("Medal.unlock",{id:id},true)}postScore(id,value){return this.call("ScoreBoard.postScore",{id:id,value:value},true)}getScores(id,user,social=0,skip=0,limit=10){return this.call("ScoreBoard.getScores",{id:id,user:user,social:social,skip:skip,limit:limit})}logView(){return this.call("App.logView",{host:this.host},true)}call(component,parameters,async=false){const call={component:component,parameters:parameters};if(this.cipher){const cryptoJS=this.cryptoJS;const aesKey=cryptoJS["enc"]["Base64"]["parse"](this.cipher);const iv=cryptoJS["lib"]["WordArray"]["random"](16);const encrypted=cryptoJS["AES"]["encrypt"](JSON.stringify(call),aesKey,{iv:iv});call["secure"]=cryptoJS["enc"]["Base64"]["stringify"](iv.concat(encrypted["ciphertext"]));call["parameters"]=0}const input={app_id:this.app_id,session_id:this.session_id,call:call};const formData=new FormData;formData.append("input",JSON.stringify(input));const xmlHttp=new XMLHttpRequest;const url="https://newgrounds.io/gateway_v3.php";xmlHttp.open("POST",url,!debugMedals&&async);try{xmlHttp.send(formData)}catch(e){debugMedals&&LOG("newgrounds call failed",e);return}debugMedals&&LOG(xmlHttp.responseText);try{return xmlHttp.responseText&&JSON.parse(xmlHttp.responseText)}catch(e){debugMedals&&LOG("newgrounds response is not valid JSON",e)}}}let postProcess;class PostProcessPlugin{constructor(shaderCode,includeMainCanvas=false,feedbackTexture=false){ASSERT(!postProcess,"Post process already initialized");ASSERT(!(includeMainCanvas&&feedbackTexture),"Post process cannot both include main canvas and use feedback texture");postProcess=this;if(!shaderCode)shaderCode="void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}";this.shader=undefined;this.texture=undefined;this.vao=undefined;initPostProcess();engineAddPlugin(undefined,postProcessRender,postProcessContextLost,postProcessContextRestored);function initPostProcess(){if(headlessMode)return;if(!glEnable){console.warn("PostProcessPlugin: WebGL not enabled!");return}postProcess.texture=glCreateTexture();postProcess.shader=glCreateProgram("#version 300 es\n"+"precision highp float;"+"in vec2 p;"+"void main(){"+"gl_Position=vec4(p+p-1.,1,1);"+"}","#version 300 es\n"+"precision highp float;"+"uniform sampler2D iChannel0;"+"uniform vec3 iResolution;"+"uniform float iTime;"+"out vec4 c;"+"\n"+shaderCode+"\n"+"void main(){"+"mainImage(c,gl_FragCoord.xy);"+"c.a=1.;"+"}");postProcess.vao=glContext.createVertexArray();glContext.bindVertexArray(postProcess.vao);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);const vertexByteStride=8;const pLocation=glContext.getAttribLocation(postProcess.shader,"p");glContext.enableVertexAttribArray(pLocation);glContext.vertexAttribPointer(pLocation,2,glContext.FLOAT,false,vertexByteStride,0)}function postProcessContextLost(){postProcess.shader=undefined;postProcess.texture=undefined;LOG("PostProcessPlugin: WebGL context lost")}function postProcessContextRestored(){initPostProcess();LOG("PostProcessPlugin: WebGL context restored")}function postProcessRender(){if(headlessMode||!glEnable)return;glFlush();glContext.bindFramebuffer(glContext.FRAMEBUFFER,null);glContext.useProgram(postProcess.shader);glContext.bindVertexArray(postProcess.vao);glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,true);glContext.disable(glContext.BLEND);glContext.activeTexture(glContext.TEXTURE0);glContext.bindTexture(glContext.TEXTURE_2D,postProcess.texture);if(includeMainCanvas){workCanvas.width=mainCanvasSize.x;workCanvas.height=mainCanvasSize.y;glCopyToContext(workContext);workContext.drawImage(mainCanvas,0,0);mainCanvas.width|=0;glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,workCanvas)}else if(!feedbackTexture){glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,glCanvas)}const uniformLocation=name=>glContext.getUniformLocation(postProcess.shader,name);glContext.uniform1i(uniformLocation("iChannel0"),0);glContext.uniform1f(uniformLocation("iTime"),time);glContext.uniform3f(uniformLocation("iResolution"),mainCanvas.width,mainCanvas.height,1);glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4);if(feedbackTexture){glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,glCanvas)}glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,false);glSetInstancedMode(true)}}}let lightSystem;class LightSystemPlugin{constructor(textureSize,ambientColor){ASSERT(!lightSystem,"LightSystemPlugin already initialized");ASSERT(!postProcess,"LightSystemPlugin must be created before PostProcessPlugin");lightSystem=this;this.enabled=true;this.ambientColor=(ambientColor||BLACK).copy();this.textureSize=textureSize?textureSize.copy():undefined;this.texture=undefined;this.lightShader=undefined;this.compositeShader=undefined;this.lightVAO=undefined;this.compositeVAO=undefined;initLightSystem();engineAddPlugin(undefined,lightSystemRender,lightSystemContextLost,lightSystemContextRestored);function initLightSystem(){if(headlessMode)return;if(!glEnable){console.warn("LightSystemPlugin: WebGL not enabled!");return}if(!lightSystem.textureSize)lightSystem.textureSize=mainCanvasSize.copy();lightSystem.texture=glContext.createTexture();glContext.bindTexture(glContext.TEXTURE_2D,lightSystem.texture);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,lightSystem.textureSize.x,lightSystem.textureSize.y,0,glContext.RGBA,glContext.UNSIGNED_BYTE,null);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,glContext.LINEAR);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,glContext.LINEAR);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,glContext.CLAMP_TO_EDGE);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,glContext.CLAMP_TO_EDGE);lightSystem.lightShader=glCreateProgram("#version 300 es\n"+"precision highp float;"+"uniform mat4 m;"+"uniform vec2 lightPos;"+"uniform float radius;"+"in vec2 g;"+"out vec2 vWorldPos;"+"void main(){"+"vec2 worldP=lightPos+(g-.5)*2.*radius;"+"gl_Position=m*vec4(worldP,1,1);"+"vWorldPos=worldP;"+"}","#version 300 es\n"+"precision highp float;"+"uniform vec2 lightPos;"+"uniform float radius;"+"uniform float fadeRange;"+"uniform vec4 color;"+"in vec2 vWorldPos;"+"out vec4 c;"+"void main(){"+"float dist=distance(vWorldPos,lightPos);"+"float t=clamp((radius-dist)/max(fadeRange,1e-6),0.,1.);"+"c=vec4(color.rgb*t*color.a,1.);"+"}");lightSystem.compositeShader=glCreateProgram("#version 300 es\n"+"precision highp float;"+"in vec2 p;"+"void main(){"+"gl_Position=vec4(p+p-1.,1,1);"+"}","#version 300 es\n"+"precision highp float;"+"uniform sampler2D s;"+"uniform vec3 iResolution;"+"out vec4 c;"+"void main(){"+"vec2 uv=gl_FragCoord.xy/iResolution.xy;"+"c=vec4(texture(s,uv).rgb,1.);"+"}");lightSystem.lightVAO=glContext.createVertexArray();glContext.bindVertexArray(lightSystem.lightVAO);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);const gLight=glContext.getAttribLocation(lightSystem.lightShader,"g");glContext.enableVertexAttribArray(gLight);glContext.vertexAttribPointer(gLight,2,glContext.FLOAT,false,8,0);lightSystem.compositeVAO=glContext.createVertexArray();glContext.bindVertexArray(lightSystem.compositeVAO);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);const pComp=glContext.getAttribLocation(lightSystem.compositeShader,"p");glContext.enableVertexAttribArray(pComp);glContext.vertexAttribPointer(pComp,2,glContext.FLOAT,false,8,0)}function lightSystemRender(){if(headlessMode||!glEnable)return;if(!lightSystem.enabled)return;if(!lightSystem.texture)return;glFlush();const prevAdditive=glAdditive;const ac=lightSystem.ambientColor;glContext.bindFramebuffer(glContext.FRAMEBUFFER,glFramebuffer);glContext.framebufferTexture2D(glContext.FRAMEBUFFER,glContext.COLOR_ATTACHMENT0,glContext.TEXTURE_2D,lightSystem.texture,0);glContext.viewport(0,0,lightSystem.textureSize.x,lightSystem.textureSize.y);glContext.clearColor(ac.r,ac.g,ac.b,ac.a);glContext.clear(glContext.COLOR_BUFFER_BIT);setAdditiveBlendMode();glContext.enable(glContext.BLEND);glContext.blendFunc(glContext.ONE,glContext.ONE);for(const o of engineObjects)o.destroyed||o.renderLight();glFlush();glContext.bindFramebuffer(glContext.FRAMEBUFFER,null);glContext.viewport(0,0,mainCanvasSize.x,mainCanvasSize.y);glContext.useProgram(lightSystem.compositeShader);glContext.bindVertexArray(lightSystem.compositeVAO);glContext.activeTexture(glContext.TEXTURE0);glContext.bindTexture(glContext.TEXTURE_2D,lightSystem.texture);const cs=lightSystem.compositeShader;glContext.uniform1i(glContext.getUniformLocation(cs,"s"),0);glContext.uniform3f(glContext.getUniformLocation(cs,"iResolution"),mainCanvas.width,mainCanvas.height,1);glContext.blendFunc(glContext.DST_COLOR,glContext.ZERO);glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4);if(glActiveTexture)glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture);setAdditiveBlendMode(prevAdditive);glSetInstancedMode(true)}function lightSystemContextLost(){lightSystem.texture=undefined;lightSystem.lightShader=undefined;lightSystem.compositeShader=undefined;lightSystem.lightVAO=undefined;lightSystem.compositeVAO=undefined;LOG("LightSystemPlugin: WebGL context lost")}function lightSystemContextRestored(){initLightSystem();LOG("LightSystemPlugin: WebGL context restored")}}drawLight(light){if(headlessMode||!glEnable||!this.lightShader)return;glFlush();glContext.useProgram(this.lightShader);glContext.bindVertexArray(this.lightVAO);const s=vec2(2*cameraScale).divide(mainCanvasSize);const rotatedCam=cameraPos.rotate(-cameraAngle);const p=vec2(-1).subtract(rotatedCam.multiply(s));const ca=cos(cameraAngle);const sa=sin(cameraAngle);const transform=[s.x*ca,s.y*sa,0,0,-s.x*sa,s.y*ca,0,0,1,1,1,0,p.x,p.y,0,1];const ls=this.lightShader;glContext.uniformMatrix4fv(glContext.getUniformLocation(ls,"m"),false,transform);glContext.uniform2f(glContext.getUniformLocation(ls,"lightPos"),light.pos.x,light.pos.y);glContext.uniform1f(glContext.getUniformLocation(ls,"radius"),light.radius);glContext.uniform1f(glContext.getUniformLocation(ls,"fadeRange"),light.fadeRange);const c=light.color;glContext.uniform4f(glContext.getUniformLocation(ls,"color"),c.r,c.g,c.b,c.a);glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4);glSetInstancedMode(true)}}class Light extends EngineObject{constructor(pos,radius,color,fadeRange){super(pos,vec2(1),undefined,0,color);ASSERT(isNumber(radius)&&radius>=0,"Light radius must be a non-negative number");ASSERT(fadeRange===undefined||isNumber(fadeRange)&&fadeRange>=0,"Light fadeRange must be a non-negative number when provided");this.radius=radius;this.fadeRange=fadeRange===undefined?radius:fadeRange}render(){}renderLight(){lightSystem&&lightSystem.drawLight(this)}}class ZzFXMusic extends Sound{constructor(zzfxMusic){super(undefined);if(!soundEnable||headlessMode)return;this.randomness=0;this.sampleChannels=zzfxM(...zzfxMusic);this.sampleRate=audioDefaultSampleRate}playMusic(volume=1,loop=true){return super.play(undefined,volume,1,0,loop)}}function zzfxM(instruments,patterns,sequence,BPM=125){let i,j,k;let instrumentParameters;let note;let sample;let patternChannel;let notFirstBeat;let stop;let instrument;let attenuation;let outSampleOffset;let isSequenceEnd;let sampleOffset=0;let nextSampleOffset;let sampleBuffer=[];let leftChannelBuffer=[];let rightChannelBuffer=[];let channelIndex=0;let panning=0;let hasMore=1;let sampleCache={};let beatLength=audioDefaultSampleRate/BPM*60>>2;for(;hasMore;channelIndex++){sampleBuffer=[hasMore=notFirstBeat=outSampleOffset=0];sequence.forEach((patternIndex,sequenceIndex)=>{patternChannel=patterns[patternIndex][channelIndex]||[0,0,0];hasMore|=patterns[patternIndex][channelIndex]&&1;nextSampleOffset=outSampleOffset+(patterns[patternIndex][0].length-2-(notFirstBeat?0:1))*beatLength;isSequenceEnd=sequenceIndex===sequence.length-1;for(i=2,k=outSampleOffset;i<patternChannel.length+isSequenceEnd;notFirstBeat=++i){note=patternChannel[i];stop=i===patternChannel.length+isSequenceEnd-1&&isSequenceEnd||instrument!==(patternChannel[0]||0)||note|0;for(j=0;j<beatLength&¬FirstBeat;j++>beatLength-99&&stop&&attenuation<1?attenuation+=1/99:0){sample=(1-attenuation)*sampleBuffer[sampleOffset++]/2||0;leftChannelBuffer[k]=(leftChannelBuffer[k]||0)-sample*panning+sample;rightChannelBuffer[k]=(rightChannelBuffer[k++]||0)+sample*panning+sample}if(note){attenuation=note%1;panning=patternChannel[1]||0;if(note|=0){sampleBuffer=sampleCache[[instrument=patternChannel[sampleOffset=0]||0,note]]=sampleCache[[instrument,note]]||(instrumentParameters=[...instruments[instrument]],instrumentParameters[2]=(instrumentParameters[2]||220)*2**(note/12-1),note>0?zzfxG(...instrumentParameters):[])}}}outSampleOffset=nextSampleOffset})}return[leftChannelBuffer,rightChannelBuffer]}let uiSystem;let uiDebug=0;function uiSetDebug(debugMode){uiDebug=typeof debugMode==="boolean"?debugMode?1:0:debugMode}class UISystemPlugin{constructor(context=mainContext){ASSERT(!uiSystem,"UI system already initialized");uiSystem=this;this.activateOnPress=false;this.defaultColor=WHITE;this.defaultLineColor=BLACK;this.defaultTextColor=BLACK;this.defaultButtonColor=hsl(0,0,.7);this.defaultHoverColor=hsl(0,0,.9);this.defaultDisabledColor=hsl(0,0,.3);this.defaultGradientColor=undefined;this.defaultLineWidth=4;this.defaultCornerRadius=0;this.defaultTextFitScale=.8;this.defaultFont=fontDefault;this.defaultSoundPress=undefined;this.defaultSoundRelease=undefined;this.defaultSoundClick=undefined;this.defaultShadowColor=CLEAR_BLACK;this.defaultShadowBlur=5;this.defaultShadowOffset=vec2(5);this.nativeHeight=0;this.navigationObject=undefined;this.navigationTimer=new Timer(undefined,true);this.navigationDelay=.2;this.navigationDirection=1;this.navigationMode=false;this.uiObjects=[];this.uiContext=context;this.activeObject=undefined;this.hoverObject=undefined;this.lastHoverObject=undefined;this.confirmDialog=undefined;this._keyInputObject=undefined;this._onKeyDown=e=>this._keyInputObject?.onKeyDown(e);engineAddPlugin(uiUpdate,uiRender);function updateTransforms(o){let targetPos,targetSize;if(o.parent){targetPos=o.parent.nativePos;targetSize=o.parent.size}else{targetPos=uiSystem.screenToNative(mainCanvasSize.scale(.5));targetSize=uiSystem.nativeHeight?vec2(mainCanvasSize.x*uiSystem.nativeHeight/mainCanvasSize.y,uiSystem.nativeHeight):mainCanvasSize}const a=o.anchor;o.nativePos=targetPos.add(targetSize.multiply(a).scale(.5)).subtract(o.size.multiply(a).scale(.5)).add(o.localPos)}function uiUpdate(){if(uiSystem.activeObject&&!uiSystem.activeObject.visible)uiSystem.activeObject=undefined;uiSystem.lastHoverObject=uiSystem.hoverObject;uiSystem.hoverObject=undefined;if(mouseWasPressed(0)){uiSystem.navigationMode=false;uiSystem.navigationObject=undefined}if(uiSystem.keyInputObject){uiSystem.activeObject=uiSystem.keyInputObject;uiSystem.hoverObject=uiSystem.keyInputObject;uiSystem.navigationMode=false;uiSystem.navigationObject=undefined}const navigableObjects=uiSystem.getNavigableObjects();if(!navigableObjects.length)uiSystem.navigationObject=undefined;else if(!uiSystem.keyInputObject){if(!navigableObjects.includes(uiSystem.navigationObject))uiSystem.navigationObject=undefined;if(!isTouchDevice)if(uiSystem.navigationMode&&!uiSystem.navigationObject){uiSystem.navigationObject=navigableObjects.find(o=>o.navigationAutoSelect)}if(!uiSystem.navigationTimer.active()){const direction=sign(uiSystem.getNavigationDirection());if(direction){let newNavigationObject;if(!uiSystem.navigationObject){newNavigationObject=navigableObjects.find(o=>o.navigationAutoSelect);if(!newNavigationObject){const newIndex=direction>0?0:navigableObjects.length-1;newNavigationObject=navigableObjects[newIndex]}}else{const currentIndex=navigableObjects.indexOf(uiSystem.navigationObject);const newIndex=mod(currentIndex+direction,navigableObjects.length);newNavigationObject=navigableObjects[newIndex]}if(uiSystem.navigationObject!==newNavigationObject){uiSystem.navigationMode=true;uiSystem.hoverObject=undefined;uiSystem.navigationObject=newNavigationObject;uiSystem.navigationTimer.set(uiSystem.navigationDelay);newNavigationObject.soundPress&&newNavigationObject.soundPress.play()}}}if(uiSystem.navigationObject)if(uiSystem.getNavigationWasPressed())uiSystem.navigationObject.navigatePressed()}for(let i=uiSystem.uiObjects.length;i--;){const o=uiSystem.uiObjects[i];o.parent||updateObject(o)}uiSystem.uiObjects=uiSystem.uiObjects.filter(o=>!o.destroyed);function updateObject(o){if(o.destroyed||!o.visible)return;updateTransforms(o);for(let i=o.children.length;i--;){const child=o.children[i];child&&updateObject(child)}if(!o.destroyed)o.update()}}function uiRender(){const context=uiSystem.uiContext;context.save();if(uiSystem.nativeHeight){const s=mainCanvasSize.y/uiSystem.nativeHeight;context.translate(-s*mainCanvasSize.x/2,0);context.scale(s,s);context.translate(mainCanvasSize.x/2/s,0)}function renderObject(o){if(!o.visible)return;updateTransforms(o);o.render();for(const c of o.children)renderObject(c)}uiSystem.uiObjects.forEach(o=>o.parent||renderObject(o));if(uiDebug>0){function renderDebug(o,visible=true){visible&&=!!o.visible;updateTransforms(o);o.renderDebug(visible);for(const c of o.children)renderDebug(c,visible)}uiSystem.uiObjects.forEach(o=>o.parent||renderDebug(o))}context.restore()}}drawRect(pos,size,color=WHITE,lineWidth=0,lineColor=BLACK,cornerRadius=0,gradientColor,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isVector2(size),"size must be a vec2");ASSERT(isColor(color),"color must be a color");ASSERT(isNumber(lineWidth),"lineWidth must be a number");ASSERT(isColor(lineColor),"lineColor must be a color");ASSERT(isNumber(cornerRadius),"cornerRadius must be a number");const context=uiSystem.uiContext;if(gradientColor){const g=context.createLinearGradient(pos.x,pos.y-size.y/2,pos.x,pos.y+size.y/2);const c=color.toString();g.addColorStop(0,c);g.addColorStop(.5,gradientColor.toString());g.addColorStop(1,c);context.fillStyle=g}else context.fillStyle=color.toString();if(shadowBlur||shadowOffset.x||shadowOffset.y)if(shadowColor.a>0){context.shadowColor=shadowColor.toString();context.shadowBlur=shadowBlur;context.shadowOffsetX=shadowOffset.x;context.shadowOffsetY=shadowOffset.y}context.beginPath();if(cornerRadius&&context["roundRect"])context["roundRect"](pos.x-size.x/2,pos.y-size.y/2,size.x,size.y,cornerRadius);else context.rect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y);context.fill();context.shadowColor="#0000";if(lineWidth&&lineColor.a>0){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}}drawLine(posA,posB,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){ASSERT(isVector2(posA),"posA must be a vec2");ASSERT(isVector2(posB),"posB must be a vec2");ASSERT(isNumber(lineWidth),"lineWidth must be a number");ASSERT(isColor(lineColor),"lineColor must be a color");const context=uiSystem.uiContext;context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.beginPath();context.lineTo(posA.x,posA.y);context.lineTo(posB.x,posB.y);context.stroke()}drawTile(pos,size,tileInfo,color=uiSystem.defaultColor,angle=0,mirror=false,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){const context=uiSystem.uiContext;if(shadowBlur||shadowOffset.x||shadowOffset.y)if(shadowColor.a>0){context.shadowColor=shadowColor.toString();context.shadowBlur=shadowBlur;context.shadowOffsetX=shadowOffset.x;context.shadowOffsetY=shadowOffset.y}drawTile(pos,size,tileInfo,color,angle,mirror,CLEAR_BLACK,false,true,context);context.shadowColor="#0000"}drawText(text,pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor,align="center",font=uiSystem.defaultFont,fontStyle="",applyMaxWidth=true,textShadow=undefined,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){const context=uiSystem.uiContext;if(shadowColor.a>0){if(textShadow)drawTextScreen(text,pos.add(textShadow),size.y,shadowColor,lineWidth,lineColor,align,font,fontStyle,applyMaxWidth?size.x:undefined,0,context);if(shadowBlur||shadowOffset.x||shadowOffset.y){context.shadowColor=shadowColor.toString();context.shadowBlur=shadowBlur;context.shadowOffsetX=shadowOffset.x;context.shadowOffsetY=shadowOffset.y}}drawTextScreen(text,pos,size.y,color,lineWidth,lineColor,align,font,fontStyle,applyMaxWidth?size.x:undefined,0,context);context.shadowColor="#0000"}setupDragAndDrop(onDrop,onDragEnter,onDragLeave,onDragOver){if(this._dragListeners)for(const[type,listener]of this._dragListeners)document.removeEventListener(type,listener);this._dragListeners=[];const setCallback=(callback,listenerType)=>{const listener=e=>{e.preventDefault();callback&&callback(e)};document.addEventListener(listenerType,listener);this._dragListeners.push([listenerType,listener])};setCallback(onDrop,"drop");setCallback(onDragEnter,"dragenter");setCallback(onDragLeave,"dragleave");setCallback(onDragOver,"dragover")}screenToNative(pos){if(!uiSystem.nativeHeight)return pos;const s=mainCanvasSize.y/uiSystem.nativeHeight;const sInv=1/s;const p=pos.copy();p.x+=s*mainCanvasSize.x/2;p.x*=sInv;p.y*=sInv;p.x-=sInv*mainCanvasSize.x/2;return p}get keyInputObject(){return this._keyInputObject}set keyInputObject(obj){const had=!!this._keyInputObject;this._keyInputObject=obj;if(!had&&obj)document.addEventListener("keydown",this._onKeyDown);else if(had&&!obj)document.removeEventListener("keydown",this._onKeyDown)}destroyObjects(){for(const o of this.uiObjects)o.parent||o.destroy();this.uiObjects=this.uiObjects.filter(o=>!o.destroyed);this.activeObject=undefined;this.hoverObject=undefined;this.lastHoverObject=undefined;this.keyInputObject=undefined}getNavigableObjects(){function getNavigableRecursive(o){if(!o.visible||o.disabled)return;if(o.isInteractive()&&o.navigationIndex!==undefined)objects.push(o);for(let i=o.children.length;i--;)getNavigableRecursive(o.children[i])}let objects=[];for(let i=uiSystem.uiObjects.length;i--;){const o=uiSystem.uiObjects[i];if(uiSystem.confirmDialog&&o!==uiSystem.confirmDialog)continue;o.parent||getNavigableRecursive(o)}objects.sort((a,b)=>a.navigationIndex-b.navigationIndex);return objects}getNavigationDirection(){const vertical=uiSystem.navigationDirection===1;const both=uiSystem.navigationDirection===2;if(isUsingGamepad){const stick=gamepadStick(0,gamepadPrimary);const dpad=gamepadDpad(gamepadPrimary);if(both)return-(stick.y||dpad.y)||(stick.x||dpad.x);return vertical?-(stick.y||dpad.y):stick.x||dpad.x}const up="ArrowUp",down="ArrowDown",left="ArrowLeft",right="ArrowRight";if(both){return keyIsDown(up)||keyIsDown(left)?-1:keyIsDown(down)||keyIsDown(right)?1:0}const back=vertical?up:left;const forward=vertical?down:right;return keyIsDown(back)?-1:keyIsDown(forward)?1:0}getNavigationOtherDirection(){if(uiSystem.navigationDirection===2)return 0;const vertical=uiSystem.navigationDirection===1;if(isUsingGamepad){const stick=gamepadStick(0,gamepadPrimary);const dpad=gamepadDpad(gamepadPrimary);return!vertical?stick.y||dpad.y:stick.x||dpad.x}const back=!vertical?"ArrowUp":"ArrowLeft";const forward=!vertical?"ArrowDown":"ArrowRight";return keyIsDown(back)?-1:keyIsDown(forward)?1:0}getNavigationWasPressed(){return isUsingGamepad?gamepadWasPressed(0,gamepadPrimary):keyWasPressed("Space")||keyWasPressed("Enter")}showConfirmDialog(text="Are you sure?",yesCallback,noCallback,size=vec2(500,250),exitKey="Escape"){ASSERT(!uiSystem.confirmDialog);const savedNavigationDirection=uiSystem.navigationDirection;uiSystem.navigationDirection=2;const confirmMenu=new UIObject(vec2(),size);uiSystem.confirmDialog=confirmMenu;confirmMenu.onRender=()=>{const backgroundColor=hsl(0,0,0,.7);uiSystem.drawRect(vec2(),vec2(1e9),backgroundColor)};confirmMenu.onUpdate=()=>{if(keyWasPressed(exitKey))closeMenu()};confirmMenu.isMouseOverlapping=()=>true;const gap=50;const textTitle=new UIText(vec2(0,-50),vec2(size.x-gap,70),text);confirmMenu.addChild(textTitle);const buttonYes=new UIButton(vec2(-80,50),vec2(120,70),"Yes");buttonYes.textHeight=40;buttonYes.navigationIndex=1;buttonYes.hoverColor=hsl(0,1,.5);buttonYes.onClick=()=>{closeMenu();yesCallback&&yesCallback()};confirmMenu.addChild(buttonYes);const buttonNo=new UIButton(vec2(80,50),vec2(120,70),"No");buttonNo.textHeight=40;buttonNo.navigationIndex=2;buttonNo.navigationAutoSelect=true;buttonNo.onClick=()=>{closeMenu();noCallback&&noCallback()};confirmMenu.addChild(buttonNo);function closeMenu(){ASSERT(uiSystem.confirmDialog===confirmMenu);confirmMenu.destroy();uiSystem.confirmDialog=undefined;uiSystem.navigationDirection=savedNavigationDirection;inputClear()}return confirmMenu}}class UIObject{constructor(pos=vec2(),size=vec2()){ASSERT(isVector2(pos),"ui object pos must be a vec2");ASSERT(isVector2(size),"ui object size must be a vec2");this.localPos=pos.copy();this.nativePos=pos.copy();this.size=size.copy();this.color=uiSystem.defaultColor.copy();this.activeColor=undefined;this.text=undefined;this.disabledColor=uiSystem.defaultDisabledColor.copy();this.disabled=false;this.textColor=uiSystem.defaultTextColor.copy();this.hoverColor=uiSystem.defaultHoverColor.copy();this.lineColor=uiSystem.defaultLineColor.copy();this.gradientColor=uiSystem.defaultGradientColor?uiSystem.defaultGradientColor.copy():undefined;this.lineWidth=uiSystem.defaultLineWidth;this.cornerRadius=uiSystem.defaultCornerRadius;this.font=uiSystem.defaultFont;this.fontStyle=undefined;this.textWidth=undefined;this.textHeight=undefined;this.textFitScale=uiSystem.defaultTextFitScale;this.textShadow=undefined;this.textLineColor=uiSystem.defaultLineColor.copy();this.textLineWidth=0;this.visible=true;this.children=[];this.parent=undefined;this.extraTouchSize=0;this.soundPress=uiSystem.defaultSoundPress;this.soundRelease=uiSystem.defaultSoundRelease;this.soundClick=uiSystem.defaultSoundClick;this.interactive=false;this.dragActivate=false;this.canBeHover=true;this.shadowColor=uiSystem.defaultShadowColor?.copy();this.shadowBlur=uiSystem.defaultShadowBlur;this.shadowOffset=uiSystem.defaultShadowOffset?.copy();this.navigationIndex=undefined;this.navigationAutoSelect=false;this.anchor=vec2();uiSystem.uiObjects.push(this)}addChild(child){ASSERT(!child.parent&&!this.children.includes(child));this.children.push(child);child.parent=this;return child}removeChild(child){ASSERT(child.parent===this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=undefined}destroy(){if(this.destroyed)return;if(uiSystem.activeObject===this)uiSystem.activeObject=undefined;if(uiSystem.hoverObject===this)uiSystem.hoverObject=undefined;if(uiSystem.lastHoverObject===this)uiSystem.lastHoverObject=undefined;if(uiSystem.navigationObject===this)uiSystem.navigationObject=undefined;if(uiSystem.keyInputObject===this)uiSystem.keyInputObject=undefined;this.destroyed=1;this.parent?.removeChild(this);for(const child of this.children){child.parent=undefined;child.destroy()}this.children.length=0}isMouseOverlapping(){if(!mouseInWindow)return false;const size=!isTouchDevice?this.size:this.size.add(vec2(this.extraTouchSize||0));const pos=uiSystem.screenToNative(mousePosScreen);return isOverlapping(this.nativePos,size,pos)}update(){this.onUpdate();if(this.disabled){if(this===uiSystem.activeObject)uiSystem.activeObject=undefined;if(this===uiSystem.keyInputObject)uiSystem.keyInputObject=undefined}if(uiSystem.keyInputObject)return;const wasHover=uiSystem.lastHoverObject===this;const isActive=this.isActiveObject();const mouseDown=mouseIsDown(0);const mousePress=this.dragActivate?mouseDown:mouseWasPressed(0);if(this.canBeHover)if(!uiSystem.navigationMode)if(mousePress||isActive||!mouseDown&&!isTouchDevice)if(!uiSystem.hoverObject&&this.isMouseOverlapping())uiSystem.hoverObject=this;if(this.isHoverObject()){if(!this.disabled){if(mousePress){if(this.interactive){if(!this.dragActivate||(!wasHover||mouseWasPressed(0)))this.onPress();this.soundPress&&this.soundPress.play();if(uiSystem.activeObject&&!isActive)uiSystem.activeObject.onRelease();uiSystem.activeObject=this;if(uiSystem.activateOnPress)this.click(!this.soundPress)}}if(!uiSystem.activateOnPress)if(!mouseDown&&this.isActiveObject()&&this.interactive)this.click()}mousePress&&inputClearKey(0,0,0,1,0)}if(isActive)if(!mouseDown||this.dragActivate&&!this.isHoverObject()){this.onRelease();this.soundRelease&&this.soundRelease.play();uiSystem.activeObject=undefined}if(this.isHoverObject()!==wasHover)this.isHoverObject()?this.onEnter():this.onLeave()}render(){this.onRender();if(!this.size.x||!this.size.y)return;const isNavigationObject=this.isNavigationObject();const lineColor=isNavigationObject?this.color:this.interactive&&this.isActiveObject()&&!this.disabled?this.color:this.lineColor;const color=isNavigationObject?this.hoverColor:this.disabled?this.disabledColor:this.interactive?this.isActiveObject()?this.activeColor||this.hoverColor:this.isHoverObject()?this.hoverColor:this.color:this.color;const lineWidth=this.lineWidth*(isNavigationObject?1.5:1);uiSystem.drawRect(this.nativePos,this.size,color,lineWidth,lineColor,this.cornerRadius,this.gradientColor,this.shadowColor,this.shadowBlur,this.shadowOffset)}getTextSize(){return vec2(this.textWidth||this.textFitScale*this.size.x,this.textHeight||this.textFitScale*this.size.y)}navigatePressed(){this.click()}isHoverObject(){return uiSystem.hoverObject===this}isActiveObject(){return uiSystem.activeObject===this}isNavigationObject(){return uiSystem.navigationObject===this}isKeyInputObject(){return uiSystem.keyInputObject===this}isInteractive(){return this.interactive&&this.visible&&!this.disabled}toString(){let text="type = "+this.constructor.name;if(this.text)text+="\ntext = "+this.text;if(this.nativePos.x||this.nativePos.y)text+="\nnativePos = "+this.nativePos;if(this.localPos.x||this.localPos.y)text+="\nlocalPos = "+this.localPos;if(this.size.x||this.size.y)text+="\nsize = "+this.size;if(this.color)text+="\ncolor = "+this.color;return text}renderDebug(visible=true){const color=!visible?GREEN:this.isHoverObject()?YELLOW:this.disabled?PURPLE:this.interactive?RED:BLUE;uiSystem.drawRect(this.nativePos,this.size,CLEAR_BLACK,4,color)}click(playSound=true){this.onClick();if(playSound&&this.soundClick)this.soundClick.play()}onUpdate(){}onRender(){}onEnter(){}onLeave(){}onPress(){}onRelease(){}onClick(){}onChange(){}}class UIText extends UIObject{constructor(pos,size,text="",align="center",font=uiSystem.defaultFont){super(pos,size);ASSERT(isStringLike(text),"ui text must be a string");ASSERT(["left","center","right"].includes(align),"ui text align must be left, center, or right");ASSERT(isStringLike(font),"ui text font must be a string");this.text=text;this.align=align;this.font=font;this.canBeHover=false;this.color=CLEAR_BLACK;this.shadowColor=CLEAR_BLACK;this.gradientColor=undefined;this.lineWidth=0;this.textFitScale=1}render(){super.render();const textSize=this.getTextSize();uiSystem.drawText(this.text,this.nativePos,textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,true,this.textShadow,this.shadowColor,this.shadowBlur,this.shadowOffset)}}class UITextInput extends UIObject{constructor(pos,size,text=""){super(pos,size);ASSERT(isStringLike(text),"ui text must be a string");this.maxLength=0;this.text=text;this.interactive=true;this.canBeHover=true}click(){uiSystem.keyInputObject=this;this.onClick()}stopEditing(){if(!this.isKeyInputObject())return;if(this.soundRelease)this.soundRelease.play();uiSystem.activeObject=undefined;uiSystem.keyInputObject=undefined;this.onChange()}onKeyDown(e){const code=e.code,key=e.key;if(code==="Backspace")this.text=this.text.slice(0,-1);else if(code==="Enter"||code==="Escape")this.stopEditing();else if(key.length===1){if(!this.maxLength||this.text.length<this.maxLength)this.text+=key}}update(){super.update();if(!this.isKeyInputObject())return;if(mouseWasPressed(0)&&!this.isMouseOverlapping()||gamepadWasPressed(0,gamepadPrimary)){this.stopEditing();inputClearKey(0,0)}}render(){super.render();const textSize=this.getTextSize();let text=this.text;if(this.isKeyInputObject())text+=timeReal%1<.5?"█":"░";uiSystem.drawText(text,this.nativePos,textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,true,this.textShadow)}}class UITile extends UIObject{constructor(pos,size,tileInfo,color=WHITE,angle=0,mirror=false){super(pos,size);ASSERT(tileInfo instanceof TileInfo,"ui tile tileInfo must be a TileInfo");ASSERT(isColor(color),"ui tile color must be a color");ASSERT(isNumber(angle),"ui tile angle must be a number");this.tileInfo=tileInfo;this.angle=angle;this.mirror=mirror;this.color=color.copy();this.shadowColor=CLEAR_BLACK}render(){uiSystem.drawTile(this.nativePos,this.size,this.tileInfo,this.color,this.angle,this.mirror,this.shadowColor,this.shadowBlur,this.shadowOffset)}}class UIButton extends UIObject{constructor(pos,size,text="",color=uiSystem.defaultButtonColor){super(pos,size);ASSERT(isStringLike(text),"ui button must be a string");ASSERT(isColor(color),"ui button color must be a color");this.textOffset=vec2();this.text=text;this.color=color.copy();this.interactive=true}render(){super.render();const textSize=this.getTextSize();uiSystem.drawText(this.text,this.nativePos.add(this.textOffset),textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,true,this.textShadow)}}class UICheckbox extends UIObject{constructor(pos,size,checked=false,text="",color=uiSystem.defaultButtonColor){super(pos,size);ASSERT(isStringLike(text),"ui checkbox must be a string");ASSERT(isColor(color),"ui checkbox color must be a color");this.checked=checked;this.text=text;this.color=color.copy();this.interactive=true}click(){this.checked=!this.checked;this.onClick();this.onChange()}render(){super.render();if(this.checked){const p=this.cornerRadius/min(this.size.x,this.size.y)*2;const length=lerp(1,2**.5/2,p)/2;let s=this.size.scale(length);uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1))),this.nativePos.add(s.multiply(vec2(1))),this.lineWidth,this.lineColor);uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1,1))),this.nativePos.add(s.multiply(vec2(1,-1))),this.lineWidth,this.lineColor)}const textSize=this.getTextSize();const pos=this.nativePos.add(vec2(this.size.x,0));uiSystem.drawText(this.text,pos,textSize,this.textColor,this.textLineWidth,this.textLineColor,"left",this.font,this.fontStyle,false,this.textShadow)}}class UISlider extends UIObject{constructor(pos,size,value=.5,text="",color=uiSystem.defaultButtonColor,handleColor=WHITE){super(pos,size);ASSERT(isNumber(value),"ui slider value must be a number");ASSERT(isStringLike(text),"ui slider must be a string");ASSERT(isColor(color),"ui slider color must be a color");ASSERT(isColor(handleColor),"ui slider handleColor must be a color");this.value=value;this.handleColor=handleColor.copy();this.fillMode=false;this.text=text;this.color=color.copy();this.interactive=true}update(){super.update();if(!this.interactive)return;const oldValue=this.value;if(this.isActiveObject()){const isHorizontal=this.size.x>this.size.y;const handleSize=isHorizontal?this.size.y:this.size.x;const barSize=isHorizontal?this.size.x:this.size.y;const centerPos=isHorizontal?this.nativePos.x:this.nativePos.y;const handleWidth=barSize-handleSize;const p1=centerPos-handleWidth/2;const p2=centerPos+handleWidth/2;const p=uiSystem.screenToNative(mousePosScreen);this.value=isHorizontal?percent(p.x,p1,p2):percent(p.y,p2,p1)}else if(this.isNavigationObject()){const direction=uiSystem.getNavigationOtherDirection();if(!uiSystem.navigationTimer.active())this.value=clamp(this.value+direction*.01)}this.value===oldValue||this.onChange()}render(){super.render();const isHorizontal=this.size.x>this.size.y;const barWidth=isHorizontal?this.size.x:this.size.y;const handleWidth=isHorizontal?this.size.y:this.size.x;if(this.fillMode){const minWidth=min(handleWidth,this.cornerRadius*2);const progressWidth=lerp(minWidth,barWidth,this.value);const p=(progressWidth-barWidth)*(isHorizontal?.5:-.5);const pos=this.nativePos.add(isHorizontal?vec2(p,0):vec2(0,p));const color=this.disabled?this.disabledColor:this.handleColor;const drawSize=isHorizontal?vec2(progressWidth,this.size.y):vec2(this.size.x,progressWidth);uiSystem.drawRect(pos,drawSize,color,this.lineWidth,this.lineColor,this.cornerRadius,this.gradientColor)}else{const value=clamp(isHorizontal?this.value:1-this.value);const p=(barWidth-handleWidth)*(value-.5);const pos=this.nativePos.add(isHorizontal?vec2(p,0):vec2(0,p));const color=this.disabled?this.disabledColor:this.handleColor;const drawSize=vec2(handleWidth);uiSystem.drawRect(pos,drawSize,color,this.lineWidth,this.lineColor,this.cornerRadius,this.gradientColor)}const textSize=this.getTextSize();uiSystem.drawText(this.text,this.nativePos,textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,true,this.textShadow)}navigatePressed(){this.value=this.value?0:1;this.onChange();this.onRelease();super.navigatePressed()}}class UIVideo extends UIObject{constructor(pos,size,src,autoplay=false,loop=false,volume=1){super(pos,size||vec2());ASSERT(isStringLike(src),"video src must be a string");ASSERT(isNumber(volume),"video volume must be a number");this.color=BLACK;this.cornerRadius=0;this.volume=volume;this.video=document.createElement("video");this.video.loop=loop;this.video.volume=clamp(volume*soundVolume);this.video.muted=!soundEnable;this.video.style.display="none";this.video.src=src;document.body.appendChild(this.video);autoplay&&this.play()}async play(){try{await this.video.play()}catch(e){}}pause(){this.video.pause()}stop(){this.video.pause();this.video.currentTime=0}isLoading(){return this.video.readyState<this.video.HAVE_CURRENT_DATA}isPaused(){return this.video.paused}isPlaying(){return!this.isPaused()&&!this.hasEnded()&&!this.isLoading()}hasEnded(){return this.video.ended}setVolume(volume){this.volume=volume;this.video.volume=clamp(volume*soundVolume)}setPlaybackRate(rate){this.video.playbackRate=rate}getCurrentTime(){return this.video.currentTime||0}getDuration(){return this.video.duration||0}getVideoSize(){return vec2(this.video.videoWidth,this.video.videoHeight)}setTime(time){this.video.currentTime=clamp(time,0,this.getDuration())}update(){super.update();this.video.volume=clamp(this.volume*soundVolume)}render(){super.render();if(this.isLoading())return;const context=uiSystem.uiContext;const s=this.size;context.save();context.translate(this.nativePos.x,this.nativePos.y);context.drawImage(this.video,-s.x/2,-s.y/2,s.x,s.y);context.restore()}destroy(){if(this.destroyed)return;this.video.pause();this.video.remove();super.destroy()}}class UILayout extends UIObject{constructor(pos,columns=1,gap=10,padding=10,transparent=false){super(pos);ASSERT(isNumber(columns)&&columns>=1,"ui layout columns must be a number >= 1");ASSERT(isNumber(gap),"ui layout gap must be a number");ASSERT(isNumber(padding),"ui layout padding must be a number");this.columns=columns;this.gap=gap;this.padding=padding;if(transparent){this.color=CLEAR_BLACK;this.gradientColor=undefined;this.lineWidth=0;this.shadowColor=CLEAR_BLACK}this.relayout()}addChild(child){super.addChild(child);this.relayout();return child}removeChild(child){super.removeChild(child);this.relayout()}relayout(){const n=this.children.length;if(!n){this.size=vec2(this.padding*2);return}const cols=this.columns;const rows=ceil(n/cols);const colWidths=new Array(cols).fill(0);const rowHeights=new Array(rows).fill(0);for(let i=0;i<n;++i){const col=i%cols;const row=floor(i/cols);const child=this.children[i];colWidths[col]=max(colWidths[col],child.size.x);rowHeights[row]=max(rowHeights[row],child.size.y)}let contentWidth=this.gap*(cols-1);for(const w of colWidths)contentWidth+=w;let contentHeight=this.gap*(rows-1);for(const h of rowHeights)contentHeight+=h;const colOffsets=new Array(cols);let xAcc=0;for(let c=0;c<cols;++c){colOffsets[c]=xAcc;xAcc+=colWidths[c]}const rowOffsets=new Array(rows);let yAcc=0;for(let r=0;r<rows;++r){rowOffsets[r]=yAcc;yAcc+=rowHeights[r]}for(let i=0;i<n;++i){const col=i%cols;const row=floor(i/cols);const x=-contentWidth/2+colOffsets[col]+this.gap*col+colWidths[col]/2;const y=-contentHeight/2+rowOffsets[row]+this.gap*row+rowHeights[row]/2;this.children[i].localPos=vec2(x,y)}this.size=vec2(contentWidth+this.padding*2,contentHeight+this.padding*2)}}let box2d;let box2dDebug=false;function box2dSetDebug(enable){box2dDebug=enable}class Box2dObject extends EngineObject{constructor(pos=vec2(),size=vec2(),tileInfo,angle=0,color,bodyType=box2d.bodyTypeDynamic,renderOrder=0){super(pos,size,tileInfo,angle,color,renderOrder);const bodyDef=new box2d.instance.b2BodyDef;bodyDef.set_type(bodyType);bodyDef.set_position(box2d.vec2dTo(pos));bodyDef.set_angle(-angle);this.body=box2d.world.CreateBody(bodyDef);this.lineColor=BLACK;this.edgeLists=[];this.edgeLoops=[];this.body.object=this;box2d.objects.push(this)}destroy(){if(this.destroyed)return;ASSERT(this.body,"Box2dObject has no body to destroy");box2d.world.DestroyBody(this.body);const i=box2d.objects.indexOf(this);if(i>=0)box2d.objects.splice(i,1);super.destroy()}updatePhysics(){}render(){if(this.tileInfo)super.render();else this.drawFixtures(this.color,this.lineColor,this.lineWidth)}renderDebugInfo(){const isAsleep=!this.getIsAwake();const isStatic=this.getBodyType()===box2d.bodyTypeStatic;const color=rgb(isAsleep?1:0,isAsleep?1:0,isStatic?1:0,.5);this.drawFixtures(color)}drawFixtures(color=WHITE,lineColor=BLACK,lineWidth=.1,useWebGL,context){this.getFixtureList().forEach(fixture=>{const shape=box2d.castShapeObject(fixture.GetShape());if(shape.GetType()!==box2d.instance.b2Shape.e_edge){box2d.drawFixture(fixture,this.pos,this.angle,color,lineColor,lineWidth,useWebGL,context)}});this.edgeLists.forEach(points=>drawLineList(points,lineWidth,lineColor,false,this.pos,this.angle));this.edgeLoops.forEach(points=>drawLineList(points,lineWidth,lineColor,true,this.pos,this.angle))}beginContact(otherObject){}endContact(otherObject){}addShape(shape,density=1,friction=.2,restitution=0,isSensor=false){ASSERT(isNumber(density),"density must be a number");ASSERT(isNumber(friction),"friction must be a number");ASSERT(isNumber(restitution),"restitution must be a number");const fd=new box2d.instance.b2FixtureDef;fd.set_shape(shape);fd.set_density(density);fd.set_friction(friction);fd.set_restitution(restitution);fd.set_isSensor(isSensor);return this.body.CreateFixture(fd)}addBox(size=vec2(1),offset=vec2(),angle=0,density,friction,restitution,isSensor){ASSERT(isVector2(size),"size must be a Vector2");ASSERT(size.x>0&&size.y>0,"size must be positive");ASSERT(isVector2(offset),"offset must be a Vector2");ASSERT(isNumber(angle),"angle must be a number");const shape=new box2d.instance.b2PolygonShape;shape.SetAsBox(size.x/2,size.y/2,box2d.vec2dTo(offset),-angle);return this.addShape(shape,density,friction,restitution,isSensor)}addPoly(points,density,friction,restitution,isSensor){ASSERT(isArray(points),"points must be an array");function box2dCreatePolygonShape(points){ASSERT(3<=points.length&&points.length<=8);const buffer=box2d.instance._malloc(points.length*8);for(let i=0,offset=0;i<points.length;++i){box2d.instance.HEAPF32[buffer+offset>>2]=points[i].x;offset+=4;box2d.instance.HEAPF32[buffer+offset>>2]=points[i].y;offset+=4}const box2dPoints=box2d.instance.wrapPointer(buffer,box2d.instance.b2Vec2);const shape=new box2d.instance.b2PolygonShape;shape.Set(box2dPoints,points.length);box2d.instance._free(buffer);return shape}const shape=box2dCreatePolygonShape(points);return this.addShape(shape,density,friction,restitution,isSensor)}addRegularPoly(diameter=1,sides=8,density,friction,restitution,isSensor){ASSERT(isNumber(diameter)&&diameter>0,"diameter must be a positive number");ASSERT(isNumber(sides)&&sides>2,"sides must be a positive number greater than 2");const points=[];const radius=diameter/2;for(let i=sides;i--;)points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));return this.addPoly(points,density,friction,restitution,isSensor)}addRandomPoly(diameter=1,density,friction,restitution,isSensor){ASSERT(isNumber(diameter)&&diameter>0,"diameter must be a positive number");const sides=randInt(3,9);const points=[];const radius=diameter/2;for(let i=sides;i--;)points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));return this.addPoly(points,density,friction,restitution,isSensor)}addCircle(diameter=1,offset=vec2(),density,friction,restitution,isSensor){ASSERT(isNumber(diameter)&&diameter>0,"diameter must be a positive number");ASSERT(isVector2(offset),"offset must be a Vector2");const shape=new box2d.instance.b2CircleShape;shape.set_m_p(box2d.vec2dTo(offset));shape.set_m_radius(diameter/2);return this.addShape(shape,density,friction,restitution,isSensor)}addEdge(point1,point2,density,friction,restitution,isSensor){ASSERT(isVector2(point1),"point1 must be a Vector2");ASSERT(isVector2(point2),"point2 must be a Vector2");const shape=new box2d.instance.b2EdgeShape;shape.Set(box2d.vec2dTo(point1),box2d.vec2dTo(point2));return this.addShape(shape,density,friction,restitution,isSensor)}addEdgeList(points,density,friction,restitution,isSensor){ASSERT(isArray(points),"points must be an array");const fixtures=[],edgePoints=[];for(let i=0;i<points.length-1;++i){const shape=new box2d.instance.b2EdgeShape;points[i-1]&&shape.set_m_vertex0(box2d.vec2dTo(points[i-1]));points[i+0]&&shape.set_m_vertex1(box2d.vec2dTo(points[i+0]));points[i+1]&&shape.set_m_vertex2(box2d.vec2dTo(points[i+1]));points[i+2]&&shape.set_m_vertex3(box2d.vec2dTo(points[i+2]));const f=this.addShape(shape,density,friction,restitution,isSensor);fixtures.push(f);edgePoints.push(points[i].copy())}edgePoints.push(points[points.length-1].copy());this.edgeLists.push(edgePoints);return fixtures}addEdgeLoop(points,density,friction,restitution,isSensor){ASSERT(isArray(points),"points must be an array");const fixtures=[],edgePoints=[];const getPoint=i=>points[mod(i,points.length)];for(let i=0;i<points.length;++i){const shape=new box2d.instance.b2EdgeShape;shape.set_m_vertex0(box2d.vec2dTo(getPoint(i-1)));shape.set_m_vertex1(box2d.vec2dTo(getPoint(i+0)));shape.set_m_vertex2(box2d.vec2dTo(getPoint(i+1)));shape.set_m_vertex3(box2d.vec2dTo(getPoint(i+2)));const f=this.addShape(shape,density,friction,restitution,isSensor);fixtures.push(f);edgePoints.push(points[i].copy())}this.edgeLoops.push(edgePoints);return fixtures}destroyFixture(fixture){this.body.DestroyFixture(fixture)}destroyAllFixtures(){this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture))}getCenterOfMass(){return box2d.vec2From(this.body.GetWorldCenter())}getLinearVelocity(){return box2d.vec2From(this.body.GetLinearVelocity())}getAngularVelocity(){return this.body.GetAngularVelocity()}getMass(){return this.body.GetMass()}getInertia(){return this.body.GetInertia()}getIsAwake(){return this.body.IsAwake()}getBodyType(){return this.body.GetType()}getSpeed(){return this.getLinearVelocity().length()}setTransform(pos,angle){this.pos=pos;this.angle=angle;this.body.SetTransform(box2d.vec2dTo(pos),-angle)}setPosition(pos){this.setTransform(pos,-this.body.GetAngle())}setAngle(angle){this.setTransform(box2d.vec2From(this.body.GetPosition()),angle)}setLinearVelocity(velocity){this.body.SetLinearVelocity(box2d.vec2dTo(velocity))}setAngularVelocity(angularVelocity){this.body.SetAngularVelocity(angularVelocity)}setLinearDamping(damping){this.body.SetLinearDamping(damping)}setAngularDamping(damping){this.body.SetAngularDamping(damping)}setGravityScale(scale=1){this.body.SetGravityScale(this.gravityScale=scale)}setBullet(isBullet=true){this.body.SetBullet(isBullet)}setAwake(isAwake=true){this.body.SetAwake(isAwake)}setBodyType(type){this.body.SetType(type)}setSleepingAllowed(isAllowed=true){this.body.SetSleepingAllowed(isAllowed)}setFixedRotation(isFixed=true){this.body.SetFixedRotation(isFixed)}setCenterOfMass(center){this.setMassData(center)}setMass(mass){this.setMassData(undefined,mass)}setMomentOfInertia(momentOfInertia){this.setMassData(undefined,undefined,momentOfInertia)}resetMassData(){this.body.ResetMassData()}setMassData(localCenter,mass,momentOfInertia){const data=new box2d.instance.b2MassData;this.body.GetMassData(data);if(localCenter!==undefined)data.set_center(box2d.vec2dTo(localCenter));if(mass!==undefined)data.set_mass(mass);if(momentOfInertia!==undefined)data.set_I(momentOfInertia);this.body.SetMassData(data)}setFilterData(categoryBits=0,ignoreCategoryBits=0,groupIndex=0){this.getFixtureList().forEach(fixture=>{const filter=fixture.GetFilterData();filter.set_categoryBits(categoryBits);filter.set_maskBits(65535&~ignoreCategoryBits);filter.set_groupIndex(groupIndex);fixture.SetFilterData(filter)})}setSensor(isSensor=true){this.getFixtureList().forEach(f=>f.SetSensor(isSensor))}applyForce(force,pos){pos||=this.getCenterOfMass();this.setAwake();this.body.ApplyForce(box2d.vec2dTo(force),box2d.vec2dTo(pos))}applyAcceleration(acceleration,pos){pos||=this.getCenterOfMass();this.setAwake();const impulse=acceleration.scale(this.getMass());this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse),box2d.vec2dTo(pos))}applyImpulse(impulse,pos){pos||=this.getCenterOfMass();this.setAwake();this.body.ApplyLinearImpulse(box2d.vec2dTo(impulse),box2d.vec2dTo(pos))}applyTorque(torque){this.setAwake();this.body.ApplyTorque(torque)}applyAngularAcceleration(acceleration){this.setAwake();this.body.ApplyAngularImpulse(acceleration*this.getInertia())}applyAngularImpulse(impulse){this.setAwake();this.body.ApplyAngularImpulse(impulse)}hasFixtures(){return!box2d.isNull(this.body.GetFixtureList())}getFixtureList(){const fixtures=[];for(let fixture=this.body.GetFixtureList();!box2d.isNull(fixture);){fixtures.push(fixture);fixture=fixture.GetNext()}return fixtures}hasJoints(){return!box2d.isNull(this.body.GetJointList())}getJointList(){const joints=[];for(let joint=this.body.GetJointList();!box2d.isNull(joint);){joints.push(joint);joint=joint.get_next()}return joints}}class Box2dStaticObject extends Box2dObject{constructor(pos,size,tileInfo,angle=0,color,renderOrder=0){const bodyType=box2d.bodyTypeStatic;super(pos,size,tileInfo,angle,color,bodyType,renderOrder)}}class Box2dKinematicObject extends Box2dObject{constructor(pos,size,tileInfo,angle=0,color,renderOrder=0){const bodyType=box2d.bodyTypeKinematic;super(pos,size,tileInfo,angle,color,bodyType,renderOrder)}}class Box2dTileLayer extends Box2dStaticObject{constructor(tileLayer){ASSERT(tileLayer instanceof TileCollisionLayer,"tileLayer must be a TileCollisionLayer");super(tileLayer.pos,tileLayer.size);this.tileLayer=tileLayer;this.addChild(tileLayer)}render(){}buildCollision(friction=.2,restitution=0){this.destroyAllFixtures();this.pos=this.tileLayer.pos.copy();this.size=this.tileLayer.size.copy();const processed=[];const getIndex=(x,y)=>x+y*this.size.x;const isSolidUnprocessed=(x,y)=>!processed[getIndex(x,y)]&&this.tileLayer.getCollisionData(vec2(x,y))>0;for(let x=0;x<this.size.x;++x)for(let y=0;y<this.size.y;++y){if(!isSolidUnprocessed(x,y))continue;let width=1,height=1,canExpand=true;while(isSolidUnprocessed(x+width,y))++width;while(canExpand){for(let checkX=0;checkX<width;++checkX){if(!isSolidUnprocessed(x+checkX,y+height)){canExpand=false;break}}if(canExpand)++height}for(let rectX=width;rectX--;)for(let rectY=height;rectY--;)processed[getIndex(x+rectX,y+rectY)]=true;const shapeSize=vec2(width,height);const offset=vec2(x+width/2,y+height/2);this.addBox(shapeSize,offset,0,0,friction,restitution)}}}class Box2dRaycastResult{constructor(fixture,point,normal,fraction){this.object=fixture.GetBody().object;this.fixture=fixture;this.point=point;this.normal=normal;this.fraction=fraction}}class Box2dJoint{constructor(jointDef){this.box2dJoint=box2d.castJointObject(box2d.world.CreateJoint(jointDef))}destroy(){box2d.world.DestroyJoint(this.box2dJoint);this.box2dJoint=0}getObjectA(){return this.box2dJoint.GetBodyA().object}getObjectB(){return this.box2dJoint.GetBodyB().object}getAnchorA(){return box2d.vec2From(this.box2dJoint.GetAnchorA())}getAnchorB(){return box2d.vec2From(this.box2dJoint.GetAnchorB())}getReactionForce(time){return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time))}getReactionTorque(time){return this.box2dJoint.GetReactionTorque(1/time)}getCollideConnected(){return this.box2dJoint.GetCollideConnected()}isActive(){return this.box2dJoint.IsActive()}}class Box2dTargetJoint extends Box2dJoint{constructor(object,fixedObject,worldPos){object.setAwake();const jointDef=new box2d.instance.b2MouseJointDef;jointDef.set_bodyA(fixedObject.body);jointDef.set_bodyB(object.body);jointDef.set_target(box2d.vec2dTo(worldPos));jointDef.set_maxForce(2e3*object.getMass());super(jointDef)}setTarget(pos){this.box2dJoint.SetTarget(box2d.vec2dTo(pos))}getTarget(){return box2d.vec2From(this.box2dJoint.GetTarget())}setMaxForce(force){this.box2dJoint.SetMaxForce(force)}getMaxForce(){return this.box2dJoint.GetMaxForce()}setFrequency(hz){this.box2dJoint.SetFrequency(hz)}getFrequency(){return this.box2dJoint.GetFrequency()}}class Box2dDistanceJoint extends Box2dJoint{constructor(objectA,objectB,anchorA,anchorB,collide=false){anchorA||=box2d.vec2From(objectA.body.GetPosition());anchorB||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchorA);const localAnchorB=objectB.worldToLocal(anchorB);const jointDef=new box2d.instance.b2DistanceJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_length(anchorA.distance(anchorB));jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setLength(length){this.box2dJoint.SetLength(length)}getLength(){return this.box2dJoint.GetLength()}setFrequency(hz){this.box2dJoint.SetFrequency(hz)}getFrequency(){return this.box2dJoint.GetFrequency()}setDampingRatio(ratio){this.box2dJoint.SetDampingRatio(ratio)}getDampingRatio(){return this.box2dJoint.GetDampingRatio()}}class Box2dPinJoint extends Box2dDistanceJoint{constructor(objectA,objectB,pos=objectA.pos,collide=false){super(objectA,objectB,undefined,pos,collide)}}class Box2dRopeJoint extends Box2dJoint{constructor(objectA,objectB,anchorA,anchorB,extraLength=0,collide=false){anchorA||=box2d.vec2From(objectA.body.GetPosition());anchorB||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchorA);const localAnchorB=objectB.worldToLocal(anchorB);const jointDef=new box2d.instance.b2RopeJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setMaxLength(length){this.box2dJoint.SetMaxLength(length)}getMaxLength(){return this.box2dJoint.GetMaxLength()}}class Box2dRevoluteJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=false){anchor||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchor);const localAnchorB=objectB.worldToLocal(anchor);const jointDef=new box2d.instance.b2RevoluteJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_referenceAngle(objectB.body.GetAngle()-objectA.body.GetAngle());jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getReferenceAngle(){return this.box2dJoint.GetReferenceAngle()}getJointAngle(){return this.box2dJoint.GetJointAngle()}getJointSpeed(){return this.box2dJoint.GetJointSpeed()}isLimitEnabled(){return this.box2dJoint.IsLimitEnabled()}enableLimit(enable=true){return this.box2dJoint.EnableLimit(enable)}getLowerLimit(){return this.box2dJoint.GetLowerLimit()}getUpperLimit(){return this.box2dJoint.GetUpperLimit()}setLimits(min,max){return this.box2dJoint.SetLimits(min,max)}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=true){return this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){return this.box2dJoint.SetMotorSpeed(speed)}getMotorSpeed(){return this.box2dJoint.GetMotorSpeed()}setMaxMotorTorque(torque){return this.box2dJoint.SetMaxMotorTorque(torque)}getMaxMotorTorque(){return this.box2dJoint.GetMaxMotorTorque()}getMotorTorque(time){return this.box2dJoint.GetMotorTorque(1/time)}}class Box2dGearJoint extends Box2dJoint{constructor(objectA,objectB,joint1,joint2,ratio=1){const jointDef=new box2d.instance.b2GearJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_joint1(joint1.box2dJoint);jointDef.set_joint2(joint2.box2dJoint);jointDef.set_ratio(ratio);super(jointDef);this.joint1=joint1;this.joint2=joint2}getJoint1(){return this.joint1}getJoint2(){return this.joint2}setRatio(ratio){return this.box2dJoint.SetRatio(ratio)}getRatio(){return this.box2dJoint.GetRatio()}}class Box2dPrismaticJoint extends Box2dJoint{constructor(objectA,objectB,anchor,worldAxis=vec2(0,1),collide=false){anchor||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchor);const localAnchorB=objectB.worldToLocal(anchor);const localAxisA=objectA.worldToLocalVector(worldAxis);const jointDef=new box2d.instance.b2PrismaticJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));jointDef.set_referenceAngle(objectB.body.GetAngle()-objectA.body.GetAngle());jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getLocalAxisA(){return box2d.vec2From(this.box2dJoint.GetLocalAxisA())}getReferenceAngle(){return this.box2dJoint.GetReferenceAngle()}getJointTranslation(){return this.box2dJoint.GetJointTranslation()}getJointSpeed(){return this.box2dJoint.GetJointSpeed()}isLimitEnabled(){return this.box2dJoint.IsLimitEnabled()}enableLimit(enable=true){return this.box2dJoint.EnableLimit(enable)}getLowerLimit(){return this.box2dJoint.GetLowerLimit()}getUpperLimit(){return this.box2dJoint.GetUpperLimit()}setLimits(min,max){return this.box2dJoint.SetLimits(min,max)}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=true){return this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){return this.box2dJoint.SetMotorSpeed(speed)}getMotorSpeed(){return this.box2dJoint.GetMotorSpeed()}setMaxMotorForce(force){return this.box2dJoint.SetMaxMotorForce(force)}getMaxMotorForce(){return this.box2dJoint.GetMaxMotorForce()}getMotorForce(time){return this.box2dJoint.GetMotorForce(1/time)}}class Box2dWheelJoint extends Box2dJoint{constructor(objectA,objectB,anchor,worldAxis=vec2(0,1),collide=false){anchor||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchor);const localAnchorB=objectB.worldToLocal(anchor);const localAxisA=objectA.worldToLocalVector(worldAxis);const jointDef=new box2d.instance.b2WheelJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_localAxisA(box2d.vec2dTo(localAxisA));jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getLocalAxisA(){return box2d.vec2From(this.box2dJoint.GetLocalAxisA())}getJointTranslation(){return this.box2dJoint.GetJointTranslation()}getJointSpeed(){return this.box2dJoint.GetJointSpeed()}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=true){return this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){return this.box2dJoint.SetMotorSpeed(speed)}getMotorSpeed(){return this.box2dJoint.GetMotorSpeed()}setMaxMotorTorque(torque){return this.box2dJoint.SetMaxMotorTorque(torque)}getMaxMotorTorque(){return this.box2dJoint.GetMaxMotorTorque()}getMotorTorque(time){return this.box2dJoint.GetMotorTorque(1/time)}setSpringFrequencyHz(hz){return this.box2dJoint.SetSpringFrequencyHz(hz)}getSpringFrequencyHz(){return this.box2dJoint.GetSpringFrequencyHz()}setSpringDampingRatio(ratio){return this.box2dJoint.SetSpringDampingRatio(ratio)}getSpringDampingRatio(){return this.box2dJoint.GetSpringDampingRatio()}}class Box2dWeldJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=false){anchor||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchor);const localAnchorB=objectB.worldToLocal(anchor);const jointDef=new box2d.instance.b2WeldJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_referenceAngle(objectB.body.GetAngle()-objectA.body.GetAngle());jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getReferenceAngle(){return this.box2dJoint.GetReferenceAngle()}setFrequency(hz){return this.box2dJoint.SetFrequency(hz)}getFrequency(){return this.box2dJoint.GetFrequency()}setSpringDampingRatio(ratio){return this.box2dJoint.SetSpringDampingRatio(ratio)}getSpringDampingRatio(){return this.box2dJoint.GetSpringDampingRatio()}}class Box2dFrictionJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=false){anchor||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchor);const localAnchorB=objectB.worldToLocal(anchor);const jointDef=new box2d.instance.b2FrictionJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_collideConnected(collide);super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setMaxForce(force){this.box2dJoint.SetMaxForce(force)}getMaxForce(){return this.box2dJoint.GetMaxForce()}setMaxTorque(torque){this.box2dJoint.SetMaxTorque(torque)}getMaxTorque(){return this.box2dJoint.GetMaxTorque()}}class Box2dPulleyJoint extends Box2dJoint{constructor(objectA,objectB,groundAnchorA,groundAnchorB,anchorA,anchorB,ratio=1,collide=false){anchorA||=box2d.vec2From(objectA.body.GetPosition());anchorB||=box2d.vec2From(objectB.body.GetPosition());const localAnchorA=objectA.worldToLocal(anchorA);const localAnchorB=objectB.worldToLocal(anchorB);const jointDef=new box2d.instance.b2PulleyJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_groundAnchorA(box2d.vec2dTo(groundAnchorA));jointDef.set_groundAnchorB(box2d.vec2dTo(groundAnchorB));jointDef.set_localAnchorA(box2d.vec2dTo(localAnchorA));jointDef.set_localAnchorB(box2d.vec2dTo(localAnchorB));jointDef.set_ratio(ratio);jointDef.set_lengthA(groundAnchorA.distance(anchorA));jointDef.set_lengthB(groundAnchorB.distance(anchorB));jointDef.set_collideConnected(collide);super(jointDef)}getGroundAnchorA(){return box2d.vec2From(this.box2dJoint.GetGroundAnchorA())}getGroundAnchorB(){return box2d.vec2From(this.box2dJoint.GetGroundAnchorB())}getLengthA(){return this.box2dJoint.GetLengthA()}getLengthB(){return this.box2dJoint.GetLengthB()}getRatio(){return this.box2dJoint.GetRatio()}getCurrentLengthA(){return this.box2dJoint.GetCurrentLengthA()}getCurrentLengthB(){return this.box2dJoint.GetCurrentLengthB()}}class Box2dMotorJoint extends Box2dJoint{constructor(objectA,objectB){const linearOffset=objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));const angularOffset=objectB.body.GetAngle()-objectA.body.GetAngle();const jointDef=new box2d.instance.b2MotorJointDef;jointDef.set_bodyA(objectA.body);jointDef.set_bodyB(objectB.body);jointDef.set_linearOffset(box2d.vec2dTo(linearOffset));jointDef.set_angularOffset(angularOffset);super(jointDef)}setLinearOffset(offset){this.box2dJoint.SetLinearOffset(box2d.vec2dTo(offset))}getLinearOffset(){return box2d.vec2From(this.box2dJoint.GetLinearOffset())}setAngularOffset(offset){this.box2dJoint.SetAngularOffset(offset)}getAngularOffset(){return this.box2dJoint.GetAngularOffset()}setMaxForce(force){this.box2dJoint.SetMaxForce(force)}getMaxForce(){return this.box2dJoint.GetMaxForce()}setMaxTorque(torque){this.box2dJoint.SetMaxTorque(torque)}getMaxTorque(){return this.box2dJoint.GetMaxTorque()}setCorrectionFactor(factor){this.box2dJoint.SetCorrectionFactor(factor)}getCorrectionFactor(){return this.box2dJoint.GetCorrectionFactor()}}class Box2dPlugin{constructor(instance){ASSERT(!box2d,"Box2D already initialized");box2d=this;this.instance=instance;this.world=new box2d.instance.b2World;this.objects=[];this.velocityIterations=8;this.positionIterations=3;this.bodyTypeStatic=instance.b2_staticBody;this.bodyTypeKinematic=instance.b2_kinematicBody;this.bodyTypeDynamic=instance.b2_dynamicBody;const listener=new box2d.instance.JSContactListener;listener.BeginContact=function(contactPtr){const contact=box2d.instance.wrapPointer(contactPtr,box2d.instance.b2Contact);const fixtureA=contact.GetFixtureA();const fixtureB=contact.GetFixtureB();const objectA=fixtureA.GetBody().object;const objectB=fixtureB.GetBody().object;if(!objectA||!objectB)return;objectA.beginContact(objectB);objectB.beginContact(objectA)};listener.EndContact=function(contactPtr){const contact=box2d.instance.wrapPointer(contactPtr,box2d.instance.b2Contact);const fixtureA=contact.GetFixtureA();const fixtureB=contact.GetFixtureB();const objectA=fixtureA.GetBody().object;const objectB=fixtureB.GetBody().object;if(!objectA||!objectB)return;objectA.endContact(objectB);objectB.endContact(objectA)};listener.PreSolve=function(){};listener.PostSolve=function(){};box2d.world.SetContactListener(listener)}step(frames=1){box2d.world.SetGravity(box2d.vec2dTo(gravity));for(let i=frames;i--;)box2d.world.Step(timeDelta,this.velocityIterations,this.positionIterations)}raycastAll(start,end){const raycastCallback=new box2d.instance.JSRayCastCallback;raycastCallback.ReportFixture=function(fixturePointer,point,normal,fraction){const fixture=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);point=box2d.vec2FromPointer(point);normal=box2d.vec2FromPointer(normal);raycastResults.push(new Box2dRaycastResult(fixture,point,normal,fraction));return 1};const raycastResults=[];box2d.world.RayCast(raycastCallback,box2d.vec2dTo(start),box2d.vec2dTo(end));debugRaycast&&debugLine(start,end,raycastResults.length?"#f00":"#00f",.02);return raycastResults}raycast(start,end){const raycastResults=box2d.raycastAll(start,end);if(!raycastResults.length)return undefined;return raycastResults.reduce((a,b)=>a.fraction<b.fraction?a:b)}boxCastAll(pos,size){const queryCallback=new box2d.instance.JSQueryCallback;queryCallback.ReportFixture=function(fixturePointer){const fixture=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);const o=fixture.GetBody().object;if(!queryObjects.includes(o))queryObjects.push(o);return true};const aabb=new box2d.instance.b2AABB;aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));let queryObjects=[];box2d.world.QueryAABB(queryCallback,aabb);debugRaycast&&debugRect(pos,size,queryObjects.length?"#f00":"#00f",.02);return queryObjects}boxCast(pos,size){const queryCallback=new box2d.instance.JSQueryCallback;queryCallback.ReportFixture=function(fixturePointer){const fixture=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);queryObject=fixture.GetBody().object;return false};const aabb=new box2d.instance.b2AABB;aabb.set_lowerBound(box2d.vec2dTo(pos.subtract(size.scale(.5))));aabb.set_upperBound(box2d.vec2dTo(pos.add(size.scale(.5))));let queryObject;box2d.world.QueryAABB(queryCallback,aabb);debugRaycast&&debugRect(pos,size,queryObject?"#f00":"#00f",.02);return queryObject}circleCastAll(pos,diameter){const radius2=(diameter/2)**2;const results=box2d.boxCastAll(pos,vec2(diameter));return results.filter(o=>o.pos.distanceSquared(pos)<radius2)}circleCast(pos,diameter){const radius2=(diameter/2)**2;let results=box2d.boxCastAll(pos,vec2(diameter));let bestResult,bestDistance2;for(const result of results){const distance2=result.pos.distanceSquared(pos);if(distance2<radius2&&(!bestResult||distance2<bestDistance2)){bestResult=result;bestDistance2=distance2}}return bestResult}pointCast(pos,dynamicOnly=true){const queryCallback=new box2d.instance.JSQueryCallback;queryCallback.ReportFixture=function(fixturePointer){const fixture=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);if(dynamicOnly&&fixture.GetBody().GetType()!==box2d.instance.b2_dynamicBody)return true;if(!fixture.TestPoint(box2d.vec2dTo(pos)))return true;queryObject=fixture.GetBody().object;return false};const aabb=new box2d.instance.b2AABB;aabb.set_lowerBound(box2d.vec2dTo(pos));aabb.set_upperBound(box2d.vec2dTo(pos));let queryObject;box2d.world.QueryAABB(queryCallback,aabb);debugRaycast&&debugRect(pos,vec2(),queryObject?"#f00":"#00f",.02);return queryObject}drawFixture(fixture,pos,angle,color=WHITE,lineColor=BLACK,lineWidth=.1,useWebGL,context){const shape=box2d.castShapeObject(fixture.GetShape());switch(shape.GetType()){case box2d.instance.b2Shape.e_polygon:{let points=[];for(let i=shape.GetVertexCount();i--;)points.push(box2d.vec2From(shape.GetVertex(i)));drawPoly(points,color,lineWidth,lineColor,pos,angle,useWebGL,false,context);break}case box2d.instance.b2Shape.e_circle:{const radius=shape.get_m_radius();drawCircle(pos,radius*2,color,lineWidth,lineColor,useWebGL,false,context);break}case box2d.instance.b2Shape.e_edge:{const v1=box2d.vec2From(shape.get_m_vertex1());const v2=box2d.vec2From(shape.get_m_vertex2());drawLine(v1,v2,lineWidth,lineColor,pos,angle,useWebGL,false,context);break}}}vec2From(v){ASSERT(v instanceof box2d.instance.b2Vec2);return new Vector2(v.get_x(),v.get_y())}vec2FromPointer(vp){const v=box2d.instance.wrapPointer(vp,box2d.instance.b2Vec2);return box2d.vec2From(v)}vec2dTo(v){ASSERT(isVector2(v));return new box2d.instance.b2Vec2(v.x,v.y)}isNull(o){return!box2d.instance.getPointer(o)}castShapeObject(o){switch(o.GetType()){case box2d.instance.b2Shape.e_circle:return box2d.instance.castObject(o,box2d.instance.b2CircleShape);case box2d.instance.b2Shape.e_edge:return box2d.instance.castObject(o,box2d.instance.b2EdgeShape);case box2d.instance.b2Shape.e_polygon:return box2d.instance.castObject(o,box2d.instance.b2PolygonShape);case box2d.instance.b2Shape.e_chain:return box2d.instance.castObject(o,box2d.instance.b2ChainShape)}ASSERT(false,"Unknown box2d object type")}castJointObject(o){switch(o.GetType()){case box2d.instance.e_revoluteJoint:return box2d.instance.castObject(o,box2d.instance.b2RevoluteJoint);case box2d.instance.e_prismaticJoint:return box2d.instance.castObject(o,box2d.instance.b2PrismaticJoint);case box2d.instance.e_distanceJoint:return box2d.instance.castObject(o,box2d.instance.b2DistanceJoint);case box2d.instance.e_pulleyJoint:return box2d.instance.castObject(o,box2d.instance.b2PulleyJoint);case box2d.instance.e_mouseJoint:return box2d.instance.castObject(o,box2d.instance.b2MouseJoint);case box2d.instance.e_gearJoint:return box2d.instance.castObject(o,box2d.instance.b2GearJoint);case box2d.instance.e_wheelJoint:return box2d.instance.castObject(o,box2d.instance.b2WheelJoint);case box2d.instance.e_weldJoint:return box2d.instance.castObject(o,box2d.instance.b2WeldJoint);case box2d.instance.e_frictionJoint:return box2d.instance.castObject(o,box2d.instance.b2FrictionJoint);case box2d.instance.e_ropeJoint:return box2d.instance.castObject(o,box2d.instance.b2RopeJoint);case box2d.instance.e_motorJoint:return box2d.instance.castObject(o,box2d.instance.b2MotorJoint)}ASSERT(false,"Unknown box2d object type")}}async function box2dInit(){new Box2dPlugin(await Box2D());setupDebugDraw();engineAddPlugin(box2dUpdate,box2dRender);return box2d;function box2dUpdate(){if(paused)return;box2d.step();box2d.objects=box2d.objects.filter(o=>!o.destroyed);for(const o of box2d.objects){if(o.body){o.pos=box2d.vec2From(o.body.GetPosition());o.angle=-o.body.GetAngle()}}}function box2dRender(){if(box2dDebug||debugPhysics)box2d.world.DrawDebugData()}function setupDebugDraw(){const debugLineWidth=.1;const debugDraw=new box2d.instance.JSDraw;const box2dColor=c=>new Color(c.get_r(),c.get_g(),c.get_b());const box2dColorPointer=c=>box2dColor(box2d.instance.wrapPointer(c,box2d.instance.b2Color));const getDebugColor=color=>box2dColorPointer(color).scale(1,.8);const getPointsList=(vertices,vertexCount)=>{const points=[];for(let i=vertexCount;i--;)points.push(box2d.vec2FromPointer(vertices+i*8));return points};debugDraw.DrawSegment=function(point1,point2,color){color=getDebugColor(color);point1=box2d.vec2FromPointer(point1);point2=box2d.vec2FromPointer(point2);drawLine(point1,point2,debugLineWidth,color,vec2(),0,false)};debugDraw.DrawPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);drawPoly(points,CLEAR_WHITE,debugLineWidth,color,vec2(),0,false)};debugDraw.DrawSolidPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);drawPoly(points,color,0,color,vec2(),0,false)};debugDraw.DrawCircle=function(center,radius,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);drawCircle(center,radius*2,CLEAR_WHITE,debugLineWidth,color,false)};debugDraw.DrawSolidCircle=function(center,radius,axis,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);axis=box2d.vec2FromPointer(axis).scale(radius);drawCircle(center,radius*2,color,debugLineWidth,color,false);drawLine(vec2(),axis,debugLineWidth,color,center,0,false)};debugDraw.DrawTransform=function(transform){transform=box2d.instance.wrapPointer(transform,box2d.instance.b2Transform);const pos=box2d.vec2From(transform.get_p());const angle=-transform.get_q().GetAngle();const p1=vec2(1,0),c1=rgb(.75,0,0,.8);const p2=vec2(0,1),c2=rgb(0,.75,0,.8);drawLine(vec2(),p1,debugLineWidth,c1,pos,angle,false);drawLine(vec2(),p2,debugLineWidth,c2,pos,angle,false)};debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);box2d.world.SetDebugDraw(debugDraw)}}function drawNineSliceScreen(pos,size,startTile,borderSize=32,extraSpace=2,angle=0){drawNineSlice(pos,size,startTile,WHITE,borderSize,BLACK,extraSpace,angle,false,true)}function drawNineSlice(pos,size,startTile,color,borderSize=1,additiveColor,extraSpace=.05,angle=0,useWebGL=glEnable,screenSpace,context){const centerTile=startTile.offset(startTile.size);const centerSize=size.add(vec2(extraSpace-borderSize*2));const cornerSize=vec2(borderSize);const cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5));const flip=screenSpace?-1:1;const rotateAngle=screenSpace?-angle:angle;drawTile(pos,centerSize,centerTile,color,angle,false,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){const horizontal=i%2;const sidePos=cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0,horizontal?0:i?-1:1));const sideSize=vec2(horizontal?borderSize:centerSize.x,horizontal?centerSize.y:borderSize);const sideTile=centerTile.offset(startTile.size.multiply(vec2(i===1?1:i===3?-1:0,i===0?-flip:i===2?flip:0)));drawTile(pos.add(sidePos.rotate(rotateAngle)),sideSize,sideTile,color,angle,false,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){const flipX=i>1;const flipY=i&&i<3;const cornerPos=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-1:1));const cornerTile=centerTile.offset(startTile.size.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));drawTile(pos.add(cornerPos.rotate(rotateAngle)),cornerSize,cornerTile,color,angle,false,additiveColor,useWebGL,screenSpace,context)}}function drawThreeSliceScreen(pos,size,startTile,borderSize=32,extraSpace=2,angle=0){drawThreeSlice(pos,size,startTile,WHITE,borderSize,BLACK,extraSpace,angle,false,true)}function drawThreeSlice(pos,size,startTile,color,borderSize=1,additiveColor,extraSpace=.05,angle=0,useWebGL=glEnable,screenSpace,context){const cornerTile=startTile.frame(0);const sideTile=startTile.frame(1);const centerTile=startTile.frame(2);const centerSize=size.add(vec2(extraSpace-borderSize*2));const cornerSize=vec2(borderSize);const cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5));const flip=screenSpace?-1:1;const rotateAngle=screenSpace?-angle:angle;drawTile(pos,centerSize,centerTile,color,angle,false,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){const a=angle+i*PI/2;const horizontal=i%2;const sidePos=cornerOffset.multiply(vec2(horizontal?i===1?1:-1:0,horizontal?0:i?-flip:flip));const sideSize=vec2(horizontal?centerSize.y:centerSize.x,borderSize);drawTile(pos.add(sidePos.rotate(rotateAngle)),sideSize,sideTile,color,a,false,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){const a=angle+i*PI/2;const flipX=!i||i>2;const flipY=i>1;const cornerPos=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-flip:flip));drawTile(pos.add(cornerPos.rotate(rotateAngle)),cornerSize,cornerTile,color,a,false,additiveColor,useWebGL,screenSpace,context)}}function drawCrescent(pos,size=1,percent=0,color=WHITE,angle=0,invert=false,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=false,context){const points=getCrescentPoints(vec2(),size,percent,0,invert);drawPoly(points,color,lineWidth,lineColor,pos,angle,useWebGL,screenSpace,context)}function getCrescentPoints(pos,size=1,percent=0,angle=0,invert=false,sides=glCircleSides){ASSERT(isVector2(pos),"pos must be a vec2");ASSERT(isNumber(size)&&isNumber(percent),"size and percent must be numbers");let p=mod(percent*4,4);if(p>=2)angle+=PI;p=p<=2?p-1:3-p;if(invert){p=-p;angle+=PI}const points=[];const segs=max(3,sides>>1);const radius=size/2;for(let i=0;i<=segs;i++){const t=i/segs*PI;points.push(vec2(radius*cos(t),radius*sin(t)).rotate(angle).add(pos))}for(let i=segs;i>=0;i--){const t=i/segs*PI;points.push(vec2(radius*cos(t),-radius*p*sin(t)).rotate(angle).add(pos))}return points}let textureSheetSize=2048;let textureSheetPadding=1;let textureSheets=[];let textureSheetQueue=Promise.resolve();let textureSheetPendingCount=0;class TextureSheet{constructor(size=textureSheetSize){ASSERT(size>0,"texture sheet size must be positive");this.size=size;this.canvas=headlessMode?undefined:new OffscreenCanvas(size,size);this.context=this.canvas?.getContext("2d");this.textureInfo=new TextureInfo(this.canvas);this.cursor=vec2();this.rowHeight=0;this.glDirty=false;if(headlessMode){this.textureInfo.size=vec2(size);this.textureInfo.sizeInverse=vec2(1/size)}}tryAdd(imageSize,frameSize=imageSize,padding=textureSheetPadding,sourcePadding=0){ASSERT(isVector2(imageSize)&&isVector2(frameSize),"sizes must be vec2");ASSERT(frameSize.x>0&&frameSize.y>0,"frame size must be positive");if(isNumber(sourcePadding))sourcePadding=vec2(sourcePadding);ASSERT(isVector2(sourcePadding)&&sourcePadding.x>=0&&sourcePadding.y>=0,"sourcePadding must be a number or vec2 >= 0");const sourceCellWidth=frameSize.x+sourcePadding.x*2;const sourceCellHeight=frameSize.y+sourcePadding.y*2;ASSERT(imageSize.x%sourceCellWidth===0&&imageSize.y%sourceCellHeight===0,"image size must be a multiple of the padded frame size");const cellWidth=frameSize.x+padding*2;const cellHeight=frameSize.y+padding*2;const maxColumns=this.size/cellWidth|0;ASSERT(maxColumns>0,"frame is too wide to fit on a texture sheet");const sourceColumns=imageSize.x/sourceCellWidth;const frameCount=sourceColumns*(imageSize.y/sourceCellHeight);const columns=min(sourceColumns,maxColumns);const blockWidth=columns*cellWidth;const blockHeight=ceil(frameCount/columns)*cellHeight;let x=this.cursor.x,y=this.cursor.y,rowHeight=this.rowHeight;if(x+blockWidth>this.size){x=0;y+=rowHeight;rowHeight=0}if(y+blockHeight>this.size)return undefined;this.cursor.x=x+blockWidth;this.cursor.y=y;this.rowHeight=max(rowHeight,blockHeight);return new TileInfo(vec2(x+padding,y+padding),frameSize,this.textureInfo,padding,0,columns)}drawImage(image,tileInfo,update=true,sourcePadding=0){ASSERT(!!this.context,"texture sheet has no canvas");if(isNumber(sourcePadding))sourcePadding=vec2(sourcePadding);const frameSize=tileInfo.size;const sourceCellWidth=frameSize.x+sourcePadding.x*2;const sourceCellHeight=frameSize.y+sourcePadding.y*2;const sourceColumns=image.width/sourceCellWidth;const frameCount=sourceColumns*(image.height/sourceCellHeight);const columns=tileInfo.columns||frameCount;const cellWidth=frameSize.x+tileInfo.padding*2;const cellHeight=frameSize.y+tileInfo.padding*2;for(let i=frameCount;i--;){const sourceX=i%sourceColumns*sourceCellWidth+sourcePadding.x;const sourceY=(i/sourceColumns|0)*sourceCellHeight+sourcePadding.y;this.context.drawImage(image,sourceX,sourceY,frameSize.x,frameSize.y,tileInfo.pos.x+i%columns*cellWidth,tileInfo.pos.y+(i/columns|0)*cellHeight,frameSize.x,frameSize.y)}this.glDirty=true;update&&this.updateTexture()}updateTexture(){if(!this.glDirty)return;this.glDirty=false;this.textureInfo.createWebGLTexture()}}function loadSprite(src,frameSize,padding=textureSheetPadding,sourcePadding=0){ASSERT(isStringLike(src),"image src must be a string");ASSERT(!frameSize||isVector2(frameSize)||isNumber(frameSize),"frameSize must be a vec2 or number");ASSERT(isNumber(padding),"padding must be a number");ASSERT(isNumber(sourcePadding)||isVector2(sourcePadding),"sourcePadding must be a number or vec2");if(isNumber(frameSize))frameSize=vec2(frameSize);const tileInfo=new TileInfo(vec2(),vec2(),undefined,padding,0);if(headlessMode)return tileInfo;tileInfo.textureInfo=(textureSheets[0]||textureSheetCreate()).textureInfo;const image=new Image;const imagePromise=new Promise(resolve=>{image.onerror=image.onload=resolve;image.crossOrigin="anonymous";image.src=src});++textureSheetPendingCount;textureSheetQueue=textureSheetQueue.then(async()=>{await imagePromise;if(image.width){const imageSize=vec2(image.width,image.height);const{sheet,tile}=textureSheetAdd(imageSize,frameSize,padding,sourcePadding);Object.assign(tileInfo,tile);sheet.drawImage(image,tileInfo,false,sourcePadding)}else{LOG("loadSprite failed to load image:",src)}if(!--textureSheetPendingCount)textureSheets.forEach(s=>s.updateTexture())});return tileInfo}function loadAtlas(imageSrc,jsonSrc,padding=textureSheetPadding){ASSERT(isStringLike(imageSrc),"atlas image src must be a string");ASSERT(isStringLike(jsonSrc)||typeof jsonSrc==="object","atlas json must be a path or object");ASSERT(isNumber(padding),"padding must be a number");const atlas={};if(headlessMode)return atlas;const jsonPromise=typeof jsonSrc==="object"?Promise.resolve(jsonSrc):fetch(jsonSrc).then(r=>r.ok&&r.json()).catch(()=>undefined);const image=new Image;const imagePromise=new Promise(resolve=>{image.onerror=image.onload=resolve;image.crossOrigin="anonymous";image.src=imageSrc});++textureSheetPendingCount;textureSheetQueue=textureSheetQueue.then(async()=>{const data=await jsonPromise;await imagePromise;if(image.width&&data){for(const group of parseAtlas(data)){const sourceSize=group.frames[0].sourceSize;const blockSize=vec2(sourceSize.x*group.frames.length,sourceSize.y);const{sheet,tile}=textureSheetAdd(blockSize,sourceSize,padding);const context=sheet.context;const cellWidth=sourceSize.x+padding*2;const cellHeight=sourceSize.y+padding*2;group.frames.forEach((f,i)=>{const x=tile.pos.x+i%tile.columns*cellWidth+f.offset.x;const y=tile.pos.y+(i/tile.columns|0)*cellHeight+f.offset.y;if(f.rotated){context.save();context.translate(x,y);context.rotate(-PI/2);context.drawImage(image,f.pos.x,f.pos.y,f.size.y,f.size.x,-f.size.y,0,f.size.y,f.size.x);context.restore()}else context.drawImage(image,f.pos.x,f.pos.y,f.size.x,f.size.y,x,y,f.size.x,f.size.y)});sheet.glDirty=true;atlas[group.name]=tile}}else{LOG("loadAtlas failed to load:",imageSrc,jsonSrc)}if(!--textureSheetPendingCount)textureSheets.forEach(s=>s.updateTexture())});return atlas}function parseAtlas(data){ASSERT(!!data?.frames,"unrecognized atlas format, expected TexturePacker or Aseprite json");const frames=(isArray(data.frames)?data.frames.map(f=>[f.filename,f]):Object.entries(data.frames)).map(([name,f])=>({name:name.replace(/\.[^.\\/]+$/,""),pos:vec2(f.frame.x,f.frame.y),size:vec2(f.frame.w,f.frame.h),offset:vec2(f.spriteSourceSize?.x??0,f.spriteSourceSize?.y??0),sourceSize:vec2(f.sourceSize?.w??f.frame.w,f.sourceSize?.h??f.frame.h),rotated:!!f.rotated}));const groups=[];const tags=data.meta?.frameTags;if(tags?.length){const tagged=new Set;for(const tag of tags){groups.push({name:tag.name,frames:frames.slice(tag.from,tag.to+1)});for(let i=tag.from;i<=tag.to;++i)tagged.add(i)}frames.forEach((f,i)=>tagged.has(i)||groups.push({name:f.name,frames:[f]}));return groups}const stems=new Map;for(const f of frames){let match=f.name.match(/^(.+?)([-_ ])?(\d+)$/);if(match&&!match[2]&&/\d$/.test(match[1]))match=undefined;const stem=match?match[1]:f.name;f.groupIndex=match?Number(match[3]):undefined;stems.has(stem)||stems.set(stem,[]);stems.get(stem).push(f)}for(const[stem,list]of stems){list.sort((a,b)=>a.groupIndex-b.groupIndex);const grouped=list.length>1&&list.every((f,i)=>f.groupIndex===list[0].groupIndex+i)&&list.every(f=>f.sourceSize.x===list[0].sourceSize.x&&f.sourceSize.y===list[0].sourceSize.y);if(grouped)groups.push({name:stem,frames:list});else list.forEach(f=>groups.push({name:f.name,frames:[f]}))}return groups}async function spritesReady(){while(textureSheetPendingCount)await textureSheetQueue}function textureSheetCreate(){const sheet=new TextureSheet;textureSheets.push(sheet);return sheet}function textureSheetAdd(imageSize,frameSize,padding,sourcePadding){let sheet,tile;for(sheet of textureSheets)if(tile=sheet.tryAdd(imageSize,frameSize,padding,sourcePadding))break;if(!tile){sheet=textureSheetCreate();tile=sheet.tryAdd(imageSize,frameSize,padding,sourcePadding);ASSERT(!!tile,"image is too large to fit on a texture sheet")}return{sheet:sheet,tile:tile}}function setTextureSheetSize(size){textureSheetSize=size}function setTextureSheetPadding(padding){textureSheetPadding=padding}const tweenActive=[];let lastTime=0;let lastTimeReal=0;function isLerpable(v){return v&&typeof v.lerp==="function"}class Tween{constructor(callback,start=0,end=1,duration=1,options={}){ASSERT(typeof callback==="function","Tween callback must be a function");if(isLerpable(start)){ASSERT(start.constructor===end.constructor,"Tween start and end must be the same type")}else{ASSERT(isNumber(start),"Tween start must be a number or have a .lerp method");ASSERT(isNumber(end),"Tween end must be a number when start is a number")}ASSERT(isNumber(duration)&&duration>0,"Tween duration must be > 0");this.callback=callback;this.start=start;this.end=end;this.duration=duration;this.life=duration;this.ease=options.ease||Ease.LINEAR;this.useRealTime=!!options.useRealTime;this.paused=!!options.paused;this.thenCallback=undefined;this.loopRemaining=0;tweenActive.push(this);callback(this.interp(duration))}setEase(easeFn){this.ease=easeFn;return this}then(callback){this.thenCallback=callback;this.loopRemaining=0;return this}loop(count=Infinity){this.loopRemaining=count;this.thenCallback=()=>loopContinuation(this);return this}pingPong(count=Infinity){this.loopRemaining=count;this.thenCallback=()=>pingPongContinuation(this);return this}pause(){this.paused=true}resume(){this.paused=false}restart(){this.life=this.duration;this.paused=false;if(tweenActive.indexOf(this)<0)tweenActive.push(this);this.callback(this.interp(this.duration))}isActive(){return!this.paused&&tweenActive.indexOf(this)>=0}getPercent(){return percent(this.duration-this.life,0,this.duration)}getValue(){return this.interp(this.life)}interp(life){const x=this.ease((this.duration-life)/this.duration);if(isLerpable(this.start))return this.start.lerp(this.end,x);return this.start+(this.end-this.start)*x}stop(){const i=tweenActive.indexOf(this);if(i>=0)tweenActive.splice(i,1);this.thenCallback=undefined}}const Ease={LINEAR:x=>x,POWER:n=>x=>x**n,SINE:x=>1-cos(x*(PI/2)),CIRC:x=>1-(1-x*x)**.5,EXPO:x=>x===0?0:2**(10*x-10),BACK:x=>x*x*(2.70158*x-1.70158),ELASTIC:x=>x===0?0:x===1?1:-(2**(10*x-10))*sin((37-40*x)*PI/6),SPRING:x=>1-(sin(PI*(1-x)*(.2+2.5*(1-x)**3))*x**2.2+(1-x))*(1+1.2*x),BOUNCE:x=>{let t=1-x,f;if(t<4/11)f=7.5625*t*t;else if(t<8/11)f=7.5625*(t-=6/11)*t+.75;else if(t<10/11)f=7.5625*(t-=9/11)*t+.9375;else f=7.5625*(t-=10.5/11)*t+.984375;return 1-f},IN:f=>f,OUT:f=>x=>1-f(1-x),IN_OUT:f=>Ease.PIECEWISE(f,Ease.OUT(f)),PIECEWISE:(...fns)=>{const n=fns.length;return x=>{const i=x*n-1e-9>>0;return(fns[i]((x-i/n)*n)+i)/n}},BEZIER:(x1,y1,x2,y2)=>{const curve=t=>{const u=1-t;const c1=3*u*u*t;const c2=3*u*t*t;const t3=t**3;return[c1*x1+c2*x2+t3,c1*y1+c2*y2+t3]};return x=>{let t0=0,t1=1;for(let i=0;i<128;i++){const tMid=(t0+t1)/2;const[bx,by]=curve(tMid);if(abs(bx-x)<1e-5)return by;if(bx<x)t0=tMid;else t1=tMid}return curve((t0+t1)/2)[1]}}};function tweenProperty(target,propertyPath,start,end,duration=1,options={}){ASSERT(target!=null&&typeof target==="object","tweenProperty target must be an object");ASSERT(isStringLike(propertyPath)&&propertyPath.length>0,"tweenProperty propertyPath must be a non-empty string");const parts=propertyPath.split(".");const lastKey=parts.pop();const callback=value=>{let obj=target;for(const k of parts){obj=obj[k];ASSERT(obj!=null,"tweenProperty path does not resolve: "+propertyPath)}obj[lastKey]=value};return new Tween(callback,start,end,duration,options)}function loopContinuation(tween){if(tween.loopRemaining!==Infinity&&tween.loopRemaining<=1)return;if(tween.loopRemaining!==Infinity)tween.loopRemaining-=1;tween.life=tween.duration;tween.thenCallback=()=>loopContinuation(tween);tweenActive.push(tween);tween.callback(tween.interp(tween.duration))}function pingPongContinuation(tween){if(tween.loopRemaining!==Infinity&&tween.loopRemaining<=1)return;if(tween.loopRemaining!==Infinity)tween.loopRemaining-=1;const tmp=tween.start;tween.start=tween.end;tween.end=tmp;tween.life=tween.duration;tween.thenCallback=()=>pingPongContinuation(tween);tweenActive.push(tween);tween.callback(tween.interp(tween.duration))}function tweenUpdate(gameDelta,realDelta){if(gameDelta===undefined){gameDelta=time-lastTime;realDelta=timeReal-lastTimeReal;lastTime=time;lastTimeReal=timeReal}else if(realDelta===undefined){realDelta=gameDelta}for(let i=tweenActive.length;i--;){const t=tweenActive[i];if(t.paused)continue;const dt=t.useRealTime?realDelta:gameDelta;if(dt<=0)continue;t.life-=dt;if(t.life>0){t.callback(t.interp(t.life))}else{t.callback(t.interp(0));tweenActive.splice(i,1);const cb=t.thenCallback;t.thenCallback=undefined;if(cb)cb()}}}function tweenStopAll(){for(const t of tweenActive)t.thenCallback=undefined;tweenActive.length=0}engineAddPlugin(tweenUpdate);const PATHFINDER_DIAGONAL_COST=Math.SQRT2;const PATHFINDER_TILE_VEC=vec2(1);class PathFinderNode{constructor(x,y){this.pos=vec2(x,y);this.posWorld=vec2();this.walkable=false;this.cost=0;this.g=0;this.f=0;this.parent=null;this.isOpen=false;this.isClosed=false}reset(){this.walkable=false;this.cost=0;this.g=0;this.f=0;this.parent=null;this.isOpen=false;this.isClosed=false}isClear(){return this.walkable&&this.cost===0}}class PathFinder{constructor(source){if(isVector2(source)){this.size=source.floor();this.tileLayer=undefined}else{ASSERT(source&&isVector2(source.size)&&typeof source.getCollisionData==="function","PathFinder requires a Vector2 size or a TileCollisionLayer");this.size=source.size;this.tileLayer=source}this.heuristicWeight=1;this.maxLoop=1e3;this.smoothPath=true;this.debug=false;this.debugTime=1;this.nodes=new Array(this.size.x*this.size.y);for(let y=0;y<this.size.y;++y)for(let x=0;x<this.size.x;++x)this.nodes[x+y*this.size.x]=new PathFinderNode(x,y);this.collisionScratch=vec2()}isWalkable(x,y){if(!this.tileLayer)return true;return!this.tileLayer.getCollisionData(this.collisionScratch.set(x,y))}getCost(x,y){return 0}getNode(x,y){if(x<0||y<0||x>=this.size.x||y>=this.size.y)return null;return this.nodes[x+y*this.size.x]}worldToTile(worldPos){const ox=this.tileLayer?this.tileLayer.pos.x:0;const oy=this.tileLayer?this.tileLayer.pos.y:0;return vec2(floor(worldPos.x-ox),floor(worldPos.y-oy))}tileToWorld(x,y){const ox=this.tileLayer?this.tileLayer.pos.x:0;const oy=this.tileLayer?this.tileLayer.pos.y:0;return vec2(x+.5+ox,y+.5+oy)}buildNodeData(){const w=this.size.x;const h=this.size.y;const ox=this.tileLayer?this.tileLayer.pos.x:0;const oy=this.tileLayer?this.tileLayer.pos.y:0;for(let y=0;y<h;++y)for(let x=0;x<w;++x){const node=this.nodes[x+y*w];node.reset();const walkable=!!this.isWalkable(x,y);const cost=walkable?max(0,this.getCost(x,y)):0;node.walkable=walkable;node.cost=cost;node.posWorld.set(x+.5+ox,y+.5+oy);if(this.debug&&this.debugTime>0){if(!walkable)debugRect(node.posWorld,PATHFINDER_TILE_VEC,rgb(1,0,0,.25),this.debugTime);else if(cost>0)debugRect(node.posWorld,PATHFINDER_TILE_VEC,rgb(1,0,0,min(.2,cost*.05)),this.debugTime)}}}aStarSearch(startNode,endNode){ASSERT(startNode&&endNode,"aStarSearch needs both endpoints");ASSERT(startNode!==endNode,"aStarSearch: start and end must differ — caller should handle trivial case");ASSERT(startNode.walkable&&endNode.walkable,"aStarSearch: endpoints must be walkable");const openList=[startNode];startNode.isOpen=true;let loopCount=0;while(openList.length>0){let bestIndex=0;let bestF=openList[0].f;for(let i=1;i<openList.length;++i){if(openList[i].f<bestF){bestF=openList[i].f;bestIndex=i}}const current=openList[bestIndex];if(current===endNode)break;if(++loopCount>this.maxLoop)break;current.isOpen=false;openList.splice(bestIndex,1);current.isClosed=true;if(this.debug&&this.debugTime>0)debugRect(current.posWorld,PATHFINDER_TILE_VEC,rgb(1,1,1,.05),this.debugTime);for(let dy=-1;dy<=1;++dy)for(let dx=-1;dx<=1;++dx){if(dx===0&&dy===0)continue;const neighbor=this.getNode(current.pos.x+dx,current.pos.y+dy);if(!neighbor||!neighbor.walkable||neighbor.isClosed)continue;let stepCost=1;if(dx!==0&&dy!==0){const card1=this.getNode(current.pos.x+dx,current.pos.y);if(!card1||!card1.walkable)continue;const card2=this.getNode(current.pos.x,current.pos.y+dy);if(!card2||!card2.walkable)continue;stepCost=PATHFINDER_DIAGONAL_COST}const tentativeG=current.g+stepCost+neighbor.cost;if(!neighbor.isOpen){neighbor.isOpen=true;openList.push(neighbor)}else if(tentativeG>=neighbor.g){continue}neighbor.parent=current;neighbor.g=tentativeG;const adx=abs(endNode.pos.x-neighbor.pos.x);const ady=abs(endNode.pos.y-neighbor.pos.y);const h=max(adx,ady)+(Math.SQRT2-1)*min(adx,ady);neighbor.f=neighbor.g+h*this.heuristicWeight}}return endNode.parent!==null}getNearestClearNode(worldPos,searchRange=10,rebuild=true){ASSERT(isVector2(worldPos),"worldPos must be a Vector2");if(rebuild)this.buildNodeData();const ox=this.tileLayer?this.tileLayer.pos.x:0;const oy=this.tileLayer?this.tileLayer.pos.y:0;const centerX=floor(worldPos.x-ox);const centerY=floor(worldPos.y-oy);for(let offset=0;offset<=searchRange;++offset){let nearest=null;let nearestDistSq=0;for(let dy=-offset;dy<=offset;++dy)for(let dx=-offset;dx<=offset;++dx){if(offset>0&&abs(dx)!==offset&&abs(dy)!==offset)continue;const node=this.getNode(centerX+dx,centerY+dy);if(!node||!node.isClear())continue;const ddx=node.posWorld.x-worldPos.x;const ddy=node.posWorld.y-worldPos.y;const distSq=ddx*ddx+ddy*ddy;if(!nearest||distSq<nearestDistSq){nearest=node;nearestDistSq=distSq}}if(nearest)return nearest}return null}smoothPathCorners(path){if(path.length<=2)return;let i=1;while(i<path.length-1){const prev=path[i-1];const node=path[i];const next=path[i+1];const dx=next.pos.x-prev.pos.x;const dy=next.pos.y-prev.pos.y;const lenSq=dx*dx+dy*dy;const stepDx=node.pos.x-prev.pos.x;const stepDy=node.pos.y-prev.pos.y;const stepDxNext=next.pos.x-node.pos.x;const stepDyNext=next.pos.y-node.pos.y;if(lenSq===1){if(this.debug&&this.debugTime>0)debugCircle(node.posWorld,.3,rgb(.5,0,.5,.5),this.debugTime);path.splice(i,1);i=max(1,i-1);continue}else if(lenSq===2){if(this.debug&&this.debugTime>0)debugCircle(node.posWorld,.3,rgb(1,0,0,.5),this.debugTime);let sx,sy;if(prev.pos.y===node.pos.y&&next.pos.x===node.pos.x){sx=prev.pos.x;sy=next.pos.y}else{sx=next.pos.x;sy=prev.pos.y}const shortcut=this.getNode(sx,sy);if(shortcut&&shortcut.isClear()){path.splice(i,1);i=max(1,i-1);continue}}else if(lenSq===5){if(this.debug&&this.debugTime>0)debugCircle(node.posWorld,.3,rgb(1,1,0,.5),this.debugTime);const prevPrev=i>=2?path[i-2]:prev;let s1x,s1y,s2x,s2y;if(stepDx===0||stepDxNext===0){s1x=next.pos.x;s1y=node.pos.y;s2x=prev.pos.x;s2y=node.pos.y}else{s1x=node.pos.x;s1y=next.pos.y;s2x=node.pos.x;s2y=prev.pos.y}const dd1x=s1x-prevPrev.pos.x;const dd1y=s1y-prevPrev.pos.y;const dd2x=s2x-prevPrev.pos.x;const dd2y=s2y-prevPrev.pos.y;const dist1Sq=dd1x*dd1x+dd1y*dd1y;const dist2Sq=dd2x*dd2x+dd2y*dd2y;const sx=dist1Sq<dist2Sq?s1x:s1x===s2x&&s1y===s2y?s1x:s2x;const sy=dist1Sq<dist2Sq?s1y:s1x===s2x&&s1y===s2y?s1y:s2y;const shortcut=this.getNode(sx,sy);if(shortcut&&shortcut!==node&&shortcut.isClear()){const ccx=next.pos.x+s2x-s1x;const ccy=next.pos.y+s2y-s1y;const cutCorner=this.getNode(ccx,ccy);if(cutCorner&&cutCorner.isClear()){path[i]=shortcut;i=max(1,i-1);continue}}}else if(lenSq===4||lenSq===8){if(this.debug&&this.debugTime>0)debugCircle(node.posWorld,.3,rgb(0,1,0,.5),this.debugTime);if(stepDx===stepDxNext&&stepDy===stepDyNext){++i;continue}else{let sx,sy;if(prev.pos.y===next.pos.y){sx=node.pos.x;sy=prev.pos.y}else{sx=prev.pos.x;sy=node.pos.y}const shortcut=this.getNode(sx,sy);if(shortcut&&shortcut.isClear()){path[i]=shortcut;i=max(1,i-1);continue}}}++i}}smoothPathStringPull(path){if(path.length<=2)return;for(const n of path){if(!n.isClear())return}const original=path.slice();path.length=0;path.push(original[0]);let searchIndex=0;for(let i=1;i<original.length;++i){const node=original[i];{const a=original[searchIndex];const b=original[i-1];if(a!==b){const cross=(b.pos.x-a.pos.x)*(node.pos.y-a.pos.y)-(b.pos.y-a.pos.y)*(node.pos.x-a.pos.x);if(cross===0)continue}}if(!this.isLineClear(node.pos,path[path.length-1].pos)){let foundClearAfter=false;for(let j=i+1;j<original.length;++j){if(this.isLineClear(original[j].pos,path[path.length-1].pos)){foundClearAfter=true;break}}if(foundClearAfter){if(this.debug&&this.debugTime>0)debugLine(node.posWorld,path[path.length-1].posWorld,rgb(0,0,1,.3),.02,this.debugTime);continue}for(;searchIndex<original.length;++searchIndex){const cand=original[searchIndex];if(this.isLineClear(node.pos,cand.pos)){path.push(cand);i=searchIndex;break}}ASSERT(searchIndex<original.length,"smoothPathStringPull: ran out of candidates")}}path.push(original[original.length-1])}dropCollinearNodes(path){for(let i=path.length-2;i>=1;--i){const a=path[i-1],b=path[i],c=path[i+1];if((b.pos.x-a.pos.x)*(c.pos.y-a.pos.y)===(b.pos.y-a.pos.y)*(c.pos.x-a.pos.x))path.splice(i,1)}}isNodeClear(x,y){const n=this.getNode(x,y);return n!==null&&n.isClear()}isLineClear(startPos,endPos){ASSERT(isVector2(startPos)&&isVector2(endPos),"isLineClear needs Vector2 endpoints");ASSERT(this.isNodeClear(startPos.x,startPos.y)&&this.isNodeClear(endPos.x,endPos.y),"isLineClear endpoints must be in-bounds and clear");const dx=endPos.x-startPos.x;const dy=endPos.y-startPos.y;const adx=abs(dx);const ady=abs(dy);const sx=sign(dx);const sy=sign(dy);let x=startPos.x;let y=startPos.y;if(ady===adx){while(x!==endPos.x){if(x!==startPos.x){if(!this.isNodeClear(x,y))return false;if(!this.isNodeClear(x,y-sy))return false}if(!this.isNodeClear(x,y+sy))return false;x+=sx;y+=sy}if(!this.isNodeClear(endPos.x,endPos.y-sy))return false}else if(ady<adx){if(dy===0){x+=sx;while(x!==endPos.x){if(!this.isNodeClear(x,y))return false;x+=sx}}else{let lastY=startPos.y;while(x!==endPos.x){y=startPos.y+Math.trunc(dy*(x-startPos.x)/dx);if(lastY!==y){if(!this.isNodeClear(x-sx,y+sy))return false;if(!this.isNodeClear(x,y-sy))return false}lastY=y;if(x!==startPos.x){if(!this.isNodeClear(x,y))return false}y+=sy;if(!this.isNodeClear(x,y))return false;x+=sx}const finalY=endPos.y-sy;if(!this.isNodeClear(endPos.x,finalY))return false}}else{if(dx===0){y+=sy;while(y!==endPos.y){if(!this.isNodeClear(x,y))return false;y+=sy}}else{let lastX=startPos.x;while(y!==endPos.y){x=startPos.x+Math.trunc(dx*(y-startPos.y)/dy);if(lastX!==x){if(!this.isNodeClear(x+sx,y-sy))return false;if(!this.isNodeClear(x-sx,y))return false}lastX=x;if(y!==startPos.y){if(!this.isNodeClear(x,y))return false}x+=sx;if(!this.isNodeClear(x,y))return false;y+=sy}const finalX=endPos.x-sx;if(!this.isNodeClear(finalX,endPos.y))return false}}return true}findPath(startPos,endPos){ASSERT(isVector2(startPos)&&isVector2(endPos),"findPath needs Vector2 endpoints");this.buildNodeData();const startNode=this.getNearestClearNode(startPos,10,false);const endNode=this.getNearestClearNode(endPos,10,false);if(!startNode||!endNode)return[];if(startNode===endNode)return[startNode.posWorld.copy()];if(!this.aStarSearch(startNode,endNode))return[];const nodePath=[];for(let n=endNode;n;n=n.parent)nodePath.push(n);nodePath.reverse();if(this.smoothPath){this.smoothPathCorners(nodePath);this.smoothPathStringPull(nodePath);this.dropCollinearNodes(nodePath)}const result=nodePath.map(n=>n.posWorld.copy());if(this.debug&&this.debugTime>0&&result.length>0){for(let i=1;i<result.length;++i)debugLine(result[i-1],result[i],RED,.1,this.debugTime);for(const p of result)debugCircle(p,.5,rgb(1,0,0,.3),this.debugTime);debugCircle(result[0],.5,rgb(0,1,0,.5),this.debugTime);debugCircle(result[result.length-1],.5,rgb(0,1,0,.5),this.debugTime)}return result}}let threeJS;class ThreeJSPlugin{constructor(THREE,cameraFOV=60){ASSERT(!threeJS,"ThreeJS plugin already initialized");threeJS=this;if(headlessMode)return;ASSERT(mainCanvas,"ThreeJS plugin must be created after engineInit, call in gameInit");ASSERT(THREE&&THREE.WebGLRenderer,"three.js module must be passed in");this.THREE=THREE;this.renderer=new THREE.WebGLRenderer({antialias:true});this.scene=new THREE.Scene;this.camera=new THREE.PerspectiveCamera(cameraFOV,1,.1,1e3);this.cameraAlign2D=true;const threeCanvas=this.renderer.domElement;const rootElement=mainCanvas.parentElement;rootElement.insertBefore(threeCanvas,rootElement.firstChild);threeCanvas.style.cssText=mainCanvas.style.cssText;setBackgroundCanvas(threeCanvas);engineAddPlugin(undefined,()=>this.render())}alignCamera2D(){const halfHeight=mainCanvasSize.y/2/cameraScale;const distance=halfHeight/tan(this.camera.fov/2*PI/180);this.camera.position.set(cameraPos.x,cameraPos.y,distance);this.camera.rotation.set(0,0,-cameraAngle)}render(){if(!this.renderer)return;const threeCanvas=this.renderer.domElement;if(threeCanvas.width!=mainCanvasSize.x||threeCanvas.height!=mainCanvasSize.y){this.renderer.setSize(mainCanvasSize.x,mainCanvasSize.y,false);this.camera.aspect=mainCanvasSize.x/mainCanvasSize.y;this.camera.updateProjectionMatrix()}if(threeCanvas.style.cssText!=mainCanvas.style.cssText)threeCanvas.style.cssText=mainCanvas.style.cssText;if(this.cameraAlign2D)this.alignCamera2D();this.renderer.render(this.scene,this.camera)}}class ThreeJSObject extends EngineObject{constructor(pos,size,mesh,z=0){super(pos,size);ASSERT(threeJS,"ThreeJS plugin must be initialized first");this.mesh=mesh;this.z=z;if(mesh){threeJS.scene.add(mesh);this.syncMesh()}}update(){super.update();this.syncMesh()}syncMesh(){if(!this.mesh)return;this.mesh.position.set(this.pos.x,this.pos.y,this.z);this.mesh.rotation.z=-this.angle}render(){}destroy(immediate){if(this.destroyed)return;this.mesh&&threeJS.scene.remove(this.mesh);super.destroy(immediate)}}export{engineName,engineVersion,frameRate,timeDelta,engineObjects,engineObjectsCollide,frame,time,timeReal,paused,getPaused,setPaused,engineInit,engineStep,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCollect,engineObjectsCallback,engineObjectsRaycast,engineAddPlugin,debug,debugOverlay,debugWatermark,ASSERT,LOG,debugPointSize,debugRect,debugPoly,debugCircle,debugPoint,debugLine,debugOverlap,debugText,debugClear,debugScreenshot,debugShowErrors,debugVideoCaptureStart,debugVideoCaptureStop,debugVideoCaptureIsActive,cameraPos,cameraAngle,cameraScale,timeScale,canvasColorTiles,canvasClearColor,canvasMaxSize,canvasMinAspect,canvasMaxAspect,canvasFixedSize,canvasPixelated,tilesPixelated,canvasPixelRatio,fontDefault,showSplashScreen,headlessMode,engineManualStep,tileDefaultSize,tileDefaultPadding,tileDefaultBleed,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultRestitution,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,glEnable,glCircleSides,gamepadsEnable,gamepadDirectionEmulateStick,gamepadAxisFilterEnable,inputWASDEmulateDirection,touchInputEnable,touchGamepadEnable,touchGamepadPassthrough,touchGamepadCenterButtonSize,touchGamepadButtonCount,touchGamepadLeftStick,touchGamepadLeftButtonCount,touchGamepadRightStick,touchGamepadAnalog,touchGamepadFloating,touchGamepadSize,touchGamepadAlpha,touchGamepadDisplayTime,touchGamepadVibration,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,setCameraPos,setCameraAngle,setCameraScale,setTimeScale,setCanvasColorTiles,setCanvasClearColor,setCanvasMaxSize,setCanvasMinAspect,setCanvasMaxAspect,setCanvasFixedSize,setCanvasPixelated,setTilesPixelated,setCanvasPixelRatio,setFontDefault,setShowSplashScreen,setHeadlessMode,setEngineManualStep,setGLEnable,setTileDefaultSize,setTileDefaultPadding,setTileDefaultBleed,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultRestitution,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setGLCircleSides,setTouchInputEnable,setGamepadsEnable,setGamepadDirectionEmulateStick,setGamepadAxisFilterEnable,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadPassthrough,setTouchGamepadCenterButtonSize,setTouchGamepadButtonCount,setTouchGamepadLeftStick,setTouchGamepadLeftButtonCount,setTouchGamepadRightStick,setTouchGamepadAnalog,setTouchGamepadFloating,setTouchGamepadSize,setTouchGamepadAlpha,setTouchGamepadDisplayTime,setTouchGamepadVibration,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setDebugWatermark,setDebugKey,PI,abs,floor,ceil,round,min,max,sign,hypot,log2,sin,cos,tan,atan2,mod,clamp,percent,distanceWrap,lerpWrap,distanceAngle,lerpAngle,lerp,percentLerp,smoothStep,nearestPowerOfTwo,isPowerOfTwo,isOverlapping,isIntersecting,lineTest,oscillate,formatTime,fetchJSON,saveText,saveCanvas,saveDataURL,shareURL,readSaveData,writeSaveData,noise1D,noise2D,rand,randInt,randBool,randSign,randInCircle,randVec2,randColor,RandomGenerator,Vector2,Color,Timer,vec2,rgb,hsl,isColor,isVector2,isNumber,isStringLike,isArray,WHITE,CLEAR_WHITE,BLACK,CLEAR_BLACK,GRAY,RED,ORANGE,YELLOW,GREEN,CYAN,BLUE,PURPLE,MAGENTA,tile,TileInfo,TextureInfo,mainCanvas,mainContext,drawContext,workCanvas,workContext,workReadCanvas,workReadContext,backgroundCanvas,mainCanvasSize,textureInfos,drawCount,primitiveCount,screenToWorld,worldToScreen,screenToWorldDelta,worldToScreenDelta,screenToWorldTransform,drawTile,drawRect,drawRectGradient,drawTextureWrapped,drawLineList,drawLine,drawPoly,drawRegularPoly,drawEllipse,drawCircle,drawEllipseGradient,drawCircleGradient,drawCanvas2D,drawText,drawTextScreen,setAdditiveBlendMode,setBackgroundCanvas,combineCanvases,engineImageFont,ImageFont,isFullscreen,toggleFullscreen,setCursor,getCameraSize,cameraFit,isOnScreen,glCanvas,glContext,glAntialias,glClearCanvas,glSetTexture,glSetTextureWrap,glCompileShader,glCreateProgram,glCreateTexture,glDeleteTexture,glSetTextureData,glFlush,glCopyToContext,glSetAntialias,glDraw,glDrawUntextured,glDrawPointsTransform,glDrawOutlineTransform,glDrawPoints,glDrawColoredPoints,glSetRenderTarget,glClearRect,keyIsDown,keyWasPressed,keyWasReleased,keyDirection,inputClear,inputClearKey,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseDelta,mouseDeltaScreen,mouseWheel,mouseInWindow,isUsingGamepad,lastInputDevice,inputMouseMoveThreshold,inputPreventDefault,gamepadPrimary,isTouchDevice,setInputPreventDefault,setInputMouseMoveThreshold,usingMouseInput,usingKeyboardInput,usingGamepadInput,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,gamepadStickCount,gamepadDpad,gamepadConnected,gamepadVibrate,gamepadVibrateStop,vibrate,vibrateStop,pointerLockRequest,pointerLockExit,pointerLockIsActive,audioContext,audioMasterGain,audioDefaultSampleRate,audioIsRunning,Sound,SoundInstance,speak,speakStop,getNoteFrequency,playSamples,playAudioBuffer,createAudioBuffer,zzfx,zzfxG,EngineObject,tileCollisionLayers,tileCollisionGetData,tileCollisionTest,tileCollisionRaycast,tileLayersLoad,TileLayerData,CanvasLayer,TileLayer,TileCollisionLayer,ParticleEmitter,Particle};export{medals,medalsPreventUnlock,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,medalsInit,medalsForEach,medalsReset,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalsPreventUnlock,Medal,newgrounds,NewgroundsPlugin,NewgroundsMedal,postProcess,PostProcessPlugin,lightSystem,LightSystemPlugin,Light,ZzFXMusic,zzfxM,uiSystem,uiDebug,uiSetDebug,UISystemPlugin,UIObject,UIText,UITextInput,UITile,UIButton,UICheckbox,UISlider,UIVideo,UILayout,box2d,box2dDebug,box2dSetDebug,box2dInit,Box2dPlugin,Box2dObject,Box2dStaticObject,Box2dKinematicObject,Box2dTileLayer,Box2dRaycastResult,Box2dJoint,Box2dTargetJoint,Box2dDistanceJoint,Box2dPinJoint,Box2dRopeJoint,Box2dRevoluteJoint,Box2dGearJoint,Box2dPrismaticJoint,Box2dWheelJoint,Box2dWeldJoint,Box2dFrictionJoint,Box2dPulleyJoint,Box2dMotorJoint,drawNineSlice,drawNineSliceScreen,drawThreeSlice,drawThreeSliceScreen,drawCrescent,getCrescentPoints,Tween,tweenProperty,tweenStopAll,tweenUpdate,Ease,PathFinder,PathFinderNode,threeJS,ThreeJSPlugin,ThreeJSObject,textureSheetSize,textureSheetPadding,setTextureSheetSize,setTextureSheetPadding,textureSheets,TextureSheet,loadSprite,loadAtlas,parseAtlas,spritesReady};
|
|
4
|
+
const engineName="LittleJS",engineVersion="1.20.0",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],engineObjectsCollideStaticLast=[],frame=0,time=0,timeReal=0,paused=!1;function getPaused(){return paused}function setPaused(isPaused=!0){paused=isPaused}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0,windowWidthLast=0,windowHeightLast=0,windowPixelRatioLast=0,engineUpdateInternal,engineFrameScheduled=!1;const engineObjectsCollidePairs=new Map;function engineObjectsCollidePairAdd(asker,other,resolve=!1){let others=engineObjectsCollidePairs.get(asker);others||engineObjectsCollidePairs.set(asker,others=new Map),others.set(other,resolve)}let engineInitialized=!1,engineObjectsUpdateCount=0;const engineChildStack=[];const pluginList=[];class EnginePlugin{constructor(update,render,glContextLost,glContextRestored,preRender){this.update=update,this.render=render,this.glContextLost=glContextLost,this.glContextRestored=glContextRestored,this.preRender=preRender}}function engineAddPlugin(update,render,glContextLost,glContextRestored,preRender){update=new EnginePlugin(update,render,glContextLost,glContextRestored,preRender);pluginList.push(update)}async function engineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources=[],rootElement){if(console.log("LittleJS Engine v1.20.0"),!engineInitialized){if(engineInitialized=!0,gameInit||=()=>{},gameUpdate||=()=>{},gameUpdatePost||=()=>{},gameRender||=()=>{},gameRenderPost||=()=>{},!headlessMode){document.body||document.documentElement.appendChild(document.createElement("body")),!function(rootElement){if(!glEnable||headlessMode)glCanBeEnabled=!1;else if(glCanvas=document.createElement("canvas"),glContext=glCanvas.getContext("webgl2",{antialias:glAntialias})){rootElement.appendChild(glCanvas),initWebGL();for(const info of glTextureInfos)info.glTexture||=glCreateTexture(info.image,info.wrap);glCanvas.addEventListener("webglcontextlost",e=>{glEnableBeforeLoss=glEnable,glEnable=!1,glCanvas.style.display="none",e.preventDefault();for(const info of glTextureInfos)info.glTexture=void 0;glActiveTexture=void 0;for(const shader of glShaderObjects)shader.program=void 0;glBatchShader=void 0,glProgramCustom=!0,glUniformLocations=new WeakMap,glBatchCount=0,glPolyMode=!1,pluginList.forEach(plugin=>plugin.glContextLost?.())}),glCanvas.addEventListener("webglcontextrestored",()=>{glEnable=glEnableBeforeLoss,glCanvas.style.display="",glMipmappedTextures=new WeakSet,glMipmapsUntilTarget=new WeakSet,glMipmapsStale.clear(),glPremultipliedTextures=new WeakSet,initWebGL();for(const info of glTextureInfos)info.glTexture=glCreateTexture(info.image,info.wrap);pluginList.forEach(plugin=>plugin.glContextRestored?.());for(const o of engineObjects)o instanceof TileLayer&&o.isUsingWebGL&&!o.destroyed&&o.redraw()})}else console.warn("WebGL2 not supported, falling back to 2D canvas rendering!"),glCanvas=glContext=void 0,glEnable=!1,glCanBeEnabled=!1;function initWebGL(){glShader=glCreateProgram(gl_VERTEX_SOURCE,"#version 300 es\nprecision highp float;uniform sampler2D s;uniform bool premultipliedTexture;in vec2 v;in vec4 d,e;out vec4 c;void main(){vec4 t=texture(s,v);"+gl_FRAGMENT_TINT_SOURCE+"}"),glShaderPremultiplied=!1,glPolyShader=glCreateProgram("#version 300 es\nprecision highp float;uniform mat4 m;in vec2 p;in vec4 c;out vec4 d;void main(){gl_Position=m*vec4(p,1,1);d=c;}","#version 300 es\nprecision highp float;in vec4 d;out vec4 c;void main(){c=d;}");var glInstanceData=new ArrayBuffer(gl_ARRAY_BUFFER_SIZE),glInstanceData=(glPositionData=new Float32Array(glInstanceData),glColorData=new Uint32Array(glInstanceData),glArrayBuffer=glContext.createBuffer(),glGeometryBuffer=glContext.createBuffer(),glFramebuffer=glContext.createFramebuffer(),glBatchCount=0,new Float32Array([0,0,1,0,0,1,1,1]));glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer),glContext.bufferData(glContext.ARRAY_BUFFER,glInstanceData,glContext.STATIC_DRAW);let offset,shader,stride;glInstanceData=(name,type,typeSize,size,divisor=0)=>{var name=glContext.getAttribLocation(shader,name),normalize=1===typeSize,fixedStride=typeSize&&stride;glContext.enableVertexAttribArray(name),glContext.vertexAttribPointer(name,size,type,normalize,fixedStride,offset),glContext.vertexAttribDivisor(name,divisor),offset+=size*typeSize};glInstancedVAO=glContext.createVertexArray(),glContext.bindVertexArray(glInstancedVAO),offset=0,shader=glShader,stride=gl_INSTANCE_BYTE_STRIDE,glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer),glInstanceData("g",glContext.FLOAT,0,2),glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer),glContext.bufferData(glContext.ARRAY_BUFFER,gl_ARRAY_BUFFER_SIZE,glContext.DYNAMIC_DRAW),glInstanceData("p",glContext.FLOAT,4,4,1),glInstanceData("u",glContext.FLOAT,4,4,1),glInstanceData("c",glContext.UNSIGNED_BYTE,1,4,1),glInstanceData("a",glContext.UNSIGNED_BYTE,1,4,1),glInstanceData("r",glContext.FLOAT,4,1,1),glPolyVAO=glContext.createVertexArray(),glContext.bindVertexArray(glPolyVAO),offset=0,shader=glPolyShader,stride=gl_POLY_VERTEX_BYTE_STRIDE,glInstanceData("p",glContext.FLOAT,4,2),glInstanceData("c",glContext.UNSIGNED_BYTE,1,4)}}(rootElement||=document.body);let styleRoot="margin:0;overflow:hidden;background:#000;user-select:none;-webkit-user-select:none;touch-action:none;-webkit-touch-callout:none;";function handleTouch(e){if(touchInputEnable){if(inputLastTouchTime=performance.now(),soundEnable&&!headlessMode&&audioContext&&!audioIsRunning()&&audioContext.resume(),!touchGamepadEnable||touchGamepadPassthrough){const isGamepadTouch=t=>touchGamepadSideZones.includes(t.target)||t.target===touchGamepadZoneC;var gameTouches=[];for(const t of e.touches)isGamepadTouch(t)||gameTouches.push(t);var pressTouch,touch,mousePosScreenLast,touching=gameTouches.length;touching?(pos=vec2((touch=(pressTouch=!inputWasTouching&&"touchstart"==e.type&&[...e.changedTouches].find(t=>!isGamepadTouch(t)))||gameTouches[0]).clientX,touch.clientY),mousePosScreenLast=mousePosScreen,mousePosScreen=mouseEventToScreen(pos),inputWasTouching&&touch.identifier===inputTouchIdentifier?mouseDeltaScreen=mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast)):pressTouch&&(inputData[0][0]=3),inputTouchIdentifier=touch.identifier):inputWasTouching&&1&inputData[0][0]&&(inputData[0][0]=2&inputData[0][0]|4),inputWasTouching=touching}var pos;return inputPreventDefault&&e.cancelable&&document.hasFocus()&&!isOnControl(e)&&(pos=document.activeElement,"touchstart"==e.type&&isTextInput(pos)&&!pos.contains(e.target)&&pos.blur(),e.preventDefault()),!0}inputWasTouching&&1&inputData[0][0]&&(inputData[0][0]=2&inputData[0][0]|4),inputWasTouching=0}function isOnControl(e){return e.target?.closest?.("input,textarea,select,button,a,label,[contenteditable]")}function isTextInput(element){var tag=element?.tagName;return"INPUT"===tag?!["button","checkbox","color","file","image","radio","range","reset","submit"].includes(element.type):!!element?.isContentEditable||"TEXTAREA"===tag||"SELECT"===tag}function mouseEventToScreen(mousePos){var rect=mainCanvas.getBoundingClientRect(),px=percent(mousePos.x,rect.left,rect.right),mousePos=percent(mousePos.y,rect.top,rect.bottom);return vec2(px*mainCanvasSize.x,mousePos*mainCanvasSize.y)}rootElement!==document.body&&rootElement.clientHeight&&"static"===getComputedStyle(rootElement).position&&(styleRoot+="position:relative;"),rootElement.style.cssText=styleRoot+rootElement.style.cssText,mainCanvas=rootElement.appendChild(document.createElement("canvas")),drawContext=mainContext=mainCanvas.getContext("2d"),headlessMode||(document.addEventListener("keydown",function(e){soundEnable&&!headlessMode&&audioContext&&!audioIsRunning()&&audioContext.resume();var typing=isTextInput(e.target)||isTextInput(document.activeElement);e.repeat||typing||(inputKeysHeld.add(e.code),inputWASDEmulateDirection&&inputKeysHeld.has(inputArrowToWASD[e.code])&&1&inputData[0][e.code]||(inputData[0][e.code]=3),(remap=function(k){return inputWASDEmulateDirection&&inputWASDToArrow[k]||k}(e.code))===e.code)||1&inputData[0][remap]||(inputData[0][remap]=3);var remap;inputPreventDefault&&e.cancelable&&document.hasFocus()&&(e.ctrlKey||e.metaKey||e.altKey||!typing&&(remap="string"==typeof e.key&&1===e.key.length,["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Space","Tab","Backspace"].includes(e.code)||remap)&&e.preventDefault())}),document.addEventListener("keyup",function onKeyUp(e){inputKeysHeld.delete(e.code);const remap=inputWASDToArrow[e.code]||e.code;for(const key of remap===e.code?[e.code]:[e.code,remap])inputKeysHeld.has(key)||inputWASDEmulateDirection&&inputKeysHeld.has(inputArrowToWASD[key])||1&inputData[0][key]&&(inputData[0][key]=2&inputData[0][key]|4);if("Meta"===e.key)for(const code of[...inputKeysHeld])onKeyUp({code:code})}),document.addEventListener("mousedown",function(e){var mousePosScreenLast;touchInputEnable&&inputIsTouchMouseEvent(e)||(soundEnable&&!headlessMode&&audioContext&&!audioIsRunning()&&audioContext.resume(),inputData[0][e.button]=3,mousePosScreenLast=mousePosScreen,mousePosScreen=mouseEventToScreen(vec2(e.x,e.y)),mouseDeltaScreen=mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast)),inputPreventDefault&&e.cancelable&&document.hasFocus()&&!isOnControl(e)&&(isTextInput(mousePosScreenLast=document.activeElement)&&!mousePosScreenLast.contains(e.target)&&mousePosScreenLast.blur(),e.preventDefault()))}),document.addEventListener("mouseup",function(e){touchInputEnable&&inputIsTouchMouseEvent(e)||(1&inputData[0][e.button]&&(inputData[0][e.button]=2&inputData[0][e.button]|4),inputPreventDefault&&e.cancelable&&(3===e.button||4===e.button)&&e.preventDefault())}),document.addEventListener("mousemove",function(e){mouseInWindow=!0;var mousePosScreenLast=mousePosScreen;mousePosScreen=mouseEventToScreen(vec2(e.x,e.y));let movement;{var rect;movement=pointerLockIsActive()?(rect=mainCanvas.getBoundingClientRect(),vec2(e.movementX*mainCanvasSize.x/rect.width,e.movementY*mainCanvasSize.y/rect.height)):mousePosScreen.subtract(mousePosScreenLast)}mouseDeltaScreen=mouseDeltaScreen.add(movement)}),document.addEventListener("mouseleave",function(){mouseInWindow=!1}),document.addEventListener("wheel",function(e){var target=e.target;target?.closest?.("input,textarea,select,[contenteditable]")||(e.ctrlKey||(mouseWheel+=sign(e.deltaY)),inputPreventDefault&&e.cancelable&&document.hasFocus()&&e.preventDefault())},{passive:!1}),document.addEventListener("contextmenu",function(e){inputPreventDefault&&!isTextInput(e.target)&&e.preventDefault()}),addEventListener("blur",function(){inputClear()}),isTouchDevice&&(document.addEventListener("touchstart",e=>handleTouch(e),{passive:!1}),document.addEventListener("touchmove",e=>handleTouch(e),{passive:!1}),document.addEventListener("touchend",e=>handleTouch(e),{passive:!1}),document.addEventListener("touchcancel",e=>handleTouch(e),{passive:!1}))),soundEnable&&!headlessMode&&document.addEventListener("visibilitychange",audioVisibilityChange);rootElement="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)",rootElement=(mainCanvas.style.cssText=rootElement,glCanvas&&(glCanvas.style.cssText=rootElement),setCanvasPixelated(canvasPixelated),engineUpdateCanvas(),glPreRender(),workContext=createCanvasContext(64),workCanvas=workContext.canvas,workReadContext=createCanvasContext(64,64,!0),workReadCanvas=workReadContext.canvas,imageSources.map((src,i)=>loadTexture(i,src)));imageSources.length||rootElement.push(loadTexture(0)),rootElement.push(async function(){const image=new Image,tilePos=(await new Promise(resolve=>{image.onerror=image.onload=resolve,image.crossOrigin="anonymous",image.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAeAQMAAABnrVXaAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAAjpJREFUOMu9kzFu2zAUhn+CAROgqrk+B2l0BWYxMjlXeYaAtFtbdA1sGgHqRQfI0CNkSG5AwYB0BQ8d5Bsomwah6CPVeGg6tEPzAxLwyI+P78cP4u9lNO9OoMKnLMOobG5020/yaj/MrRcCGh1gBbyiLTPJEYaIiom5KM9Jq7KgynMGtb6L4GL4MF2H4LQKCXTvDVw2I4MsgZT7QLExdiutH+D08VOP3INXRrWX1/mmpbkNgAPYRVANb4xpcegYvhiNbIXauQICEjBuYLfMakaakWQeXxiZ0VDtuJCKs3ztMV59QtsHJNcRxDzfdL21ty3PrfIcXTN+E+GFAv6T5nbT9jd50/WFxb5ksdAv49qS6ouymG66ji08UMT6moykYLAo+V0j23GN4m829ZySAD5K7QsBfQTvOG8eE+gTeGYRAmnNAubN3hf5Zv9tJWDHp/VTuaSm7SN4fyINQqaNO3RMVxvpSPXnOChnRNvFcGY0gnwiPswYwTKVPE0zVtX3mTEIOoFzaqLrGuJaV+Uqumb71fVk/VoOH3cdLNQP/FHi8hV0CQNoqBZsUPlLPMsdCJro9QAaQQ0woDy9BJm0eTxCFnO9srcYlhNVlfR2EyTrph1uUtbUtAJifwRgrKuYdXVHeb0YI3QpawohQHkloI3J5FuVwI5ORxC9k2Tuz9Ir1IjgeIPGMHYkAZe2RuYkmWFmt3gGbTPOmBUWVTmRmHtGrfpzG/yuQNOKa6gBB/WA9khitPgl6/GP+gl2Af6tCbvaygAAAABJRU5ErkJggg=="}),vec2()),tileSize=vec2(8),textureInfo=new TextureInfo(image),tileInfo=new TileInfo(tilePos,tileSize,textureInfo,1,0);engineImageFont=new ImageFont(tileInfo)}()),showSplashScreen&&(imageSources=new Promise(resolve=>{let t=0;!function updateSplash(){inputClear();drawEngineLogo(t+=.01);1<t?resolve():setTimeout(updateSplash,16)}()}),rootElement.push(imageSources)),await Promise.all(rootElement)}return startEngine()}function engineUpdate(frameTimeMS=0){var manualStepAtStart=engineManualStep;let frameTimeDeltaMS=frameTimeMS-frameTimeLastMS;frameTimeLastMS||(frameTimeDeltaMS=0),frameTimeLastMS=frameTimeMS,(debug||debugWatermark)&&(averageFPS=lerp(averageFPS,1e3/(frameTimeDeltaMS||1),.05)),audioUpdateVolume();var frameTimeMS=debug&&debugKeysAlways,debugSpeedUp=frameTimeMS&&keyIsDown("Equal"),frameTimeMS=frameTimeMS&&keyIsDown("Minus"),debugScale=debugSpeedUp?10:frameTimeMS?.1:1,debugSpeedUp=frameTimeDeltaMS,frameTimeMS=(timeReal+=frameTimeDeltaMS*debugScale/1e3,timeScale*debugScale),frozen=(frameTimeDeltaMS*=frameTimeMS,paused||!frameTimeMS);frameTimeBufferMS+=frozen?debugSpeedUp:frameTimeDeltaMS,frameTimeBufferMS=min(frameTimeBufferMS,50*(frozen?1:max(1,frameTimeMS)));let wasUpdated=!1,deltaSmooth=0;for(frameTimeBufferMS<0&&-9<frameTimeBufferMS&&(deltaSmooth=frameTimeBufferMS,frameTimeBufferMS=0);0<=frameTimeBufferMS;frameTimeBufferMS-=1e3/frameRate){var frozenTick=paused||!(timeScale*debugScale);if(frozenTick||(time=frame++/frameRate),wasUpdated=!0,engineUpdateCanvas(),!function(){if(!headlessMode){if(touchInputEnable&&isTouchDevice||document.hasFocus()||inputClear(),mousePos=screenToWorld(mousePosScreen),mouseDelta=screenToWorldDelta(mouseDeltaScreen),!touchGamepadOverlay&&touchGamepadEnable&&isTouchDevice&&!headlessMode&&document.body){var overlay=touchGamepadOverlay=document.createElement("div");overlay.style.cssText="position:fixed;inset:0;z-index:50;pointer-events:none;opacity:0;touch-action:none;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;transition:opacity .2s;box-sizing:border-box;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)";const stage=touchGamepadStage=document.createElement("div");stage.style.cssText="position:relative;width:100%;height:100%;pointer-events:none",overlay.appendChild(stage);var svg=touchGamepadSvg=document.createElementNS(touchGamepadSvgNS,"svg"),svg=(svg.style.cssText="position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible;fill:none;stroke:#fff;stroke-width:3",stage.appendChild(svg),()=>{const z=document.createElement("div");return z.style.cssText="position:absolute;pointer-events:auto;touch-action:none",z.addEventListener("pointerdown",e=>function(e,zone){var W,H,hit,side;touchGamepadEnable&&"mouse"!==e.pointerType&&(e.preventDefault(),zone.setPointerCapture(e.pointerId),touchGamepadTimer.set(),soundEnable&&!headlessMode&&audioContext&&!audioIsRunning()&&audioContext.resume(),paused?touchGamepadCenterButtonSize&&!touchGamepadButtons[9]&&(touchGamepadButtons[9]=touchGamepadButtonsPressed[9]=1,touchGamepadPointerRole.set(e.pointerId,"start")):(zone=touchGamepadStageRect(),W=zone.width,H=zone.height,zone=vec2(e.clientX-zone.left,e.clientY-zone.top),(hit=function(p,W,H){var S=touchGamepadSize,leftHalf=p.x<W/2,floatTop=.4*H;if(touchGamepadCenterButtonSize&&vec2(W/2,H/2).distance(p)<touchGamepadCenterButtonSize&&!function(p,W,H){for(let side=0;side<2;side++)if(touchGamepadStartBlockSide(side)&&touchGamepadSideCenter(side,W,H).distance(p)<2*touchGamepadSize)return 1;return}(p,W,H))return{role:"start"};for(let side=0;side<2;side++){var onHalf=side?!leftHalf:leftHalf;if(touchGamepadSideStick(side)){var otherControl=touchGamepadSideHasControl(side?0:1);if(touchGamepadFloating?(!otherControl||onHalf)&&p.y>floatTop:onHalf&&touchGamepadSideCenter(side,W,H).distance(p)<2*S)return{role:"stick",side:side}}else if(1<=touchGamepadSideButtonCount(side)){otherControl=function(side,p,W,H){var count=touchGamepadSideButtonCount(side),base=side?0:4,W=touchGamepadSideCenter(side,W,H);if(W.distance(p)>=touchGamepadSize)return-1;if(1===count)return base;H=W.subtract(p);side||(H.x*=-1);W=2===count?H.x<H.y?1:0:mod(H.direction()+2,4);return(W=touchGamepadFaceIndex(W))<count?base+W:-1}(side,p,W,H);if(0<=otherControl)return{role:"face",btn:otherControl}}}}(zone,W,H))&&("stick"===hit.role?(side=hit.side,void 0===touchGamepadStickPointerId[side]&&(touchGamepadStickAnchors[side]=touchGamepadFloating?zone:touchGamepadSideCenter(side,W,H),touchGamepadStickPointerId[side]=e.pointerId,touchGamepadPointerRole.set(e.pointerId,"stick"+side),touchGamepadNeedRelayout=!0,touchGamepadApplyStick(side,zone),touchGamepadButtonsPressed[touchGamepadStickButton(side)]=1)):"face"===hit.role?touchGamepadButtons[hit.btn]||(touchGamepadButtons[hit.btn]=touchGamepadButtonsPressed[hit.btn]=1,touchGamepadPointerRole.set(e.pointerId,"face"+hit.btn)):touchGamepadButtons[9]||(touchGamepadButtons[9]=touchGamepadButtonsPressed[9]=1,touchGamepadPointerRole.set(e.pointerId,"start")))))}(e,z)),z.addEventListener("pointermove",e=>function(e){var role=touchGamepadPointerRole.get(e.pointerId);role&&(e.preventDefault(),e=function(e){var r=touchGamepadStageRect();return vec2(e.clientX-r.left,e.clientY-r.top)}(e),"stick0"!==role&&"stick1"!==role||touchGamepadApplyStick("stick1"===role?1:0,e))}(e)),z.addEventListener("pointerup",e=>touchGamepadPointerUp(e)),z.addEventListener("pointercancel",e=>touchGamepadPointerUp(e)),stage.appendChild(z),z});touchGamepadSideZones[0]=svg(),touchGamepadSideZones[1]=svg(),touchGamepadZoneC=svg(),addEventListener("resize",()=>touchGamepadNeedRelayout=!0),document.body.appendChild(overlay),touchGamepadNeedRelayout=!0}{const applyDeadZones=v=>{var deadZone=v=>.3<v?percent(v,.3,.8):v<-.3?-percent(-v,.3,.8):0;return vec2(deadZone(v.x),deadZone(-v.y)).clampLength()};if(touchGamepadEnable&&isTouchDevice&&touchGamepadTimer.isSet()&&gamepadsEnable&&function(){for(const gamepad of inputGetGamepads())if(gamepad){for(const button of gamepad.buttons)if(button.pressed)return 1;if("standard"===gamepad.mapping)for(let j=0;j<4&&j<gamepad.axes.length;++j)if(.5<abs(gamepad.axes[j]))return 1}return}()&&touchGamepadTimer.unset(),touchGamepadEnable&&isTouchDevice&&touchGamepadTimer.isSet()){gamepadPrimary=0;var out,x,touchStick,sticks=gamepadStickData[0]??(gamepadStickData[0]=[]),dpad=gamepadDpadData[0]??(gamepadDpadData[0]=vec2());sticks.length=0,dpad.set();for(let side=0;side<2;side++)touchGamepadSideStick(side)&&(out=touchGamepadStickOut(side),sticks[out]=vec2(),touchStick=touchGamepadSticks[side]??vec2(),touchGamepadAnalog?sticks[out]=applyDeadZones(touchStick):.3<touchStick.lengthSquared()&&(x=clamp(round(touchStick.x),-1,1),touchStick=clamp(round(touchStick.y),-1,1),sticks[out]=vec2(x,-touchStick).clampLength(),out||dpad.set(x,-touchStick)));var data=inputData[1]??(inputData[1]=[]);for(let i=12;i--;){var wasDown=gamepadIsDown(i,0),isDown=touchGamepadButtons[i],pressed=touchGamepadButtonsPressed[i]||isDown&&!wasDown;data[i]=isDown?pressed?3:1:pressed?6:wasDown?4:0,touchGamepadVibration&&2&data[i]&&(9===i||function(i){for(let side=0;side<2;side++){var base=touchGamepadSideButtonBase(side);if(!touchGamepadSideStick(side)&&base<=i&&i<base+touchGamepadSideButtonCount(side))return 1}return}(i))&&vibrate(touchGamepadVibration)}touchGamepadButtonsPressed.length=0,gamepadButtonsLast[0]=void 0}else if(!gamepadsEnable||debug||document.hasFocus()){var gamepads=gamepadsEnable?inputGetGamepads():[];for(let i=0;i<8;++i){const gamepad=gamepads[i];if(gamepad){var data=inputData[i+1]??(inputData[i+1]=[]),buttonsLast=gamepadButtonsLast[i]??(gamepadButtonsLast[i]=[]),sticks=gamepadStickData[i]??(gamepadStickData[i]=[]),dpad=gamepadDpadData[i]??(gamepadDpadData[i]=vec2());const isStandard="standard"===gamepad.mapping,centered=gamepadAxisCentered[i]??(gamepadAxisCentered[i]=[]);var readAxis=j=>{var frames,v=gamepad.axes[j];return isStandard&&j<4||!gamepadAxisFilterEnable||(frames=0|centered[j])>gamepadAxisCenteredFrames?v:(centered[j]=abs(v)<.3?1+frames:0,0)};for(let j=0;j<gamepad.axes.length-1;j+=2)sticks[j>>1]=applyDeadZones(vec2(readAxis(j),readAxis(j+1)));let hadInput=sticks.some(stick=>.25<stick.lengthSquared());for(let j=gamepad.buttons.length;j--;){var button=gamepad.buttons[j],wasDown=buttonsLast[j];buttonsLast[j]=button.pressed,data[j]=button.pressed?wasDown?1:3:wasDown?4:0,button.pressed&&(!button.value||.9<button.value)&&(hadInput=!0)}hadInput&&(gamepadHadInput[i]=!0,gamepadHadInput[gamepadPrimary]||(gamepadPrimary=i)),"standard"===gamepad.mapping&&dpad.set((gamepadIsDown(15,i)&&1)-(gamepadIsDown(14,i)&&1),(gamepadIsDown(12,i)&&1)-(gamepadIsDown(13,i)&&1)),gamepadDirectionEmulateStick&&(dpad.x||dpad.y)&&(sticks[0]=dpad.clampLength())}else inputData[i+1]=void 0,gamepadStickData[i]=void 0,gamepadDpadData[i]=void 0,gamepadHadInput[i]=void 0,gamepadAxisCentered[i]=void 0,gamepadButtonsLast[i]=void 0}touchGamepadEnable&&isUsingGamepad&&touchGamepadTimer.unset()}else gamepadButtonsLast.length=0}{svg=mouseIsDown(0)||mouseIsDown(1)||mouseIsDown(2)||mouseDeltaScreen.length()>inputMouseMoveThreshold;let gamepadActive=!1;for(let s=gamepadStickCount();s--&&!gamepadActive;)gamepadActive=.2<gamepadStick(s).lengthSquared();for(let b=17;b--&&!gamepadActive;)gamepadActive=gamepadIsDown(b);let keyboardActive=!1;for(const k in inputData[0])if(isNaN(+k)&&1&inputData[0][k]){keyboardActive=!0;break}gamepadActive?lastInputDevice="gamepad":svg?lastInputDevice="mouse":keyboardActive&&(lastInputDevice="keyboard"),isUsingGamepad="gamepad"===lastInputDevice}}}(),frozenTick||gameUpdate(),pluginList.forEach(plugin=>plugin.update?.()),frozenTick){for(const o of engineObjects)o.parent||o.updateTransforms();engineObjects=engineObjects.filter(o=>!o.destroyed)}else engineObjectsUpdate();if(gameUpdatePost(),!headlessMode){for(const deviceInputData of inputData)for(const i in deviceInputData)deviceInputData[i]&=1;mouseWheel=0,mouseDelta=vec2(),mouseDeltaScreen=vec2()}}frameTimeBufferMS+=deltaSmooth,engineManualStep&&!manualStepAtStart&&(frameTimeBufferMS=-500/frameRate);let windowChanged=!1;function renderFrame(){if(!headlessMode){wasUpdated||engineUpdateCanvas(),mainContext.imageSmoothingEnabled=!tilesPixelated,glPreRender(),setShader(),pluginList.forEach(plugin=>plugin.preRender?.()),gameRender(),engineObjects.sort((a,b)=>a.renderOrder-b.renderOrder);for(const o of engineObjects)o.destroyed||(setShader(o.shader),o.render());if(setShader(),gameRenderPost(),setShader(),setAdditiveBlendMode(!1),pluginList.forEach(plugin=>plugin.render?.()),touchGamepadOverlay&&!headlessMode)if(touchGamepadEnable&&isTouchDevice){touchGamepadOverlay.style.display="";var dbg=debug&&debugGamepads,layout=[touchGamepadButtonCount,touchGamepadLeftButtonCount,touchGamepadLeftStick,touchGamepadRightStick,touchGamepadAnalog,touchGamepadSize,touchGamepadFloating,touchGamepadCenterButtonSize,paused,dbg].join(),layout=(layout!==touchGamepadLastLayout&&(touchGamepadLastLayout=layout,touchGamepadNeedRelayout=!0),touchGamepadNeedRelayout&&!function(){if(touchGamepadOverlay){var r=touchGamepadStageRect(),W=r.width,r=r.height,S=touchGamepadSize,setZone=(z,css)=>z.style.cssText="position:absolute;pointer-events:auto;touch-action:none;"+css;if(paused){for(const zone of touchGamepadSideZones)zone.style.display="none";touchGamepadCenterButtonSize?(setZone(touchGamepadZoneC,"inset:0"),touchGamepadZoneC.style.display=""):touchGamepadZoneC.style.display="none"}else{for(let side=0;side<2;side++){var width,zone=touchGamepadSideZones[side],edge=side?"right":"left";zone.style.display=touchGamepadSideHasControl(side)?"":"none",touchGamepadFloating&&touchGamepadSideStick(side)?(width=touchGamepadSideHasControl(side?0:1)?"50%":"100%",setZone(zone,edge+`:0;bottom:0;width:${width};height:60%`)):setZone(zone,edge+`:0;bottom:0;width:${3*S}px;height:${3*S}px`)}touchGamepadZoneC.style.display=touchGamepadCenterButtonSize?"":"none";var c=touchGamepadCenterButtonSize;setZone(touchGamepadZoneC,`left:50%;top:50%;width:${2*c}px;height:${2*c}px;transform:translate(-50%,-50%)`)}!function(W,H){const svg=touchGamepadSvg;for(;svg.firstChild;)svg.removeChild(svg.firstChild);const els=touchGamepadSvgEls={face:[],thumb:[]},S=touchGamepadSize,circle=(cx,cy,rr,fill)=>{var c=document.createElementNS(touchGamepadSvgNS,"circle");return c.setAttribute("cx",cx),c.setAttribute("cy",cy),c.setAttribute("r",rr),fill&&c.setAttribute("fill",fill),svg.appendChild(c),c};for(let side=0;side<2;side++){var count=touchGamepadSideButtonCount(side),base=touchGamepadSideButtonBase(side),ctr=touchGamepadSideCenter(side,W,H);if(touchGamepadSideStick(side))touchGamepadAnalog?circle(ctr.x,ctr.y,S/2):(ctr=>{var a=.18*S,b=.5*S,x=ctr.x,ctr=ctr.y,p=document.createElementNS(touchGamepadSvgNS,"path");p.setAttribute("d",`M ${x-a} ${ctr-b} H ${x+a} V ${ctr-a} H ${x+b} V ${ctr+a} H ${x+a} `+`V ${ctr+b} H ${x-a} V ${ctr+a} H ${x-b} V ${ctr-a} H ${x-a} Z`),svg.appendChild(p)})(ctr),els.thumb[side]=circle(ctr.x,ctr.y,S/4,"#fff");else if(1===count)els.face[base]=circle(ctr.x,ctr.y,S/2,"#000");else for(let i=0;i<count;i++){var button,j=mod(i-1,4),j=(button=touchGamepadFaceIndex(2<count?j:min(j,count-1)),vec2().setDirection(j,S/2)),j=(2===count&&(j.x*=-1),side||(j.x*=-1),ctr.add(j));els.face[base+button]=circle(j.x,j.y,S/4,"#000")}}debug&&debugGamepads&&!function(W,H){const S=touchGamepadSize,svg=touchGamepadSvg,shape=(tag,attrs,stroke)=>{var el=document.createElementNS(touchGamepadSvgNS,tag);for(const k in attrs)el.setAttribute(k,attrs[k]);el.setAttribute("stroke",stroke),el.setAttribute("stroke-width",2),el.setAttribute("fill","none"),svg.appendChild(el)},ring=(c,rr,stroke)=>shape("circle",{cx:c.x,cy:c.y,r:rr},stroke);shape("line",{x1:W/2,y1:0,x2:W/2,y2:H},"#0f0");for(let side=0;side<2;side++){var top,full,x;touchGamepadSideStick(side)?touchGamepadFloating?(top=.4*H,full=!touchGamepadSideHasControl(side?0:1),x=!full&&side?W/2:0,shape("rect",{x:x,y:top,width:full?W:W/2,height:H-top},"#0ff")):ring(touchGamepadSideCenter(side,W,H),2*S,"#0ff"):1<=touchGamepadSideButtonCount(side)&&ring(touchGamepadSideCenter(side,W,H),S,"#0ff")}if(touchGamepadCenterButtonSize){ring(vec2(W/2,H/2),touchGamepadCenterButtonSize,"#ff0");for(let side=0;side<2;side++)touchGamepadStartBlockSide(side)&&ring(touchGamepadSideCenter(side,W,H),2*S,"#f0f")}}(W,H)}(W,r),touchGamepadNeedRelayout=!1}}(),touchGamepadDisplayTime&&!touchGamepadPointerRole.size?percent(touchGamepadTimer.get(),touchGamepadDisplayTime+1,touchGamepadDisplayTime):1),visible=dbg||touchGamepadTimer.isSet()&&0<layout&&!paused;if(touchGamepadOverlay.style.opacity=visible?dbg?1:layout*touchGamepadAlpha:0,visible){var t,dbg=touchGamepadStageRect(),W=dbg.width,H=dbg.height,S=touchGamepadSize,els=touchGamepadSvgEls;if(els){for(let side=0;side<2;side++)touchGamepadSideStick(side)&&els.thumb[side]&&(t=touchGamepadSideCenter(side,W,H).add((touchGamepadSticks[side]??vec2()).scale(S/2)),els.thumb[side].setAttribute("cx",t.x),els.thumb[side].setAttribute("cy",t.y));for(let i=0;i<els.face.length;i++)els.face[i]&&els.face[i].setAttribute("fill",touchGamepadButtons[i]?"#fff":"#000")}}}else"none"!==touchGamepadOverlay.style.display&&(touchGamepadOverlay.style.display="none",touchGamepadPointerRole.clear(),touchGamepadButtons.length=0,touchGamepadButtonsPressed.length=0,touchGamepadSticks.length=0,touchGamepadStickPointerId.length=0,inputData[1]=void 0,gamepadStickData[0]=gamepadDpadData[0]=void 0);glFlush(),drawCount=0,primitiveCount=0}}headlessMode||(debugSpeedUp=devicePixelRatio,windowChanged=windowWidthLast!==innerWidth||windowHeightLast!==innerHeight||windowPixelRatioLast!==debugSpeedUp,windowWidthLast=innerWidth,windowHeightLast=innerHeight,windowPixelRatioLast=debugSpeedUp),(wasUpdated||windowChanged)&&renderFrame(),engineManualStep||engineScheduleFrame()}async function startEngine(){await gameInit(),engineUpdateInternal=engineUpdate,engineManualStep||engineUpdate()}}function engineUpdateCanvas(){if(!headlessMode){var dpr=getCanvasPixelRatio();if(canvasFixedSize.x){mainCanvasSize=canvasFixedSize.copy();var innerAspect=innerWidth/innerHeight,fixedAspect=canvasFixedSize.x/canvasFixedSize.y,w=innerAspect<fixedAspect?"100%":"",innerAspect=innerAspect<fixedAspect?"":"100%";mainCanvas.style.width=w,mainCanvas.style.height=innerAspect,glCanvas&&(glCanvas.style.width=w,glCanvas.style.height=innerAspect)}else{mainCanvasSize.x=0|min(innerWidth,canvasMaxSize.x),mainCanvasSize.y=0|min(innerHeight,canvasMaxSize.y);const innerAspect=mainCanvasSize.x/mainCanvasSize.y;if(canvasMaxAspect&&innerAspect>canvasMaxAspect){const w=mainCanvasSize.y*canvasMaxAspect|0;mainCanvasSize.x=min(w,canvasMaxSize.x)}else if(innerAspect<canvasMinAspect){const h=mainCanvasSize.x/canvasMinAspect|0;mainCanvasSize.y=min(h,canvasMaxSize.y)}mainCanvas.style.width=mainCanvasSize.x+"px",mainCanvas.style.height=mainCanvasSize.y+"px",glCanvas&&(glCanvas.style.width=mainCanvasSize.x+"px",glCanvas.style.height=mainCanvasSize.y+"px")}fixedAspect=mainCanvasSize.x*dpr|0,w=mainCanvasSize.y*dpr|0;mainCanvas.width!==fixedAspect||mainCanvas.height!==w?(mainCanvas.width=fixedAspect,mainCanvas.height=w):(mainContext.setTransform(1,0,0,1,0,0),mainContext.globalCompositeOperation="source-over",mainContext.clearRect(0,0,fixedAspect,w)),mainContext.setTransform(dpr,0,0,dpr,0,0),0<canvasClearColor.a&&!glEnable&&(mainContext.fillStyle=canvasClearColor.toString(),mainContext.fillRect(0,0,mainCanvasSize.x,mainCanvasSize.y),mainContext.fillStyle=BLACK.toString()),mainContext.lineJoin="round",mainContext.lineCap="round"}}function engineScheduleFrame(){if(!engineFrameScheduled){engineFrameScheduled=!0;const next=frameTimeMS=>{engineFrameScheduled=!1,engineManualStep||engineUpdateInternal(frameTimeMS)};"function"==typeof requestAnimationFrame?requestAnimationFrame(next):setTimeout(()=>next(performance.now()),1e3/frameRate)}}function engineStep(frames=1){if(engineManualStep&&engineUpdateInternal)for(let i=frames=min(frames,36e3);0<i&&engineManualStep;--i)engineUpdateInternal(frameTimeLastMS+1e3/frameRate)}function engineObjectsUpdate(){++engineObjectsUpdateCount,engineObjectsCollidePairs.clear(),engineObjects.sort((a,b)=>a.renderOrder-b.renderOrder),engineObjectsCollide=engineObjects.filter(o=>o.collideSolidObjects),engineObjectsCollideStaticLast=engineObjectsCollide.filter(o=>o.mass).concat(engineObjectsCollide.filter(o=>!o.mass));for(const o of engineObjects)o.parent||o.destroyed||o.updatePhysics();function updateChildObjects(children){if(children.length){var o,start=engineChildStack.length;for(const child of children)engineChildStack.push(child);for(let i=start;i<engineChildStack.length;++i)(o=engineChildStack[i]).destroyed||o.updatePass===pass||(o.updatePass=pass,o.updateTransforms(!1),o.update(),o.children.length&&o.updateTransforms(!1),updateChildObjects(o.children));engineChildStack.length=start}}const pass=engineObjectsUpdateCount;function updateTopObject(o){o.parent||o.destroyed||o.updatePass===pass||(o.updatePass=pass,o.update(),updateChildObjects(o.children),o.updateTransforms())}for(const o of engineObjects)updateTopObject(o);for(const o of engineObjects)updateTopObject(o);engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsDestroy(immediate=!0){for(const o of engineObjects)o.parent||o.persistent||o.destroy(immediate);engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsCollect(pos,size,objects=engineObjects,testCenters=!1){var collectedObjects=[];if(pos)if(!size||size instanceof Vector2){var boxSize=size instanceof Vector2?size:vec2();for(const o of objects)o.destroyed||(testCenters?isOverlapping(pos,boxSize,o.pos):o.isOverlapping(pos,boxSize))&&collectedObjects.push(o)}else{var dx,radiusSquared=(size/2)**2;for(const o of objects)o.destroyed||(dx=testCenters?pos.x-o.pos.x:max(abs(pos.x-o.pos.x)-o.size.x/2,0))*dx+(dx=testCenters?pos.y-o.pos.y:max(abs(pos.y-o.pos.y)-o.size.y/2,0))*dx<radiusSquared&&collectedObjects.push(o)}else for(const o of objects)o.destroyed||collectedObjects.push(o);return collectedObjects}function engineObjectsCallback(pos,size,callbackFunction,objects=engineObjects,testCenters=!1){for(const o of engineObjectsCollect(pos,size,objects,testCenters))o.destroyed||callbackFunction(o)}function engineObjectsRaycast(start,end,objects=engineObjects){var hitObjects=[];for(const o of objects)o.collideRaycast&&!o.destroyed&&isIntersecting(start,end,o.pos,o.size)&&hitObjects.push(o);return hitObjects}let debugWatermark=0,debugKey="",debugKeysAlways=!1,debugTweakables=!1;const debug=0,debugOverlay=0,debugGamepads=0;var debugPointSize=.5;function ASSERT(){}function LOG(){}function debugRect(){}function debugPoly(){}function debugCircle(){}function debugPoint(){}function debugLine(){}function debugOverlap(){}function debugText(){}function debugClear(){}function debugScreenshot(){}function debugShowErrors(){}function setDebugOverlay(){}function debugVideoCaptureIsActive(){return!1}function debugVideoCaptureStart(){}function debugVideoCaptureStop(){}function tweak(){}function tweakButton(){}function tweakDivider(){}function tweakEngineDefaults(){}var levelEditor={isOpen:!1,open(){},close(){},onTile:void 0,onRestart:void 0,onPlayFrom:void 0};function debugBox3D(){}function debugSphere3D(){}function debugLine3D(){}function debugPoint3D(){}const PI=Math.PI,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,min=Math.min,max=Math.max,sign=x=>Math.sign(x),hypot=(...values)=>Math.hypot(...values),log2=x=>Math.log2(x),sin=Math.sin,cos=Math.cos,tan=Math.tan,atan2=Math.atan2;function mod(dividend,divisor=1){return(dividend%divisor+divisor)%divisor}function clamp(value,min=0,max=1){return value<min?min:max<value?max:value}function percent(value,valueA,valueB){return(valueB-=valueA)?clamp((value-valueA)/valueB):0}function lerp(valueA,valueB,percent){return valueA+clamp(percent)*(valueB-valueA)}function percentLerp(value,percentA,percentB,lerpA,lerpB){return lerp(lerpA,lerpB,percent(value,percentA,percentB))}function distanceWrap(valueA,valueB,wrapSize=1){valueA=(valueA-valueB)%wrapSize;return 2*valueA%wrapSize-valueA}function lerpWrap(valueA,valueB,percent,wrapSize=1){return valueA+clamp(percent)*distanceWrap(valueB,valueA,wrapSize)}function distanceAngle(angleA,angleB){return distanceWrap(angleA,angleB,2*PI)}function lerpAngle(angleA,angleB,percent){return lerpWrap(angleA,angleB,percent,2*PI)}function smoothStep(percent){return(percent=clamp(percent))*percent*(3-2*percent)}function isPowerOfTwo(value){return 0<value&&value%1==0&&2**round(log2(value))===value}function nearestPowerOfTwo(value){return 2**ceil(log2(value))}function isOverlapping(posA,sizeA,posB,sizeB=vec2()){var dx=2*(posA.x-posB.x),posA=2*(posA.y-posB.y),posB=sizeA.x+sizeB.x,sizeA=sizeA.y+sizeB.y;return abs(dx)<posB&&abs(posA)<sizeA}function isIntersecting(start,end,pos,size){var pos=pos.subtract(size.scale(.5)),size=pos.add(size),end=end.subtract(start),pos=start.subtract(pos),start=start.subtract(size),p=[-end.x,end.x,-end.y,end.y],q=[pos.x,-start.x,pos.y,-start.y];let tMin=0,tMax=1;for(let i=4;i--;)if(p[i]){var t=q[i]/p[i];if(p[i]<0){if(t>tMax)return!1;tMin=max(t,tMin)}else{if(t<tMin)return!1;tMax=min(t,tMax)}}else if(q[i]<0)return!1;return!0}function collideCircleCircle(posA,radiusA,posB,radiusB){posA=posA.subtract(posB),posB=radiusA+radiusB,radiusA=posA.length();if(!(posB<=radiusA))return posA.normalize(posB-radiusA)}function collideCircleBox(pos,radius,boxPos,boxSize){var boxSize=boxSize.scale(.5),closest=vec2(clamp(pos.x,boxPos.x-boxSize.x,boxPos.x+boxSize.x),clamp(pos.y,boxPos.y-boxSize.y,boxPos.y+boxSize.y)),closest=pos.subtract(closest),distSq=closest.lengthSquared();return distSq?radius*radius<=distSq?void 0:closest.normalize(radius-distSq**.5):pushOutAxis(closest=pos.subtract(boxPos),boxSize.x-abs(closest.x),boxSize.y-abs(closest.y),radius)}function collideBoxBox(posA,sizeA,posB,sizeB){posA=posA.subtract(posB),posB=(sizeA.x+sizeB.x)/2-abs(posA.x),sizeA=(sizeA.y+sizeB.y)/2-abs(posA.y);if(!(posB<=0||sizeA<=0))return pushOutAxis(posA,posB,sizeA)}function pushOutAxis(d,penX,penY,extra=0){var s=v=>0<=v?1:-1;return penX<=penY?vec2(s(d.x)*(penX+extra),0):vec2(0,s(d.y)*(penY+extra))}function oscillate(frequency=1,amplitude=1,t=time,offset=0,type=0){offset=mod(offset+t*frequency,1);let value;return amplitude/2*((value=1===type?1-2*abs(2*offset-1):2===type?offset<.5?-1:1:3===type?2*offset-1:-cos(2*offset*PI))+1)}function isNumber(n){return"number"==typeof n&&!isNaN(n)}function isStringLike(s){return null!=s&&"function"==typeof s.toString&&"string"==typeof s.toString()}function isArray(a){return Array.isArray(a)}function lineTest(posStart,posEnd,testFunction,normal){var dx=posEnd.x-posStart.x,posEnd=posEnd.y-posStart.y,totalLength=(dx*dx+posEnd*posEnd)**.5;if(totalLength){var hitPos,hitPosFloor,pos=posStart.floor(),dirX=dx/totalLength,dirY=posEnd/totalLength,stepX=sign(dirX),stepY=sign(dirY),tDeltaX=dirX?abs(1/dirX):1/0,tDeltaY=dirY?abs(1/dirY):1/0,dx=0<stepX?pos.x+1:pos.x,posEnd=0<stepY?pos.y+1:pos.y;let t=0,tX=dirX?(dx-posStart.x)/dirX:1/0,tY=dirY?(posEnd-posStart.y)/dirY:1/0,wasX=tDeltaX<tDeltaY;for(;t<totalLength;){if(testFunction(pos))return(hitPosFloor=(hitPos=vec2(posStart.x+dirX*t,posStart.y+dirY*t)).floor()).x<pos.x?hitPos.x=pos.x:hitPosFloor.x>pos.x&&(hitPos.x=pos.x+1-1e-9),hitPosFloor.y<pos.y?hitPos.y=pos.y:hitPosFloor.y>pos.y&&(hitPos.y=pos.y+1-1e-9),normal&&(wasX?normal.set(-stepX,0):normal.set(0,-stepY)),hitPos;(wasX=tX<tY)?(pos.x+=stepX,t=tX,tX+=tDeltaX):(pos.y+=stepY,t=tY,tY+=tDeltaY)}}}function rand(valueA=1,valueB=0){return valueB+Math.random()*(valueA-valueB)}function randInt(valueA,valueB=0){return floor(rand(valueA,valueB))}function randBool(chance=.5){return rand()<chance}function randSign(){return 2*randInt(2)-1}function randVec2(length=1){return(new Vector2).setAngle(rand(2*PI),length)}function randInCircle(radius=1,minRadius=0){return radius<=0?new Vector2:randVec2(radius*rand((minRadius=clamp(minRadius/radius))*minRadius,1)**.5)}function randColor(colorA=new Color,colorB=new Color(0,0,0,1),linear=!1){return linear?colorA.lerp(colorB,rand()):new Color(rand(colorA.r,colorB.r),rand(colorA.g,colorB.g),rand(colorA.b,colorB.b),rand(colorA.a,colorB.a))}class RandomGenerator{constructor(seed=123456789){this.seed=seed}float(valueA=1,valueB=0){return this.seed=0|this.seed||123456789,this.seed^=this.seed<<13,this.seed^=this.seed>>>17,this.seed^=this.seed<<5,valueB+(valueA-valueB)*noiseHash(this.seed)}int(valueA,valueB=0){return floor(this.float(valueA,valueB))}bool(chance=.5){return this.float()<chance}sign(){return.5<this.float()?1:-1}floatSign(valueA=1,valueB=0){var lo=min(valueA,valueB),valueA=max(valueA,valueB)-lo,valueB=this.float(2*valueA);return valueB<valueA?lo+valueB:valueA-lo-valueB}angle(){return this.float(-PI,PI)}vec2(valueA=1,valueB=0){return vec2(this.float(valueA,valueB),this.float(valueA,valueB))}direction(length=1){return(new Vector2).setAngle(this.float(2*PI),length)}randColor(colorA=new Color,colorB=new Color(0,0,0,1),linear=!1){return linear?colorA.lerp(colorB,this.float()):new Color(this.float(colorA.r,colorB.r),this.float(colorA.g,colorB.g),this.float(colorA.b,colorB.b),this.float(colorA.a,colorB.a))}mutateColor(color,amount=.05,alphaAmount=0){return new Color(color.r+this.float(amount,-amount),color.g+this.float(amount,-amount),color.b+this.float(amount,-amount),color.a+this.float(alphaAmount,-alphaAmount)).clamp()}}function vec2(x=0,y){return new Vector2(x,y??x)}function isVector2(v){return v instanceof Vector2&&v.isValid()}class Vector2{constructor(x=0,y=0){this.x=x,this.y=y}set(x=0,y=0){return this.x=x,this.y=y,this}setFrom(v){return this.set(v.x,v.y)}copy(){return new Vector2(this.x,this.y)}add(v){return new Vector2(this.x+v.x,this.y+v.y)}subtract(v){return new Vector2(this.x-v.x,this.y-v.y)}multiply(v){return new Vector2(this.x*v.x,this.y*v.y)}divide(v){return new Vector2(this.x/v.x,this.y/v.y)}scale(s){return new Vector2(this.x*s,this.y*s)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(v){return this.distanceSquared(v)**.5}distanceSquared(v){return(this.x-v.x)**2+(this.y-v.y)**2}normalize(length=1){var l=this.length();return l?this.scale(length/l):new Vector2(0,length)}clampLength(length=1){var l=this.length();return length<l?this.scale(length/l):this.copy()}dot(v){return this.x*v.x+this.y*v.y}cross(v){return this.x*v.y-this.y*v.x}reflect(normal,restitution=1){return this.subtract(normal.scale((1+restitution)*this.dot(normal)))}angle(){return atan2(this.x,this.y)}setAngle(angle=0,length=1){return this.x=length*sin(angle),this.y=length*cos(angle),this}rotate(angle){var c=cos(-angle),angle=sin(-angle);return new Vector2(this.x*c-this.y*angle,this.x*angle+this.y*c)}setDirection(direction,length=1){return direction=mod(direction,4),this.x=direction%2?direction-1?-length:length:0,this.y=direction%2?0:direction?-length:length,this}direction(){return abs(this.x)>abs(this.y)?this.x<0?3:1:this.y<0?2:0}abs(){return new Vector2(abs(this.x),abs(this.y))}floor(){return new Vector2(floor(this.x),floor(this.y))}round(){return new Vector2(round(this.x),round(this.y))}snap(grid){return new Vector2(floor(this.x*grid)/grid,floor(this.y*grid)/grid)}mod(divisor=1){return new Vector2(mod(this.x,divisor),mod(this.y,divisor))}area(){return abs(this.x*this.y)}lerp(v,percent){percent=clamp(percent);return new Vector2(v.x*percent+this.x*(1-percent),v.y*percent+this.y*(1-percent))}arrayCheck(arraySize){return 0<=this.x&&0<=this.y&&this.x<arraySize.x&&this.y<arraySize.y}toString(digits=3){return this.isValid()?`(${(this.x<0?"":" ")+this.x.toFixed(digits)},${(this.y<0?"":" ")+this.y.toFixed(digits)} )`:`(${this.x}, ${this.y})`}isValid(){return isNumber(this.x)&&isNumber(this.y)}}function rgb(r,g,b,a){return new Color(r,g,b,a)}function hsl(h,s,l,a){return(new Color).setHSLA(h,s,l,a)}function isColor(c){return c instanceof Color&&c.isValid()}class Color{constructor(r=1,g=1,b=1,a=1){this.r=r,this.g=g,this.b=b,this.a=a}set(r=1,g=1,b=1,a=1){return this.r=r,this.g=g,this.b=b,this.a=a,this}setFrom(c){return this.set(c.r,c.g,c.b,c.a)}setAlpha(a=1){return this.a=a,this}copy(){return new Color(this.r,this.g,this.b,this.a)}withAlpha(a=1){return new Color(this.r,this.g,this.b,a)}add(c){return new Color(this.r+c.r,this.g+c.g,this.b+c.b,this.a+c.a)}subtract(c){return new Color(this.r-c.r,this.g-c.g,this.b-c.b,this.a-c.a)}multiply(c){return new Color(this.r*c.r,this.g*c.g,this.b*c.b,this.a*c.a)}divide(c){return new Color(this.r/c.r,this.g/c.g,this.b/c.b,this.a/c.a)}scale(scale,alphaScale=scale){return new Color(this.r*scale,this.g*scale,this.b*scale,this.a*alphaScale)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(c,percent){percent=clamp(percent);return new Color(c.r*percent+this.r*(1-percent),c.g*percent+this.g*(1-percent),c.b*percent+this.b*(1-percent),c.a*percent+this.a*(1-percent))}setHSLA(h=0,s=0,l=1,a=1){h=mod(h,1),s=clamp(s);var s=(l=clamp(l))<.5?l*(1+s):l+s-l*s,l=2*l-s,f=(p,q,t)=>6*(t=mod(t,1))<1?p+6*(q-p)*t:2*t<1?q:3*t<2?p+(q-p)*(4-6*t):p;return this.r=f(l,s,h+1/3),this.g=f(l,s,h),this.b=f(l,s,h-1/3),this.a=a,this}HSLA(){var d,r=clamp(this.r),g=clamp(this.g),b=clamp(this.b),a=clamp(this.a),maxC=max(r,g,b),minC=min(r,g,b),l=(maxC+minC)/2;let h=0,s=0;return maxC!==minC&&(d=maxC-minC,s=.5<l?d/(2-maxC-minC):d/(maxC+minC),r===maxC?h=(g-b)/d+(g<b?6:0):g===maxC?h=(b-r)/d+2:b===maxC&&(h=(r-g)/d+4)),[h/6,s,l,a]}mutate(amount=.05,alphaAmount=0){return new Color(this.r+rand(amount,-amount),this.g+rand(amount,-amount),this.b+rand(amount,-amount),this.a+rand(alphaAmount,-alphaAmount)).clamp()}toString(useAlpha=!0){var toHex;return debug&&!this.isValid()?"#000":"#"+(toHex=c=>((c=round(255*clamp(c)))<16?"0":"")+c.toString(16))(this.r)+toHex(this.g)+toHex(this.b)+(useAlpha?toHex(this.a):"")}setHex(hex){if(hex.length<6){var fromHex=c=>clamp(parseInt(hex[c],16)/15);this.r=fromHex(1),this.g=fromHex(2),this.b=fromHex(3),this.a=5===hex.length?fromHex(4):1}else{const fromHex=c=>clamp(parseInt(hex.slice(c,c+2),16)/255);this.r=fromHex(1),this.g=fromHex(3),this.b=fromHex(5),this.a=9===hex.length?fromHex(7):1}return this}rgbaInt(){return(255*clamp(this.r)+.5|0)+(255*clamp(this.g)+.5<<8)+(255*clamp(this.b)+.5<<16)+(255*clamp(this.a)+.5<<24)}isValid(){return isNumber(this.r)&&isNumber(this.g)&&isNumber(this.b)&&isNumber(this.a)}}const WHITE=rgb(),CLEAR_WHITE=rgb(1,1,1,0),BLACK=rgb(0,0,0),CLEAR_BLACK=rgb(0,0,0,0);var GRAY=rgb(.5,.5,.5);const RED=rgb(1,0,0);var ORANGE=rgb(1,.5,0);const YELLOW=rgb(1,1,0),GREEN=rgb(0,1,0);var CYAN=rgb(0,1,1);const BLUE=rgb(0,0,1),PURPLE=rgb(.5,0,1);var MAGENTA=rgb(1,0,1);class Timer{constructor(timeLeft,useRealTime=!1){this.useRealTime=useRealTime;useRealTime=this.getGlobalTime();this.time=void 0===timeLeft?void 0:useRealTime+timeLeft,this.setTime=timeLeft}set(timeLeft=0){var globalTime=this.getGlobalTime();this.time=globalTime+timeLeft,this.setTime=timeLeft}setUseRealTime(useRealTime=!0){this.useRealTime=useRealTime}unset(){this.time=void 0}isSet(){return void 0!==this.time}active(){return this.getGlobalTime()<this.time}elapsed(){return this.getGlobalTime()>=this.time}get(){return this.isSet()?this.getGlobalTime()-this.time:0}getPercent(){return this.isSet()?this.setTime?1-percent(this.time-this.getGlobalTime(),0,this.setTime):1:0}getSetTime(){return this.isSet()?this.setTime:0}getGlobalTime(){return this.useRealTime?timeReal:time}toString(){return this.isSet()?abs(this.get())+" seconds "+(this.get()<0?"before":"after"):"unset"}valueOf(){return this.get()}}function formatTime(t){return(t<=-1?"-":"")+((t=0|abs(t))/60|0)+":"+(t%60<10?"0":"")+t%60}async function fetchJSON(url){var response=await fetch(url);if(response.ok)return await response.json();throw new Error(`Failed to fetch JSON from ${url}: ${response.status} `+response.statusText)}function saveText(text,filename="text",type="text/plain"){saveDataURL(URL.createObjectURL(new Blob([text],{type:type})),filename,1e3)}function createCanvasContext(width,height=width,willReadFrequently=!1){return new OffscreenCanvas(width,height).getContext("2d",{willReadFrequently:willReadFrequently})}function saveCanvas(canvas,filename="screenshot",type="image/png"){var saveCanvas;canvas instanceof OffscreenCanvas?((saveCanvas=document.createElement("canvas")).width=canvas.width,saveCanvas.height=canvas.height,saveCanvas.getContext("2d").drawImage(canvas,0,0),saveDataURL(saveCanvas.toDataURL(type),filename)):saveDataURL(canvas.toDataURL(type),filename)}function saveDataURL(url,filename="download",revokeTime){var link=document.createElement("a");link.download=filename,link.href=url,link.click(),void 0!==revokeTime&&setTimeout(()=>URL.revokeObjectURL(url),revokeTime)}function shareURL(title,url,callback){navigator.share?.({title:title,url:url}).then(()=>callback?.(),()=>{})}function readSaveData(saveName,defaultSaveData){let loadedData={};try{var data=localStorage.getItem(saveName);if(data)try{loadedData=JSON.parse(data)}catch{}}catch{}return{...defaultSaveData,...loadedData}}function writeSaveData(saveName,saveData){try{localStorage.setItem(saveName,JSON.stringify(saveData))}catch{}}function noiseHash(i){i=2654435769^(0|i),i=Math.imul(i^i>>>16,2246822507);return i=Math.imul(i^i>>>13,3266489909),((i^=i>>>16)>>>0)/2**32}function noise1D(x){var i=floor(x);return lerp(noiseHash(i),noiseHash(i+1),smoothStep(x-i))}function noise2D(x,y){var ix=floor(x),iy=floor(y),x=smoothStep(x-ix),y=smoothStep(y-iy),h=(a,b)=>noiseHash(a+374761393*b);return lerp(lerp(h(ix,iy),h(ix+1,iy),x),lerp(h(ix,iy+1),h(ix+1,iy+1),x),y)}let cameraPos=vec2(),cameraAngle=0,cameraScale=32,timeScale=1,canvasColorTiles=!0,canvasClearColor=CLEAR_BLACK,canvasMaxSize=vec2(3840,2160),canvasMinAspect=0,canvasMaxAspect=0,canvasFixedSize=vec2(),canvasPixelated=!1,tilesPixelated=!0,canvasPixelRatio=1,fontDefault="arial",showSplashScreen=!1,headlessMode=!1,engineManualStep=!1,glEnable=!0,drawScreenSpace=!1,glCircleSides=32,tileDefaultSize=vec2(16),tileDefaultPadding=0,tileDefaultBleed=0,enablePhysicsSolver=!0,objectDefaultMass=1,objectDefaultDamping=1,objectDefaultAngleDamping=1,objectDefaultRestitution=0,objectDefaultFriction=.8,objectMaxSpeed=1,gravity=vec2(),particleEmitRateScale=1,gamepadsEnable=!0,gamepadDirectionEmulateStick=!0,gamepadAxisFilterEnable=!0,inputWASDEmulateDirection=!0,touchInputEnable=!0,touchGamepadEnable=!1,touchGamepadPassthrough=!1,touchGamepadCenterButtonSize=0,touchGamepadButtonCount=4,touchGamepadLeftStick=!0,touchGamepadLeftButtonCount=0,touchGamepadRightStick=!1,touchGamepadAnalog=!0,touchGamepadFloating=!1,touchGamepadSize=100,touchGamepadAlpha=.3,touchGamepadDisplayTime=3,touchGamepadVibration=0,vibrateEnable=!0,soundEnable=!0,soundVolume=.3,soundDefaultRange=40,soundDefaultTaper=.7,soundPauseWhenHidden=!0;function setCameraPos(pos){cameraPos=pos.copy()}function setCameraAngle(angle){cameraAngle=angle}function setCameraScale(scale){cameraScale=scale}function setTimeScale(scale){timeScale=max(0,scale)}function setCanvasColorTiles(colorTiles){canvasColorTiles=colorTiles}function setCanvasClearColor(color){canvasClearColor=color.copy()}function setCanvasMaxSize(size){canvasMaxSize=size.copy()}function setCanvasMinAspect(aspect){canvasMinAspect=aspect}function setCanvasMaxAspect(aspect){canvasMaxAspect=aspect}function setCanvasFixedSize(size){canvasFixedSize=size.copy()}function setCanvasPixelated(pixelated){canvasPixelated=pixelated,mainCanvas&&(mainCanvas.style.imageRendering=pixelated?"pixelated":""),glCanvas&&(glCanvas.style.imageRendering=pixelated?"pixelated":"")}function setTilesPixelated(pixelated){tilesPixelated=pixelated}function setCanvasPixelRatio(pixelRatio){canvasPixelRatio=pixelRatio}function getCanvasPixelRatio(){return canvasPixelRatio??(devicePixelRatio||1)}function setFontDefault(font){fontDefault=font}function setShowSplashScreen(show){showSplashScreen=show}function setHeadlessMode(headless){headlessMode=headless}function setEngineManualStep(enable=!0){var resume=engineManualStep&&!enable;enable&&!engineManualStep&&(frameTimeBufferMS=-500/frameRate),engineManualStep=enable,resume&&engineUpdateInternal&&(frameTimeLastMS=performance.now(),engineScheduleFrame())}function setDrawScreenSpace(screenSpace){drawScreenSpace=screenSpace}function setGLEnable(enable){enable&&!glCanBeEnabled?console.warn("Can not enable WebGL if it was disabled on start."):(glEnable=enable,glCanvas&&(glCanvas.style.display=enable?"":"none"))}function setGLCircleSides(sides){glCircleSides=sides}function setTileDefaultSize(size){tileDefaultSize=size.copy()}function setTileDefaultPadding(padding){tileDefaultPadding=padding}function setTileDefaultBleed(bleed){tileDefaultBleed=bleed}function setEnablePhysicsSolver(enable){enablePhysicsSolver=enable}function setObjectDefaultMass(mass){objectDefaultMass=mass}function setObjectDefaultDamping(damp){objectDefaultDamping=damp}function setObjectDefaultAngleDamping(damp){objectDefaultAngleDamping=damp}function setObjectDefaultRestitution(restitution){objectDefaultRestitution=restitution}function setObjectDefaultFriction(friction){objectDefaultFriction=friction}function setObjectMaxSpeed(speed){objectMaxSpeed=speed}function setGravity(newGravity){gravity=newGravity.copy()}function setParticleEmitRateScale(scale){particleEmitRateScale=scale}function setGamepadsEnable(enable){gamepadsEnable=enable}function setGamepadDirectionEmulateStick(enable){gamepadDirectionEmulateStick=enable}function setGamepadAxisFilterEnable(enable){gamepadAxisFilterEnable=enable}function setInputWASDEmulateDirection(enable){inputWASDEmulateDirection=enable}function setTouchInputEnable(enable){touchInputEnable=enable}function setTouchGamepadEnable(enable){touchGamepadEnable=enable}function setTouchGamepadPassthrough(passthrough){touchGamepadPassthrough=passthrough}function setTouchGamepadCenterButtonSize(size){touchGamepadCenterButtonSize=size}function setTouchGamepadButtonCount(count){0<(touchGamepadButtonCount=count)&&(touchGamepadRightStick=!1)}function setTouchGamepadLeftStick(enable){(touchGamepadLeftStick=enable)&&(touchGamepadLeftButtonCount=0)}function setTouchGamepadLeftButtonCount(count){0<(touchGamepadLeftButtonCount=count)&&(touchGamepadLeftStick=!1)}function setTouchGamepadRightStick(rightStick){(touchGamepadRightStick=rightStick)&&(touchGamepadButtonCount=0)}function setTouchGamepadAnalog(analog){touchGamepadAnalog=analog}function setTouchGamepadFloating(floating){touchGamepadFloating=floating}function setTouchGamepadSize(size){touchGamepadSize=size}function setTouchGamepadAlpha(alpha){touchGamepadAlpha=alpha}function setTouchGamepadDisplayTime(time){touchGamepadDisplayTime=time}function setTouchGamepadVibration(ms){touchGamepadVibration=ms}function setVibrateEnable(enable){vibrateEnable=enable}function setSoundEnable(enable){soundEnable=enable}function setSoundVolume(volume){soundVolume=volume,audioUpdateVolume()}function setSoundDefaultRange(range){soundDefaultRange=range}function setSoundDefaultTaper(taper){soundDefaultTaper=taper}function setSoundPauseWhenHidden(pause){soundPauseWhenHidden=pause}function setDebugWatermark(show){debugWatermark=show}function setDebugKey(key){debugKey=key}function setDebugKeysAlways(enable=!0){debugKeysAlways=enable}function setDebugTweakables(show=!0){debugTweakables=show}class EngineObject{constructor(pos=vec2(),size=vec2(1),tileInfo,angle=0,color=WHITE,renderOrder=0){this.pos=pos.copy(),this.size=size.copy(),this.drawSize=void 0,this.tileInfo=tileInfo,this.angle=angle,this.color=color.copy(),this.additiveColor=void 0,this.shader=void 0,this.castShadow=!0,this.emissive=0,this.mirror=!1,this.destroyed=!1,this.updatePass=0,this.mass=objectDefaultMass,this.damping=objectDefaultDamping,this.angleDamping=objectDefaultAngleDamping,this.restitution=objectDefaultRestitution,this.friction=objectDefaultFriction,this.gravityScale=1,this.renderOrder=renderOrder,this.velocity=vec2(),this.angleVelocity=0,this.spawnTime=time,this.children=[],this.clampSpeed=!0,this.groundObject=void 0,this.parent=void 0,this.localPos=void 0,this.localAngle=0,this.collideLevel=!1,this.collideSolidObjects=!1,this.isSolid=!1,this.collideRaycast=!1,this.persistent=!1,engineObjects.push(this)}updateTransforms(updateChildren=!0){var mirror,pp,lx,lp,c,s,parent=this.parent;if(parent&&(mirror=parent.getMirrorSign(),lp=this.localPos,pp=parent.pos,lx=lp.x*mirror,lp=lp.y,(parent=parent.angle)?(c=cos(-parent),s=sin(-parent),this.pos.set(lx*c-lp*s+pp.x,lx*s+lp*c+pp.y)):this.pos.set(lx+pp.x,lp+pp.y),this.angle=mirror*this.localAngle+parent),updateChildren)for(const child of this.children)child.updateTransforms()}updatePhysics(){if(!this.destroyed){this.clampSpeed&&(this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed),this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed));var solve=enablePhysicsSolver&&this.mass,oldPos=solve?this.pos.copy():void 0;if(this.velocity.x*=this.damping,this.velocity.y*=this.damping,this.mass&&(this.velocity.x+=gravity.x*this.gravityScale,this.velocity.y+=gravity.y*this.gravityScale),this.pos.x+=this.velocity.x,this.pos.y+=this.velocity.y,this.angle+=this.angleVelocity*=this.angleDamping,solve){var gravityY=this.gravityScale<0?-gravity.y:gravity.y,wasFalling=this.velocity.y<0&&gravityY<0||0<this.velocity.y&&0<gravityY;if(this.groundObject&&(solve=max(this.friction,this.groundObject.friction),groundSpeed=this.groundObject.velocity.x,this.velocity.x=groundSpeed+(this.velocity.x-groundSpeed)*solve,this.groundObject=void 0),this.collideSolidObjects&&this.size.x&&this.size.y)for(const o of engineObjectsCollideStaticLast)if(!o.destroyed&&!o.parent&&o!==this&&o.size.x&&o.size.y&&(this.isSolid||o.isSolid)&&this.isOverlappingObject(o)){asker=o,other=this;var asker=engineObjectsCollidePairs.get(asker)?.get(other);if(!1!==asker){if(!asker){var other=this.collideWithObject(o),asker=o.collideWithObject(this);if(!other||!asker){engineObjectsCollidePairAdd(this,o);continue}}other=isOverlapping(oldPos,this.size,o.pos,o.size);if(!other||o.mass&&!o.groundObject)if(other){asker=oldPos.subtract(o.pos),other=asker.length(),asker=other<.001?vec2(0,.001):asker.scale(.001/other);this.velocity=this.velocity.add(asker),o.mass&&(o.velocity=o.velocity.subtract(asker)),engineObjectsCollidePairAdd(this,o)}else{var elastic1,other=this.size.add(o.size),asker=2*(0<gravityY?o.pos.y-oldPos.y:oldPos.y-o.pos.y)>other.y-abs(gravityY),isBlockedX=2*abs(oldPos.y-o.pos.y)<other.y,isBlockedY=2*abs(oldPos.x-o.pos.x)<other.x,restitution=max(this.restitution,o.restitution);if(!asker&&!isBlockedY&&isBlockedX||(this.pos.y=o.pos.y+(other.y/2+.001)*sign(oldPos.y-o.pos.y),o.groundObject&&wasFalling||!o.mass?(wasFalling&&(this.groundObject=o),this.velocity.y=o.velocity.y-(this.velocity.y-o.velocity.y)*restitution):(isBlockedY=(this.mass*this.velocity.y+o.mass*o.velocity.y)/(this.mass+o.mass),elastic0=this.velocity.y*(this.mass-o.mass)/(this.mass+o.mass)+2*o.velocity.y*o.mass/(this.mass+o.mass),elastic1=o.velocity.y*(o.mass-this.mass)/(this.mass+o.mass)+2*this.velocity.y*this.mass/(this.mass+o.mass),this.velocity.y=lerp(isBlockedY,elastic0,restitution),o.velocity.y=lerp(isBlockedY,elastic1,restitution))),!asker&&isBlockedX)if(this.pos.x=o.pos.x+(other.x/2+.001)*sign(oldPos.x-o.pos.x),o.mass){const inelastic=(this.mass*this.velocity.x+o.mass*o.velocity.x)/(this.mass+o.mass),elastic0=this.velocity.x*(this.mass-o.mass)/(this.mass+o.mass)+2*o.velocity.x*o.mass/(this.mass+o.mass),elastic1=o.velocity.x*(o.mass-this.mass)/(this.mass+o.mass)+2*this.velocity.x*this.mass/(this.mass+o.mass);this.velocity.x=lerp(inelastic,elastic0,restitution),o.velocity.x=lerp(inelastic,elastic1,restitution)}else this.velocity.x=o.velocity.x-(this.velocity.x-o.velocity.x)*restitution;engineObjectsCollidePairAdd(this,o,!0)}else{var elastic0=collideBoxBox(this.pos,this.size,o.pos,o.size);if(elastic0){this.pos.x+=elastic0.x+.001*sign(elastic0.x),this.pos.y+=elastic0.y+.001*sign(elastic0.y);const restitution=max(this.restitution,o.restitution);if(elastic0.x){isBlockedY=this.velocity.x-o.velocity.x;isBlockedY*elastic0.x<0&&(this.velocity.x=o.velocity.x-isBlockedY*restitution)}else{const v=this.velocity.y-o.velocity.y;v*elastic0.y<0&&(this.velocity.y=o.velocity.y-v*restitution),elastic0.y*gravityY<0&&(this.groundObject=o)}}engineObjectsCollidePairAdd(this,o)}}}if(this.collideLevel){var groundSpeed=tileCollisionTest(this.pos,this.size,this);if(groundSpeed&&!tileCollisionTest(oldPos,this.size,this)){const isBlockedX=tileCollisionTest(vec2(this.pos.x,oldPos.y),this.size,this),isBlockedY=tileCollisionTest(vec2(oldPos.x,this.pos.y),this.size,this),restitution=max(this.restitution,groundSpeed.restitution);if(isBlockedX){const epsilon=.001;solve=0<gravityY?ceil(oldPos.y+this.size.y/2-1)-this.size.y/2-.001:floor(oldPos.y-this.size.y/2+1)+this.size.y/2+.001;if(abs(solve-this.pos.y)<.1&&!tileCollisionTest(vec2(this.pos.x,solve),this.size,this))return void(this.pos.y=solve);this.pos.x=oldPos.x,this.velocity.x*=-restitution}if(isBlockedY||!isBlockedX){if(wasFalling){const epsilon=1e-4;var solve=this.size.y/2+1e-4,layerY=groundSpeed.pos.y;this.pos.y=layerY+(gravityY<0?floor(oldPos.y-layerY-this.size.y/2)+solve:ceil(oldPos.y-layerY+this.size.y/2)-solve),this.groundObject=groundSpeed}else this.pos.y=oldPos.y,this.groundObject=void 0;this.velocity.y*=-restitution}}}}}}update(){}render(){drawTile(this.pos,this.drawSize||this.size,this.tileInfo,this.color,this.angle,this.mirror,this.additiveColor,glEnable,!1)}renderLight(){}renderShadow(){this.render()}renderEmissive(){this.render()}destroy(immediate=!1){if(!this.destroyed){this.destroyed=!0,this.parent?.removeChild(this);for(const child of this.children)child.parent=void 0,child.destroy(immediate)}}localToWorld(pos){return this.pos.add(pos.rotate(this.angle))}worldToLocal(pos){return pos.subtract(this.pos).rotate(-this.angle)}localToWorldVector(vec){return vec.rotate(this.angle)}worldToLocalVector(vec){return vec.rotate(-this.angle)}collideWithTile(tileData,pos){return 0<tileData}collideWithObject(object,push){return!0}getUp(scale=1){return vec2().setAngle(this.angle,scale)}getRight(scale=1){return vec2().setAngle(this.angle+PI/2,scale)}getAliveTime(){return time-this.spawnTime}getSpeed(){return this.velocity.length()}applyAcceleration(acceleration){this.mass&&(this.velocity=this.velocity.add(acceleration))}applyAngularAcceleration(acceleration){this.mass&&(this.angleVelocity+=acceleration)}applyForce(force){this.mass&&this.applyAcceleration(force.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(child,localPos=vec2(),localAngle=0){if(!this.destroyed){for(let p=this;p;p=p.parent);this.children.push(child),child.parent=this,child.localPos=localPos.copy(),child.localAngle=localAngle,child.updateTransforms()}return child}attach(child){child.parent?.removeChild(child);var mirror=this.getMirrorSign(),local=this.worldToLocal(child.pos);return local.x*=mirror,this.addChild(child,local,mirror*(child.angle-this.angle))}removeChild(child){var i=this.children.indexOf(child);i<0||(this.children.splice(i,1),child.parent=child.localPos=void 0)}isOverlappingObject(object){return this.isOverlapping(object.pos,object.size)}isOverlapping(pos,size=vec2()){return isOverlapping(this.pos,this.size,pos,size)}setCollision(collideSolidObjects=!0,isSolid=!0,collideLevel=!0,collideRaycast=!0){this.collideSolidObjects=collideSolidObjects,this.isSolid=isSolid,this.collideLevel=collideLevel,this.collideRaycast=collideRaycast}get collideTiles(){return this.collideLevel}set collideTiles(collide){this.collideLevel=collide}toString(){let text="type = "+this.constructor.name;return(this.pos.x||this.pos.y)&&(text+="\npos = "+this.pos),(this.velocity.x||this.velocity.y)&&(text+="\nvelocity = "+this.velocity),(this.size.x||this.size.y)&&(text+="\nsize = "+this.size),this.angle&&(text+="\nangle = "+this.angle.toFixed(3)),text+="\ncolor = "+this.color}renderDebugInfo(){var hasPhysics,size;debug&&((hasPhysics=this.collideLevel||this.collideSolidObjects||this.isSolid)||this.parent)&&(size=vec2(max(this.size.x,.2),max(this.size.y,.2)),rgb(this.collideLevel?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,.5),this.pos,hasPhysics||this.angle,this.parent&&(this.pos,size.scale(.8),rgb(1,1,1,.5),this.angle),this.parent)&&(this.pos,this.parent.pos,rgb(1,1,1,.5))}}let mainCanvas,mainContext,drawContext,workCanvas,workContext,workReadCanvas,workReadContext,backgroundCanvas,mainCanvasSize=vec2(),textureInfos=[],drawCount=0,primitiveCount=0;function isWhite(c){return 1<=c.r&&1<=c.g&&1<=c.b}function isBlack(c){return c.r<=0&&c.g<=0&&c.b<=0&&c.a<=0}function tile(index=0,size=tileDefaultSize,texture=0,padding=tileDefaultPadding,bleed=tileDefaultBleed){"number"==typeof size&&(size=new Vector2(size,size));texture="number"==typeof texture?textureInfos[texture]:texture;if(headlessMode&&!texture?.size.x)return new TileInfo(new Vector2,size.copy(),texture,padding,bleed);var sizePaddedX=size.x+2*padding,sizePaddedY=size.y+2*padding;let x,y;y="number"==typeof index?(cols=texture.size.x/sizePaddedX|0,x=index%cols,index/cols|0):(x=index.x,index.y);var cols=new Vector2(x*sizePaddedX+padding,y*sizePaddedY+padding);return new TileInfo(cols,size,texture,padding,bleed)}class TileInfo{constructor(pos=vec2(),size=tileDefaultSize,textureInfo=textureInfos[0],padding=tileDefaultPadding,bleed=tileDefaultBleed,columns=0){this.pos=pos.copy(),this.size=size.copy(),this.padding=padding,this.textureInfo=textureInfo,this.bleed=bleed,this.columns=columns}offset(offset){return new TileInfo(this.pos.add(offset),this.size,this.textureInfo,this.padding,this.bleed,this.columns)}frame(frame){var w=this.size.x+2*this.padding,h=this.size.y+2*this.padding,w=(this.columns?frame%this.columns:frame)*w,frame=(this.columns?frame/this.columns|0:0)*h;return this.offset(new Vector2(w,frame))}setColumns(columns=0){return this.columns=columns,this}index(index){return tile(index,this.size,this.textureInfo,this.padding,this.bleed).setColumns(this.columns)}setFullImage(textureInfo=this.textureInfo){return this.textureInfo=textureInfo,this.pos=new Vector2,this.size=textureInfo.size.copy(),this.bleed=this.padding=this.columns=0,this}}class TextureInfo{constructor(image,useWebGL=!0,wrap=!1){this.image=image,this.size=image?vec2(image.width,image.height):vec2(),this.sizeInverse=image?vec2(1/image.width,1/image.height):vec2(),this.glTexture=void 0,this.wrap=wrap,useWebGL&&this.createWebGLTexture()}createWebGLTexture(){var image=this.image;image?.width&&(this.size=vec2(image.width,image.height),this.sizeInverse=vec2(1/image.width,1/image.height)),image=this,headlessMode||(glTextureInfos.add(image),glContext&&(image.glTexture?glSetTextureData(image.glTexture,image.image):image.glTexture=glCreateTexture(image.image,image.wrap)))}destroyWebGLTexture(){var textureInfo,glTexture;textureInfo=this,headlessMode||(glTextureInfos.delete(textureInfo),glTexture=textureInfo.glTexture,textureInfo.glTexture=void 0,glTexture&&glTexture===glActiveTexture&&(glFlush(),glActiveTexture=void 0),glMipmapsStale.delete(glTexture),glPremultipliedTextures.delete(glTexture),glDeleteTexture(glTexture))}hasWebGL(){return!!this.glTexture}setWrap(wrap=!0){this.wrap=wrap,glSetTextureWrap(this.glTexture,wrap)}}class SpriteAnimation{constructor(tileInfo,frameCount,frameTime=.1){this.firstTile=tileInfo,this.frameCount=frameCount,this.frameTime=frameTime,this.speed=1,this.mode="loop",this.startTime=time,this.heldFrame=void 0}loop(){return this.restart("loop")}play(){return this.restart("once")}pingPong(){return this.restart("pingPong")}stop(){return this.heldFrame=this.frame,this}restart(mode=this.mode){return this.mode=mode,this.startTime=time,this.heldFrame=void 0,this}get elapsedFrames(){return(time-this.startTime)*this.speed/this.frameTime}get frame(){var n,f,k;return void 0!==this.heldFrame?this.heldFrame:(n=this.frameCount,f=floor(this.elapsedFrames),"once"==this.mode?clamp(this.speed<0?n-1+ceil(this.elapsedFrames):f,0,n-1):"loop"==this.mode?mod(f,n):(k=mod(f,f=max(2*n-2,1)))<n?k:f-k)}get tileInfo(){return this.firstTile.frame(this.frame)}get isDone(){return"once"==this.mode&&void 0===this.heldFrame&&abs(this.elapsedFrames)>=this.frameCount}}class Shader{constructor(fragmentCode){this.fragmentCode=String(fragmentCode),this.program=void 0,this.program3D=void 0,glShaderObjects.push(this)}}function drawTile(pos,size=vec2(1),tileInfo,color=WHITE,angle=0,mirror,additiveColor,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context){const textureInfo=tileInfo?.textureInfo;if(!textureInfo||tileInfo.size.x&&tileInfo.size.y){const bleed=tileInfo?.bleed??0;if(useWebGL&&glEnable){if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}var x,y,w,h,bleedX;textureInfo?(useWebGL=textureInfo.sizeInverse,x=tileInfo.pos.x*useWebGL.x,y=tileInfo.pos.y*useWebGL.y,w=tileInfo.size.x*useWebGL.x,h=tileInfo.size.y*useWebGL.y,glSetTexture(textureInfo.glTexture),bleed?(bleedX=useWebGL.x*bleed,useWebGL=useWebGL.y*bleed,glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x+bleedX,y+useWebGL,x-bleedX+w,y-useWebGL+h,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt())):glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x,y,x+w,y+h,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt())):(bleedX=additiveColor?color.add(additiveColor):color,glDrawUntextured(pos.x,pos.y,size.x,size.y,angle,bleedX.rgbaInt()))}else++drawCount,++primitiveCount,drawCanvas2D(pos,size,angle,mirror,context=>{var y,w,h,x;textureInfo?(context.scale(1,-1),x=tileInfo.pos.x,y=tileInfo.pos.y,w=tileInfo.size.x,h=tileInfo.size.y,function(context,image,sx,sy,sWidth,sHeight,dx,dy,dWidth,dHeight,color,additiveColor,bleed){var sx2=bleed,sy2=bleed,sWidth2=(sWidth=max(1,0|sWidth),sHeight=max(1,0|sHeight),sWidth-2*bleed),bleed=sHeight-2*bleed;!canvasColorTiles||isWhite(color)&&(!additiveColor||isBlack(additiveColor))?(context.globalAlpha=color.a,context.drawImage(image,sx+sx2,sy+sy2,sWidth2,bleed,dx,dy,dWidth,dHeight),context.globalAlpha=1):(workReadCanvas.width=sWidth,workReadCanvas.height=sHeight,workReadContext.drawImage(image,0|sx,0|sy,sWidth,sHeight,0,0,sWidth,sHeight),image=workReadContext.getImageData(0,0,sWidth,sHeight),sx=tintImageData(image.data,color,additiveColor),workReadContext.putImageData(image,0,0),sx||(context.globalAlpha=color.a),context.drawImage(workReadCanvas,sx2,sy2,sWidth2,bleed,dx,dy,dWidth,dHeight),sx||(context.globalAlpha=1))}(context,textureInfo.image,x,y,w,h,-.5,-.5,1,1,color,additiveColor,bleed)):(x=additiveColor?color.add(additiveColor):color,context.fillStyle=x.toString(),context.fillRect(-.5,-.5,1,1))},screenSpace,context)}}}function drawRect(pos,size,color,angle,useWebGL,screenSpace,context){drawTile(pos,size,void 0,color,angle,!1,void 0,useWebGL,screenSpace,context)}function drawRectGradient(pos,size,colorTop=WHITE,colorBottom=CLEAR_WHITE,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context)if(useWebGL&&glEnable){if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}var points=[],colors=[],halfSizeX=size.x/2,halfSizeY=size.y/2,colorTopInt=colorTop.rgbaInt(),colorBottomInt=colorBottom.rgbaInt(),c=cos(-angle),s=sin(-angle);for(let i=4;i--;){var x=1&i?halfSizeX:-halfSizeX,y=2&i?halfSizeY:-halfSizeY,color=2&i?colorTopInt:colorBottomInt;points.push(vec2(pos.x+(x*c-y*s),pos.y+(x*s+y*c))),colors.push(color)}glDrawColoredPoints(points,colors)}else++drawCount,++primitiveCount,drawCanvas2D(pos,size,angle,!1,context=>{var gradient=context.createLinearGradient(0,.5,0,-.5);gradient.addColorStop(0,colorTop.toString()),gradient.addColorStop(1,colorBottom.toString()),context.fillStyle=gradient,context.fillRect(-.5,-.5,1,1)},screenSpace,context)}function drawTextureWrapped(pos,size,wrapCount,texture=0,color=WHITE,angle=0,additiveColor,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context){texture="number"==typeof texture?textureInfos[texture]:texture;if(useWebGL&&glEnable){if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}glSetTexture(texture.glTexture),glDraw(pos.x,pos.y,size.x,size.y,angle,0,0,wrapCount.x,wrapCount.y,color.rgbaInt(),additiveColor&&additiveColor.rgbaInt())}else{++drawCount,++primitiveCount,screenSpace||(pos=worldToScreen(pos),size=size.scale(cameraScale),angle-=cameraAngle);useWebGL=!canvasColorTiles||isWhite(color)&&(!additiveColor||isBlack(additiveColor)),screenSpace=!useWebGL&&additiveColor&&!isBlack(additiveColor),useWebGL=useWebGL?texture.image:function(image,color,additiveColor){var w=0|image.width,h=0|image.height,image=(workReadCanvas.width=w,workReadCanvas.height=h,workReadContext.drawImage(image,0,0),workReadContext.getImageData(0,0,w,h));return tintImageData(image.data,color,additiveColor),workReadContext.putImageData(image,0,0),workReadCanvas}(texture.image,color,additiveColor),texture=((context=context||drawContext).save(),context.translate(pos.x+.5,pos.y+.5),context.rotate(angle),context.globalAlpha=screenSpace?1:color.a,context.createPattern(useWebGL,"repeat")),additiveColor=(new DOMMatrix).translate(-size.x/2,-size.y/2).scale(size.x/(wrapCount.x*useWebGL.width),size.y/(wrapCount.y*useWebGL.height));texture.setTransform(additiveColor),context.fillStyle=texture,context.fillRect(-size.x/2,-size.y/2,size.x,size.y),context.globalAlpha=1,context.restore()}}}function drawLineList(points,width=.1,color=WHITE,wrap=!1,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context)if(useWebGL&&glEnable){let size=vec2(1);if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}glDrawOutlineTransform(points,color.rgbaInt(),width,pos.x,pos.y,size.x,size.y,angle,wrap)}else++drawCount,++primitiveCount,drawCanvas2D(pos,vec2(1),angle,!1,context=>{if(width){context.strokeStyle=color.toString(),context.lineWidth=abs(width),context.beginPath();for(let i=0;i<points.length;++i){var point=points[i];context.lineTo(point.x,point.y)}wrap&&context.closePath(),context.stroke()}},screenSpace,context)}function drawLine(posA,posB,width=.1,color=WHITE,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){posB=vec2((posB.x-posA.x)/2,(posB.y-posA.y)/2),width=vec2(width,2*posB.length()),posA=posA.add(posB);pos=pos.add(angle?posA.rotate(screenSpace?-angle:angle):posA),screenSpace&&(posB.y*=-1),drawRect(pos,width,color,angle+=posB.angle(),useWebGL,screenSpace,context)}function drawRegularPoly(pos,size=vec2(1),sides=3,color=WHITE,lineWidth=0,lineColor=BLACK,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){var points=[],sizeX=size.x/2,sizeY=size.y/2;for(let i=sides;i--;){var a=i/sides*PI*2;points.push(vec2(sin(a)*sizeX,cos(a)*sizeY))}drawPoly(points,color,lineWidth,lineColor,pos,angle,useWebGL,screenSpace,context)}function drawPoly(points,color=WHITE,lineWidth=0,lineColor=BLACK,pos=vec2(),angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context=void 0){if(!headlessMode||context)if(useWebGL&&glEnable){let size=vec2(1);if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}glDrawPointsTransform(points,color.rgbaInt(),pos.x,pos.y,size.x,size.y,angle),0<lineWidth&&glDrawOutlineTransform(points,lineColor.rgbaInt(),lineWidth,pos.x,pos.y,size.x,size.y,angle)}else++drawCount,++primitiveCount,drawCanvas2D(pos,vec2(1),angle,!1,context=>{context.fillStyle=color.toString(),context.beginPath();for(const point of points)context.lineTo(point.x,point.y);context.closePath(),context.fill(),0<lineWidth&&(context.strokeStyle=lineColor.toString(),context.lineWidth=lineWidth,context.stroke())},screenSpace,context)}const drawEllipseRings=new Map;function drawEllipse(pos,size=vec2(1),color=WHITE,angle=0,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context)if(lineWidth=clamp(lineWidth,0,min(abs(size.x),abs(size.y))),useWebGL&&glEnable){var sides=glCircleSides;if(0<lineWidth)drawRegularPoly(pos,size,sides,color,lineWidth,lineColor,angle,useWebGL,screenSpace,context);else{let ring=drawEllipseRings.get(sides);if(!ring){var points=[];for(let i=sides;i--;){var a=i/sides*PI*2;points.push(vec2(sin(a),cos(a)))}drawEllipseRings.set(sides,ring=glPolyStrip(points))}if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}glDrawPointsTransform(ring,color.rgbaInt(),pos.x,pos.y,size.x/2,size.y/2,angle,!1)}}else++drawCount,++primitiveCount,drawCanvas2D(pos,vec2(1),angle,!1,context=>{context.fillStyle=color.toString(),context.beginPath(),context.ellipse(0,0,abs(size.x)/2,abs(size.y)/2,0,0,9),context.fill(),0<lineWidth&&(context.strokeStyle=lineColor.toString(),context.lineWidth=lineWidth,context.stroke())},screenSpace,context)}function drawCircle(pos,size=1,color=WHITE,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=drawScreenSpace,context){drawEllipse(pos,vec2(size),color,0,lineWidth,lineColor,useWebGL,screenSpace,context)}let drawEllipseGradientOffset=0;function drawEllipseGradient(pos,size=vec2(1),colorInner=WHITE,colorOuter=CLEAR_WHITE,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(!headlessMode||context)if(useWebGL&&glEnable){if(screenSpace){if(glSkipScreenSpace)return;[pos,size,angle]=screenToWorldTransform(pos,size,angle)}var sides=glCircleSides;const radiusX=size.x/2,radiusY=size.y/2;var innerInt=colorInner.rgbaInt(),outerInt=colorOuter.rgbaInt(),offset=drawEllipseGradientOffset++;const c=cos(-angle),s=sin(-angle);var rim=a=>{var lx=sin(a)*radiusX,a=cos(a)*radiusY;return vec2(pos.x+lx*c-a*s,pos.y+lx*s+a*c)},points=[rim(offset%sides/sides*PI*2)],colors=[outerInt];for(let i=sides;i--;){var a=(i+offset)%sides/sides*PI*2;points.push(pos),colors.push(innerInt),points.push(rim(a)),colors.push(outerInt)}glDrawColoredPoints(points,colors)}else++drawCount,++primitiveCount,drawCanvas2D(pos,size,angle,!1,context=>{var gradient=context.createRadialGradient(0,0,0,0,0,.5);gradient.addColorStop(0,colorInner.toString()),gradient.addColorStop(1,colorOuter.toString()),context.fillStyle=gradient,context.beginPath(),context.ellipse(0,0,.5,.5,0,0,9),context.fill()},screenSpace,context)}function drawCircleGradient(pos,size=1,colorInner=WHITE,colorOuter=CLEAR_WHITE,useWebGL=glEnable,screenSpace=drawScreenSpace,context){drawEllipseGradient(pos,vec2(size),colorInner,colorOuter,0,useWebGL,screenSpace,context)}function drawCanvas2D(pos,size,angle=0,mirror=!1,drawFunction,screenSpace=drawScreenSpace,context=drawContext){headlessMode&&!context||(screenSpace||(pos=worldToScreen(pos),size=size.scale(cameraScale),angle-=cameraAngle),context.save(),context.translate(pos.x+.5,pos.y+.5),context.rotate(angle),context.scale(mirror?-size.x:size.x,-size.y),drawFunction(context),context.restore())}function drawText(text,pos,size=1,color=WHITE,lineWidth=0,lineColor=BLACK,textAlign="center",font=fontDefault,fontStyle="",maxWidth,angle=0,context=drawContext){pos=worldToScreen(pos),size*=cameraScale,lineWidth*=cameraScale,void 0!==maxWidth&&(maxWidth*=cameraScale),drawTextScreen(text,pos,size,color,lineWidth,lineColor,textAlign,font,fontStyle,maxWidth,angle-=cameraAngle,context)}function drawTextScreen(text,pos,size,color=WHITE,lineWidth=0,lineColor=BLACK,textAlign="center",font=fontDefault,fontStyle="",maxWidth,angle=0,context=drawContext){if(!headlessMode||context){text=(text+"").split("\n");context.save(),context.fillStyle=color.toString(),context.strokeStyle=lineColor.toString(),context.lineWidth=lineWidth,context.textAlign=textAlign,context.font=fontStyle+" "+size+"px "+font,context.textBaseline="middle",context.translate(pos.x+.5,pos.y+.5),context.rotate(angle);let yOffset=-(text.length-1)*size/2;text.forEach(line=>{lineWidth&&context.strokeText(line,0,yOffset,maxWidth),context.fillText(line,0,yOffset,maxWidth),yOffset+=size}),context.restore()}}async function loadTexture(textureIndex,src){var old=textureInfos[textureIndex];old?.destroyWebGLTexture();const image=new Image;return src&&await new Promise(resolve=>{image.onload=resolve,image.onerror=()=>{console.warn("failed to load image: "+src),resolve()},image.crossOrigin="anonymous",image.src=src}),textureInfos[textureIndex]=new TextureInfo(image)}function screenToWorld(screenPos){let x=(screenPos.x-mainCanvasSize.x/2+.5)/cameraScale,y=(screenPos.y-mainCanvasSize.y/2+.5)/-cameraScale;var xr,s;return cameraAngle&&(screenPos=cos(-cameraAngle),s=sin(-cameraAngle),xr=x*screenPos-y*s,s=x*s+y*screenPos,x=xr,y=s),new Vector2(x+cameraPos.x,y+cameraPos.y)}function worldToScreen(worldPos){let x=worldPos.x-cameraPos.x,y=worldPos.y-cameraPos.y;var xr,s;return cameraAngle&&(worldPos=cos(cameraAngle),s=sin(cameraAngle),xr=x*worldPos-y*s,s=x*s+y*worldPos,x=xr,y=s),new Vector2(x*cameraScale+mainCanvasSize.x/2-.5,y*-cameraScale+mainCanvasSize.y/2-.5)}function screenToWorldDelta(screenDelta){let x=screenDelta.x/cameraScale,y=screenDelta.y/-cameraScale;var xr,s;return cameraAngle&&(screenDelta=cos(-cameraAngle),s=sin(-cameraAngle),xr=x*screenDelta-y*s,s=x*s+y*screenDelta,x=xr,y=s),new Vector2(x,y)}function worldToScreenDelta(worldDelta){let x=worldDelta.x,y=worldDelta.y;var xr,s;return cameraAngle&&(worldDelta=cos(cameraAngle),s=sin(cameraAngle),xr=x*worldDelta-y*s,s=x*s+y*worldDelta,x=xr,y=s),new Vector2(x*cameraScale,y*-cameraScale)}function screenToWorldTransform(screenPos,screenSize,screenAngle=0){return[screenToWorld(screenPos),screenSize.scale(1/cameraScale),screenAngle+cameraAngle]}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function cameraFit(center,size,worldMargin,screenInset){var c,s,fitW,worldMargin=padSides(worldMargin),screenInset=padSides(screenInset),worldW=size.x+worldMargin.left+worldMargin.right,size=size.y+worldMargin.top+worldMargin.bottom,viewW=mainCanvasSize.x-screenInset.left-screenInset.right,viewH=mainCanvasSize.y-screenInset.top-screenInset.bottom;return 0<worldW&&0<size&&0<viewW&&0<viewH&&(c=cos(cameraAngle),s=sin(cameraAngle),fitW=abs(c)*worldW+abs(s)*size,worldW=abs(s)*worldW+abs(c)*size,cameraScale=min(viewW/fitW,viewH/worldW),size=vec2(worldMargin.right-worldMargin.left,worldMargin.top-worldMargin.bottom).scale(.5),viewH=vec2((viewW=.5*(screenInset.right-screenInset.left)/cameraScale)*c+(fitW=.5*(screenInset.top-screenInset.bottom)/cameraScale)*s,fitW*c-viewW*s),cameraPos=center.add(size).add(viewH)),cameraScale;function padSides(p){return isVector2(p=void 0!==p&&!isNumber(p)?p:vec2(p))?{top:p.y,right:p.x,bottom:p.y,left:p.x}:{top:p.top||0,right:p.right||0,bottom:p.bottom||0,left:p.left||0}}}function isOnScreen(pos,size=0){if(!cameraScale)return!1;let x=pos.x-cameraPos.x,y=pos.y-cameraPos.y;cameraAngle&&(pos=cos(cameraAngle),s=sin(cameraAngle),xr=x*pos-y*s,s=x*s+y*pos,x=xr,y=s),x*=2*cameraScale,y*=2*-cameraScale,size instanceof Vector2&&(size=size.length()),size*=cameraScale;var s,pos=mainCanvasSize.x,xr=mainCanvasSize.y;return x+size>-pos&&x-size<pos&&y+size>-xr&&y-size<xr}function setAdditiveBlendMode(additive=!0){glAdditive=additive,drawContext.globalCompositeOperation=additive?"lighter":"source-over"}function setShader(shader){glCustomShader=shader||void 0}function setBackgroundCanvas(canvas){backgroundCanvas=canvas}function combineCanvases(){var w=mainCanvas.width,h=mainCanvas.height;workCanvas.width=w,workCanvas.height=h,workContext.fillStyle="#000",workContext.fillRect(0,0,w,h),backgroundCanvas&&workContext.drawImage(backgroundCanvas,0,0,w,h),glCopyToContext(workContext),workContext.drawImage(mainCanvas,0,0),mainContext.save(),mainContext.setTransform(1,0,0,1,0,0),mainContext.drawImage(workCanvas,0,0),mainContext.restore()}function tintImageData(data,color,additiveColor){if(additiveColor&&!isBlack(additiveColor)){var colorMultiply=[color.r,color.g,color.b,color.a],colorAdd=[255*additiveColor.r,255*additiveColor.g,255*additiveColor.b,255*additiveColor.a];for(let i=0;i<data.length;++i)data[i]=data[i]*colorMultiply[3&i]+colorAdd[3&i]|0;return!0}for(let i=0;i<data.length;i+=4)data[i]*=color.r,data[i+1]*=color.g,data[i+2]*=color.b;return!1}function isFullscreen(){return!!document.fullscreenElement}function toggleFullscreen(){var rootElement=mainCanvas.parentElement;isFullscreen()?document.exitFullscreen&&document.exitFullscreen():rootElement.requestFullscreen&&rootElement.requestFullscreen()}function setCursor(cursorStyle="auto"){mainCanvas.parentElement.style.cursor=cursorStyle}let engineImageFont;class ImageFont{constructor(tileInfo){this.tileInfo=tileInfo}drawText(text,pos,size=1,center,color,useWebGL,context){size="number"==typeof size?(size*=cameraScale,new Vector2(size,size)):size.scale(cameraScale);var skip=glSkipScreenSpace;glSkipScreenSpace=!1,this.drawTextScreen(text,worldToScreen(pos),size,center,color,useWebGL,context),glSkipScreenSpace=skip}drawTextScreen(text,pos,size,center=!0,color=WHITE,useWebGL=glEnable,context){const glyphSize="number"==typeof size?new Vector2(size,size):size,drawPos=new Vector2,tileInfo=this.tileInfo.frame(0);size=(text+"").split("\n");const centerOffsetY=center?(size.length-1)*glyphSize.y/2:0;size.forEach((line,j)=>{var centerOffset=center?(line.length-1)*glyphSize.x/2:0;for(let i=line.length;i--;){var charCode=line.charCodeAt(i);this.getGlyphPos(charCode<32||127<charCode?95:charCode-32,tileInfo.pos),drawPos.x=ceil(pos.x+i*glyphSize.x-centerOffset-glyphSize.x/2)+glyphSize.x/2-.5,drawPos.y=ceil(pos.y+j*glyphSize.y-centerOffsetY-glyphSize.y/2)+glyphSize.y/2-.5,drawTile(drawPos,glyphSize,tileInfo,color,0,!1,void 0,useWebGL,!0,context)}})}getGlyphPos(index,pos=new Vector2){var columns,t=this.tileInfo,padding=t.padding,w=t.size.x+2*padding,h=t.size.y+2*padding;return t.columns?pos.set(t.pos.x+index%t.columns*w,t.pos.y+(index/t.columns|0)*h):(columns=t.textureInfo.size.x/w|0,t=((t.pos.y-padding)/h|0)*columns+((t.pos.x-padding)/w|0)+index,pos.set(t%columns*w+padding,(t/columns|0)*h+padding))}}let mousePos=vec2(),mousePosScreen=vec2(),mouseDelta=vec2(),mouseDeltaScreen=vec2(),mouseWheel=0,mouseInWindow=!0,isUsingGamepad=!1,lastInputDevice="mouse",inputMouseMoveThreshold=6,inputPreventDefault=!0,gamepadPrimary=0;const isTouchDevice=!headlessMode&&"undefined"!=typeof window&&void 0!==window.ontouchstart;function setInputPreventDefault(preventDefault=!0){inputPreventDefault=preventDefault}function setInputMouseMoveThreshold(threshold){inputMouseMoveThreshold=threshold}function usingMouseInput(){return"mouse"===lastInputDevice}function usingKeyboardInput(){return"keyboard"===lastInputDevice}function usingGamepadInput(){return"gamepad"===lastInputDevice}function inputClearKey(key,device=0,clearDown=!0,clearPressed=!0,clearReleased=!0){inputData[device]&&(inputData[device][key]&=~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0)))}const inputWASDToArrow={KeyW:"ArrowUp",KeyS:"ArrowDown",KeyA:"ArrowLeft",KeyD:"ArrowRight"},inputArrowToWASD={ArrowUp:"KeyW",ArrowDown:"KeyS",ArrowLeft:"KeyA",ArrowRight:"KeyD"},inputKeysHeld=new Set;let inputWasTouching=0,inputTouchIdentifier,inputLastTouchTime=-1e9;function inputIsTouchMouseEvent(e){return e.sourceCapabilities?.firesTouchEvents||performance.now()-inputLastTouchTime<500}function inputClear(){for(let i=inputData.length;i--;)i&&!inputData[i]||(inputData[i]=[]);inputKeysHeld.clear(),inputWasTouching=0,touchGamepadPointerRole.clear(),touchGamepadButtons.length=0,touchGamepadButtonsPressed.length=0,touchGamepadSticks.length=0,touchGamepadStickPointerId.length=0,gamepadStickData.length=0,gamepadDpadData.length=0}function keyIsDown(key,device=0){return!!(1&inputData[device]?.[key])}function keyWasPressed(key,device=0){return!!(2&inputData[device]?.[key])}function keyWasReleased(key,device=0){return!!(4&inputData[device]?.[key])}function keyDirection(up="ArrowUp",down="ArrowDown",left="ArrowLeft",right="ArrowRight"){var k=key=>keyIsDown(key)?1:0;return vec2(k(right)-k(left),k(up)-k(down))}function mouseIsDown(button){return keyIsDown(button)}function mouseWasPressed(button){return keyWasPressed(button)}function mouseWasReleased(button){return keyWasReleased(button)}function gamepadIsDown(button,gamepad=gamepadPrimary){return keyIsDown(button,gamepad+1)}function gamepadWasPressed(button,gamepad=gamepadPrimary){return keyWasPressed(button,gamepad+1)}function gamepadWasReleased(button,gamepad=gamepadPrimary){return keyWasReleased(button,gamepad+1)}function gamepadStick(stick,gamepad=gamepadPrimary){return gamepadStickData[gamepad]?.[stick]??vec2()}function gamepadDpad(gamepad=gamepadPrimary){return gamepadDpadData[gamepad]?.copy()??vec2()}function gamepadConnected(gamepad=gamepadPrimary){return!!inputData[gamepad+1]}function gamepadStickCount(gamepad=gamepadPrimary){return gamepadStickData[gamepad]?.length??0}function gamepadVibrate(gamepad=gamepadPrimary,duration=200,strongMagnitude=1,weakMagnitude=1,startDelay=0){vibrateEnable&&!headlessMode&&inputGetGamepads()[gamepad]?.vibrationActuator?.playEffect?.("dual-rumble",{duration:duration,strongMagnitude:strongMagnitude,weakMagnitude:weakMagnitude,startDelay:startDelay})?.catch?.(()=>{})}function gamepadVibrateStop(gamepad=gamepadPrimary){vibrateEnable&&!headlessMode&&inputGetGamepads()[gamepad]?.vibrationActuator?.reset?.()}function inputGetGamepads(){try{return navigator?.getGamepads?.()||[]}catch(e){return[]}}function vibrate(pattern=100){vibrateEnable&&!headlessMode&&navigator?.vibrate?.(pattern)}function vibrateStop(){vibrate(0)}function pointerLockRequest(){try{(mainCanvas.requestPointerLock?.())?.catch?.(()=>{})}catch{}}function pointerLockExit(){document.exitPointerLock?.()}function pointerLockIsActive(){return document.pointerLockElement===mainCanvas}const inputData=[[]],gamepadStickData=[],gamepadDpadData=[],gamepadHadInput=[],gamepadButtonsLast=[],gamepadAxisCentered=[],gamepadAxisCenteredFrames=15,touchGamepadTimer=new Timer(void 0,!0),touchGamepadButtons=[],touchGamepadSticks=[],touchGamepadButtonsPressed=[],touchGamepadStickAnchors=[],touchGamepadStickPointerId=[],touchGamepadPointerRole=new Map;let touchGamepadOverlay,touchGamepadStage,touchGamepadSvg,touchGamepadSvgEls,touchGamepadSideZones=[],touchGamepadZoneC,touchGamepadNeedRelayout=!0,touchGamepadLastLayout;const touchGamepadSvgNS="http://www.w3.org/2000/svg";function touchGamepadStageRect(){return touchGamepadStage.getBoundingClientRect()}function touchGamepadSideStick(side){return side?touchGamepadRightStick:touchGamepadLeftStick}function touchGamepadSideButtonCount(side){return side?touchGamepadButtonCount:touchGamepadLeftButtonCount}function touchGamepadSideButtonBase(side){return side?0:4}function touchGamepadStickOut(side){return side&&touchGamepadLeftStick?1:0}function touchGamepadStickButton(side){return touchGamepadStickOut(side)?11:10}function touchGamepadFaceIndex(b){return 3===b?2:2===b?3:b}function touchGamepadSideHasControl(side){return touchGamepadSideStick(side)||0<touchGamepadSideButtonCount(side)}function touchGamepadSideCenter(side,W,H){if(touchGamepadFloating&&touchGamepadSideStick(side)&&touchGamepadStickAnchors[side])return touchGamepadStickAnchors[side];let y=H-touchGamepadSize;H=touchGamepadSideButtonCount(side);return touchGamepadSideStick(side)||2!==H&&3!==H||(y-=touchGamepadSize/4),vec2(side?W-touchGamepadSize:touchGamepadSize,y)}function touchGamepadApplyStick(side,p){p=p.subtract(touchGamepadStickAnchors[side]);touchGamepadSticks[side]=p.scale(2/touchGamepadSize).clampLength(),touchGamepadButtons[touchGamepadStickButton(side)]=1}function touchGamepadStartBlockSide(side){return touchGamepadSideHasControl(side)&&(!touchGamepadFloating||!touchGamepadSideStick(side))}function touchGamepadPointerUp(e){var role=touchGamepadPointerRole.get(e.pointerId);role&&(touchGamepadPointerRole.delete(e.pointerId),"stick0"===role||"stick1"===role?(e="stick1"===role?1:0,touchGamepadStickPointerId[e]=void 0,touchGamepadSticks[e]=vec2(),delete touchGamepadButtons[touchGamepadStickButton(e)]):"start"===role?delete touchGamepadButtons[9]:delete touchGamepadButtons[+role.slice(4)],touchGamepadTimer.set())}let audioContext="undefined"==typeof AudioContext?void 0:new AudioContext,audioMasterGain=audioContext?.createGain(),audioMasterVolume;function audioUpdateVolume(){audioMasterGain&&soundVolume!==audioMasterVolume&&(audioMasterGain.gain.value=audioMasterVolume=soundVolume)}audioMasterGain&&(audioMasterGain.connect(audioContext.destination),audioMasterGain.gain.value=audioMasterVolume=soundVolume,audioContext.addEventListener?.("statechange",function(){if(audioIsRunning()){audioWaitingPrune();var instances=[...audioWaitingInstances.keys()];audioWaitingInstances.clear();for(const instance of instances)instance.resume()}}));let audioMasterEffectInput,audioMasterEffectOutput,audioMasterEffectOutputIsEffect;const audioDefaultSampleRate=44100;function audioIsRunning(){return"running"===audioContext?.state}let audioSuspendedWhenHidden=!1;function audioVisibilityChange(){document.hidden?soundPauseWhenHidden&&"running"==audioContext.state&&(audioSuspendedWhenHidden=!0,audioContext.suspend()):audioSuspendedWhenHidden&&(audioSuspendedWhenHidden=!1,audioContext.resume())}const audioWaitingInstances=new Map;function audioWaitingPrune(now=performance.now()){for(var[instance,startTime]of audioWaitingInstances){var remaining=(instance.getDuration()-instance.pausedTime)/instance.rate;!instance.loop&&now-startTime>1e3*remaining&&audioWaitingInstances.delete(instance)}}function setAudioMasterEffect(input,output){var outputArg=output||input,outputArg=!!outputArg&&"input"in outputArg;if(output=audioEffectNode(output,"output")||audioEffectNode(input,"output"),input=audioEffectNode(input,"input"),audioMasterGain){if(audioMasterGain.disconnect(audioMasterEffectInput||audioContext.destination),audioMasterEffectOutput){let fedSpeakers=!0;try{audioMasterEffectOutput.disconnect(audioContext.destination)}catch{fedSpeakers=!1}audioMasterEffectOutputIsEffect&&fedSpeakers&&audioMasterEffectOutput.connect(audioMasterGain)}audioMasterEffectInput=input,audioMasterEffectOutput=output,audioMasterEffectOutputIsEffect=outputArg,(input?(audioMasterGain.connect(input),output.disconnect(),output):audioMasterGain).connect(audioContext.destination)}}function audioEffectNode(effectOrNode,key){return effectOrNode&&"input"in effectOrNode?effectOrNode[key]:effectOrNode}function audioParamRamp(param,value,fadeTime=0){var startTime=audioContext.currentTime;return param.cancelScheduledValues(startTime),fadeTime?(param.setValueAtTime(param.value,startTime),param.linearRampToValueAtTime(value,startTime+fadeTime)):param.value=value,startTime+fadeTime}class Sound{constructor(asset,randomness,range=soundDefaultRange,taper=soundDefaultTaper,onloadCallback){if(soundEnable&&!headlessMode)if(this.range=range,this.taper=taper,this.randomness=randomness??0,this.sampleRate=44100,this.sampleLength=0,this.sampleBuffer=void 0,this._sampleChannels=void 0,this.loadedPercent=0,this.onloadCallback=onloadCallback,this.output=void 0,isArray(asset)){range=asset.slice();this.randomness=randomness??range[1]??.05,range[1]=0,this.sampleChannels=[zzfxG(...range)],this.buildSampleBuffer(),this.loadedPercent=1,onloadCallback?.(this)}else if(asset){const filename=asset+"";this.loadSound(filename).catch(e=>!1)}}get sampleChannels(){var buffer=this.sampleBuffer;if(!this._sampleChannels&&buffer){var channels=[];for(let i=0;i<buffer.numberOfChannels;i++)channels.push(buffer.getChannelData(i).slice());this._sampleChannels=channels}return this._sampleChannels}set sampleChannels(sampleChannels){this._sampleChannels=sampleChannels,this.sampleBuffer=void 0,this.sampleLength=sampleChannels?.[0]?.length||0}buildSampleBuffer(){this.sampleBuffer||!this._sampleChannels||headlessMode||(this.sampleBuffer=createAudioBuffer(this._sampleChannels,this.sampleRate),this._sampleChannels=void 0)}play(pos,volume=1,pitch=1,randomnessScale=1,loop=!1,paused=!1){if(soundEnable&&!headlessMode&&(this.sampleBuffer||this._sampleChannels)){let pan;if(pos){var range=this.range;if(range){var lengthSquared=cameraPos.distanceSquared(pos);if(range*range<lengthSquared)return;var lengthSquared=lengthSquared**.5,taperRange=range*this.taper;taperRange<lengthSquared&&(volume*=percent(lengthSquared,range,taperRange))}pan=2*worldToScreen(pos).x/mainCanvasSize.x-1}lengthSquared=pitch+pitch*this.randomness*randomnessScale*rand(-1,1),range=new SoundInstance(this,volume,lengthSquared,pan,loop,paused);return debug,range}}playLoop(pos,volume=1,pitch=1,randomnessScale=1,paused=!1){return this.play(pos,volume,pitch,randomnessScale,!0,paused)}playMusic(volume=1,loop=!0,paused=!1){return this.play(void 0,volume,1,0,loop,paused)}playNote(semitoneOffset=0,pos,volume){semitoneOffset=getNoteFrequency(semitoneOffset,1);return this.play(pos,volume,semitoneOffset,0)}getDuration(){return this.sampleLength/this.sampleRate||0}isLoaded(){return 1===this.loadedPercent}async loadSound(filename){var response=await fetch(filename);if(!response.ok)throw new Error(`Failed to load sound from ${filename}: ${response.status} `+response.statusText);filename=await response.arrayBuffer(),response=await audioContext.decodeAudioData(filename);this.sampleRate=response.sampleRate,this.sampleLength=response.length,this.sampleBuffer=response,this._sampleChannels=void 0,this.loadedPercent=1,this.onloadCallback?.(this)}}class SoundInstance{constructor(sound,volume=1,rate=1,pan=0,loop=!1,paused=!1){this.sound=sound,this.volume=volume,this.rate=rate,this.pan=pan,this.loop=loop,this.pausedTime=0,this.startTime=void 0,this.startOffset=0,this.gainNode=void 0,this.pannerNode=void 0,this.source=void 0,this.output=sound.output,this.onendedCallback=void 0,this.sourceEnded=source=>{source===this.source&&(this.source=this.gainNode=this.pannerNode=void 0,this.startTime=void 0,this.pausedTime=0,this.onendedCallback?.(source))},paused||this.start()}start(offset=0){if(this.isPlaying()&&this.stop(),this.gainNode=audioContext.createGain(),this.pannerNode=new StereoPannerNode(audioContext,{pan:clamp(this.pan,-1,1)}),this.sound.buildSampleBuffer(),this.source=this.sound.sampleBuffer?playAudioBuffer(this.sound.sampleBuffer,this.volume,this.rate,this.pan,this.loop,this.gainNode,offset,this.sourceEnded,this.output,this.pannerNode):playSamples(this.sound.sampleChannels,this.volume,this.rate,this.pan,this.loop,this.sound.sampleRate,this.gainNode,offset,this.sourceEnded,this.output,this.pannerNode),audioWaitingInstances.delete(this),this.source)this.startTime=audioContext.currentTime,this.startOffset=offset,this.pausedTime=void 0;else if(this.startTime=this.gainNode=this.pannerNode=void 0,this.pausedTime=offset,!audioIsRunning()){audioWaitingPrune();for(const other of audioWaitingInstances.keys())other.sound===this.sound&&audioWaitingInstances.delete(other);audioWaitingInstances.set(this,performance.now())}}setVolume(volume,fadeTime=0){this.volume=volume,this.gainNode&&audioParamRamp(this.gainNode.gain,volume,fadeTime)}setPan(pan){this.pan=pan,this.pannerNode&&(this.pannerNode.pan.value=clamp(pan,-1,1))}setRate(rate){this.isPlaying()&&(this.startOffset=this.getCurrentTime(),this.startTime=audioContext.currentTime),this.rate=rate,this.source&&(this.source.playbackRate.value=rate)}stop(fadeTime=0){audioWaitingInstances.delete(this),this.isPlaying()&&(fadeTime?this.source.stop(audioParamRamp(this.gainNode.gain,0,fadeTime)):this.source.stop()),this.pausedTime=0,this.source=void 0,this.startTime=void 0,this.gainNode=void 0,this.pannerNode=void 0}pause(){audioWaitingInstances.delete(this),this.isPaused()||(this.pausedTime=this.getCurrentTime(),this.source.stop(),this.source=void 0,this.startTime=void 0,this.gainNode=void 0,this.pannerNode=void 0)}resume(){this.isPaused()&&this.start(this.pausedTime)}isPlaying(){return!!this.source}isPaused(){return!this.isPlaying()}getCurrentTime(){var duration,place;return this.isPlaying()?(duration=this.getDuration(),place=this.startOffset+(audioContext.currentTime-this.startTime)*this.rate,duration?mod(place,duration):0):this.pausedTime}getDuration(){return this.sound.getDuration()}getSource(){return this.source}}function speak(text,volume=1,rate=1,pitch=1,language=""){if(soundEnable&&!headlessMode&&"undefined"!=typeof speechSynthesis)return(text=new SpeechSynthesisUtterance(text)).lang=language,text.volume=clamp(volume*soundVolume),text.rate=rate,text.pitch=pitch,speechSynthesis.speak(text),text}function speakStop(){"undefined"!=typeof speechSynthesis&&speechSynthesis.cancel()}function getNoteFrequency(semitoneOffset,rootFrequency=220){return rootFrequency*2**(semitoneOffset/12)}function playSamples(sampleChannels,volume=1,rate=1,pan=0,loop=!1,sampleRate=44100,gainNode,offset=0,onended,output,pannerNode){if(soundEnable&&!headlessMode){if(audioIsRunning())return playAudioBuffer(createAudioBuffer(sampleChannels,sampleRate),volume,rate,pan,loop,gainNode,offset,onended,output,pannerNode);audioSuspendedWhenHidden||audioContext.resume()}}function createAudioBuffer(sampleChannels,sampleRate=44100){var channelCount=sampleChannels.length,sampleLength=sampleChannels[0].length;const buffer=audioContext.createBuffer(channelCount,sampleLength,sampleRate);return sampleChannels.forEach((c,i)=>buffer.getChannelData(i).set(c)),buffer}function playAudioBuffer(buffer,volume=1,rate=1,pan=0,loop=!1,gainNode,offset=0,onended,output,pannerNode){if(soundEnable&&!headlessMode){if(audioIsRunning()){const source=audioContext.createBufferSource();source.buffer=buffer,source.playbackRate.value=rate,source.loop=loop,(gainNode=gainNode||audioContext.createGain()).gain.value=volume;buffer=audioEffectNode(output,"input")||audioMasterGain;gainNode.connect(buffer);const panner=pannerNode||new StereoPannerNode(audioContext,{pan:clamp(pan,-1,1)});return source.connect(panner).connect(gainNode),source.addEventListener("ended",()=>{gainNode.disconnect(),panner.disconnect(),onended&&onended(source)}),source.start(0,offset),debug,source}audioSuspendedWhenHidden||audioContext.resume()}}function zzfx(...zzfxSound){return playSamples([zzfxG(...zzfxSound)])}function zzfxG(volume=1,randomness=.05,frequency=220,attack=0,sustain=0,release=.1,shape=0,shapeCurve=1,slide=0,deltaSlide=0,pitchJump=0,pitchJumpTime=0,repeatTime=0,noise=0,modulation=0,bitCrush=0,delay=0,sustainVolume=1,decay=0,tremolo=0,filter=0){let PI2=2*PI,startSlide=slide*=500*PI2/44100/44100,startFrequency=frequency*=(1+rand(randomness,-randomness))*PI2/44100,modOffset=0,repeat=0,crush=0,jump=1,length,b=[],t=0,i=0,s=0,f,w=PI2*min(abs(filter),11024)*2/44100,cosw=cos(w),alpha=sin(w)/2/2,a0=1+alpha,a1=-2*cosw/a0,a2=(1-alpha)/a0,b0=(1+sign(filter)*cosw)/2/a0,b1=-(sign(filter)+cosw)/a0,b2=b0,x2=0,x1=0,y2=0,y1=0;for(deltaSlide*=500*PI2/44100**3,modulation*=PI2/44100,pitchJump*=PI2/44100,pitchJumpTime*=44100,repeatTime=44100*repeatTime|0,length=(attack=44100*attack||9)+(decay*=44100)+(sustain*=44100)+(release*=44100)+(delay*=44100)|0;i<length;b[i++]=s*volume)++crush%(100*bitCrush|0)||(s=shape?1<shape?2<shape?3<shape?4<shape?t/PI2%1<shapeCurve/2?1:-1:sin(t**3):max(min(tan(t),1),-1):1-(2*t/PI2%2+2)%2:1-4*abs(round(t/PI2)-t/PI2):sin(t),s=(repeatTime?1-tremolo+tremolo*sin(PI2*i/repeatTime):1)*(4<shape?s:sign(s)*abs(s)**shapeCurve)*(i<attack?i/attack:i<attack+decay?1-(i-attack)/decay*(1-sustainVolume):i<attack+decay+sustain?sustainVolume:i<length-delay?(length-i-delay)/release*sustainVolume:0),s=delay?s/2+(delay>i?0:(i<length-delay?1:(length-i)/delay)*b[i-delay|0]/2/(volume||1)):s,filter&&(s=y1=b2*x2+b1*(x2=x1)+b0*(x1=s)-a2*y2-a1*(y2=y1))),f=(frequency+=slide+=deltaSlide)*cos(modulation*modOffset++),t+=f+f*noise*sin(i**5),jump&&++jump>pitchJumpTime&&(frequency+=pitchJump,startFrequency+=pitchJump,jump=0),!repeatTime||++repeat%repeatTime||(frequency=startFrequency,slide=startSlide,jump||=1);return b}const tileCollisionLayers=[];function tileCollisionTester(callbackObject){return callbackObject?"function"==typeof callbackObject?(tileData,pos)=>callbackObject(tileData,pos):(tileData,pos)=>callbackObject.collideWithTile(tileData,pos):tileData=>0<tileData}function tileCollisionGetData(pos,solidOnly=!0){let found=0;for(const layer of tileCollisionLayers)if(!solidOnly||layer.isSolid){var x=pos.x-layer.pos.x,y=pos.y-layer.pos.y,size=layer.size;if(0<=x&&0<=y&&x<size.x&&y<size.y){y=layer.collisionData[(0|y)*size.x+(0|x)];if(0<y)return y;y&&!found&&(found=y)}}return found}function tileCollisionTest(pos,size=vec2(),callbackObject,solidOnly=!0){for(const layer of tileCollisionLayers)if((!solidOnly||layer.isSolid)&&layer.collisionTest(pos,size,callbackObject))return layer}function tileCollisionRaycast(posStart,posEnd,callbackObject,normal,solidOnly=!0){let closestHit,closestDistSq,closestNormal;var hitPos,d,scratchNormal=normal&&vec2();for(const layer of tileCollisionLayers)solidOnly&&!layer.isSolid||(hitPos=layer.collisionRaycast(posStart,posEnd,callbackObject,scratchNormal))&&(d=posStart.distanceSquared(hitPos),void 0===closestHit||d<closestDistSq)&&(closestHit=hitPos,closestDistSq=d,normal)&&(closestNormal=scratchNormal.copy());return closestHit&&normal&&normal.setFrom(closestNormal),closestHit}const tileLayersTiledFlips=[[0,0],[3,1],[2,1],[3,0],[0,1],[1,0],[2,0],[1,1]];function tileLayersLoad(tileMapData,tileInfo=tileLayerDefaultTile(),renderOrder=0,collisionLayer,draw=!0){tileMapData||((tileMapData={}).height=tileMapData.width=50,tileMapData.layers=[{}],tileMapData.layers[0].data=new Array(2500).fill(0));const layers=[],addLayers=(dataLayers,groupColor,groupVisible)=>{for(const dataLayer of dataLayers){var tint,visible,type=dataLayer.type;type&&"tilelayer"!==type&&"group"!==type?layers.push({dataLayer:dataLayer}):(visible=((tint=(tint=dataLayer.tintcolor)?tileLayersColor(tint):(dataLayer.color||WHITE).copy()).a*=dataLayer.opacity??1,groupVisible&&!1!==dataLayer.visible),"group"===type?addLayers(dataLayer.layers||[],groupColor.multiply(tint),visible):layers.push({dataLayer:dataLayer,color:groupColor.multiply(tint),visible:visible}))}};addLayers(tileMapData.layers,WHITE,!0);var tileLayers=[],levelSize=vec2(tileMapData.width,tileMapData.height),layerCount=layers.length;for(let layerIndex=layerCount;layerIndex--;){var{dataLayer,color:layerColor,visible}=layers[layerIndex];if(layerColor){var layerRenderOrder=renderOrder-(layerCount-1-layerIndex),tileLayer=new TileCollisionLayer(vec2(),levelSize,tileInfo,layerRenderOrder);tileLayer.isSolid=layerIndex===collisionLayer,tileLayers[layerIndex]=tileLayer,visible||(tileLayer.render=()=>{});for(let x=levelSize.x;x--;)for(let y=levelSize.y;y--;){var direction,mirror,pos=vec2(x,levelSize.y-1-y),data=dataLayer.data[x+y*levelSize.x];data&&([direction,mirror]=tileLayersTiledFlips[data>>>29],data=(268435455&data)-1,data=new TileLayerData(data,direction,!!mirror,layerColor),tileLayer.setData(pos,data),layerIndex===collisionLayer)&&tileLayer.setCollisionData(pos,1)}draw&&visible&&tileLayer.redraw()}}return layers,tileLayers}function tileLayersColor(hex){return(new Color).setHex(9===hex.length?"#"+hex.slice(3)+hex.slice(1,3):hex)}const objectLayersTypes=new Map;function objectLayersAddType(name,make,defaults={},tileInfo){objectLayersTypes.set(String(name),{make:make,defaults:defaults,tileInfo:tileInfo})}function objectLayersLoad(tileMapData){const made=[],addObjects=dataLayers=>{for(const dataLayer of dataLayers||[])if("group"===dataLayer.type&&addObjects(dataLayer.layers),"objectgroup"===dataLayer.type)for(const object of dataLayer.objects||[]){var result=function(tileMapData,object){var name=object.type||object.class,type=objectLayersTypes.get(name);if(!type)return void(debug&&console.warn(`objectLayersLoad: no type added for ${name}, skipped`));var{height:name=0,tilewidth:tileMapData=1,tileheight=1}=tileMapData,tileMapData=vec2(object.x/tileMapData,name-object.y/tileheight),name=type["make"],tileheight=name.prototype?new name(tileMapData):name(tileMapData);if(tileheight&&"object"==typeof tileheight)return Object.assign(tileheight,function(type,object){var properties={};for(var[key,value]of Object.entries(type.defaults))properties[key]=value?.copy?value.copy():value;for(const property of object.properties||[]){const{name,value}=property;var parts,x,y;"color"===property.type?value&&(properties[name]=tileLayersColor(value)):isVector2(type.defaults[name])?(parts=String(value).split(","),[x,y]=parts.map(Number),2===parts.length&&parts.every(part=>part.trim())&&isNumber(x)&&isNumber(y)&&(properties[name]=vec2(x,y))):properties[name]=value}return properties}(type,object))}(tileMapData,object);dataLayer,object,result&&made.push(result)}};return addObjects(tileMapData?.layers),made}class TileLayerData{constructor(tile,direction=0,mirror=!1,color=new Color){this.tile=tile,this.direction=direction,this.mirror=mirror,this.color=color.copy()}clear(){this.tile=void 0,this.direction=0,this.mirror=!1,this.color=new Color}}class CanvasLayer extends EngineObject{constructor(pos,size,angle=0,renderOrder=0,canvasSize=vec2(512),useWebGL=!0){super(pos,size,void 0,angle,WHITE,renderOrder),this.context=headlessMode?void 0:createCanvasContext(canvasSize.x,canvasSize.y),this.canvas=this.context?.canvas,this.textureInfo=new TextureInfo(this.canvas,useWebGL);pos=useWebGL&&glContext?glContext.getParameter(glContext.MAX_TEXTURE_SIZE):0;pos&&max(canvasSize.x,canvasSize.y)>pos&&console.warn(`LittleJS: a ${canvasSize.x}x${canvasSize.y} layer is over this device's ${pos} pixel texture limit and draws black, split it into smaller layers`),this.mass=0}destroy(immediate=!1){this.destroyed||(this.textureInfo.destroyWebGLTexture(),super.destroy(immediate))}render(){this.draw(this.pos,this.size,this.color,this.angle,this.mirror,this.additiveColor,!1)}draw(pos,size,color=WHITE,angle=0,mirror=!1,additiveColor,screenSpace=drawScreenSpace,context){var t=this.textureInfo,c=this.canvas,c=(!c||this.hasWebGL()||c.width===t.size.x&&c.height===t.size.y||(t.size=vec2(c.width,c.height),t.sizeInverse=vec2(1/c.width,1/c.height)),(new TileInfo).setFullImage(t));drawTile(pos,size,c,color,angle,mirror,additiveColor,!context&&this.hasWebGL(),screenSpace,context)}updateWebGL(){this.textureInfo.createWebGLTexture()}hasWebGL(){return glEnable&&this.textureInfo.hasWebGL()}}function tileLayerDefaultTile(){return textureInfos[0]?.size.x?tile():void 0}class TileLayer extends CanvasLayer{constructor(pos,size,tileInfo=tileLayerDefaultTile(),renderOrder=0,useWebGL=!0){size=size.floor();var canvasSize=tileInfo?size.multiply(tileInfo.size):size;super(pos,size,0,renderOrder,canvasSize,useWebGL),this.tileInfo=void 0,this.data=[],this.isUsingWebGL=!1,this.tilesInWebGL=void 0,this.debugShow=!0,tileInfo&&(this.tileInfo=tileInfo.frame(0),this.tileInfo.bleed=0);for(let j=this.size.area();j--;)this.data.push(new TileLayerData);headlessMode&&(this.render=()=>{},this.redraw=()=>{},this.redrawStart=()=>{},this.redrawEnd=()=>{},this.drawTileData=()=>{},this.redrawTileData=()=>{},this.drawLayerTile=()=>{},this.drawLayerRect=()=>{},this.drawTile=()=>{},this.drawRect=()=>{},this.clearLayerRect=()=>{})}setData(layerPos,data,redraw=!1){(layerPos=layerPos.floor()).arrayCheck(this.size)&&(this.data[(0|layerPos.y)*this.size.x+(0|layerPos.x)]=data,redraw)&&(drawContext===this.context?this.drawTileData(layerPos):this.redrawTileData(layerPos))}clearData(layerPos,redraw=!1){this.setData(layerPos,new TileLayerData,redraw)}getData(layerPos){return layerPos.arrayCheck(this.size)?this.data[(0|layerPos.y)*this.size.x+(0|layerPos.x)]:void 0}update(){}render(){this.redrawIfSwitched();var size=this.drawSize||this.size,pos=this.pos.add(size.scale(.5));this.draw(pos,size,this.color,this.angle,this.mirror,this.additiveColor,!1)}onRedraw(){}redraw(){this.redrawStart(!0);for(let x=this.size.x;x--;)for(let y=this.size.y;y--;)this.drawTileData(vec2(x,y),!1);this.isUsingWebGL&&glFlush(),this.onRedraw(),this.redrawEnd(),this.tilesInWebGL=this.isUsingWebGL}redrawIfSwitched(){void 0!==this.tilesInWebGL&&this.hasWebGL()!==this.tilesInWebGL&&this.redraw()}redrawStart(clear=!1){var tileSize;this.context&&(clear||this.redrawIfSwitched(),this.savedRenderSettings=[drawContext,mainCanvasSize,cameraPos,cameraScale,cameraAngle,canvasClearColor,glCustomShader],setShader(),this.savedRenderTarget=[glRenderTarget,glColorMask,glColorAdditive,glSkipScreenSpace],glColorMask=-1,glColorAdditive=0,glSkipScreenSpace=!1,drawContext=this.context,tileSize=this.tileInfo?.size??vec2(1),mainCanvasSize=this.size.multiply(tileSize),canvasClearColor=CLEAR_BLACK,cameraPos=this.size.multiply(tileSize).scale(.5),cameraScale=1,cameraAngle=0,this.isUsingWebGL=this.hasWebGL(),this.isUsingWebGL?glSetRenderTarget(this.textureInfo.glTexture,clear):(clear&&(this.canvas.width=mainCanvasSize.x,this.canvas.height=mainCanvasSize.y),this.context.imageSmoothingEnabled=!tilesPixelated))}redrawEnd(){var target,colorMask,colorAdditive,skipScreenSpace;this.context&&([drawContext,mainCanvasSize,cameraPos,cameraScale,cameraAngle,canvasClearColor,glCustomShader]=this.savedRenderSettings,[target,colorMask,colorAdditive,skipScreenSpace]=this.savedRenderTarget,this.isUsingWebGL&&glSetRenderTarget(target),glColorMask=colorMask,glColorAdditive=colorAdditive,glSkipScreenSpace=skipScreenSpace)}drawTileData(layerPos,clear=!0){var cellPixels,drawPos;this.context&&(cellPixels=this.tileInfo?.size??vec2(1),drawPos=layerPos.multiply(cellPixels),clear&&this.clearLayerRect(drawPos,cellPixels),clear=this.getData(layerPos))&&void 0!==clear.tile&&(layerPos=(layerPos=this.tileInfo)&&(layerPos.columns?layerPos.frame(clear.tile):layerPos.index(clear.tile)),this.drawLayerTile(drawPos,cellPixels,layerPos,clear.color,clear.direction*PI/2,clear.mirror))}redrawTileData(layerPos,clear=!0){this.context&&(this.redrawStart(),this.drawTileData(layerPos,clear),this.redrawEnd())}drawLayerTile(pos,size=vec2(1),tileInfo,color=WHITE,angle=0,mirror,additiveColor){drawTile(pos.add(size.scale(.5)),size,tileInfo,color,angle,mirror,additiveColor,this.isUsingWebGL,!1)}drawLayerRect(pos,size,color,angle=0){this.drawLayerTile(pos,size,void 0,color,angle)}drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle=0,mirror=!1){var tileSize=this.tileInfo?.size??vec2(1),tileSize=(pos=pos.subtract(this.pos).multiply(tileSize),size=size.multiply(tileSize),pos.x-=.5,pos.y=this.canvas.height-pos.y-.5,mainCanvasSize),oldTarget=glRenderTarget,oldColorMask=glColorMask,oldSkip=glSkipScreenSpace,oldColorAdditive=glColorAdditive,oldShader=glCustomShader,useWebGL=(mainCanvasSize=vec2(this.canvas.width,this.canvas.height),glColorMask=-1,glColorAdditive=0,setShader(),glSkipScreenSpace=!1,this.hasWebGL()),drawContext=(useWebGL&&glSetRenderTarget(this.textureInfo.glTexture),useWebGL?void 0:this.context);drawTile(pos,size,tileInfo,color,angle,mirror,void 0,useWebGL,!0,drawContext),mainCanvasSize=tileSize,useWebGL&&glSetRenderTarget(oldTarget),glColorMask=oldColorMask,glColorAdditive=oldColorAdditive,glSkipScreenSpace=oldSkip,setShader(oldShader)}drawRect(pos,size,color,angle){this.drawTile(pos,size,void 0,color,angle)}clearLayerRect(pos,size){var x=pos.x,pos=this.canvas.height-pos.y-size.y;this.hasWebGL()?glClearRect(x,pos,size.x,size.y):this.context.clearRect(x,pos,size.x,size.y)}}class TileCollisionLayer extends TileLayer{constructor(pos,size,tileInfo=tileLayerDefaultTile(),renderOrder=0,useWebGL=!0){super(pos,size.floor(),tileInfo,renderOrder,useWebGL),this.collisionData=[],this.initCollision(this.size),tileCollisionLayers.push(this),this.isSolid=!0,this.shadowSolidOnly=!0}renderShadow(){if(!this.shadowSolidOnly||this.angle||this.mirror)return super.renderShadow();var size=this.size,cellWorld=(this.drawSize||size).divide(size),cellPixels=this.tileInfo?this.tileInfo.size:vec2(1),view=getCameraSize().scale(.5),low=cameraPos.subtract(view),view=cameraPos.add(view),x0=max(0,floor((low.x-this.pos.x)/cellWorld.x)),x1=min(size.x,ceil((view.x-this.pos.x)/cellWorld.x)),low=max(0,floor((low.y-this.pos.y)/cellWorld.y)),y1=min(size.y,ceil((view.y-this.pos.y)/cellWorld.y)),textureHeight=size.y*cellPixels.y,useWebGL=this.hasWebGL();for(let y=low;y<y1;++y)for(let x=x0;x<x1;++x)if(this.collisionData[y*size.x+x]){let end=x+1;for(;end<x1&&this.collisionData[y*size.x+end];)++end;var count=end-x,tileInfo=new TileInfo(vec2(x*cellPixels.x,textureHeight-(y+1)*cellPixels.y),vec2(count*cellPixels.x,cellPixels.y),this.textureInfo,0,0);drawTile(vec2(this.pos.x+(x+count/2)*cellWorld.x,this.pos.y+(y+.5)*cellWorld.y),vec2(count*cellWorld.x,cellWorld.y),tileInfo,this.color,0,!1,void 0,useWebGL,!1),x=end}}destroy(immediate=!1){var index;this.destroyed||(0<=(index=tileCollisionLayers.indexOf(this))&&tileCollisionLayers.splice(index,1),super.destroy(immediate))}initCollision(size){this.size=size.floor(),this.collisionData=new Array(this.size.area()).fill(0)}setCollisionData(layerPos,data=1){var i=(0|layerPos.y)*this.size.x+(0|layerPos.x);layerPos.arrayCheck(this.size)&&(this.collisionData[i]=data)}clearCollisionData(layerPos){this.setCollisionData(layerPos,0)}getCollisionData(layerPos){var i=(0|layerPos.y)*this.size.x+(0|layerPos.x);return layerPos.arrayCheck(this.size)?this.collisionData[i]:0}collisionTest(pos,size=new Vector2,callbackObject){var collisionTest=tileCollisionTester(callbackObject),callbackObject=pos.x-this.pos.x,pos=pos.y-this.pos.y;if(!(callbackObject+size.x/2<0||callbackObject-size.x/2>this.size.x||pos+size.y/2<0||pos-size.y/2>this.size.y)){var minX=max(callbackObject-size.x/2|0,0),minY=max(pos-size.y/2|0,0),maxX=min(size.x?callbackObject+size.x/2:minX+1,this.size.x),maxY=min(size.y?pos+size.y/2:minY+1,this.size.y);for(let y=minY;y<maxY;++y)for(let x=minX;x<maxX;++x){var tileData=this.collisionData[y*this.size.x+x];if(tileData&&collisionTest(tileData,vec2(x+this.pos.x,y+this.pos.y)))return!0}}return!1}collisionRaycast(posStart,posEnd,callbackObject,normal){const collisionTest=tileCollisionTester(callbackObject),offset=this.pos;callbackObject=lineTest(posStart.subtract(offset),posEnd.subtract(offset),pos=>{var tileData=this.getCollisionData(pos);return tileData&&collisionTest(tileData,vec2(pos.x+offset.x,pos.y+offset.y))},normal);return callbackObject&&(callbackObject.x+=offset.x,callbackObject.y+=offset.y),callbackObject}}class ParticleEmitter extends EngineObject{constructor(pos,angle,emitSize=0,emitTime=0,emitRate=100,emitConeAngle=PI,tileInfo,colorStartA=WHITE,colorStartB=WHITE,colorEndA=CLEAR_WHITE,colorEndB=CLEAR_WHITE,particleTime=.5,sizeStart=.1,sizeEnd=1,speed=.1,angleSpeed=.05,damping=1,angleDamping=1,gravityScale=0,particleConeAngle=PI,fadeRate=.1,randomness=.2,collideLevel=!1,additive=!1,randomColorLinear=!0,renderOrder=additive?1e9:0,localSpace=!1){super(pos,vec2(),tileInfo,angle,void 0,renderOrder),this.emitCircle="number"==typeof emitSize,this.emitSize="number"==typeof emitSize?vec2(emitSize):emitSize.copy(),this.emitTime=emitTime,this.emitRate=emitRate,this.emitConeAngle=emitConeAngle,this.colorStartA=colorStartA.copy(),this.colorStartB=colorStartB.copy(),this.colorEndA=colorEndA.copy(),this.colorEndB=colorEndB.copy(),this.randomColorLinear=randomColorLinear,this.particleTime=particleTime,this.sizeStart=sizeStart,this.sizeEnd=sizeEnd,this.speed=speed,this.angleSpeed=angleSpeed,this.damping=damping,this.angleDamping=angleDamping,this.gravityScale=gravityScale,this.particleConeAngle=particleConeAngle,this.fadeRate=fadeRate,this.randomness=randomness,this.collideLevel=collideLevel,this.additive=additive,this.localSpace=localSpace,this.trailScale=0,this.particleCreateCallback=void 0,this.particleDestroyCallback=void 0,this.particleCollideCallback=void 0,this.particleUpdateCallback=void 0,this.velocityInheritance=0,this.emitTimeBuffer=1,this.particles=[],this.previousPos=void 0,this.previousAngle=this.angle}update(){if(this.previousPos||(this.previousPos=this.pos.copy(),this.previousAngle=this.angle),this.velocityInheritance&&(p=this.velocityInheritance,this.velocity.x=p*(this.pos.x-this.previousPos.x),this.velocity.y=p*(this.pos.y-this.previousPos.y),this.angleVelocity=p*(this.angle-this.previousAngle)),this.previousAngle=this.angle,this.previousPos.x=this.pos.x,this.previousPos.y=this.pos.y,this.isActive()){var p=this.emitRate*particleEmitRateScale;if(0<p&&p<1/0)for(this.emitTimeBuffer+=p*timeDelta;1<=this.emitTimeBuffer;--this.emitTimeBuffer)this.emitParticle()}else 0===this.particles.length&&this.destroy(!0);var particles=this.particles;let alive=0;for(let i=0;i<particles.length;++i){const p=particles[i];p.update(),p.destroyed||(particles[alive++]=p)}particles.length=alive}emitParticle(){let pos=this.emitCircle?randInCircle(this.emitSize.x/2):vec2(rand(-.5,.5),rand(-.5,.5)).multiply(this.emitSize),angle=rand(this.particleConeAngle,-this.particleConeAngle);this.localSpace||((pos=this.emitCircle?pos:pos.rotate(this.angle)).x+=this.pos.x,pos.y+=this.pos.y,angle+=this.angle);const randomness=this.randomness;var randomizeScale=v=>v+v*rand(randomness,-randomness),particleTime=randomizeScale(this.particleTime),sizeStart=randomizeScale(this.sizeStart),sizeEnd=randomizeScale(this.sizeEnd),speed=randomizeScale(this.speed),randomizeScale=randomizeScale(this.angleSpeed)*randSign(),coneAngle=rand(this.emitConeAngle,-this.emitConeAngle),colorStart=randColor(this.colorStartA,this.colorStartB,this.randomColorLinear),colorEnd=randColor(this.colorEndA,this.colorEndB,this.randomColorLinear),coneAngle=this.localSpace?coneAngle:this.angle+coneAngle,speed=vec2(speed*sin(coneAngle),speed*cos(coneAngle));let angleVelocity=randomizeScale;!this.localSpace&&0<this.velocityInheritance&&(speed.x+=this.velocity.x,speed.y+=this.velocity.y,angleVelocity+=this.angleVelocity);coneAngle=new Particle(this,pos,angle,colorStart,colorEnd,particleTime,sizeStart,sizeEnd,speed,angleVelocity);return this.particles.push(coneAngle),this.particleCreateCallback?.(coneAngle),coneAngle}updatePhysics(){}render(){this.additive&&setAdditiveBlendMode();for(const particle of this.particles)particle.render();this.additive&&setAdditiveBlendMode(!1)}isActive(){return!this.emitTime||this.getAliveTime()<this.emitTime}destroy(immediate=!1){this.destroyed||(super.destroy(immediate),!immediate&&0<this.particles.length&&(this.destroyed=!1,this.emitTime=-1))}}const particleDrawPos=new Vector2,particleDrawSize=new Vector2,particleCollidePos=new Vector2;function particleCollideTest(particle,collideCallback,x,y){var data=tileCollisionGetData(particleCollidePos.set(x,y));return collideCallback?!!data&&!!collideCallback(particle,data,vec2(x,y)):0<data}class Particle{constructor(emitter,pos,angle,colorStart,colorEnd,lifeTime,sizeStart,sizeEnd,velocity=vec2(),angleVelocity=0){this.emitter=emitter,this.pos=pos,this.angle=angle,this.size=vec2(sizeStart),this.color=colorStart.copy(),this.colorStart=colorStart,this.colorEnd=colorEnd,this.lifeTime=lifeTime,this.sizeStart=sizeStart,this.sizeEnd=sizeEnd,this.velocity=velocity,this.angleVelocity=angleVelocity,this.spawnTime=time,this.mirror=randBool(),this.groundObject=void 0,this.destroyed=!1,this.tileInfo=emitter.tileInfo}update(){if(!this.destroyed){var emitter=this.emitter,damping=emitter.damping,angleDamping=emitter.angleDamping,restitution=emitter.restitution,friction=emitter.friction,gravityScale=emitter.gravityScale,collideLevel=emitter.collideLevel,collideCallback=emitter.particleCollideCallback,updateCallback=emitter.particleUpdateCallback;if(this.lifeTime<=0||time-this.spawnTime>this.lifeTime)this.destroy();else{var collideLevel=enablePhysicsSolver&&collideLevel,oldX=this.pos.x,oldY=this.pos.y;let gravityX=gravity.x*gravityScale,gravityY=gravity.y*gravityScale;if(emitter.localSpace&&emitter.angle&&(gravityScale=cos(emitter.angle),emitter=sin(emitter.angle),[gravityX,gravityY]=[gravityX*gravityScale-gravityY*emitter,gravityX*emitter+gravityY*gravityScale]),this.velocity.x=this.velocity.x*damping+gravityX,this.velocity.y=this.velocity.y*damping+gravityY,collideLevel){var emitter=this.velocity.lengthSquared();if(emitter>objectMaxSpeed*objectMaxSpeed){const s=objectMaxSpeed/emitter**.5;this.velocity.x*=s,this.velocity.y*=s}}this.pos.x+=this.velocity.x,this.pos.y+=this.velocity.y,this.angle+=this.angleVelocity*=angleDamping,collideLevel&&(this.groundObject=void 0,particleCollideTest(this,collideCallback,this.pos.x,this.pos.y)&&(gravityScale=tileCollisionTest(this.pos),particleCollideTest(this,collideCallback,oldX,oldY)||(damping=particleCollideTest(this,collideCallback,this.pos.x,oldY),emitter=particleCollideTest(this,collideCallback,oldX,this.pos.y),angleDamping=gravityScale?max(restitution,gravityScale.restitution):restitution,collideLevel=gravityScale?max(friction,gravityScale.friction):friction,damping&&(this.pos.x=oldX,this.velocity.x*=-angleDamping,this.velocity.y*=collideLevel),!emitter&&damping)||((this.velocity.y<0&&gravity.y<0||0<this.velocity.y&&0<gravity.y)&&(this.groundObject=gravityScale),this.pos.y=oldY,this.velocity.y*=-angleDamping,this.velocity.x*=collideLevel)),this.destroyed)||updateCallback?.(this)}}}destroy(){var destroyCallback,c;this.destroyed||(destroyCallback=this.emitter.particleDestroyCallback,c=this.colorEnd,this.color.set(c.r,c.g,c.b,c.a),this.size.set(this.sizeEnd,this.sizeEnd),this.destroyed=!0,destroyCallback?.(this))}render(){var s,emitter=this.emitter,localSpace=emitter.localSpace,additive=emitter.additive&&!glAdditive,trailScale=emitter.trailScale,fadeRate=emitter.fadeRate/2,p1=0<this.lifeTime?min((time-this.spawnTime)/this.lifeTime,1):1,p2=1-p1,sizeNow=p2*this.sizeStart+p1*this.sizeEnd,sizeNow=(this.size.set(sizeNow,sizeNow),particleDrawSize.set(sizeNow,sizeNow)),fadeRate=p1<fadeRate?p1/fadeRate:1-fadeRate<p1?(1-p1)/fadeRate:1,p2=(this.color.r=p2*this.colorStart.r+p1*this.colorEnd.r,this.color.g=p2*this.colorStart.g+p1*this.colorEnd.g,this.color.b=p2*this.colorStart.b+p1*this.colorEnd.b,this.color.a=(p2*this.colorStart.a+p1*this.colorEnd.a)*fadeRate,particleDrawPos.set(this.pos.x,this.pos.y));let angle=this.angle;localSpace&&(p1=emitter.angle,fadeRate=cos(-p1),s=sin(-p1),p2.set(emitter.pos.x+p2.x*fadeRate-p2.y*s,emitter.pos.y+p2.x*s+p2.y*fadeRate),angle+=p1),additive&&setAdditiveBlendMode(),trailScale&&(fadeRate=(s=localSpace?this.velocity.rotate(emitter.angle):this.velocity).length())&&(sizeNow.y=max(sizeNow.x,fadeRate*trailScale),angle=atan2(s.x,s.y)),drawTile(p2,sizeNow,this.tileInfo,this.color,angle,this.mirror,void 0,glEnable,!1),additive&&setAdditiveBlendMode(!1)}}let glCanvas,glContext,glAntialias=!0,glMipmappedTextures=new WeakSet,glMipmapsUntilTarget=new WeakSet,glMipmapsStale=new Set,glPremultipliedTextures=new WeakSet,glShaderPremultiplied,glEnableBeforeLoss=!0,glShader,glPolyShader,glPolyMode,glAdditive,glBatchAdditive,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glBatchCount,glTextureInfos=new Set,glInstancedVAO,glPolyVAO,glFramebuffer,glRenderTarget,glShaderObjects=[],glCustomShader,glBatchShader,glProgramCustom,glTransform,glRenderTargetSaved,glUniformLocations=new WeakMap,glCanBeEnabled=!0,glColorMask=-1,glColorAdditive=0,glRenderTargetBase,glSkipScreenSpace=!1;const gl_ARRAY_BUFFER_SIZE=5e5,gl_INDICES_PER_INSTANCE=11,gl_INSTANCE_BYTE_STRIDE=4*gl_INDICES_PER_INSTANCE,gl_MAX_INSTANCES=gl_ARRAY_BUFFER_SIZE/gl_INSTANCE_BYTE_STRIDE|0,gl_INDICES_PER_POLY_VERTEX=3,gl_POLY_VERTEX_BYTE_STRIDE=4*gl_INDICES_PER_POLY_VERTEX,gl_MAX_POLY_VERTEXES=gl_ARRAY_BUFFER_SIZE/gl_POLY_VERTEX_BYTE_STRIDE|0,gl_VERTEX_SOURCE="#version 300 es\nprecision highp float;uniform mat4 m;layout(location=0) in vec2 g;layout(location=1) in vec4 p;layout(location=2) in vec4 u;layout(location=3) in vec4 c;layout(location=4) in vec4 a;layout(location=5) in float r;out vec2 v,l;out vec4 d,e;void main(){vec2 s=(g-.5)*p.zw;gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);v=mix(u.xw,u.zy,g);l=g;d=c;e=a;}",gl_FRAGMENT_TINT_SOURCE="c=t*d+e;if(premultipliedTexture){c.a=min(c.a,1.);c.rgb=min(t.rgb*d.rgb*min(d.a+e.a,1.)+e.rgb*c.a,c.a);}";function glSetInstancedMode(force=!1){(force||glPolyMode)&&(glFlush(),glPolyMode=!1,glContext.useProgram(glShader),glContext.bindVertexArray(glInstancedVAO),glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer))}function glPreRender(clear=!0){var dpr,sa,bufferSizeX;glEnable&&glContext&&(dpr=glRenderTarget?1:getCanvasPixelRatio(),bufferSizeX=mainCanvasSize.x*dpr|0,dpr=mainCanvasSize.y*dpr|0,glRenderTarget||glCanvas.width===bufferSizeX&&glCanvas.height===dpr||(glCanvas.width=bufferSizeX,glCanvas.height=dpr),glContext.viewport(0,0,bufferSizeX,dpr),clear&&glClearCanvas(),bufferSizeX=vec2(2*cameraScale).divide(mainCanvasSize),glRenderTarget&&(bufferSizeX.y=-bufferSizeX.y),dpr=cameraPos.rotate(-cameraAngle),clear=vec2(-1).subtract(dpr.multiply(bufferSizeX)),dpr=cos(cameraAngle),sa=sin(cameraAngle),sa=[bufferSizeX.x*dpr,bufferSizeX.y*sa,0,0,-bufferSizeX.x*sa,bufferSizeX.y*dpr,0,0,1,1,1,0,clear.x,clear.y,0,1],glTransform=sa,(bufferSizeX=(program,uniform,value)=>{glContext.useProgram(program),glContext.uniformMatrix4fv(glUniformLocation(program,uniform),!1,value)})(glPolyShader,"m",sa),bufferSizeX(glShader,"m",sa),glContext.activeTexture(glContext.TEXTURE0),textureInfos[0]&&(glActiveTexture=textureInfos[0].glTexture,glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)),glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer),glSetInstancedMode(!(glAdditive=glBatchAdditive=!1)))}function glClearCanvas(){var color;glContext&&(color=canvasClearColor,glContext.clearColor(color.r*color.a,color.g*color.a,color.b*color.a,color.a),glContext.clear(glContext.COLOR_BUFFER_BIT))}function glSetTexture(texture){glContext&&(texture!==glActiveTexture&&(glFlush(),glActiveTexture=texture,glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)),glMipmapsStale.size)&&glUpdateMipmaps(texture)}function glUpdateMipmaps(texture){glMipmapsStale.has(texture)&&(glMipmapsStale.delete(texture),glContext.generateMipmap(glContext.TEXTURE_2D))}const glWrapRepeat=Object.freeze([10497,10497]),glWrapClamp=Object.freeze([33071,33071]);function glWrapModes(wrap){return isArray(wrap)?wrap:wrap?glWrapRepeat:glWrapClamp}function glSetTextureWrap(texture,wrap=!0){var isCurrent;glContext&&texture&&([texture,wrap]=((isCurrent=texture===glActiveTexture)?glFlush():glContext.bindTexture(glContext.TEXTURE_2D,texture),glWrapModes(wrap)),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,texture),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,wrap),!isCurrent)&&glActiveTexture&&glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)}function glCompileShader(source,type){if(glContext){type=glContext.createShader(type);if(glContext.shaderSource(type,source),glContext.compileShader(type),debug&&!glContext.getShaderParameter(type,glContext.COMPILE_STATUS))throw glContext.getShaderInfoLog(type);return type}}function glCreateProgram(vsSource,fsSource){if(glContext){var program=glContext.createProgram();if(glContext.attachShader(program,glCompileShader(vsSource,glContext.VERTEX_SHADER)),glContext.attachShader(program,glCompileShader(fsSource,glContext.FRAGMENT_SHADER)),glContext.linkProgram(program),debug&&!glContext.getProgramParameter(program,glContext.LINK_STATUS))throw glContext.getProgramInfoLog(program);return program}}function glUniformLocation(program,name){let cache=glUniformLocations.get(program);return cache||glUniformLocations.set(program,cache={}),cache[name]??=glContext.getUniformLocation(program,name)}function glCreateTexture(image,wrap=!1){if(glContext){var texture=glContext.createTexture();let mipMap=!1;image?.width?(glSetTextureData(texture,image),glContext.bindTexture(glContext.TEXTURE_2D,texture),!(mipMap=!tilesPixelated)||isPowerOfTwo(image.width)&&isPowerOfTwo(image.height)||glMipmapsUntilTarget.add(texture)):(image=new Uint8Array([255,255,255,255]),glContext.bindTexture(glContext.TEXTURE_2D,texture),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,1,1,0,glContext.RGBA,glContext.UNSIGNED_BYTE,image));var image=tilesPixelated?glContext.NEAREST:glContext.LINEAR,minFilter=mipMap?glContext.LINEAR_MIPMAP_LINEAR:image,[image,minFilter]=(glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,image),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,minFilter),glWrapModes(wrap));return glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,image),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,minFilter),mipMap&&(glContext.generateMipmap(glContext.TEXTURE_2D),glMipmappedTextures.add(texture)),glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture),texture}}function glDeleteTexture(texture){glContext&&glContext.deleteTexture(texture)}function glSetTextureData(texture,image){var canvas;glContext&&(texture===glActiveTexture&&glFlush(),glContext.bindTexture(glContext.TEXTURE_2D,texture),"undefined"!=typeof ImageBitmap&&image instanceof ImageBitmap&&((canvas=new OffscreenCanvas(image.width,image.height)).getContext("2d").drawImage(image,0,0),image=canvas),canvas=!tilesPixelated,glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL,canvas),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,image),glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),canvas?glPremultipliedTextures.add(texture):glPremultipliedTextures.delete(texture),glMipmappedTextures.has(texture)&&glContext.generateMipmap(glContext.TEXTURE_2D),glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture))}function glFlush(){if(glEnable&&glContext&&glBatchCount){var height,premultiplied=!glPolyMode&&glPremultipliedTextures.has(glActiveTexture),sourceBlend=premultiplied?glContext.ONE:glContext.SRC_ALPHA,destBlend=glBatchAdditive?glContext.ONE:glContext.ONE_MINUS_SRC_ALPHA;if(glContext.blendFuncSeparate(sourceBlend,destBlend,glContext.ONE,destBlend),glContext.enable(glContext.BLEND),!glPolyMode&&(glBatchShader||glProgramCustom)){const program=glBatchShader?(sourceBlend=glBatchShader).program||=glCreateProgram(gl_VERTEX_SOURCE,"#version 300 es\nprecision highp float;uniform sampler2D iChannel0;uniform vec3 iResolution;uniform float iTime;uniform bool premultipliedTexture;in vec2 v,l;in vec4 d,e;out vec4 c;\n#define localUV l\n"+sourceBlend.fragmentCode+"\nvoid main(){vec4 t;mainImage(t,v);"+gl_FRAGMENT_TINT_SOURCE+"}"):glShader;glContext.useProgram(program),glProgramCustom=!!glBatchShader,glBatchShader&&(destBlend=name=>glUniformLocation(program,name),glContext.uniformMatrix4fv(destBlend("m"),!1,glTransform),glContext.uniform1f(destBlend("iTime"),time),sourceBlend=glRenderTarget?mainCanvasSize.x:glCanvas.width,height=glRenderTarget?mainCanvasSize.y:glCanvas.height,glContext.uniform3f(destBlend("iResolution"),sourceBlend,height,1),glContext.uniform1i(destBlend("premultipliedTexture"),+premultiplied))}glPolyMode||glBatchShader||glShaderPremultiplied===premultiplied||(glContext.uniform1i(glUniformLocation(glShader,"premultipliedTexture"),+premultiplied),glShaderPremultiplied=premultiplied);sourceBlend=glBatchCount*(glPolyMode?gl_INDICES_PER_POLY_VERTEX:gl_INDICES_PER_INSTANCE);glContext.bufferSubData(glContext.ARRAY_BUFFER,0,glPositionData,0,sourceBlend),glPolyMode?glContext.drawArrays(glContext.TRIANGLE_STRIP,0,glBatchCount):glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP,0,4,glBatchCount),++drawCount,primitiveCount+=glBatchCount,glBatchCount=0}glBatchAdditive=glAdditive,glBatchShader=glCustomShader}function glCopyToContext(context){glEnable&&glContext&&(glFlush(),context.drawImage(glCanvas,0,0))}function glSetAntialias(antialias=!0){glAntialias=antialias}function glDraw(x,y,sizeX,sizeY,angle=0,uv0X=0,uv0Y=0,uv1X=1,uv1Y=1,rgba=-1,rgbaAdditive=0){(glBatchCount>=gl_MAX_INSTANCES||glBatchAdditive!==glAdditive||glBatchShader!==glCustomShader)&&glFlush(),glSetInstancedMode();var offset=glBatchCount++*gl_INDICES_PER_INSTANCE;glPositionData[offset++]=x,glPositionData[offset++]=y,glPositionData[offset++]=sizeX,glPositionData[offset++]=sizeY,glPositionData[offset++]=uv0X,glPositionData[offset++]=uv0Y,glPositionData[offset++]=uv1X,glPositionData[offset++]=uv1Y,glColorData[offset++]=rgba&glColorMask,glColorData[offset++]=rgbaAdditive&glColorMask|glColorAdditive,glPositionData[+offset]=angle}function glDrawUntextured(x,y,sizeX,sizeY,angle,rgba){glDraw(x,y,sizeX,sizeY,angle,0,0,0,0,0,rgba)}function glDrawPointsTransform(points,rgba,x,y,sx,sy,angle,tristrip=!0){var pointsOut=[],sa=sin(-angle),ca=cos(-angle);for(const p of points){var px=p.x*sx,py=p.y*sy;pointsOut.push(vec2(x+ca*px-sa*py,y+sa*px+ca*py))}glDrawPoints(tristrip?glPolyStrip(pointsOut):pointsOut,rgba)}function glDrawOutlineTransform(points,rgba,lineWidth,x,y,sx,sy,angle,wrap=!0){glDrawPointsTransform(function(points,width,wrap=!0){if(points.length<2)return[];var halfWidth=width/2,strip=[],n=points.length,e=1e-6;for(let i=0;i<n;i++){var prev=points[wrap?(i-1+n)%n:max(i-1,0)],curr=points[i],next=points[wrap?(i+1)%n:min(i+1,n-1)],dx1=curr.x-prev.x,prev=curr.y-prev.y,len1=(dx1*dx1+prev*prev)**.5,dx2=next.x-curr.x,next=next.y-curr.y,len2=(dx2*dx2+next*next)**.5;if(!(len1<e&&len2<e)){prev=e<len1?-prev/len1:0,dx1=e<len1?dx1/len1:0;let nx=prev+(e<len2?-next/len2:0),ny=dx1+(e<len2?dx2/len2:0);len1=(nx*nx+ny*ny)**.5,len2=(len1<e?(nx=prev,ny=dx1):(nx/=len1,ny/=len1,next=prev*nx+dx1*ny,e<next&&(dx2=min(1/next,10),nx*=dx2,ny*=dx2)),vec2(curr.x-nx*halfWidth,curr.y-ny*halfWidth)),len1=vec2(curr.x+nx*halfWidth,curr.y+ny*halfWidth);strip.push(len2),strip.push(len1)}}1<strip.length&&wrap&&(strip.push(strip[0]),strip.push(strip[1]));return strip}(points,lineWidth,wrap),rgba,x,y,sx,sy,angle,!1)}function glDrawPoints(points,rgba){glDrawPolyStrip(points,rgba)}function glDrawColoredPoints(points,pointColors){glDrawPolyStrip(points,0,pointColors)}function glDrawPolyStrip(points,rgba,pointColors){if(glEnable&&!(points.length<3)){var vertCount=points.length+2;if((glBatchCount+vertCount>=gl_MAX_POLY_VERTEXES||glBatchAdditive!==glAdditive)&&glFlush(),!(vertCount>=gl_MAX_POLY_VERTEXES)){glPolyMode||(glFlush(),glPolyMode=!0,glContext.useProgram(glPolyShader),glContext.bindVertexArray(glPolyVAO));let offset=glBatchCount*gl_INDICES_PER_POLY_VERTEX;for(let i=vertCount;i--;){var j=clamp(i-1,0,vertCount-3),point=points[j],j=pointColors?pointColors[j]:rgba;glPositionData[offset++]=point.x,glPositionData[offset++]=point.y,glColorData[offset++]=j&glColorMask|glColorAdditive}glBatchCount+=vertCount}}}function glSetRenderTarget(texture,clear=!1){glFlush();var baseSize,previousTarget=glRenderTarget;if(texture)glMipmapsUntilTarget.has(texture)&&(glMipmapsUntilTarget.delete(texture),glMipmappedTextures.delete(texture),glContext.bindTexture(glContext.TEXTURE_2D,texture),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,glContext.LINEAR),glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)),glPremultipliedTextures.add(texture),glRenderTarget||(glRenderTargetSaved=[glTransform,glAdditive]),glRenderTarget=texture,glContext.bindFramebuffer(glContext.FRAMEBUFFER,glFramebuffer),glContext.framebufferTexture2D(glContext.FRAMEBUFFER,glContext.COLOR_ATTACHMENT0,glContext.TEXTURE_2D,texture,0),glPreRender(clear);else if(glRenderTarget=void 0,glRenderTargetBase?([clear,baseSize]=glRenderTargetBase,glContext.bindFramebuffer(glContext.FRAMEBUFFER,glFramebuffer),glContext.framebufferTexture2D(glContext.FRAMEBUFFER,glContext.COLOR_ATTACHMENT0,glContext.TEXTURE_2D,clear,0),glContext.viewport(0,0,baseSize.x,baseSize.y)):(glContext.bindFramebuffer(glContext.FRAMEBUFFER,null),glContext.viewport(0,0,glCanvas.width,glCanvas.height)),glRenderTargetSaved){[glTransform,glAdditive]=glRenderTargetSaved,glRenderTargetSaved=void 0;for(const program of[glPolyShader,glShader])glContext.useProgram(program),glContext.uniformMatrix4fv(glUniformLocation(program,"m"),!1,glTransform);glBatchAdditive=glAdditive,glSetInstancedMode(!0)}previousTarget&&previousTarget!==texture&&glMipmappedTextures.has(previousTarget)&&glMipmapsStale.add(previousTarget)}function glClearRect(x,y,width,height){glEnable&&(glFlush(),glContext.enable(glContext.SCISSOR_TEST),glContext.scissor(x,y,width,height),glContext.clearColor(0,0,0,0),glContext.clear(glContext.COLOR_BUFFER_BIT),glContext.disable(glContext.SCISSOR_TEST))}function glPolyStrip(points){if(points.length<3)return[];const cross=(a,b,c)=>(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);(poly=>{let area=0;for(let i=poly.length;i--;){var j=(i+1)%poly.length;area+=poly[i].cross(poly[j])}return area})(points)<0&&(points=points.slice().reverse());const e=1e-9,n=points.length;let convex=!0,flips=0,lastSide=0;for(let i=0;convex&&i<n;++i){var a=points[(i+n-1)%n],b=points[i],dx=points[(i+1)%n].x-b.x,a=(convex=cross(a,b,points[(i+1)%n])>-e,dx>e?1:dx<-e?-1:0);a&&a!==lastSide&&(lastSide&&++flips,lastSide=a)}if(convex&&flips<=2){const strip=[points[0]];for(let i=1,j=n-1;i<=j;++i,--j)strip.push(points[i]),i!==j&&strip.push(points[j]);return strip}var indices=[];for(let i=0;i<points.length;++i)indices[i]=i;var triangles=[];let attempts=0;for(var maxAttempts=points.length**2+100;3<indices.length&&attempts++<maxAttempts;){let foundEar=!1;for(let i=0;i<indices.length;i++){var i0=indices[(i+indices.length-1)%indices.length],i1=indices[i],i2=indices[(i+1)%indices.length];const a=points[i0],b=points[i1],c=points[i2];if(!(cross(a,b,c)<e)){let hasInside=!1;for(let j=0;j<indices.length;j++){var k=indices[j];if(k!==i0&&k!==i1&&k!==i2){k=points[k];if(hasInside=((p,a,b,c)=>{var c1=cross(a,b,p),b=cross(b,c,p),c=cross(c,a,p),a=(c1<-e?1:0)+(b<-e?1:0)+(c<-e?1:0),p=(c1>e?1:0)+(b>e?1:0)+(c>e?1:0);return!(a&&p)})(k,a,b,c))break}}if(!hasInside){triangles.push([i0,i1,i2]),indices.splice(i,1),foundEar=!0;break}}}if(!foundEar){let worstIndex=-1,worstValue=1/0;for(let i=0;i<indices.length;i++){const i0=indices[(i+indices.length-1)%indices.length],i1=indices[i],i2=indices[(i+1)%indices.length];var value=abs(cross(points[i0],points[i1],points[i2]));value<worstValue&&(worstValue=value,worstIndex=i)}if(worstIndex<0)break;const i0=indices[(worstIndex+indices.length-1)%indices.length],i1=indices[worstIndex],i2=indices[(worstIndex+1)%indices.length];triangles.push([i0,i1,i2]),indices.splice(worstIndex,1)}}if(3===indices.length&&triangles.push([indices[0],indices[1],indices[2]]),!triangles.length)return[];const strip=[];let[a0,b0,c0]=triangles[0];strip.push(points[a0],points[b0],points[c0]);for(let i=1;i<triangles.length;i++){const[a,b,c]=triangles[i];strip.push(points[c0],points[a]),strip.push(points[a],points[b],points[c]),c0=c}return strip}function drawEngineLogo(t){const blackAndWhite=0;engineUpdateCanvas();const x=mainContext,w=mainCanvasSize.x;var h=mainCanvasSize.y,p3=percent(t,1,.8),p4=percent(t,0,.5),g=x.createRadialGradient(w/2,h/2,0,w/2,h/2,.6*hypot(w,h));g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString()),g.addColorStop(1,hsl(0,0,0,p3).toString()),x.save(),x.fillStyle=g,x.fillRect(0,0,w,h);const gradient=(X1,Y1,X2,Y2,C,S=1)=>{0<=C?blackAndWhite?x.fillStyle="#fff":((X1=x.fillStyle=x.createLinearGradient(X1,Y1,X2,Y2)).addColorStop(0,color(C,2)),X1.addColorStop(1,color(C,1))):x.fillStyle="#000",-1<=C&&(x.fill(),!S)||x.stroke()};var circle=(X,Y,R,A=0,B=2*PI,C,S)=>{x.beginPath(),x.arc(X,Y,R,p*A,p*B),gradient(X,Y-R,X,Y+R,C,S)},p4=(X,Y,W,H,C)=>{x.beginPath(),x.rect(X,Y,W,H*p),gradient(X,Y+H,X+W,Y,C)},poly=(points,C,Y,H)=>{x.beginPath();for(const p of points)x.lineTo(p.x,p.y);x.closePath(),gradient(0,Y,0,Y+H,C)};const color=(c,l)=>l?`hsl(${360*[.95,.56,.13][c%3]} 99%${[0,50,75][l]}%)`:"#000",p=percent(oscillate(1,1,t),.1,.5);p3=min(6,min(w,h)/99);x.translate(w/2,h/2),x.scale(p3,p3),x.translate(-40,-35),p<1&&x.setLineDash([99*p,99]),x.lineJoin=x.lineCap="round",x.lineWidth=.1+1.9*p;{var s="LittleJS";x.font="900 15.5px arial",x.lineWidth=.1+3.9*p,x.textAlign="center",x.textBaseline="top",p4(11,55,59,8*p,-1),x.beginPath();let w2=0;for(let i=0;i<s.length;++i)w2+=x.measureText(s[i]).width;for(let j=2;j--;)for(let i=0,X=40-w2/2;i<s.length;++i){const w=x.measureText(s[i]).width,X2=X+w/2;gradient(X2,54,X2+2,67,5<i?1:0),x[j?"strokeText":"fillText"](s[i],X2,54.5,17*p),X+=w}x.lineWidth=.1+1.9*p,p4(3,54,73,0)}p4(7,15,26,-7,0),p4(25,15,8,25,-1),p4(10,40,15,-25,1),p4(14,21,7,9,2),p4(38,20,6,-6,2),p4(49,20,10,-6,0),poly([vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)],2,8,6*p),p4(44,8,20,-7,0);for(let i=5;i--;)circle(59-6*i*p,30,10,0,2*PI,1,0);circle(59,30,4,0,7,2),p4(35,20,24,0),circle(59,30,10),circle(47,30,10,PI/2,3*PI/2),circle(35,30,10,PI/2,3*PI/2),p4(7,40,13,7,-1),p4(17,40,43,14,-1);for(let i=3;i--;)for(let j=2;j--;)circle(17+15*i,47,j?7:1,0,2*PI,2);for(let i=2;i--;){let o=53+6*p*i;poly([vec2(7+o,54),vec2(o,40),vec2(o+6*p,40),vec2(7+o+6*p,54)],0,40,14)}x.restore()}let medalDisplayTime=5,medalDisplaySlideTime=.5,medalDisplaySize=vec2(640,80),medalsPreventUnlock=!1;const medals={};let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast,medalsRenderAdded,medalsLoadWaiting=!1;function medalsInit(saveName){medalsSaveName=saveName,medalsLoad(),medalsRenderAdded||engineAddPlugin(void 0,medalsRender),medalsRenderAdded=!0}function medalsLoad(){if(medalsLoadWaiting||=!Object.keys(medals).length,medalsSaveName){const saved=readSaveData(medalsSaveName);medalsForEach(medal=>{medal.isLocal()&&(medal.unlocked=!!saved[medal.id]?.unlocked)}),medalsSave()}}function medalsRender(){var medal,elapsed,slideTime,slideOffTime;medalsDisplayQueue.length&&(medal=medalsDisplayQueue[0],elapsed=timeReal-medalsDisplayTimeLast,medalsDisplayTimeLast?elapsed>medalDisplayTime?(medalsDisplayTimeLast=0,medalsDisplayQueue.shift()):(slideTime=min(medalDisplaySlideTime,medalDisplayTime/2),slideOffTime=medalDisplayTime-slideTime,medal.render(elapsed<slideTime?1-elapsed/slideTime:slideOffTime<elapsed?(elapsed-slideOffTime)/slideTime:0)):medalsDisplayTimeLast=timeReal)}function medalsForEach(callback){Object.values(medals).forEach(medal=>callback(medal))}function medalsReset(){if(medalsForEach(medal=>medal.isLocal()&&(medal.unlocked=!1)),medalsLoadWaiting&&medalsSaveName){var saved=readSaveData(medalsSaveName);for(const id in saved)!medals[id]&&saved[id]&&"object"==typeof saved[id]&&(saved[id].unlocked=!1);writeSaveData(medalsSaveName,saved)}medalsSave()}function medalsSave(){if(medalsSaveName){const saved=readSaveData(medalsSaveName),data=medalsLoadWaiting?{...saved}:{};for(const key in saved)isNumber(+key)||(data[key]=saved[key]);medalsForEach(medal=>{var entry;medal.isLocal()?(entry={name:medal.name,description:medal.description,icon:medal.icon,unlocked:medal.unlocked},medal.image&&(entry.src=medal.image.src),data[medal.id]=entry):saved[medal.id]&&(data[medal.id]=saved[medal.id])}),writeSaveData(medalsSaveName,data)}}class Medal{constructor(id,name,description="",icon="🏆",src){this.id=id,this.name=name,this.description=description,this.icon=icon,this.unlocked=!1,this.image=void 0,src&&((this.image=new Image).src=src),medals[id]=this,medalsLoadWaiting&&this.isLocal()&&(this.unlocked=!!readSaveData(medalsSaveName)[id]?.unlocked)}unlock(){return medalsPreventUnlock||this.unlocked||(this.unlocked=!0,medalsSave(),medalsDisplayQueue.push(this)),Promise.resolve(this.unlocked)}isLocal(){return!0}render(hidePercent=0){var context=mainContext,width=min(medalDisplaySize.x,mainCanvasSize.x),height=medalDisplaySize.y,x=mainCanvasSize.x-width,hidePercent=-height*hidePercent,backgroundColor=hsl(0,0,.9),backgroundColor=(context.save(),context.beginPath(),context.fillStyle=backgroundColor.toString(),context.strokeStyle=BLACK.toString(),context.lineWidth=3,context.rect(1.5+x,1.5+hidePercent,width-3,height-3),context.fill(),context.stroke(),context.clip(),vec2(.1,.05).scale(height)),iconSize=height-2*backgroundColor.x,nameSize=(this.renderIcon(vec2(x+backgroundColor.x+iconSize/2,hidePercent+height/2),iconSize),.5*height),descriptionSize=.3*height,x=vec2(x+iconSize+2*backgroundColor.x,hidePercent+2*backgroundColor.y+nameSize/2),width=width-iconSize-3*backgroundColor.x;drawTextScreen(this.name,x,nameSize,BLACK,0,void 0,"left",void 0,void 0,width),x.y=hidePercent+height-2*backgroundColor.y-descriptionSize/2,drawTextScreen(this.description,x,descriptionSize,BLACK,0,void 0,"left",void 0,void 0,width),context.restore()}renderIcon(pos,size){var image=this.image;image&&image.complete&&image.naturalWidth?mainContext.drawImage(image,pos.x-size/2,pos.y-size/2,size,size):drawTextScreen(this.icon,pos,.7*size,BLACK)}}function setMedalDisplayTime(time){medalDisplayTime=time}function setMedalDisplaySlideTime(time){medalDisplaySlideTime=time}function setMedalDisplaySize(size){medalDisplaySize=size.copy()}function setMedalsPreventUnlock(preventUnlock){medalsPreventUnlock=preventUnlock}let newgrounds;const newgroundsUnlocksToResend=new Set,newgroundsUnlocksRefused=new Set,newgroundsSecureComponents=["Medal.unlock","ScoreBoard.postScore"],newgroundsSessionErrors=[104,110,111];function newgroundsSessionLost(response){return newgroundsSessionErrors.includes(response?.result?.data?.error?.code??response?.error?.code)}function newgroundsNotifyPage(component,id){globalThis.top?.postMessage(JSON.stringify({ngioComponent:component,id:id}),"*")}class NewgroundsMedal extends Medal{constructor(id,name,description,icon,src){super(id,name,description,icon,src),this.difficulty=void 0,this.value=void 0}isLocal(){return!newgrounds?.session_id}unlock(){if(medalsPreventUnlock||this.unlocked||this.isLocal())return super.unlock();if(newgroundsUnlocksRefused.has(this))return Promise.resolve(!1);const pending=newgrounds.pendingUnlocks;var request;return pending.has(this)?pending.get(this):(request=newgrounds.unlockMedal(this.id).then(response=>{newgroundsSessionLost(response)&&newgrounds.dropSession();var serverMedal=response?.result?.data?.medal;return!serverMedal?.unlocked||medalsPreventUnlock?this.isLocal()?this.unlocked:((!response||serverMedal?.unlocked?newgroundsUnlocksToResend:(pending.delete(this),newgroundsUnlocksRefused)).add(this),!1):((response=newgrounds.medals.find(m=>m.id==this.id))&&Object.assign(response,serverMedal),serverMedal.icon&&serverMedal.icon!=this.image?.src&&((this.image=new Image).src=serverMedal.icon),pending.delete(this),newgroundsNotifyPage("Medal.unlock",this.id),super.unlock())}),pending.set(this,request),request)}}class NewgroundsPlugin{constructor(app_id,cipher){(newgrounds=this).app_id=app_id,this.cipher=cipher,this.cryptoKey=void 0;app_id="undefined"!=typeof location;this.host=app_id?location.hostname:"",this.medals=[],this.scoreboards=[],this.user=null,this.pendingUnlocks=new Map,this.session_id=app_id?new URL(location.href).searchParams.get("ngio_session_id"):null,this.session_id&&medalsForEach(medal=>medal instanceof NewgroundsMedal&&(medal.unlocked=!1)),this.ready=this.init()}logView(){}async init(){this.call("App.logView",{host:this.host});let medalList;this.session_id&&(session=(session=(await this.call("App.checkSession"))?.result?.data?.session)&&!session.expired&&session.user,(medalList=session&&(await this.call("Medal.getList"))?.result?.data?.medals)&&this.session_id?this.user=session:(this.dropSession(),medalList=void 0)),medalList=medalList||(await this.call("Medal.getList"))?.result?.data?.medals,this.medals=medalList||[];for(const newgroundsMedal of this.medals){var medal=medals[newgroundsMedal.id];medal instanceof NewgroundsMedal&&(medal.image=new Image,medal.image.src=newgroundsMedal.icon,medal.name=newgroundsMedal.name,medal.description=newgroundsMedal.description,medal.unlocked=medal.unlocked||!!newgroundsMedal.unlocked,medal.difficulty=newgroundsMedal.difficulty,medal.value=newgroundsMedal.value,medal.value&&(medal.description+=` (${medal.value})`),this.session_id)&&medal.unlocked&&(newgroundsMedal.unlocked=!0,this.pendingUnlocks.delete(medal),newgroundsUnlocksToResend.delete(medal),newgroundsUnlocksRefused.delete(medal))}var session=await this.call("ScoreBoard.getBoards");if(this.scoreboards=session?.result?.data?.scoreboards||[],this.session_id){const keepAlive=setInterval(async()=>{var response,session;return this.session_id?(session=(response=await this.call("App.checkSession"))?.result?.data?.session,newgroundsSessionLost(response)||session&&(session.expired||!session.user)?this.dropSession():void this.resendUnlocks()):clearInterval(keepAlive)},6e4)}return this}dropSession(){if(this.session_id){var confirmed=Object.values(medals).filter(medal=>medal.unlocked),confirmed=(this.session_id=null,this.user=null,medalsLoad(),confirmed.forEach(medal=>medal.unlocked=!0),medalsSave(),[...this.pendingUnlocks.keys()]);this.pendingUnlocks.clear(),newgroundsUnlocksToResend.clear(),newgroundsUnlocksRefused.clear();for(const medal of confirmed)medal.unlock()}}resendUnlocks(){if(!medalsPreventUnlock){for(const medal of newgroundsUnlocksToResend)this.pendingUnlocks.delete(medal),medal.unlock();newgroundsUnlocksToResend.clear()}}unlockMedal(id){return this.call("Medal.unlock",{id:id})}postScore(id,value){return this.call("ScoreBoard.postScore",{id:id,value:value}).then(response=>(response?.result?.data?.success&&newgroundsNotifyPage("ScoreBoard.postScore",id),newgroundsSessionLost(response)&&this.dropSession(),response))}getScores(id,user,social=!1,skip=0,limit=10,period){var session_id=user||social?this.session_id:null;return this.call("ScoreBoard.getScores",{id:id,user:user,social:social,skip:skip,limit:limit,period:period},session_id)}async encrypt(text){this.cryptoKey||(keyBytes=Uint8Array.from(atob(this.cipher),c=>c.charCodeAt(0)),this.cryptoKey=await crypto.subtle.importKey("raw",keyBytes,"AES-CBC",!1,["encrypt"]));var keyBytes=crypto.getRandomValues(new Uint8Array(16)),text=new Uint8Array(await crypto.subtle.encrypt({name:"AES-CBC",iv:keyBytes},this.cryptoKey,(new TextEncoder).encode(text))),bytes=new Uint8Array(keyBytes.length+text.length);bytes.set(keyBytes),bytes.set(text,keyBytes.length);let binary="";for(const b of bytes)binary+=String.fromCharCode(b);return btoa(binary)}async call(component,parameters,session_id=this.session_id){try{let execute={component:component,parameters:parameters};if(this.cipher&&newgroundsSecureComponents.includes(component)){var secure=await this.encrypt(JSON.stringify(execute)).catch(e=>!1);if(!secure)return{success:!1,error:{code:201,message:"Invalid Encryption: the cipher is not a Base64 AES-128 key"}};execute={secure:secure}}var request={app_id:this.app_id,session_id:session_id,execute:execute},formData=new FormData,signal=(formData.append("request",JSON.stringify(request)),globalThis.AbortSignal?.timeout?.(15e3)),text=await(await fetch("https://www.newgrounds.io/gateway_v3.php",{method:"POST",body:formData,signal:signal})).text();return text?JSON.parse(text):void 0}catch(e){}}}let postProcess;class PostProcessPlugin{constructor(shaderCode,includeMainCanvas=!1,feedbackTexture=!1){function initPostProcess(){var pLocation;headlessMode||(glEnable?(feedbackTexture&&(postProcess.feedbackTexture=glCreateTexture(),glContext.bindTexture(glContext.TEXTURE_2D,postProcess.feedbackTexture),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,1,1,0,glContext.RGBA,glContext.UNSIGNED_BYTE,new Uint8Array([0,0,0,255])),glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)),postProcess.texture=glCreateTexture(),postProcess.shader=glCreateProgram("#version 300 es\nprecision highp float;in vec2 p;void main(){gl_Position=vec4(p+p-1.,1,1);}","#version 300 es\nprecision highp float;uniform sampler2D iChannel0;uniform sampler2D iChannel1;uniform vec3 iResolution;uniform float iTime;out vec4 c;\n"+shaderCode+"\nvoid main(){mainImage(c,gl_FragCoord.xy);c.a=1.;}"),postProcess.vao=glContext.createVertexArray(),glContext.bindVertexArray(postProcess.vao),glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer),pLocation=glContext.getAttribLocation(postProcess.shader,"p"),glContext.enableVertexAttribArray(pLocation),glContext.vertexAttribPointer(pLocation,2,glContext.FLOAT,!1,8,0)):console.warn("PostProcessPlugin: WebGL not enabled!"))}postProcess=this,shaderCode=shaderCode||"void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}",this.shader=void 0,this.texture=void 0,this.feedbackTexture=void 0,this.vao=void 0,initPostProcess(),engineAddPlugin(void 0,function(){if(!headlessMode&&glEnable){if(!postProcess.shader){if(glContext.isContextLost())return;initPostProcess()}glFlush(),glContext.bindFramebuffer(glContext.FRAMEBUFFER,null),glContext.useProgram(postProcess.shader),glContext.bindVertexArray(postProcess.vao),glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,!0),glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),glContext.disable(glContext.BLEND),glContext.activeTexture(glContext.TEXTURE0),glContext.bindTexture(glContext.TEXTURE_2D,postProcess.texture),includeMainCanvas?(workCanvas.width=mainCanvas.width,workCanvas.height=mainCanvas.height,glCopyToContext(workContext),workContext.drawImage(mainCanvas,0,0),mainContext.save(),mainContext.setTransform(1,0,0,1,0,0),mainContext.clearRect(0,0,mainCanvas.width,mainCanvas.height),mainContext.restore(),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,workCanvas)):glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,glCanvas),feedbackTexture&&(glContext.activeTexture(glContext.TEXTURE1),glContext.bindTexture(glContext.TEXTURE_2D,postProcess.feedbackTexture));var uniformLocation=name=>glUniformLocation(postProcess.shader,name);glContext.uniform1i(uniformLocation("iChannel0"),0),glContext.uniform1i(uniformLocation("iChannel1"),1),glContext.uniform1f(uniformLocation("iTime"),time),glContext.uniform3f(uniformLocation("iResolution"),mainCanvas.width,mainCanvas.height,1),glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4),feedbackTexture&&(glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,glCanvas),glContext.activeTexture(glContext.TEXTURE0)),glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,!1),glContext.pixelStorei(glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),glActiveTexture&&glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture),glSetInstancedMode(!0)}},function(){postProcess.shader=void 0,postProcess.texture=void 0,postProcess.feedbackTexture=void 0},function(){initPostProcess()})}}function postProcessBloomShader(threshold=.6,strength=1,size=6){let code="",taps=0;for(let j=0;j<3;++j){var radius=((j+.5)/3)**.5*size,count=1|max(5+2*j,round(2*radius));taps+=count,code+=`
|
|
5
|
+
for (int k = 0; k < ${count}; ++k)
|
|
6
|
+
{
|
|
7
|
+
float a = float(k) * ${(2*PI/count).toFixed(7)}${j?" + "+(2.3999632*j).toFixed(7):""};
|
|
8
|
+
glow += max(vec3(0), texture(iChannel0, uv + vec2(cos(a), sin(a)) * ${radius.toFixed(4)} / iResolution.xy).rgb - ${threshold.toFixed(4)});
|
|
9
|
+
}`}return`
|
|
10
|
+
void mainImage(out vec4 color, vec2 pixel)
|
|
11
|
+
{
|
|
12
|
+
vec2 uv = pixel / iResolution.xy;
|
|
13
|
+
color = texture(iChannel0, uv);
|
|
14
|
+
vec3 glow = vec3(0);${code}
|
|
15
|
+
color.rgb += glow * ${(strength/taps).toFixed(6)};
|
|
16
|
+
}`}function postProcessBloom(threshold=.6,strength=1,size=6,includeMainCanvas=!1){return new PostProcessPlugin(postProcessBloomShader(threshold,strength,size),includeMainCanvas)}let lightSystem;class LightSystemPlugin{constructor(textureSize,ambientColor){function initLightSystem(){headlessMode||(glEnable?(lightSystem.shadowContext||=new OffscreenCanvas(1,1).getContext("2d"),lightSystem.textureSizeAuto&&(lightSystem.textureSize=mainCanvasSize.copy()),lightSystem.texture=glContext.createTexture(),glContext.bindTexture(glContext.TEXTURE_2D,lightSystem.texture),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,lightSystem.textureSize.x,lightSystem.textureSize.y,0,glContext.RGBA,glContext.UNSIGNED_BYTE,null),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,glContext.LINEAR),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,glContext.LINEAR),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,glContext.CLAMP_TO_EDGE),glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,glContext.CLAMP_TO_EDGE),lightSystem.lightShader=glCreateProgram("#version 300 es\nprecision highp float;uniform mat4 m;uniform vec2 lightPos;uniform float radius;in vec2 g;out vec2 vWorldPos;out vec2 vUV;void main(){vec2 worldP=lightPos+(g-.5)*2.*radius;gl_Position=m*vec4(worldP,1,1);vWorldPos=worldP;vUV=g;}","#version 300 es\nprecision highp float;uniform vec2 lightPos;uniform float radius;uniform float fadeRange;uniform vec4 color;uniform sampler2D shadowTexture;uniform bool useShadow;in vec2 vWorldPos;in vec2 vUV;out vec4 c;void main(){float dist=distance(vWorldPos,lightPos);float t=clamp((radius-dist)/max(fadeRange,1e-6),0.,1.);c=vec4(color.rgb*t*color.a,1.);if(useShadow)c.rgb*=texture(shadowTexture,vUV).rgb;}"),glContext.useProgram(lightSystem.lightShader),glContext.uniform1i(glUniformLocation(lightSystem.lightShader,"shadowTexture"),1),lightSystem.compositeShader=glCreateProgram("#version 300 es\nprecision highp float;in vec2 p;void main(){gl_Position=vec4(p+p-1.,1,1);}","#version 300 es\nprecision highp float;uniform sampler2D s;uniform vec3 iResolution;out vec4 c;void main(){vec2 uv=gl_FragCoord.xy/iResolution.xy;c=vec4(texture(s,uv).rgb,1.);}"),lightSystem.lightVAO=createQuadVAO(lightSystem.lightShader,"g"),lightSystem.compositeVAO=createQuadVAO(lightSystem.compositeShader,"p")):console.warn("LightSystemPlugin: WebGL not enabled!"))}function createQuadVAO(program,attribute){var gl=glContext,vao=gl.createVertexArray(),program=(gl.bindVertexArray(vao),gl.bindBuffer(gl.ARRAY_BUFFER,glGeometryBuffer),gl.getAttribLocation(program,attribute));return gl.enableVertexAttribArray(program),gl.vertexAttribPointer(program,2,gl.FLOAT,!1,8,0),vao}function createTexture(size){var gl=glContext,texture=gl.createTexture();return gl.bindTexture(gl.TEXTURE_2D,texture),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,size,size,0,gl.RGBA,gl.UNSIGNED_BYTE,null),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),texture}function clearShadows(){var ls=lightSystem;ls.shadowMap=ls.shadowTextureA=ls.shadowTextureB=void 0,ls.shadowCopyShader=ls.shadowStretchShader=void 0,ls.shadowCopyVAO=ls.shadowStretchVAO=void 0}(lightSystem=this).enabled=!0,this.ambientColor=(ambientColor||BLACK).copy(),this.textureSize=textureSize?textureSize.copy():void 0,this.textureSizeAuto=!textureSize,this.texture=void 0,this.lightShader=void 0,this.compositeShader=void 0,this.lightVAO=void 0,this.compositeVAO=void 0,this.shadows=!1,this.shadowMapSize=1024,this.shadowMapScale=2,this.shadowTextureSize=256,this.shadowPassCount=16,this.shadowSoftness=.5,this.shadowPass=!1,this.emissivePass=!1,this.shadowMap=void 0,this.shadowTextureA=void 0,this.shadowTextureB=void 0,this.shadowCopyShader=void 0,this.shadowStretchShader=void 0,this.shadowCopyVAO=void 0,this.shadowStretchVAO=void 0,this.shadowContext=void 0,this.shadowMapOrigin=vec2(),this.shadowMapWorldSize=0,this.shadowMapSizeAllocated=0,this.shadowTextureSizeAllocated=0,initLightSystem(),engineAddPlugin(void 0,function(){if(!headlessMode&&glEnable&&lightSystem.enabled){if(!lightSystem.texture){if(glContext.isContextLost())return;if(initLightSystem(),!lightSystem.texture)return}glFlush();var prevAdditive=glAdditive,size=(lightSystem.shadows&&!function(){var ls=lightSystem;ls.shadowMap&&ls.shadowMapSize===ls.shadowMapSizeAllocated&&ls.shadowTextureSize===ls.shadowTextureSizeAllocated||(ls.shadowMap&&function(){var gl=glContext,ls=lightSystem;gl.deleteTexture(ls.shadowMap),gl.deleteTexture(ls.shadowTextureA),gl.deleteTexture(ls.shadowTextureB),gl.deleteProgram(ls.shadowCopyShader),gl.deleteProgram(ls.shadowStretchShader),gl.deleteVertexArray(ls.shadowCopyVAO),gl.deleteVertexArray(ls.shadowStretchVAO),clearShadows()}(),function(){var gl=glContext,ls=lightSystem;ls.shadowMap=createTexture(ls.shadowMapSize),ls.shadowTextureA=createTexture(ls.shadowTextureSize),ls.shadowTextureB=createTexture(ls.shadowTextureSize),ls.shadowMapSizeAllocated=ls.shadowMapSize,ls.shadowTextureSizeAllocated=ls.shadowTextureSize,glActiveTexture&&gl.bindTexture(gl.TEXTURE_2D,glActiveTexture);var quadVertex="#version 300 es\nprecision highp float;in vec2 p;out vec2 uv;void main(){gl_Position=vec4(p+p-1.,1,1);uv=p;}";ls.shadowCopyShader=glCreateProgram(quadVertex,"#version 300 es\nprecision highp float;uniform sampler2D s;uniform vec2 lightPos;uniform float radius;uniform vec2 mapOrigin;uniform float mapInvSize;uniform float tap;uniform float core;in vec2 uv;out vec4 c;void main(){vec2 w=lightPos+(uv-.5)*2.*radius;vec3 o=vec3(1);for(int i=0;i<4;i++){vec2 m=(w+tap*(vec2(i%2,i/2)*2.-1.)-mapOrigin)*mapInvSize;o=min(o,m==clamp(m,0.,1.)?texture(s,vec2(m.x,1.-m.y)).rgb:vec3(1));}c=vec4(length(w-lightPos)<core?vec3(1):o,1);}"),ls.shadowStretchShader=glCreateProgram(quadVertex,"#version 300 es\nprecision highp float;uniform sampler2D s;uniform float scale;uniform float brightness;in vec2 uv;out vec4 c;void main(){float mask=1.-smoothstep(.35,.525,length(uv-.5));vec3 b=texture(s,(uv-.5)/scale+.5).rgb;c=vec4(min(texture(s,uv).rgb+brightness*mask*b,b),1);}"),ls.shadowCopyVAO=createQuadVAO(ls.shadowCopyShader,"p"),ls.shadowStretchVAO=createQuadVAO(ls.shadowStretchShader,"p"),gl.useProgram(ls.shadowCopyShader),gl.uniform1i(glUniformLocation(ls.shadowCopyShader,"s"),1),gl.useProgram(ls.shadowStretchShader),gl.uniform1i(glUniformLocation(ls.shadowStretchShader,"s"),1)}());var size=ls.shadowMapSize,view=mainCanvasSize.scale(1/cameraScale),view=ls.shadowMapScale*max(view.x,view.y),texel=view/size,texel=vec2(floor(cameraPos.x/texel)*texel,floor(cameraPos.y/texel)*texel),saved=(ls.shadowMapOrigin=texel.subtract(vec2(view/2)),ls.shadowMapWorldSize=view,[drawContext,mainCanvasSize,cameraPos,cameraScale,cameraAngle,canvasClearColor,glCustomShader]);drawContext=ls.shadowContext,mainCanvasSize=vec2(size),cameraPos=texel,cameraScale=size/view,cameraAngle=0,canvasClearColor=WHITE,glSetRenderTarget(ls.shadowMap,!0),glSkipScreenSpace=!0,ls.shadowPass=!0;try{for(const o of engineObjects)!o.destroyed&&o.castShadow&&(glColorMask=4278190080,setShader(o.shader),o.renderShadow())}finally{ls.shadowPass=!1,glColorMask=-1,glSkipScreenSpace=!1,glSetRenderTarget(),[drawContext,mainCanvasSize,cameraPos,cameraScale,cameraAngle,canvasClearColor,glCustomShader]=saved}}(),lightSystem.textureSize),size=(!lightSystem.textureSizeAuto||size.x===mainCanvasSize.x&&size.y===mainCanvasSize.y||(lightSystem.textureSize=mainCanvasSize.copy(),glContext.bindTexture(glContext.TEXTURE_2D,lightSystem.texture),glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,mainCanvasSize.x,mainCanvasSize.y,0,glContext.RGBA,glContext.UNSIGNED_BYTE,null),glActiveTexture&&glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture)),lightSystem.ambientColor),size=(glContext.bindFramebuffer(glContext.FRAMEBUFFER,glFramebuffer),glContext.framebufferTexture2D(glContext.FRAMEBUFFER,glContext.COLOR_ATTACHMENT0,glContext.TEXTURE_2D,lightSystem.texture,0),glContext.viewport(0,0,lightSystem.textureSize.x,lightSystem.textureSize.y),glContext.clearColor(size.r,size.g,size.b,size.a),glContext.clear(glContext.COLOR_BUFFER_BIT),setAdditiveBlendMode(),glContext.enable(glContext.BLEND),glContext.blendFunc(glContext.ONE,glContext.ONE),lightSystem.lightShader);glContext.useProgram(size),glContext.uniformMatrix4fv(glUniformLocation(size,"m"),!1,glTransform),glSetInstancedMode(!0),glRenderTargetBase=[lightSystem.texture,lightSystem.textureSize];try{for(const o of engineObjects)o.destroyed||(glAdditive||setAdditiveBlendMode(),o.renderLight());var level,saved=[drawContext,glCustomShader];drawContext=lightSystem.shadowContext,lightSystem.emissivePass=!0;try{for(const o of engineObjects)!o.destroyed&&0<o.emissive&&(level=255*clamp(o.emissive)+.5|0,glColorMask=4278190080,glColorAdditive=level|level<<8|level<<16,glAdditive||setAdditiveBlendMode(),setShader(o.shader),o.renderEmissive());glFlush()}finally{lightSystem.emissivePass=!1,glColorMask=-1,glColorAdditive=0,[drawContext,glCustomShader]=saved}}finally{glRenderTargetBase=void 0}glFlush(),glContext.bindFramebuffer(glContext.FRAMEBUFFER,null),glContext.viewport(0,0,glCanvas.width,glCanvas.height),glContext.useProgram(lightSystem.compositeShader),glContext.bindVertexArray(lightSystem.compositeVAO),glContext.activeTexture(glContext.TEXTURE0),glContext.bindTexture(glContext.TEXTURE_2D,lightSystem.texture);size=lightSystem.compositeShader;glContext.uniform1i(glUniformLocation(size,"s"),0),glContext.uniform3f(glUniformLocation(size,"iResolution"),mainCanvas.width,mainCanvas.height,1),glContext.blendFunc(glContext.DST_COLOR,glContext.ZERO),glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4),glActiveTexture&&glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture),setAdditiveBlendMode(prevAdditive),glSetInstancedMode(!0)}},function(){lightSystem.texture=void 0,lightSystem.lightShader=void 0,lightSystem.compositeShader=void 0,lightSystem.lightVAO=void 0,lightSystem.compositeVAO=void 0,clearShadows()},function(){initLightSystem()})}drawLight(light){var ls,useShadow;!headlessMode&&glEnable&&this.lightShader&&isOnScreen(light.pos,2*light.radius)&&(glFlush(),(useShadow=this.shadows&&light.castShadow&&!!this.shadowMap)&&this.renderLightShadow(light),glContext.useProgram(this.lightShader),glContext.bindVertexArray(this.lightVAO),glContext.enable(glContext.BLEND),glContext.blendFunc(glContext.ONE,glContext.ONE),ls=this.lightShader,glContext.uniform2f(glUniformLocation(ls,"lightPos"),light.pos.x,light.pos.y),glContext.uniform1f(glUniformLocation(ls,"radius"),light.radius),glContext.uniform1f(glUniformLocation(ls,"fadeRange"),light.fadeRange),glContext.uniform1i(glUniformLocation(ls,"useShadow"),useShadow?1:0),useShadow=light.color,glContext.uniform4f(glUniformLocation(ls,"color"),useShadow.r,useShadow.g,useShadow.b,useShadow.a),glContext.drawArrays(glContext.TRIANGLE_STRIP,0,4),glSetInstancedMode(!0))}renderLightShadow(light){var gl=glContext,size=this.shadowTextureSize,cs=(gl.disable(gl.BLEND),gl.viewport(0,0,size,size),gl.activeTexture(gl.TEXTURE1),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,this.shadowTextureA,0),this.shadowCopyShader),ss=(gl.useProgram(cs),gl.bindVertexArray(this.shadowCopyVAO),gl.bindTexture(gl.TEXTURE_2D,this.shadowMap),gl.uniform2f(glUniformLocation(cs,"lightPos"),light.pos.x,light.pos.y),gl.uniform1f(glUniformLocation(cs,"radius"),light.radius),gl.uniform2f(glUniformLocation(cs,"mapOrigin"),this.shadowMapOrigin.x,this.shadowMapOrigin.y),gl.uniform1f(glUniformLocation(cs,"mapInvSize"),1/this.shadowMapWorldSize),gl.uniform1f(glUniformLocation(cs,"tap"),light.radius/size),gl.uniform1f(glUniformLocation(cs,"core"),light.shadowCore),gl.drawArrays(gl.TRIANGLE_STRIP,0,4),this.shadowStretchShader);gl.useProgram(ss),gl.bindVertexArray(this.shadowStretchVAO);let src=this.shadowTextureA,dst=this.shadowTextureB;for(let k=0;k<this.shadowPassCount;++k)gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,dst,0),gl.bindTexture(gl.TEXTURE_2D,src),gl.uniform1f(glUniformLocation(ss,"scale"),1+2*1.5**k/256),gl.uniform1f(glUniformLocation(ss,"brightness"),clamp((10.1-k)/7.2)**2*this.shadowSoftness),gl.drawArrays(gl.TRIANGLE_STRIP,0,4),[src,dst]=[dst,src];gl.bindTexture(gl.TEXTURE_2D,src),gl.activeTexture(gl.TEXTURE0),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,this.texture,0),gl.viewport(0,0,this.textureSize.x,this.textureSize.y),gl.enable(gl.BLEND),gl.blendFunc(gl.ONE,gl.ONE)}setShadowTransparent(transparent=!0){this.shadowPass&&(glColorMask=transparent?-1:4278190080)}}class Light extends EngineObject{constructor(pos,radius,color,fadeRange){super(pos,vec2(1),void 0,0,color),this.mass=0,this.radius=radius,this.fadeRange=void 0===fadeRange?radius:fadeRange,this.shadowCore=0}render(){}renderLight(){lightSystem&&lightSystem.drawLight(this)}}class ZzFXMusic extends Sound{constructor(zzfxMusic){var patterns,sequence,BPM;super(void 0),soundEnable&&!headlessMode&&([zzfxMusic,patterns,sequence,BPM]=zzfxMusic,super.sampleChannels=zzfxM(zzfxMusic,patterns,sequence,BPM),this.buildSampleBuffer(),this.loadedPercent=1)}}function zzfxM(instruments,patterns,sequence,BPM=125){let i,j,k,instrumentParameters,note,sample,patternChannel,notFirstBeat,stop,instrument,attenuation,outSampleOffset,isSequenceEnd,sampleOffset=0,nextSampleOffset,sampleBuffer=[],leftChannelBuffer=[],rightChannelBuffer=[],channelIndex=0,panning=0,hasMore=1,sampleCache={},beatLength=44100/BPM*60>>2;for(;hasMore;channelIndex++)sampleBuffer=[hasMore=notFirstBeat=outSampleOffset=0],sequence.forEach((patternIndex,sequenceIndex)=>{for(patternChannel=patterns[patternIndex][channelIndex]||[0,0,0],hasMore|=patterns[patternIndex][channelIndex]&&1,nextSampleOffset=outSampleOffset+(patterns[patternIndex][0].length-2-(notFirstBeat?0:1))*beatLength,isSequenceEnd=sequenceIndex===sequence.length-1,i=2,k=outSampleOffset;i<patternChannel.length+isSequenceEnd;notFirstBeat=++i){for(note=patternChannel[i],stop=i===patternChannel.length+isSequenceEnd-1&&isSequenceEnd||instrument!==(patternChannel[0]||0)||0|note,j=0;j<beatLength&¬FirstBeat;j++>beatLength-99&&stop&&attenuation<1&&(attenuation+=1/99))sample=(1-attenuation)*sampleBuffer[sampleOffset++]/2||0,leftChannelBuffer[k]=(leftChannelBuffer[k]||0)-sample*panning+sample,rightChannelBuffer[k]=(rightChannelBuffer[k++]||0)+sample*panning+sample;note&&(attenuation=note%1,panning=patternChannel[1]||0,note|=0)&&(sampleBuffer=sampleCache[[instrument=patternChannel[sampleOffset=0]||0,note]]=sampleCache[[instrument,note]]||((instrumentParameters=[...instruments[instrument]])[2]=(instrumentParameters[2]||220)*2**(note/12-1),0<note?zzfxG(...instrumentParameters):[]))}outSampleOffset=nextSampleOffset});return[leftChannelBuffer,rightChannelBuffer]}class AudioEffect{constructor(mix=1){this.mix=clamp(mix),audioContext&&(this.input=audioContext.createGain(),this.output=audioContext.createGain(),this.dryGain=audioContext.createGain(),this.wetGain=audioContext.createGain(),this.input.connect(this.dryGain).connect(this.output),this.wetGain.connect(this.output),this.setMix(mix),this.output.connect(audioMasterGain))}setMix(mix,fadeTime=0){this.mix=mix=clamp(mix),audioContext&&(this.rampParam(this.dryGain.gain,1-mix,fadeTime),this.rampParam(this.wetGain.gain,mix,fadeTime))}rampParam(param,value,fadeTime=0){if(audioParamRamp(param,value,fadeTime),fadeTime){const keepAlive=new ConstantSourceNode(audioContext,{offset:0});keepAlive.connect(this.input),keepAlive.onended=()=>keepAlive.disconnect(),keepAlive.start(),keepAlive.stop(audioContext.currentTime+fadeTime)}}connect(target){var node;return audioContext&&(node=target&&"input"in target?target.input:target,this.output.disconnect(),this.output.connect(node)),target}disconnect(){this.output?.disconnect()}connectEffect(first,last=first){this.input.connect(first),last.connect(this.wetGain)}}class AudioFilter extends AudioEffect{constructor(type="lowpass",frequency=1e3,q=1,mix=1){super(mix),audioContext&&(this.node=audioContext.createBiquadFilter(),this.node.type=type,this.node.frequency.value=frequency,this.node.Q.value=q,this.connectEffect(this.node))}setFrequency(frequency,fadeTime=0){audioContext&&this.rampParam(this.node.frequency,frequency,fadeTime)}setQ(q,fadeTime=0){audioContext&&this.rampParam(this.node.Q,q,fadeTime)}}class AudioReverb extends AudioEffect{constructor(duration=2,decay=2,mix=.5){super(mix),audioContext&&(this.node=audioContext.createConvolver(),this.setRoom(duration,decay),this.connectEffect(this.node))}setRoom(duration,decay=2){audioContext&&(this.node.buffer=this.createImpulse(duration,decay))}createImpulse(duration,decay){var sampleRate=audioContext.sampleRate,length=max(1,sampleRate*duration|0),buffer=audioContext.createBuffer(2,length,sampleRate);for(let channel=2;channel--;){var samples=buffer.getChannelData(channel);for(let i=length;i--;)samples[i]=rand(-1,1)*(1-i/length)**decay}return buffer}}class AudioDelay extends AudioEffect{constructor(time=.3,feedback=.4,mix=.5){super(mix),audioContext&&(this.node=audioContext.createDelay(5),this.feedbackGain=audioContext.createGain(),this.node.connect(this.feedbackGain).connect(this.node),this.connectEffect(this.node),this.setTime(time),this.setFeedback(feedback))}setTime(time,fadeTime=0){audioContext&&this.rampParam(this.node.delayTime,time,fadeTime)}setFeedback(feedback,fadeTime=0){audioContext&&this.rampParam(this.feedbackGain.gain,clamp(feedback,0,.95),fadeTime)}}class AudioDistortion extends AudioEffect{constructor(amount=.5,mix=1){super(mix),this.amount=clamp(amount),audioContext&&(this.node=audioContext.createWaveShaper(),this.node.oversample="2x",this.setAmount(amount),this.connectEffect(this.node))}setAmount(amount){if(this.amount=amount=clamp(amount),audioContext){var drive=100*amount*amount,curve=new Float32Array(1024);for(let i=1024;i--;){var x=2*i/1023-1;curve[i]=(1+drive)*x/(1+drive*abs(x))}this.node.curve=curve}}}class AudioCompressor extends AudioEffect{constructor(threshold=-24,ratio=12,mix=1){super(mix),audioContext&&(this.node=audioContext.createDynamicsCompressor(),this.node.threshold.value=threshold,this.node.ratio.value=ratio,this.connectEffect(this.node))}setThreshold(threshold,fadeTime=0){audioContext&&this.rampParam(this.node.threshold,threshold,fadeTime)}setRatio(ratio,fadeTime=0){audioContext&&this.rampParam(this.node.ratio,ratio,fadeTime)}}let uiSystem,uiDebug=0;function uiSetDebug(debugMode){uiDebug="boolean"==typeof debugMode?debugMode?1:0:debugMode}class UISystemPlugin{constructor(context=mainContext){function updateTransforms(o){let targetPos,targetSize;targetSize=o.parent?(targetPos=o.parent.nativePos,o.parent.size):(targetPos=uiSystem.screenToNative(mainCanvasSize.scale(.5)),uiSystem.nativeHeight?vec2(mainCanvasSize.x*uiSystem.nativeHeight/mainCanvasSize.y,uiSystem.nativeHeight):mainCanvasSize);var a=o.anchor;o.nativePos=targetPos.add(targetSize.multiply(a).scale(.5)).subtract(o.size.multiply(a).scale(.5)).add(o.localPos)}(uiSystem=this).activateOnPress=!1,this.defaultColor=WHITE,this.defaultLineColor=BLACK,this.defaultTextColor=BLACK,this.defaultButtonColor=hsl(0,0,.7),this.defaultHoverColor=hsl(0,0,.9),this.defaultDisabledColor=hsl(0,0,.3),this.defaultGradientColor=void 0,this.defaultLineWidth=4,this.defaultCornerRadius=0,this.defaultTextFitScale=.8,this.defaultFont=fontDefault,this.defaultSoundPress=void 0,this.defaultSoundRelease=void 0,this.defaultSoundClick=void 0,this.defaultShadowColor=CLEAR_BLACK,this.defaultShadowBlur=5,this.defaultShadowOffset=vec2(5),this.defaultSlice=void 0,this.defaultHandleSlice=void 0,this.nativeHeight=0,this.navigationObject=void 0,this.navigationTimer=new Timer(void 0,!0),this.navigationDelay=.2,this.navigationDirection=1,this.navigationMode=!1,this.uiObjects=[],this.uiContext=context,this.activeObject=void 0,this.hoverObject=void 0,this.lastHoverObject=void 0,this.confirmDialog=void 0,this._keyInputObject=void 0,this._dragListeners=void 0,this._onKeyDown=e=>{var key,o=this._keyInputObject;o&&uiObjectIsUsable(o)&&!uiObjectIsBehindDialog(o)&&(e.stopPropagation(),key=e.key||"",e.ctrlKey||e.metaKey||e.altKey||1!==key.length&&!/^(Backspace|Delete|Tab|Arrow(Up|Down|Left|Right)|Home|End|Enter|Escape)$/.test(key)||e.preventDefault(),"keydown"===e.type)&&o.onKeyDown(e)},engineAddPlugin(function(){var activeObject=uiSystem.activeObject;activeObject&&!uiObjectIsUsable(activeObject)&&(uiSystem.activeObject=void 0,activeObject.destroyed||activeObject===uiSystem.keyInputObject||activeObject.onRelease());activeObject=uiSystem.keyInputObject;!activeObject||uiObjectIsUsable(activeObject)&&!uiObjectIsBehindDialog(activeObject)||(activeObject instanceof UITextInput&&!activeObject.destroyed?activeObject.stopEditing():uiSystem.keyInputObject=void 0);var activeObject=uiSystem.keyInputObject,clickedOff=activeObject instanceof UITextInput&&mouseWasPressed(0)&&!activeObject.isMouseOverlapping(),activeObject=(clickedOff&&activeObject.stopEditing(),uiSystem.lastHoverObject=uiSystem.hoverObject,uiSystem.hoverObject=void 0,uiSystem.lastHoverObject);activeObject&&!activeObject.destroyed&&function(o){for(;o;o=o.parent)if(!o.visible)return 1;return}(activeObject)&&(uiSystem.lastHoverObject=void 0,activeObject.onLeave());mouseWasPressed(0)&&(uiSystem.navigationMode=!1,uiSystem.navigationObject=void 0);uiSystem.keyInputObject&&(uiSystem.activeObject=uiSystem.keyInputObject,uiSystem.hoverObject=uiSystem.lastHoverObject);activeObject=uiSystem.getNavigableObjects();if(activeObject.length){if(!uiSystem.keyInputObject){if(activeObject.includes(uiSystem.navigationObject)||(uiSystem.navigationObject=void 0),isTouchDevice||uiSystem.navigationMode&&!uiSystem.navigationObject&&(uiSystem.navigationObject=activeObject.find(o=>o.navigationAutoSelect)),!uiSystem.navigationTimer.active()){var newIndex,newIndex,direction=sign(uiSystem.getNavigationDirection());if(direction){let newNavigationObject;uiSystem.navigationObject?(newIndex=mod(activeObject.indexOf(uiSystem.navigationObject)+direction,activeObject.length),newNavigationObject=activeObject[newIndex]):(newNavigationObject=activeObject.find(o=>o.navigationAutoSelect))||(newIndex=0<direction?0:activeObject.length-1,newNavigationObject=activeObject[newIndex]),uiSystem.navigationObject!==newNavigationObject&&(uiSystem.navigationMode=!0,uiSystem.hoverObject=void 0,uiSystem.navigationObject=newNavigationObject,uiSystem.navigationTimer.set(uiSystem.navigationDelay),newNavigationObject.soundPress)&&newNavigationObject.soundPress.play()}}uiSystem.navigationObject&&uiSystem.getNavigationWasPressed()&&(uiSystem.navigationObject.navigatePressed(),isUsingGamepad?inputClearKey(0,gamepadPrimary+1,!1,!0,!1):(inputClearKey("Space",0,!1,!0,!1),inputClearKey("Enter",0,!1,!0,!1)))}}else uiSystem.navigationObject=void 0;var uiObjects=uiSystem.uiObjects,dialog=uiSystem.confirmDialog;dialog&&updateObject(dialog);for(let i=uiObjects.length;i--;){var o=uiObjects[i];o.parent||o===dialog||updateObject(o)}function updateObject(o){if(!o.destroyed&&o.visible){updateTransforms(o);var children=o.children.slice();for(let i=children.length;i--;)updateObject(children[i]);o.destroyed||o.update()}}clickedOff&&mouseWasPressed(0)&&inputClearKey(0,0),uiSystem.uiObjects=uiSystem.uiObjects.filter(o=>!o.destroyed)},function(){var context=uiSystem.uiContext;{var s;context.save(),uiSystem.nativeHeight&&(s=mainCanvasSize.y/uiSystem.nativeHeight,context.translate(-s*mainCanvasSize.x/2,0),context.scale(s,s),context.translate(mainCanvasSize.x/2/s,0))}function renderObject(o){if(!o.destroyed&&o.visible){updateTransforms(o),o.render();for(const c of o.children)renderObject(c)}}const dialog=uiSystem.confirmDialog;{function renderDebug(o,visible=!0){if(visible&&=!!o.visible,!(o.destroyed||!visible&&uiDebug<2)){updateTransforms(o),o.renderDebug(visible);for(const c of o.children)renderDebug(c,visible)}}uiSystem.uiObjects.forEach(o=>o.parent||o===dialog||renderObject(o)),dialog&&renderObject(dialog),0<uiDebug&&(uiSystem.uiObjects.forEach(o=>o.parent||o===dialog||renderDebug(o)),dialog)&&renderDebug(dialog)}context.restore()})}drawRect(pos,size,color=WHITE,lineWidth=0,lineColor=BLACK,cornerRadius=0,gradientColor,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){var g,c,context=uiSystem.uiContext;gradientColor?(g=context.createLinearGradient(pos.x,pos.y-size.y/2,pos.x,pos.y+size.y/2),c=color.toString(),g.addColorStop(0,c),g.addColorStop(.5,gradientColor.toString()),g.addColorStop(1,c),context.fillStyle=g):context.fillStyle=color.toString(),uiSetShadow(context,shadowColor,shadowBlur,shadowOffset),context.beginPath(),cornerRadius&&context.roundRect?context.roundRect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y,cornerRadius):context.rect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y),context.fill(),context.shadowColor="#0000",lineWidth&&0<lineColor.a&&(context.strokeStyle=lineColor.toString(),context.lineWidth=lineWidth,context.stroke())}drawSlice(slice,pos,size,color=WHITE){0<color.a&&slice.drawScreen(pos,size,color,void 0,0,!1,uiSystem.uiContext)}drawLine(posA,posB,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){var context=uiSystem.uiContext;context.strokeStyle=lineColor.toString(),context.lineWidth=lineWidth,context.beginPath(),context.lineTo(posA.x,posA.y),context.lineTo(posB.x,posB.y),context.stroke()}drawTile(pos,size,tileInfo,color=uiSystem.defaultColor,angle=0,mirror=!1,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){var context=uiSystem.uiContext;uiSetShadow(context,shadowColor,shadowBlur,shadowOffset),drawTile(pos,size,tileInfo,color,angle,mirror,CLEAR_BLACK,!1,!0,context),context.shadowColor="#0000"}drawText(text,pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor,align="center",font=uiSystem.defaultFont,fontStyle="",applyMaxWidth=!0,textShadow=void 0,shadowColor=BLACK,shadowBlur=0,shadowOffset=vec2()){var context=uiSystem.uiContext;textShadow&&0<shadowColor.a&&drawTextScreen(text,pos.add(textShadow),size.y,shadowColor,lineWidth,lineColor,align,font,fontStyle,applyMaxWidth?size.x:void 0,0,context),uiSetShadow(context,shadowColor,shadowBlur,shadowOffset),drawTextScreen(text,pos,size.y,color,lineWidth,lineColor,align,font,fontStyle,applyMaxWidth?size.x:void 0,0,context),context.shadowColor="#0000"}setupDragAndDrop(onDrop,onDragEnter,onDragLeave,onDragOver){if(this._dragListeners)for(var[type,listener]of this._dragListeners)document.removeEventListener(type,listener);this._dragListeners=[];var setCallback=(callback,listenerType,when=()=>!0)=>{var listener=e=>{e.preventDefault(),when()&&callback&&callback(e)};document.addEventListener(listenerType,listener),this._dragListeners.push([listenerType,listener])};let depth=0;setCallback(onDrop,"drop",()=>!(depth=0)),setCallback(onDragEnter,"dragenter",()=>!depth++),setCallback(onDragLeave,"dragleave",()=>!!depth&&!--depth),setCallback(onDragOver,"dragover")}screenToNative(pos){var s,sInv,p;return uiSystem.nativeHeight?(sInv=1/(s=mainCanvasSize.y/uiSystem.nativeHeight),(p=pos.copy()).x+=s*mainCanvasSize.x/2,p.x*=sInv,p.y*=sInv,p.x-=sInv*mainCanvasSize.x/2,p):pos}get keyInputObject(){return this._keyInputObject}set keyInputObject(obj){var had=!!this._keyInputObject;if(this._keyInputObject=obj,!had&&obj){addEventListener("keydown",this._onKeyDown,!0),addEventListener("keyup",this._onKeyDown,!0);var keys=inputData[0];for(const key in keys)isNaN(+key)&&(keys[key]=0);inputKeysHeld.clear();var held=this.activeObject;held&&mouseIsDown(0)&&(this.activeObject=void 0,held.onRelease(),held.soundRelease)&&held.soundRelease.play()}else had&&!obj&&(removeEventListener("keydown",this._onKeyDown,!0),removeEventListener("keyup",this._onKeyDown,!0))}destroyObjects(){for(const o of this.uiObjects)o.parent||o.destroy();this.uiObjects=this.uiObjects.filter(o=>!o.destroyed),this.activeObject=void 0,this.hoverObject=void 0,this.lastHoverObject=void 0,this.keyInputObject=void 0,this.confirmDialog=void 0}getNavigableObjects(){function getNavigableRecursive(o){if(!o.destroyed&&o.visible&&!o.disabled){o.isInteractive()&&void 0!==o.navigationIndex&&objects.push(o);for(const child of o.children)getNavigableRecursive(child)}}const objects=[];if(uiSystem.confirmDialog)getNavigableRecursive(uiSystem.confirmDialog);else for(const o of uiSystem.uiObjects)o.parent||getNavigableRecursive(o);return objects.sort((a,b)=>a.navigationIndex-b.navigationIndex),objects}isMouseOverUI(){function isOverRecursive(o){return!(o.destroyed||!o.visible)&&(!(!o.canBeHover||!o.isMouseOverlapping())||o.children.some(isOverRecursive))}return!!uiSystem.keyInputObject||(uiSystem.confirmDialog?isOverRecursive(uiSystem.confirmDialog):uiSystem.uiObjects.some(o=>!o.parent&&isOverRecursive(o)))}getNavigationDirection(){var stick,dpad,vertical=1===uiSystem.navigationDirection,both=2===uiSystem.navigationDirection;return isUsingGamepad?(stick=gamepadStick(0,gamepadPrimary),dpad=gamepadDpad(gamepadPrimary),both?-(stick.y||dpad.y)||stick.x||dpad.x:vertical?-(stick.y||dpad.y):stick.x||dpad.x):(stick="ArrowUp",both?keyIsDown(stick)||keyIsDown("ArrowLeft")?-1:keyIsDown("ArrowDown")||keyIsDown("ArrowRight")?1:0:(dpad=vertical?"ArrowDown":"ArrowRight",keyIsDown(vertical?stick:"ArrowLeft")?-1:keyIsDown(dpad)?1:0))}getNavigationOtherDirection(){var vertical,stick,dpad;return 2===uiSystem.navigationDirection?0:(vertical=1===uiSystem.navigationDirection,isUsingGamepad?(stick=gamepadStick(0,gamepadPrimary),dpad=gamepadDpad(gamepadPrimary),vertical?stick.x||dpad.x:stick.y||dpad.y):vertical?keyIsDown("ArrowLeft")?-1:keyIsDown("ArrowRight")?1:0:keyIsDown("ArrowUp")?1:keyIsDown("ArrowDown")?-1:0)}getNavigationWasPressed(){return isUsingGamepad?gamepadWasPressed(0,gamepadPrimary):keyWasPressed("Space")||keyWasPressed("Enter")}showConfirmDialog(text="Are you sure?",yesCallback,noCallback,size=vec2(500,250),exitKey="Escape"){const savedNavigationDirection=uiSystem.navigationDirection,savedNavigationObject=uiSystem.navigationObject,confirmMenu=(uiSystem.navigationDirection=2,new UIObject(vec2(),size));let opened=!((uiSystem.confirmDialog=confirmMenu).onRender=()=>{var backgroundColor=hsl(0,0,0,.7);uiSystem.drawRect(vec2(),vec2(1e9),backgroundColor)});confirmMenu.onUpdate=()=>{opened&&(keyWasPressed(exitKey)||gamepadWasPressed(1))&&(closeMenu(),noCallback)&&noCallback(),opened=!0},confirmMenu.isMouseOverlapping=()=>!0;var scaleX=size.x/500,scaleY=size.y/250,y=size.y/5,size=new UIText(vec2(0,-y),vec2(size.x-50,70*scaleY),text),text=(confirmMenu.addChild(size),vec2(120*scaleX,70*scaleY)),size=new UIButton(vec2(-80*scaleX,y),text,"Yes"),size=(size.textHeight=40*scaleY,size.navigationIndex=1,size.hoverColor=hsl(0,1,.5),size.onClick=()=>{closeMenu(),yesCallback&&yesCallback()},confirmMenu.addChild(size),new UIButton(vec2(80*scaleX,y),text,"No"));size.textHeight=40*scaleY,size.navigationIndex=2,size.navigationAutoSelect=!0,size.onClick=()=>{closeMenu(),noCallback&&noCallback()},confirmMenu.addChild(size);const destroy=confirmMenu.destroy.bind(confirmMenu);function closeMenu(){confirmMenu.destroy(),inputClear()}return confirmMenu.destroy=()=>{var closing=uiSystem.confirmDialog===confirmMenu;closing&&(uiSystem.confirmDialog=void 0,uiSystem.navigationDirection=savedNavigationDirection),destroy(),closing&&savedNavigationObject&&!savedNavigationObject.destroyed&&(uiSystem.navigationObject=savedNavigationObject)},confirmMenu}}function uiObjectIsUsable(o){for(;o;o=o.parent)if(o.destroyed||!o.visible||o.disabled)return!1;return!0}function uiSetShadow(context,shadowColor,shadowBlur,shadowOffset){0<shadowColor.a&&(shadowBlur||shadowOffset.x||shadowOffset.y)&&(context.shadowColor=shadowColor.toString(),context.shadowBlur=shadowBlur,context.shadowOffsetX=shadowOffset.x,context.shadowOffsetY=shadowOffset.y)}function uiObjectIsBehindDialog(o){var dialog=uiSystem.confirmDialog;if(dialog){for(;o;o=o.parent)if(o===dialog)return;return 1}}function uiObjectIsDisabled(o){for(;o;o=o.parent)if(o.disabled)return!0;return!1}class UIObject{constructor(pos=vec2(),size=vec2()){this.localPos=pos.copy(),this.nativePos=pos.copy(),this.size=size.copy(),this.color=uiSystem.defaultColor.copy(),this.activeColor=void 0,this.text=void 0,this.disabledColor=uiSystem.defaultDisabledColor.copy(),this.disabled=!1,this.textColor=uiSystem.defaultTextColor.copy(),this.hoverColor=uiSystem.defaultHoverColor.copy(),this.lineColor=uiSystem.defaultLineColor.copy(),this.gradientColor=uiSystem.defaultGradientColor?uiSystem.defaultGradientColor.copy():void 0,this.lineWidth=uiSystem.defaultLineWidth,this.cornerRadius=uiSystem.defaultCornerRadius,this.slice=uiSystem.defaultSlice,this.font=uiSystem.defaultFont,this.fontStyle=void 0,this.textWidth=void 0,this.textHeight=void 0,this.textFitScale=uiSystem.defaultTextFitScale,this.textShadow=void 0,this.textLineColor=uiSystem.defaultLineColor.copy(),this.textLineWidth=0,this.visible=!0,this.children=[],this.parent=void 0,this.extraTouchSize=0,this.soundPress=uiSystem.defaultSoundPress,this.soundRelease=uiSystem.defaultSoundRelease,this.soundClick=uiSystem.defaultSoundClick,this.interactive=!1,this.dragActivate=!1,this.canBeHover=!0,this.shadowColor=uiSystem.defaultShadowColor?.copy(),this.shadowBlur=uiSystem.defaultShadowBlur,this.shadowOffset=uiSystem.defaultShadowOffset?.copy(),this.navigationIndex=void 0,this.navigationAutoSelect=!1,this.anchor=vec2(),this.align="center",this.destroyed=!1,uiSystem.uiObjects.push(this)}addChild(child){return this.children.push(child),child.parent=this,child}removeChild(child){this.children.splice(this.children.indexOf(child),1),child.parent=void 0}destroy(){if(!this.destroyed){uiSystem.activeObject===this&&(uiSystem.activeObject=void 0),uiSystem.hoverObject===this&&(uiSystem.hoverObject=void 0),uiSystem.lastHoverObject===this&&(uiSystem.lastHoverObject=void 0),uiSystem.navigationObject===this&&(uiSystem.navigationObject=void 0),uiSystem.keyInputObject===this&&(uiSystem.keyInputObject=void 0),this.destroyed=!0,this.parent?.removeChild(this);for(const child of this.children)child.parent=void 0,child.destroy();this.children.length=0}}isMouseOverlapping(){var size,pos;return!!mouseInWindow&&(size=isTouchDevice?this.size.add(vec2(this.extraTouchSize||0)):this.size,pos=uiSystem.screenToNative(mousePosScreen),isOverlapping(this.nativePos,size,pos))}update(){if(this.onUpdate(),!this.destroyed){var disabled=uiObjectIsDisabled(this);if(!(disabled&&this===uiSystem.activeObject&&(uiSystem.activeObject=void 0,this!==uiSystem.keyInputObject)&&(this.onRelease(),this.destroyed)||uiSystem.keyInputObject)){var wasHover=uiSystem.lastHoverObject===this,isActive=this.isActiveObject(),mouseDown=mouseIsDown(0),mousePress=mouseWasPressed(0)||this.dragActivate&&mouseDown;if(!this.canBeHover||uiSystem.navigationMode||(mousePress||isActive||!mouseDown&&!isTouchDevice)&&!uiSystem.hoverObject&&this.isMouseOverlapping()&&(uiSystem.hoverObject=this),this.isHoverObject()){if(!disabled){if(mousePress&&this.interactive){disabled=!this.dragActivate||!wasHover||mouseWasPressed(0);if(disabled){if(this.onPress(),this.destroyed)return void inputClearKey(0,0,!1,!0,!1);this.soundPress&&this.soundPress.play(),uiSystem.activeObject&&!isActive&&uiSystem.activeObject.onRelease()}if(uiSystem.keyInputObject&&uiSystem.keyInputObject!==this?(this.onRelease(),this.soundRelease&&this.soundRelease.play()):uiSystem.activeObject=this,disabled&&uiSystem.activateOnPress&&this.click(!this.soundPress),this.destroyed)return void inputClearKey(0,0,!1,!0,!1)}if(uiSystem.activateOnPress||!mouseDown&&this.isActiveObject()&&this.interactive&&this.click(),this.destroyed)return}mousePress&&inputClearKey(0,0,!1,!0,!1)}!isActive||mouseDown&&(!this.dragActivate||this.isHoverObject())||(this.onRelease(),this.soundRelease&&this.soundRelease.play(),uiSystem.activeObject=void 0),this.isHoverObject()!==wasHover&&(this.isHoverObject()?this.onEnter():this.onLeave())}}}render(){var lineColor,disabled,isNavigationObject;this.onRender(),this.size.x&&this.size.y&&(isNavigationObject=this.isNavigationObject(),disabled=this.interactive?uiObjectIsDisabled(this):this.disabled,lineColor=isNavigationObject||this.interactive&&this.isActiveObject()&&!disabled?this.color:this.lineColor,disabled=isNavigationObject?this.hoverColor:disabled?this.disabledColor:this.interactive?this.isActiveObject()?this.activeColor||this.hoverColor:this.isHoverObject()?this.hoverColor:this.color:this.color,isNavigationObject=this.lineWidth*(isNavigationObject?1.5:1),this.slice?uiSystem.drawSlice(this.slice,this.nativePos,this.size,disabled):uiSystem.drawRect(this.nativePos,this.size,disabled,isNavigationObject,lineColor,this.cornerRadius,this.gradientColor,this.shadowColor||CLEAR_BLACK,this.shadowBlur,this.shadowOffset))}getTextSize(){var lines=this.textHeight?1:(this.text+"").split("\n").length;return vec2(this.textWidth||this.textFitScale*this.size.x,this.textHeight||this.textFitScale*this.size.y/lines)}getTextPos(textSize){var side="left"===this.align?-1:"right"===this.align?1:0;return this.nativePos.add(vec2(side*textSize.x/2,0))}navigatePressed(){this.click()}isHoverObject(){return uiSystem.hoverObject===this}isActiveObject(){return uiSystem.activeObject===this}isNavigationObject(){return uiSystem.navigationObject===this}isKeyInputObject(){return uiSystem.keyInputObject===this}isInteractive(){return this.interactive&&uiObjectIsUsable(this)}toString(){let text="type = "+this.constructor.name;return this.text&&(text+="\ntext = "+this.text),(this.nativePos.x||this.nativePos.y)&&(text+="\nnativePos = "+this.nativePos),(this.localPos.x||this.localPos.y)&&(text+="\nlocalPos = "+this.localPos),(this.size.x||this.size.y)&&(text+="\nsize = "+this.size),this.color&&(text+="\ncolor = "+this.color),text}renderDebug(visible=!0){visible=visible?this.isHoverObject()?YELLOW:uiObjectIsDisabled(this)?PURPLE:this.interactive?RED:BLUE:GREEN;uiSystem.drawRect(this.nativePos,this.size,CLEAR_BLACK,4,visible)}click(playSound=!0){this.onClick(),playSound&&this.soundClick&&this.soundClick.play()}onUpdate(){}onRender(){}onEnter(){}onLeave(){}onPress(){}onRelease(){}onClick(){}onChange(){}onKeyDown(e){}}class UIText extends UIObject{constructor(pos,size,text="",align="center",font=uiSystem.defaultFont){super(pos,size),this.text=text,this.align=align,this.font=font,this.canBeHover=!1,this.color=CLEAR_BLACK.copy(),this.shadowColor=CLEAR_BLACK.copy(),this.gradientColor=void 0,this.lineWidth=0,this.textFitScale=1}render(){super.render();var textSize=this.getTextSize();uiSystem.drawText(this.text,this.getTextPos(textSize),textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,!0,this.textShadow,this.shadowColor||CLEAR_BLACK,this.shadowBlur,this.shadowOffset)}}class UITextInput extends UIObject{constructor(pos,size,text=""){super(pos,size),this.maxLength=0,this.text=text+"",this.interactive=!0,this.canBeHover=!0}click(playSound=!0){var editing=uiSystem.keyInputObject;editing!==this&&editing instanceof UITextInput&&editing.stopEditing(),uiSystem.keyInputObject=this,inputClearKey(0,gamepadPrimary+1,!1,!0,!1),this.onClick(),playSound&&this.soundClick&&this.soundClick.play()}stopEditing(){this.isKeyInputObject()&&(this.soundRelease&&this.soundRelease.play(),uiSystem.activeObject=void 0,uiSystem.keyInputObject=void 0,this.onChange())}onKeyDown(e){var code=e.code,key=e.key;e.repeat&&("Enter"===key||"Space"===code)||(this.text+="","Backspace"===key?this.text=[...this.text].slice(0,-1).join(""):"Enter"===key||"Escape"===key?this.stopEditing():1!==key.length||(e.ctrlKey||e.metaKey)&&!e.getModifierState?.("AltGraph")||this.maxLength&&!(this.text.length<this.maxLength)||(this.text+=key))}update(){super.update(),this.isKeyInputObject()&&(gamepadWasPressed(0,gamepadPrimary)?(this.stopEditing(),inputClearKey(0,gamepadPrimary+1,!1,!0,!1)):mouseWasPressed(0)&&inputClearKey(0,0,!1,!0,!1))}render(){super.render();var textSize=this.getTextSize();let text=this.text;this.isKeyInputObject()&&(text+=timeReal%1<.5?"█":"░"),uiSystem.drawText(text,this.getTextPos(textSize),textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,!0,this.textShadow)}}class UITile extends UIObject{constructor(pos,size,tileInfo,color=WHITE,angle=0,mirror=!1){super(pos,size),this.tileInfo=tileInfo,this.angle=angle,this.mirror=mirror,this.color=color.copy(),this.shadowColor=CLEAR_BLACK.copy()}render(){this.onRender(),uiSystem.drawTile(this.nativePos,this.size,this.tileInfo,this.color,this.angle,this.mirror,this.shadowColor||CLEAR_BLACK,this.shadowBlur,this.shadowOffset)}}class UIButton extends UIObject{constructor(pos,size,text="",color=uiSystem.defaultButtonColor){super(pos,size),this.textOffset=vec2(),this.text=text,this.color=color.copy(),this.interactive=!0}render(){super.render();var textSize=this.getTextSize();uiSystem.drawText(this.text,this.getTextPos(textSize).add(this.textOffset),textSize,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,!0,this.textShadow)}}class UICheckbox extends UIObject{constructor(pos,size,checked=!1,text="",color=uiSystem.defaultButtonColor){super(pos,size),this.checked=checked,this.text=text,this.color=color.copy(),this.interactive=!0}click(playSound=!0){this.checked=!this.checked,super.click(playSound),this.onChange()}render(){super.render(),this.checked&&(length=lerp(1,2**.5/2,this.cornerRadius/min(this.size.x,this.size.y)*2)/2,length=this.size.scale(length),uiSystem.drawLine(this.nativePos.add(length.multiply(vec2(-1))),this.nativePos.add(length.multiply(vec2(1))),this.lineWidth,this.lineColor),uiSystem.drawLine(this.nativePos.add(length.multiply(vec2(-1,1))),this.nativePos.add(length.multiply(vec2(1,-1))),this.lineWidth,this.lineColor));var length=this.getTextSize(),pos=this.nativePos.add(vec2(this.size.x,0));uiSystem.drawText(this.text,pos,length,this.textColor,this.textLineWidth,this.textLineColor,"left",this.font,this.fontStyle,!1,this.textShadow)}}class UISlider extends UIObject{constructor(pos,size,value=.5,text="",color=uiSystem.defaultButtonColor,handleColor=WHITE){super(pos,size),this.value=value,this.handleColor=handleColor.copy(),this.handleSlice=uiSystem.defaultHandleSlice,this.fillMode=!1,this.text=text,this.color=color.copy(),this.interactive=!0}update(){var oldValue,handleSize,centerPos,barSize,isHorizontal;super.update(),this.interactive&&(oldValue=this.value,this.isActiveObject()?(handleSize=(isHorizontal=this.size.x>this.size.y)?this.size.y:this.size.x,barSize=isHorizontal?this.size.x:this.size.y,handleSize=(centerPos=isHorizontal?this.nativePos.x:this.nativePos.y)-(barSize=barSize-handleSize)/2,centerPos=centerPos+barSize/2,barSize=uiSystem.screenToNative(mousePosScreen),this.value=isHorizontal?percent(barSize.x,handleSize,centerPos):percent(barSize.y,centerPos,handleSize)):this.isNavigationObject()&&(isHorizontal=uiSystem.getNavigationOtherDirection(),uiSystem.navigationTimer.active()||(this.value=clamp(this.value+.01*isHorizontal))),this.value!==oldValue)&&this.onChange()}drawHandle(pos,size,color){var slice=this.handleSlice||this.slice;slice?uiSystem.drawSlice(slice,pos,size,color):uiSystem.drawRect(pos,size,color,this.lineWidth,this.lineColor,this.cornerRadius,this.gradientColor)}render(){super.render();var isHorizontal=this.size.x>this.size.y,barWidth=isHorizontal?this.size.x:this.size.y,handleWidth=isHorizontal?this.size.y:this.size.x,color=uiObjectIsDisabled(this)?this.disabledColor:this.handleColor;if(this.fillMode){var progressWidth=lerp(min(handleWidth,2*this.cornerRadius),barWidth,this.value),p=(progressWidth-barWidth)*(isHorizontal?.5:-.5),p=this.nativePos.add(isHorizontal?vec2(p,0):vec2(0,p)),progressWidth=isHorizontal?vec2(progressWidth,this.size.y):vec2(this.size.x,progressWidth);this.drawHandle(p,progressWidth,color)}else{const p=(barWidth-handleWidth)*(clamp(isHorizontal?this.value:1-this.value)-.5),pos=this.nativePos.add(isHorizontal?vec2(p,0):vec2(0,p)),drawSize=vec2(handleWidth);this.drawHandle(pos,drawSize,color)}p=this.getTextSize();uiSystem.drawText(this.text,this.getTextPos(p),p,this.textColor,this.textLineWidth,this.textLineColor,this.align,this.font,this.fontStyle,!0,this.textShadow)}navigatePressed(){this.value=this.value?0:1,this.onChange(),this.onRelease(),super.navigatePressed()}}class UIVideo extends UIObject{constructor(pos,size,src,autoplay=!1,loop=!1,volume=1){super(pos,size||vec2()),this.color=BLACK.copy(),this.cornerRadius=0,this.volume=volume,this.video=document.createElement("video"),this.video.loop=loop,this.video.volume=clamp(volume*soundVolume),this.video.muted=!soundEnable,this._soundEnabled=soundEnable,this.video.style.display="none",this.video.playsInline=!0,this.video.src=src,document.body.appendChild(this.video),autoplay&&this.play()}async play(){try{return await this.video.play(),!0}catch(e){return!1}}pause(){this.video.pause()}stop(){this.video.pause(),this.video.currentTime=0}isLoading(){return this.video.readyState<this.video.HAVE_CURRENT_DATA}isPaused(){return this.video.paused}isPlaying(){return!this.isPaused()&&!this.hasEnded()&&!this.isLoading()}hasEnded(){return this.video.ended}setVolume(volume){this.volume=volume,this.video.volume=clamp(volume*soundVolume)}setPlaybackRate(rate){this.video.playbackRate=rate}getCurrentTime(){return this.video.currentTime||0}getDuration(){return this.video.duration||0}getVideoSize(){return vec2(this.video.videoWidth,this.video.videoHeight)}setTime(time){var duration=this.video.duration;this.video.currentTime=duration?clamp(time,0,duration):max(time,0)}update(){super.update(),this.video.volume=clamp(this.volume*soundVolume),this._soundEnabled!==soundEnable&&(this.video.muted=!(this._soundEnabled=soundEnable))}render(){var context,s;super.render(),this.isLoading()||(context=uiSystem.uiContext,s=this.size,context.save(),context.translate(this.nativePos.x,this.nativePos.y),context.drawImage(this.video,-s.x/2,-s.y/2,s.x,s.y),context.restore())}destroy(){this.destroyed||(this.video.pause(),this.video.remove(),super.destroy())}}class UILayout extends UIObject{constructor(pos,columns=1,gap=10,padding=10,transparent=!1){super(pos),this.columns=columns,this.gap=gap,this.padding=padding,transparent&&(this.color=CLEAR_BLACK.copy(),this.gradientColor=void 0,this.lineWidth=0,this.shadowColor=CLEAR_BLACK.copy(),this.canBeHover=!1),this.relayout()}addChild(child){return super.addChild(child),this.relayout(),child}removeChild(child){super.removeChild(child),this.relayout()}relayout(){var children=this.children.filter(child=>child.visible),n=children.length;if(n){var cols=clamp(floor(this.columns),1,n),rows=ceil(n/cols),colWidths=new Array(cols).fill(0),rowHeights=new Array(rows).fill(0);for(let i=0;i<n;++i){var col=i%cols,row=floor(i/cols),child=children[i];colWidths[col]=max(colWidths[col],child.size.x),rowHeights[row]=max(rowHeights[row],child.size.y)}let contentWidth=this.gap*(cols-1);for(const w of colWidths)contentWidth+=w;let contentHeight=this.gap*(rows-1);for(const h of rowHeights)contentHeight+=h;var colOffsets=new Array(cols);let xAcc=0;for(let c=0;c<cols;++c)colOffsets[c]=xAcc,xAcc+=colWidths[c];var rowOffsets=new Array(rows);let yAcc=0;for(let r=0;r<rows;++r)rowOffsets[r]=yAcc,yAcc+=rowHeights[r];for(let i=0;i<n;++i){const col=i%cols,row=floor(i/cols);var x=-contentWidth/2+colOffsets[col]+this.gap*col+colWidths[col]/2,y=-contentHeight/2+rowOffsets[row]+this.gap*row+rowHeights[row]/2;children[i].localPos=vec2(x,y)}this.size=vec2(contentWidth+2*this.padding,contentHeight+2*this.padding)}else this.size=vec2(2*this.padding);this.parent instanceof UILayout&&this.parent.relayout()}}let box2d,box2dDebug=!1,box2dTempVectors;function box2dTemp(v,slot=0){slot=(box2dTempVectors||=[new box2d.instance.b2Vec2,new box2d.instance.b2Vec2])[slot];return slot.Set(v.x,v.y),slot}const box2dQueryObjects={},box2dGravity={x:NaN,y:NaN};function box2dQueryObject(key,type){return box2dQueryObjects[key]||=new box2d.instance[type]}function box2dFixtureOverlaps(fixture,aabb){var shape=fixture.GetShape(),transform=fixture.GetBody().GetTransform(),shapeBox=box2dQueryObject("shapeAABB","b2AABB"),lower=aabb.get_lowerBound(),upper=aabb.get_upperBound();for(let i=shape.GetChildCount();i--;){shape.ComputeAABB(shapeBox,transform,i);var a=shapeBox.get_lowerBound(),b=shapeBox.get_upperBound();if(a.get_x()<=upper.get_x()&&b.get_x()>=lower.get_x()&&a.get_y()<=upper.get_y()&&b.get_y()>=lower.get_y())return 1}}function box2dEdgePoints(points,loop){var kept=[];for(const p of points)(!kept.length||25e-6<p.distanceSquared(kept[kept.length-1]))&&kept.push(p);for(;loop&&1<kept.length&&kept[kept.length-1].distanceSquared(kept[0])<=25e-6;)kept.pop();return kept}function box2dWakeWithContacts(body){body.SetAwake(!0);for(let edge=body.GetContactList();!box2d.isNull(edge);edge=edge.get_next())edge.get_other().SetAwake(!0)}function box2dWakeJoint(joint){joint.GetBodyA().SetAwake(!0),joint.GetBodyB().SetAwake(!0)}function box2dCenterInertia(mass,x,y){var f=Math.fround;return f(f(mass)*f(f(f(x)*f(x))+f(f(y)*f(y))))}const box2dPending=[];let box2dPendingBusy=0;function box2dWhenUnlocked(f){box2dPending.push(f),box2d.world.IsLocked()||box2dPendingBusy||box2dRunPending()}function box2dRunPending(){++box2dPendingBusy;try{for(;box2dPending.length;)box2dPending.shift()()}finally{--box2dPendingBusy}}const box2dJoints=new Map,box2dGearJoints=new Set;function box2dDestroyGears(joint){for(const gear of box2dGearJoints)gear.box2dJoint?gear.joint1!==joint&&gear.joint2!==joint||(box2dGearJoints.delete(gear),gear.destroy()):box2dGearJoints.delete(gear)}function box2dSetDebug(enable){box2dDebug=enable}class Box2dObject extends EngineObject{constructor(pos=vec2(),size=vec2(),tileInfo,angle=0,color,bodyType=box2d.bodyTypeDynamic,renderOrder=0){super(pos,size,tileInfo,angle,color,renderOrder);size=new box2d.instance.b2BodyDef;size.set_type(bodyType),size.set_position(box2dTemp(pos)),size.set_angle(-angle),this.body=box2d.world.CreateBody(size),box2d.instance.destroy(size),this.lineColor=BLACK.copy(),this.lineWidth=.1,this.edgeLists=[],this.edgeLoops=[],this.edgeListFixtures=new Map,this.body.object=this,box2d.objects.push(this)}destroy(immediate=!1){if(!this.destroyed){const body=this.body;box2dWhenUnlocked(()=>{this.body===body&&(box2d.world.DestroyBody(body),body.object=void 0,this.body=void 0)}),super.destroy(immediate)}}updatePhysics(){}updateTransforms(updateChildren=!0){super.updateTransforms(updateChildren)}render(){this.tileInfo?super.render():this.drawFixtures(this.color,this.lineColor,this.lineWidth)}renderDebugInfo(){var isAsleep=!this.getIsAwake(),isAsleep=rgb(isAsleep?1:0,isAsleep?1:0,this.getBodyType()===box2d.bodyTypeStatic?1:0,.5);this.drawFixtures(isAsleep)}drawFixtures(color=WHITE,lineColor=BLACK,lineWidth=.1,useWebGL,context){const edgeFixtures=this.edgeListFixtures;this.getFixtureList().forEach(fixture=>{edgeFixtures.has(box2d.instance.getPointer(fixture))||box2d.drawFixture(fixture,this.pos,this.angle,color,lineColor,lineWidth,useWebGL,context)}),this.edgeLists.forEach(points=>drawLineList(points,lineWidth,lineColor,!1,this.pos,this.angle,useWebGL,!1,context)),this.edgeLoops.forEach(points=>drawLineList(points,lineWidth,lineColor,!0,this.pos,this.angle,useWebGL,!1,context))}beginContact(otherObject,fixture,otherFixture){}endContact(otherObject,fixture,otherFixture){}addShape(shape,density=1,friction=.2,restitution=0,isSensor=!1){var fd=new box2d.instance.b2FixtureDef,shape=(fd.set_shape(shape),fd.set_density(density),fd.set_friction(friction),fd.set_restitution(restitution),fd.set_isSensor(isSensor),this.body.CreateFixture(fd));return box2d.instance.destroy(fd),shape}addBox(size=vec2(1),offset=vec2(),angle=0,density,friction,restitution,isSensor){var shape;if(1e-6<size.x*size.y)return(shape=new box2d.instance.b2PolygonShape).SetAsBox(size.x/2,size.y/2,box2dTemp(offset),-angle),size=this.addShape(shape,density,friction,restitution,isSensor),box2d.instance.destroy(shape),size}addPoly(points,density,friction,restitution,isSensor){points=function(points){let area=0;if(3<=(points=points.filter((p,i)=>points.slice(0,i).every(q=>1e-6<p.distanceSquared(q)))).length&&points.length<=8)for(const a of points)for(const b of points)for(const c of points)area=max(area,abs(b.subtract(a).cross(c.subtract(a))));if(1e-6<=area){var buffer=box2d.instance._malloc(8*points.length);for(let i=0,offset=0;i<points.length;++i)box2d.instance.HEAPF32[buffer+offset>>2]=points[i].x,offset+=4,box2d.instance.HEAPF32[buffer+offset>>2]=points[i].y,offset+=4;var box2dPoints=box2d.instance.wrapPointer(buffer,box2d.instance.b2Vec2),shape=new box2d.instance.b2PolygonShape;return shape.Set(box2dPoints,points.length),box2d.instance._free(buffer),shape}}(points);if(points)return density=this.addShape(points,density,friction,restitution,isSensor),box2d.instance.destroy(points),density}addRegularPoly(diameter=1,sides=8,density,friction,restitution,isSensor){var points=[],radius=diameter/2;for(let i=sides=min(sides,8);i--;)points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));return this.addPoly(points,density,friction,restitution,isSensor)}addRandomPoly(diameter=1,density,friction,restitution,isSensor){var sides=randInt(3,9),points=[],radius=diameter/2;for(let i=sides;i--;)points.push(vec2(rand(radius/2,1.5*radius),0).rotate(i/sides*PI*2));return this.addPoly(points,density,friction,restitution,isSensor)}addCircle(diameter=1,offset=vec2(),density,friction,restitution,isSensor){var shape=new box2d.instance.b2CircleShape,offset=(shape.set_m_p(box2dTemp(offset)),shape.set_m_radius(diameter/2),this.addShape(shape,density,friction,restitution,isSensor));return box2d.instance.destroy(shape),offset}addEdge(point1,point2,density,friction,restitution,isSensor){var shape=new box2d.instance.b2EdgeShape,point1=(shape.Set(box2dTemp(point1),box2dTemp(point2,1)),this.addShape(shape,density,friction,restitution,isSensor));return box2d.instance.destroy(shape),point1}addEdgeList(points,density,friction,restitution,isSensor){points=box2dEdgePoints(points);const fixtures=[],edgePoints=[];if(!(points.length<2)){for(let i=0;i<points.length-1;++i){var shape=new box2d.instance.b2EdgeShape,f=(points[i-1]&&shape.set_m_vertex0(box2dTemp(points[i-1])),points[i+0]&&shape.set_m_vertex1(box2dTemp(points[i+0])),points[i+1]&&shape.set_m_vertex2(box2dTemp(points[i+1])),points[i+2]&&shape.set_m_vertex3(box2dTemp(points[i+2])),shape.set_m_hasVertex0(!!points[i-1]),shape.set_m_hasVertex3(!!points[i+2]),this.addShape(shape,density,friction,restitution,isSensor));box2d.instance.destroy(shape),fixtures.push(f),edgePoints.push(points[i].copy())}edgePoints.push(points[points.length-1].copy()),this.edgeLists.push(edgePoints),fixtures.forEach(f=>this.edgeListFixtures.set(box2d.instance.getPointer(f),edgePoints))}return fixtures}addEdgeLoop(points,density,friction,restitution,isSensor){points=box2dEdgePoints(points,!0);const fixtures=[],edgePoints=[];if(!(points.length<2)){var getPoint=i=>points[mod(i,points.length)];for(let i=0;i<points.length;++i){var shape=new box2d.instance.b2EdgeShape,f=(shape.set_m_vertex0(box2dTemp(getPoint(i-1))),shape.set_m_vertex1(box2dTemp(getPoint(i+0))),shape.set_m_vertex2(box2dTemp(getPoint(i+1))),shape.set_m_vertex3(box2dTemp(getPoint(i+2))),shape.set_m_hasVertex0(!0),shape.set_m_hasVertex3(!0),this.addShape(shape,density,friction,restitution,isSensor));box2d.instance.destroy(shape),fixtures.push(f),edgePoints.push(points[i].copy())}this.edgeLoops.push(edgePoints),fixtures.forEach(f=>this.edgeListFixtures.set(box2d.instance.getPointer(f),edgePoints))}return fixtures}destroyFixture(fixture){const pointer=box2d.instance.getPointer(fixture),edgeFixtures=this.edgeListFixtures,points=edgeFixtures.get(pointer);points&&(this.edgeLists=this.edgeLists.filter(p=>p!==points),this.edgeLoops=this.edgeLoops.filter(p=>p!==points),edgeFixtures.forEach((p,pointer)=>p===points&&edgeFixtures.delete(pointer))),box2dWhenUnlocked(()=>this.body&&this.getFixtureList().some(f=>box2d.instance.getPointer(f)===pointer)&&this.body.DestroyFixture(fixture))}destroyAllFixtures(){this.edgeLists=[],this.edgeLoops=[],this.edgeListFixtures.clear();const fixtures=this.getFixtureList(),getPointer=box2d.instance.getPointer;box2dWhenUnlocked(()=>{if(this.body){var alive=new Set(this.getFixtureList().map(getPointer));for(const fixture of fixtures)alive.has(getPointer(fixture))&&this.body.DestroyFixture(fixture)}})}getCenterOfMass(){return box2d.vec2From(this.body.GetWorldCenter())}getLinearVelocity(){return box2d.vec2From(this.body.GetLinearVelocity())}getAngularVelocity(){return-this.body.GetAngularVelocity()}getMass(){return this.body.GetMass()}getInertia(){var center=this.body.GetLocalCenter();return max(0,Math.fround(this.body.GetInertia()-box2dCenterInertia(this.getMass(),center.get_x(),center.get_y())))}getIsAwake(){return this.body.IsAwake()}getBodyType(){return this.body.GetType()}getSpeed(){return this.getLinearVelocity().length()}setTransform(pos,angle){this.pos=pos.copy(),this.angle=angle;const x=pos.x,y=pos.y;box2dWhenUnlocked(()=>{this.body&&(this.body.SetTransform(box2dTemp(vec2(x,y)),-angle),box2dWakeWithContacts(this.body))})}setPosition(pos){this.pos=pos.copy();const x=pos.x,y=pos.y;box2dWhenUnlocked(()=>{this.body&&(this.body.SetTransform(box2dTemp(vec2(x,y)),this.body.GetAngle()),box2dWakeWithContacts(this.body))})}setAngle(angle){this.angle=angle,box2dWhenUnlocked(()=>{this.body&&(this.body.SetTransform(this.body.GetPosition(),-angle),box2dWakeWithContacts(this.body))})}setLinearVelocity(velocity){this.body.SetLinearVelocity(box2dTemp(velocity))}setAngularVelocity(angularVelocity){this.body.SetAngularVelocity(-angularVelocity)}setLinearDamping(damping){this.body.SetLinearDamping(damping)}setAngularDamping(damping){this.body.SetAngularDamping(damping)}setGravityScale(scale=1){this.body.SetGravityScale(this.gravityScale=scale),this.body.SetAwake(!0)}setBullet(isBullet=!0){this.body.SetBullet(isBullet)}setAwake(isAwake=!0){this.body.SetAwake(isAwake)}setBodyType(type){box2dWhenUnlocked(()=>this.body&&this.body.SetType(type))}setSleepingAllowed(isAllowed=!0){this.body.SetSleepingAllowed(isAllowed)}setFixedRotation(isFixed=!0){this.body.SetFixedRotation(isFixed),this.body.SetAwake(!0)}setCenterOfMass(center){this.setMassData(center)}setMass(mass){this.setMassData(void 0,mass)}setMomentOfInertia(momentOfInertia){this.setMassData(void 0,void 0,momentOfInertia)}resetMassData(){box2dWhenUnlocked(()=>this.body&&this.body.ResetMassData())}setMassData(localCenter,mass,momentOfInertia){localCenter=localCenter&&localCenter.copy(),box2dWhenUnlocked(()=>{var data,cx,cy,f,center,oldMass,I;this.body&&(data=box2dQueryObject("massData","b2MassData"),this.body.GetMassData(data),center=data.get_center(),oldMass=data.get_mass(),cx=localCenter?localCenter.x:center.get_x(),cy=localCenter?localCenter.y:center.get_y(),center=(f=Math.fround)(data.get_I()-box2dCenterInertia(oldMass,center.get_x(),center.get_y())),I=f((center=momentOfInertia??center)+(oldMass=box2dCenterInertia(0<(mass??=oldMass)?mass:1,cx,cy))),data.set_mass(mass),data.set_center(box2dTemp(vec2(cx,cy))),data.set_I(0<center&&0<f(I-oldMass)?I:0),this.body.SetMassData(data),this.body.SetAwake(!0))})}setFilterData(categoryBits=1,ignoreCategoryBits=0,groupIndex=0){this.getFixtureList().forEach(fixture=>{var filter=fixture.GetFilterData();filter.set_categoryBits(categoryBits),filter.set_maskBits(65535&~ignoreCategoryBits),filter.set_groupIndex(groupIndex),fixture.SetFilterData(filter)})}setSensor(isSensor=!0){this.getFixtureList().forEach(f=>f.SetSensor(isSensor)),box2dWakeWithContacts(this.body)}applyForce(force,pos){pos||=this.getCenterOfMass(),this.setAwake(),this.body.ApplyForce(box2dTemp(force),box2dTemp(pos,1))}applyAcceleration(acceleration,pos){pos||=this.getCenterOfMass(),this.setAwake();acceleration=acceleration.scale(this.getMass());this.body.ApplyLinearImpulse(box2dTemp(acceleration),box2dTemp(pos,1))}applyImpulse(impulse,pos){pos||=this.getCenterOfMass(),this.setAwake(),this.body.ApplyLinearImpulse(box2dTemp(impulse),box2dTemp(pos,1))}applyTorque(torque){this.setAwake(),this.body.ApplyTorque(-torque)}applyAngularAcceleration(acceleration){this.getBodyType()!==box2d.bodyTypeDynamic||this.body.IsFixedRotation()||(this.setAwake(),this.setAngularVelocity(this.getAngularVelocity()+acceleration))}applyAngularImpulse(impulse){this.setAwake(),this.body.ApplyAngularImpulse(-impulse)}hasFixtures(){return!box2d.isNull(this.body.GetFixtureList())}getFixtureList(){var fixtures=[];for(let fixture=this.body.GetFixtureList();!box2d.isNull(fixture);)fixtures.push(fixture),fixture=fixture.GetNext();return fixtures}hasJoints(){return 0<this.getJointList().length}getJointList(){var joints=[];for(let edge=this.body.GetJointList();!box2d.isNull(edge);edge=edge.get_next()){var joint=edge.get_joint(),wrapper=box2dJoints.get(box2d.instance.getPointer(joint));wrapper&&!wrapper.box2dJoint||joints.push(wrapper||box2d.castJointObject(joint))}return joints}}class Box2dStaticObject extends Box2dObject{constructor(pos,size,tileInfo,angle=0,color,renderOrder=0){super(pos,size,tileInfo,angle,color,box2d.bodyTypeStatic,renderOrder)}}class Box2dKinematicObject extends Box2dObject{constructor(pos,size,tileInfo,angle=0,color,renderOrder=0){super(pos,size,tileInfo,angle,color,box2d.bodyTypeKinematic,renderOrder)}}class Box2dTileLayer extends Box2dStaticObject{constructor(tileLayer){super(tileLayer.pos,tileLayer.size),this.tileLayer=tileLayer,this.addChild(tileLayer),this.buildCollision()}render(){}buildCollision(friction=.2,restitution=0){this.destroyAllFixtures();const processed=[],getIndex=(x,y)=>x+y*this.size.x;var isSolidUnprocessed=(x,y)=>!processed[getIndex(x,y)]&&0<this.tileLayer.getCollisionData(vec2(x,y));for(let x=0;x<this.size.x;++x)for(let y=0;y<this.size.y;++y)if(isSolidUnprocessed(x,y)){let width=1,height=1,canExpand=!0;for(;isSolidUnprocessed(x+width,y);)++width;for(;canExpand;){for(let checkX=0;checkX<width;++checkX)if(!isSolidUnprocessed(x+checkX,y+height)){canExpand=!1;break}canExpand&&++height}for(let rectX=width;rectX--;)for(let rectY=height;rectY--;)processed[getIndex(x+rectX,y+rectY)]=!0;var shapeSize=vec2(width,height),offset=vec2(x+width/2,y+height/2);this.addBox(shapeSize,offset,0,0,friction,restitution)}}}class Box2dRaycastResult{constructor(fixture,point,normal,fraction){this.object=fixture.GetBody().object,this.fixture=fixture,this.point=point,this.normal=normal,this.fraction=fraction}}class Box2dJoint{constructor(jointDef){var bodyA=jointDef.get_bodyA(),bodyB=jointDef.get_bodyB(),bodyA=!box2d.isNull(bodyA)&&!box2d.isNull(bodyB);this.box2dJoint=0,bodyA?(this.box2dJoint=box2d.castJointObject(box2d.world.CreateJoint(jointDef)),box2d.instance.destroy(jointDef),box2dJoints.set(box2d.instance.getPointer(this.box2dJoint),this)):box2d.instance.destroy(jointDef)}destroy(){const joint=this.box2dJoint;if(joint){this.box2dJoint=0,box2dDestroyGears(this);const pointer=box2d.instance.getPointer(joint);box2dWhenUnlocked(()=>{box2dJoints.get(pointer)===this&&(box2dJoints.delete(pointer),box2d.world.DestroyJoint(joint))})}}getObjectA(){return this.box2dJoint.GetBodyA().object}getObjectB(){return this.box2dJoint.GetBodyB().object}getAnchorA(){return box2d.vec2From(this.box2dJoint.GetAnchorA())}getAnchorB(){return box2d.vec2From(this.box2dJoint.GetAnchorB())}getReactionForce(time=timeDelta){return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time))}getReactionTorque(time=timeDelta){return-this.box2dJoint.GetReactionTorque(1/time)}getCollideConnected(){return this.box2dJoint.GetCollideConnected()}isActive(){return!!this.box2dJoint&&this.box2dJoint.IsActive()}isDestroyed(){return!this.box2dJoint}}class Box2dTargetJoint extends Box2dJoint{constructor(object,fixedObject,worldPos){object.setAwake();var jointDef=new box2d.instance.b2MouseJointDef;jointDef.set_bodyA(fixedObject.body),jointDef.set_bodyB(object.body),jointDef.set_target(box2dTemp(worldPos)),jointDef.set_maxForce(2e3*object.getMass()),super(jointDef)}setTarget(pos){this.box2dJoint.SetTarget(box2dTemp(pos))}getTarget(){return box2d.vec2From(this.box2dJoint.GetTarget())}setMaxForce(force){this.box2dJoint.SetMaxForce(force),box2dWakeJoint(this.box2dJoint)}getMaxForce(){return this.box2dJoint.GetMaxForce()}setFrequency(hz){this.box2dJoint.SetFrequency(max(hz,.001)),box2dWakeJoint(this.box2dJoint)}getFrequency(){return this.box2dJoint.GetFrequency()}}class Box2dDistanceJoint extends Box2dJoint{constructor(objectA,objectB,anchorA,anchorB,collide=!1){anchorA||=box2d.vec2From(objectA.body.GetPosition()),anchorB||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchorA),localAnchorB=objectB.worldToLocal(anchorB),jointDef=new box2d.instance.b2DistanceJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(localAnchorB)),jointDef.set_length(anchorA.distance(anchorB)),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setLength(length){this.box2dJoint.SetLength(length),box2dWakeJoint(this.box2dJoint)}getLength(){return this.box2dJoint.GetLength()}setFrequency(hz){this.box2dJoint.SetFrequency(hz),box2dWakeJoint(this.box2dJoint)}getFrequency(){return this.box2dJoint.GetFrequency()}setDampingRatio(ratio){this.box2dJoint.SetDampingRatio(ratio),box2dWakeJoint(this.box2dJoint)}getDampingRatio(){return this.box2dJoint.GetDampingRatio()}}class Box2dRopeJoint extends Box2dJoint{constructor(objectA,objectB,anchorA,anchorB,extraLength=0,collide=!1){anchorA||=box2d.vec2From(objectA.body.GetPosition()),anchorB||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchorA),localAnchorB=objectB.worldToLocal(anchorB),jointDef=new box2d.instance.b2RopeJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(localAnchorB)),jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setMaxLength(length){this.box2dJoint.SetMaxLength(length),box2dWakeJoint(this.box2dJoint)}getMaxLength(){return this.box2dJoint.GetMaxLength()}}class Box2dRevoluteJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=!1){anchor||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchor),anchor=objectB.worldToLocal(anchor),jointDef=new box2d.instance.b2RevoluteJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(anchor)),jointDef.set_referenceAngle(objectB.body.GetAngle()-objectA.body.GetAngle()),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getReferenceAngle(){return-this.box2dJoint.GetReferenceAngle()}getJointAngle(){return-this.box2dJoint.GetJointAngle()}getJointSpeed(){return-this.box2dJoint.GetJointSpeed()}isLimitEnabled(){return this.box2dJoint.IsLimitEnabled()}enableLimit(enable=!0){this.box2dJoint.EnableLimit(enable)}getLowerLimit(){return-this.box2dJoint.GetUpperLimit()}getUpperLimit(){return-this.box2dJoint.GetLowerLimit()}setLimits(min,max){max<min&&([min,max]=[max,min]),this.box2dJoint.SetLimits(-max,-min)}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=!0){this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){this.box2dJoint.SetMotorSpeed(-speed)}getMotorSpeed(){return-this.box2dJoint.GetMotorSpeed()}setMaxMotorTorque(torque){this.box2dJoint.SetMaxMotorTorque(torque)}getMaxMotorTorque(){return this.box2dJoint.GetMaxMotorTorque()}getMotorTorque(time=timeDelta){return-this.box2dJoint.GetMotorTorque(1/time)}}class Box2dPinJoint extends Box2dRevoluteJoint{constructor(objectA,objectB,pos=objectA.pos,collide=!1){super(objectA,objectB,pos,collide)}}class Box2dGearJoint extends Box2dJoint{constructor(objectA,objectB,joint1,joint2,ratio=1){var ratioSign=joint1 instanceof Box2dRevoluteJoint==joint2 instanceof Box2dRevoluteJoint?1:-1,jointDef=new box2d.instance.b2GearJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_joint1(joint1.box2dJoint),jointDef.set_joint2(joint2.box2dJoint),jointDef.set_ratio(ratio*ratioSign),super(jointDef),this.joint1=joint1,this.joint2=joint2,this.ratioSign=ratioSign,box2dGearJoints.add(this)}getJoint1(){return this.joint1}getJoint2(){return this.joint2}setRatio(ratio){this.box2dJoint.SetRatio(ratio*this.ratioSign),box2dWakeJoint(this.box2dJoint)}getRatio(){return this.box2dJoint.GetRatio()*this.ratioSign}}class Box2dPrismaticJoint extends Box2dJoint{constructor(objectA,objectB,anchor,worldAxis=vec2(0,1),collide=!1){anchor||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchor),anchor=objectB.worldToLocal(anchor),worldAxis=objectA.worldToLocalVector(worldAxis),jointDef=new box2d.instance.b2PrismaticJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(anchor)),jointDef.set_localAxisA(box2dTemp(worldAxis)),jointDef.set_referenceAngle(objectB.body.GetAngle()-objectA.body.GetAngle()),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getLocalAxisA(){return box2d.vec2From(this.box2dJoint.GetLocalAxisA())}getReferenceAngle(){return-this.box2dJoint.GetReferenceAngle()}getJointTranslation(){return this.box2dJoint.GetJointTranslation()}getJointSpeed(){return this.box2dJoint.GetJointSpeed()}isLimitEnabled(){return this.box2dJoint.IsLimitEnabled()}enableLimit(enable=!0){this.box2dJoint.EnableLimit(enable)}getLowerLimit(){return this.box2dJoint.GetLowerLimit()}getUpperLimit(){return this.box2dJoint.GetUpperLimit()}setLimits(min,max){max<min&&([min,max]=[max,min]),this.box2dJoint.SetLimits(min,max)}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=!0){this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){this.box2dJoint.SetMotorSpeed(speed)}getMotorSpeed(){return this.box2dJoint.GetMotorSpeed()}setMaxMotorForce(force){this.box2dJoint.SetMaxMotorForce(force)}getMaxMotorForce(){return this.box2dJoint.GetMaxMotorForce()}getMotorForce(time=timeDelta){return this.box2dJoint.GetMotorForce(1/time)}}class Box2dWheelJoint extends Box2dJoint{constructor(objectA,objectB,anchor,worldAxis=vec2(0,1),collide=!1){anchor||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchor),anchor=objectB.worldToLocal(anchor),worldAxis=objectA.worldToLocalVector(worldAxis).normalize(),jointDef=new box2d.instance.b2WheelJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(anchor)),jointDef.set_localAxisA(box2dTemp(worldAxis)),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getLocalAxisA(){return box2d.vec2From(this.box2dJoint.GetLocalAxisA())}getJointTranslation(){return this.box2dJoint.GetJointTranslation()}getJointSpeed(){return-this.box2dJoint.GetJointSpeed()}isMotorEnabled(){return this.box2dJoint.IsMotorEnabled()}enableMotor(enable=!0){this.box2dJoint.EnableMotor(enable)}setMotorSpeed(speed){this.box2dJoint.SetMotorSpeed(-speed)}getMotorSpeed(){return-this.box2dJoint.GetMotorSpeed()}setMaxMotorTorque(torque){this.box2dJoint.SetMaxMotorTorque(torque)}getMaxMotorTorque(){return this.box2dJoint.GetMaxMotorTorque()}getMotorTorque(time=timeDelta){return-this.box2dJoint.GetMotorTorque(1/time)}setSpringFrequencyHz(hz){this.box2dJoint.SetSpringFrequencyHz(hz),box2dWakeJoint(this.box2dJoint)}getSpringFrequencyHz(){return this.box2dJoint.GetSpringFrequencyHz()}setSpringDampingRatio(ratio){this.box2dJoint.SetSpringDampingRatio(ratio),box2dWakeJoint(this.box2dJoint)}getSpringDampingRatio(){return this.box2dJoint.GetSpringDampingRatio()}}class Box2dWeldJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=!1){anchor||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchor),anchor=objectB.worldToLocal(anchor),referenceAngle=objectB.body.GetAngle()-objectA.body.GetAngle(),jointDef=new box2d.instance.b2WeldJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(anchor)),jointDef.set_referenceAngle(referenceAngle),jointDef.set_collideConnected(collide),super(jointDef),this.referenceAngle=-referenceAngle}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}getReferenceAngle(){return this.referenceAngle}setFrequency(hz){this.box2dJoint.SetFrequency(hz),box2dWakeJoint(this.box2dJoint)}getFrequency(){return this.box2dJoint.GetFrequency()}setDampingRatio(ratio){this.box2dJoint.SetDampingRatio(ratio),box2dWakeJoint(this.box2dJoint)}getDampingRatio(){return this.box2dJoint.GetDampingRatio()}setSpringDampingRatio(ratio){this.setDampingRatio(ratio)}getSpringDampingRatio(){return this.getDampingRatio()}}class Box2dFrictionJoint extends Box2dJoint{constructor(objectA,objectB,anchor,collide=!1){anchor||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchor),anchor=objectB.worldToLocal(anchor),jointDef=new box2d.instance.b2FrictionJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(anchor)),jointDef.set_collideConnected(collide),super(jointDef)}getLocalAnchorA(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorA())}getLocalAnchorB(){return box2d.vec2From(this.box2dJoint.GetLocalAnchorB())}setMaxForce(force){this.box2dJoint.SetMaxForce(max(force,0))}getMaxForce(){return this.box2dJoint.GetMaxForce()}setMaxTorque(torque){this.box2dJoint.SetMaxTorque(max(torque,0))}getMaxTorque(){return this.box2dJoint.GetMaxTorque()}}class Box2dPulleyJoint extends Box2dJoint{constructor(objectA,objectB,groundAnchorA,groundAnchorB,anchorA,anchorB,ratio=1,collide=!1){anchorA||=box2d.vec2From(objectA.body.GetPosition()),anchorB||=box2d.vec2From(objectB.body.GetPosition());var localAnchorA=objectA.worldToLocal(anchorA),localAnchorB=objectB.worldToLocal(anchorB),jointDef=new box2d.instance.b2PulleyJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_groundAnchorA(box2dTemp(groundAnchorA)),jointDef.set_groundAnchorB(box2dTemp(groundAnchorB)),jointDef.set_localAnchorA(box2dTemp(localAnchorA)),jointDef.set_localAnchorB(box2dTemp(localAnchorB)),jointDef.set_ratio(ratio),jointDef.set_lengthA(groundAnchorA.distance(anchorA)),jointDef.set_lengthB(groundAnchorB.distance(anchorB)),jointDef.set_collideConnected(collide),super(jointDef)}getGroundAnchorA(){return box2d.vec2From(this.box2dJoint.GetGroundAnchorA())}getGroundAnchorB(){return box2d.vec2From(this.box2dJoint.GetGroundAnchorB())}getLengthA(){return this.box2dJoint.GetLengthA()}getLengthB(){return this.box2dJoint.GetLengthB()}getRatio(){return this.box2dJoint.GetRatio()}getCurrentLengthA(){return this.box2dJoint.GetCurrentLengthA()}getCurrentLengthB(){return this.box2dJoint.GetCurrentLengthB()}}class Box2dMotorJoint extends Box2dJoint{constructor(objectA,objectB){var linearOffset=objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition())),angularOffset=objectB.body.GetAngle()-objectA.body.GetAngle(),jointDef=new box2d.instance.b2MotorJointDef;jointDef.set_bodyA(objectA.body),jointDef.set_bodyB(objectB.body),jointDef.set_linearOffset(box2dTemp(linearOffset)),jointDef.set_angularOffset(angularOffset),super(jointDef)}setLinearOffset(offset){this.box2dJoint.SetLinearOffset(box2dTemp(offset))}getLinearOffset(){return box2d.vec2From(this.box2dJoint.GetLinearOffset())}setAngularOffset(offset){this.box2dJoint.SetAngularOffset(-offset)}getAngularOffset(){return-this.box2dJoint.GetAngularOffset()}setMaxForce(force){this.box2dJoint.SetMaxForce(max(force,0)),box2dWakeJoint(this.box2dJoint)}getMaxForce(){return this.box2dJoint.GetMaxForce()}setMaxTorque(torque){this.box2dJoint.SetMaxTorque(max(torque,0)),box2dWakeJoint(this.box2dJoint)}getMaxTorque(){return this.box2dJoint.GetMaxTorque()}setCorrectionFactor(factor){this.box2dJoint.SetCorrectionFactor(clamp(factor)),box2dWakeJoint(this.box2dJoint)}getCorrectionFactor(){return this.box2dJoint.GetCorrectionFactor()}}class Box2dPlugin{constructor(instance){(box2d=this).instance=instance,this.world=new box2d.instance.b2World,this.objects=[],this.velocityIterations=8,this.positionIterations=3,this.bodyTypeStatic=instance.b2_staticBody,this.bodyTypeKinematic=instance.b2_kinematicBody,this.bodyTypeDynamic=instance.b2_dynamicBody;instance=new box2d.instance.JSDestructionListener,instance.SayGoodbyeJoint=function(jointPointer){var joint=box2dJoints.get(jointPointer);joint&&(box2dDestroyGears(joint),joint.box2dJoint=0),box2dJoints.delete(jointPointer)},instance.SayGoodbyeFixture=function(){},box2d.world.SetDestructionListener(instance),instance=new box2d.instance.JSContactListener;instance.BeginContact=function(contactPtr){var contactPtr=box2d.instance.wrapPointer(contactPtr,box2d.instance.b2Contact),fixtureA=contactPtr.GetFixtureA(),contactPtr=contactPtr.GetFixtureB(),objectA=fixtureA.GetBody().object,objectB=contactPtr.GetBody().object;objectA&&objectB&&(objectA.beginContact(objectB,fixtureA,contactPtr),objectB.beginContact(objectA,contactPtr,fixtureA))},instance.EndContact=function(contactPtr){var contactPtr=box2d.instance.wrapPointer(contactPtr,box2d.instance.b2Contact),fixtureA=contactPtr.GetFixtureA(),contactPtr=contactPtr.GetFixtureB(),objectA=fixtureA.GetBody().object,objectB=contactPtr.GetBody().object;objectA&&objectB&&(objectA.endContact(objectB,fixtureA,contactPtr),objectB.endContact(objectA,contactPtr,fixtureA))},instance.PreSolve=function(){},instance.PostSolve=function(){},box2d.world.SetContactListener(instance)}step(frames=1){if(gravity.x!==box2dGravity.x||gravity.y!==box2dGravity.y){box2dGravity.x=gravity.x,box2dGravity.y=gravity.y,box2d.world.SetGravity(box2dTemp(gravity));for(let b=box2d.world.GetBodyList();!box2d.isNull(b);b=b.GetNext())b.SetAwake(!0)}for(let i=frames;i--;)box2d.world.Step(timeDelta,this.velocityIterations,this.positionIterations),box2dRunPending();this.objects=this.objects.filter(o=>!o.destroyed)}raycastAll(start,end,includeSensors=!1){var f=Math.fround,dx=f(f(end.x)-f(start.x)),dy=f(f(end.y)-f(start.y)),dx=f(f(dx*dx)+f(dy*dy));if(!(0<dx&&dx<1/0))return[];f=box2dQueryObject("rayCast","JSRayCastCallback");f.ReportFixture=function(fixturePointer,point,normal,fraction){var fixturePointer=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture),o=fixturePointer.GetBody().object;if(o&&!o.destroyed){if(!includeSensors&&fixturePointer.IsSensor())return-1;point=box2d.vec2FromPointer(point),normal=box2d.vec2FromPointer(normal),raycastResults.push(new Box2dRaycastResult(fixturePointer,point,normal,fraction))}return 1};const raycastResults=[];return box2d.world.RayCast(f,box2dTemp(start),box2dTemp(end,1)),raycastResults.sort((a,b)=>a.fraction-b.fraction),raycastResults}raycast(start,end,includeSensors=!1){return box2d.raycastAll(start,end,includeSensors)[0]}boxCastAll(pos,size,includeSensors=!1){var queryCallback=box2dQueryObject("query","JSQueryCallback");queryCallback.ReportFixture=function(fixturePointer){var o,fixturePointer=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);return!includeSensors&&fixturePointer.IsSensor()||(o=fixturePointer.GetBody().object)&&!o.destroyed&&!queryObjects.includes(o)&&box2dFixtureOverlaps(fixturePointer,aabb)&&queryObjects.push(o),!0};const aabb=box2dQueryObject("aabb","b2AABB");aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5)))),aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));let queryObjects=[];return box2d.world.QueryAABB(queryCallback,aabb),queryObjects}boxCast(pos,size,includeSensors=!1){var queryCallback=box2dQueryObject("query","JSQueryCallback");queryCallback.ReportFixture=function(fixturePointer){var o,fixturePointer=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);return!(includeSensors||!fixturePointer.IsSensor())||!((o=fixturePointer.GetBody().object)&&!o.destroyed&&box2dFixtureOverlaps(fixturePointer,aabb)&&(queryObject=o,1))};const aabb=box2dQueryObject("aabb","b2AABB");aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5)))),aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));let queryObject;return box2d.world.QueryAABB(queryCallback,aabb),queryObject}circleCastAll(pos,diameter,includeSensors=!1){const radius2=(diameter/2)**2;diameter=box2d.objects.filter(o=>!o.destroyed&&o.body&&o.pos.distanceSquared(pos)<radius2&&(includeSensors||o.getFixtureList().some(fixture=>!fixture.IsSensor())));return diameter}circleCast(pos,diameter,includeSensors=!1){let bestResult,bestDistance2;for(const result of box2d.circleCastAll(pos,diameter,includeSensors)){var distance2=result.pos.distanceSquared(pos);(!bestResult||distance2<bestDistance2)&&(bestResult=result,bestDistance2=distance2)}return bestResult}pointCast(pos,dynamicOnly=!0,includeSensors=!1){var queryCallback=box2dQueryObject("query","JSQueryCallback"),aabb=(queryCallback.ReportFixture=function(fixturePointer){var fixturePointer=box2d.instance.wrapPointer(fixturePointer,box2d.instance.b2Fixture);return!((includeSensors||!fixturePointer.IsSensor())&&(!dynamicOnly||fixturePointer.GetBody().GetType()===box2d.bodyTypeDynamic)&&fixturePointer.TestPoint(box2dTemp(pos))&&(fixturePointer=fixturePointer.GetBody().object)&&!fixturePointer.destroyed&&(queryObject=fixturePointer,1))},box2dQueryObject("aabb","b2AABB"));aabb.set_lowerBound(box2dTemp(pos)),aabb.set_upperBound(box2dTemp(pos));let queryObject;return box2d.world.QueryAABB(queryCallback,aabb),queryObject}drawFixture(fixture,pos,angle,color=WHITE,lineColor=BLACK,lineWidth=.1,useWebGL,context){var shape=box2d.castShapeObject(fixture.GetShape());switch(shape.GetType()){case box2d.instance.b2Shape.e_polygon:var points=[];for(let i=shape.GetVertexCount();i--;)points.push(box2d.vec2From(shape.GetVertex(i)));drawPoly(points,color,lineWidth,lineColor,pos,angle,useWebGL,!1,context);break;case box2d.instance.b2Shape.e_circle:var radius=shape.get_m_radius(),offset=box2d.vec2From(shape.get_m_p());drawCircle(pos.add(offset.rotate(angle)),2*radius,color,lineWidth,lineColor,useWebGL,!1,context);break;case box2d.instance.b2Shape.e_edge:drawLine(box2d.vec2From(shape.get_m_vertex1()),box2d.vec2From(shape.get_m_vertex2()),lineWidth,lineColor,pos,angle,useWebGL,!1,context)}}vec2From(v){return new Vector2(v.get_x(),v.get_y())}vec2FromPointer(vp){vp=box2d.instance.wrapPointer(vp,box2d.instance.b2Vec2);return box2d.vec2From(vp)}vec2dTo(v){return new box2d.instance.b2Vec2(v.x,v.y)}isNull(o){return!box2d.instance.getPointer(o)}castShapeObject(o){switch(o.GetType()){case box2d.instance.b2Shape.e_circle:return box2d.instance.castObject(o,box2d.instance.b2CircleShape);case box2d.instance.b2Shape.e_edge:return box2d.instance.castObject(o,box2d.instance.b2EdgeShape);case box2d.instance.b2Shape.e_polygon:return box2d.instance.castObject(o,box2d.instance.b2PolygonShape);case box2d.instance.b2Shape.e_chain:return box2d.instance.castObject(o,box2d.instance.b2ChainShape)}}castJointObject(o){switch(o.GetType()){case box2d.instance.e_revoluteJoint:return box2d.instance.castObject(o,box2d.instance.b2RevoluteJoint);case box2d.instance.e_prismaticJoint:return box2d.instance.castObject(o,box2d.instance.b2PrismaticJoint);case box2d.instance.e_distanceJoint:return box2d.instance.castObject(o,box2d.instance.b2DistanceJoint);case box2d.instance.e_pulleyJoint:return box2d.instance.castObject(o,box2d.instance.b2PulleyJoint);case box2d.instance.e_mouseJoint:return box2d.instance.castObject(o,box2d.instance.b2MouseJoint);case box2d.instance.e_gearJoint:return box2d.instance.castObject(o,box2d.instance.b2GearJoint);case box2d.instance.e_wheelJoint:return box2d.instance.castObject(o,box2d.instance.b2WheelJoint);case box2d.instance.e_weldJoint:return box2d.instance.castObject(o,box2d.instance.b2WeldJoint);case box2d.instance.e_frictionJoint:return box2d.instance.castObject(o,box2d.instance.b2FrictionJoint);case box2d.instance.e_ropeJoint:return box2d.instance.castObject(o,box2d.instance.b2RopeJoint);case box2d.instance.e_motorJoint:return box2d.instance.castObject(o,box2d.instance.b2MotorJoint)}}}async function box2dInit(){new Box2dPlugin(await Box2D());{const debugDraw=new box2d.instance.JSDraw,box2dColor=c=>new Color(c.get_r(),c.get_g(),c.get_b()),getDebugColor=color=>{return color=color,box2dColor(box2d.instance.wrapPointer(color,box2d.instance.b2Color)).scale(1,.8)},getPointsList=(vertices,vertexCount)=>{var points=[];for(let i=vertexCount;i--;)points.push(box2d.vec2FromPointer(vertices+8*i));return points};debugDraw.DrawSegment=function(point1,point2,color){color=getDebugColor(color),drawLine(point1=box2d.vec2FromPointer(point1),point2=box2d.vec2FromPointer(point2),.1,color,vec2(),0,!1,!1)},debugDraw.DrawPolygon=function(vertices,vertexCount,color){color=getDebugColor(color),drawPoly(getPointsList(vertices,vertexCount),CLEAR_WHITE,.1,color,vec2(),0,!1,!1)},debugDraw.DrawSolidPolygon=function(vertices,vertexCount,color){color=getDebugColor(color),drawPoly(getPointsList(vertices,vertexCount),color,0,color,vec2(),0,!1,!1)},debugDraw.DrawCircle=function(center,radius,color){color=getDebugColor(color),drawCircle(center=box2d.vec2FromPointer(center),2*radius,CLEAR_WHITE,.1,color,!1,!1)},debugDraw.DrawSolidCircle=function(center,radius,axis,color){color=getDebugColor(color),center=box2d.vec2FromPointer(center),axis=box2d.vec2FromPointer(axis).scale(radius),drawCircle(center,2*radius,color,.1,color,!1,!1),drawLine(vec2(),axis,.1,color,center,0,!1,!1)},debugDraw.DrawTransform=function(transform){transform=box2d.instance.wrapPointer(transform,box2d.instance.b2Transform);var pos=box2d.vec2From(transform.get_p()),transform=-transform.get_q().GetAngle(),p1=vec2(1,0),c1=rgb(.75,0,0,.8),p2=vec2(0,1),c2=rgb(0,.75,0,.8);drawLine(vec2(),p1,.1,c1,pos,transform,!1,!1),drawLine(vec2(),p2,.1,c2,pos,transform,!1,!1)},debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit),debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit),box2d.world.SetDebugDraw(debugDraw)}return engineAddPlugin(function(){if(paused||!timeScale)box2d.objects=box2d.objects.filter(o=>!o.destroyed);else{box2d.step();for(const o of box2d.objects)o.body&&(o.pos=box2d.vec2From(o.body.GetPosition()),o.angle=-o.body.GetAngle())}},function(){box2dDebug&&box2d.world.DrawDebugData()}),box2d}function drawNineSliceScreen(pos,size,startTile,color=WHITE,borderSize=32,additiveColor,extraSpace=2,angle=0,useWebGL=!1,context){isNumber(color)&&([borderSize,extraSpace,angle]=[color,arguments[4]??2,arguments[5]??0],color=WHITE,additiveColor=void 0),drawNineSlice(pos,size,startTile,color,borderSize,additiveColor,extraSpace,angle,useWebGL,!0,context)}function drawNineSlice(pos,size,startTile,color,borderSize,additiveColor,extraSpace,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(borderSize??=screenSpace?32:1,extraSpace??=screenSpace?2:.05,size.x&&size.y){color&&color.a<1&&(extraSpace=0);const step=startTile.size.add(vec2(2*startTile.padding));if(screenSpace&&!angle)drawSliceSnapped(pos,size,borderSize,(col,row)=>[startTile.offset(step.multiply(vec2(col,row))),0],color,additiveColor,useWebGL,context);else{borderSize=min(borderSize,abs(size.x)/2,abs(size.y)/2);var centerTile=startTile.offset(step),centerSize=size.add(vec2(extraSpace-2*borderSize)),cornerSize=vec2(borderSize),cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5)),flip=screenSpace?-1:1,rotateAngle=screenSpace?-angle:angle;drawTile(pos,centerSize,centerTile,color,angle,!1,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){var horizontal=i%2,sidePos=cornerOffset.multiply(vec2(horizontal?1===i?1:-1:0,horizontal?0:i?-1:1)),horizontal=vec2(horizontal?borderSize:centerSize.x,horizontal?centerSize.y:borderSize),sideTile=centerTile.offset(step.multiply(vec2(1===i?1:3===i?-1:0,0===i?-flip:2===i?flip:0)));drawTile(pos.add(sidePos.rotate(rotateAngle)),horizontal,sideTile,color,angle,!1,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){var flipX=1<i,flipY=i&&i<3,cornerPos=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-1:1)),flipX=centerTile.offset(step.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));drawTile(pos.add(cornerPos.rotate(rotateAngle)),cornerSize,flipX,color,angle,!1,additiveColor,useWebGL,screenSpace,context)}}}}function drawThreeSliceScreen(pos,size,startTile,color=WHITE,borderSize=32,additiveColor,extraSpace=2,angle=0,useWebGL=!1,context){isNumber(color)&&([borderSize,extraSpace,angle]=[color,arguments[4]??2,arguments[5]??0],color=WHITE,additiveColor=void 0),drawThreeSlice(pos,size,startTile,color,borderSize,additiveColor,extraSpace,angle,useWebGL,!0,context)}function drawThreeSlice(pos,size,startTile,color,borderSize,additiveColor,extraSpace,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(borderSize??=screenSpace?32:1,extraSpace??=screenSpace?2:.05,size.x&&size.y){color&&color.a<1&&(extraSpace=0);const cornerTile=startTile.frame(0),sideTile=startTile.frame(1),centerTile=startTile.frame(2);if(screenSpace&&!angle){const turns=[[0,0,1],[3,-1,1],[3,2,2]];void drawSliceSnapped(pos,size,borderSize,(col,row)=>{var turn=turns[row][col],col=1!==col&&1!==row;return turn<0?[centerTile,0]:[col?cornerTile:sideTile,turn*PI/2]},color,additiveColor,useWebGL,context)}else{borderSize=min(borderSize,abs(size.x)/2,abs(size.y)/2);var centerSize=size.add(vec2(extraSpace-2*borderSize)),cornerSize=vec2(borderSize),cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5)),flip=screenSpace?-1:1,rotateAngle=screenSpace?-angle:angle;drawTile(pos,centerSize,centerTile,color,angle,!1,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){var a=angle+i*PI/2,horizontal=i%2,sidePos=cornerOffset.multiply(vec2(horizontal?1===i?1:-1:0,horizontal?0:i?-flip:flip)),horizontal=vec2(horizontal?centerSize.y:centerSize.x,borderSize);drawTile(pos.add(sidePos.rotate(rotateAngle)),horizontal,sideTile,color,a,!1,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){const a=angle+i*PI/2;var flipX=!i||2<i,flipY=1<i,flipX=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-flip:flip));drawTile(pos.add(flipX.rotate(rotateAngle)),cornerSize,cornerTile,color,a,!1,additiveColor,useWebGL,screenSpace,context)}}}}class TileSlice{constructor(tileInfo,slices=9,borderSize,extraSpace){this.tileInfo=tileInfo,this.slices=slices,this.borderSize=borderSize,this.extraSpace=extraSpace}draw(pos,size,color=WHITE,additiveColor,angle=0,useWebGL=glEnable,screenSpace=drawScreenSpace,context){if(screenSpace)return this.drawScreen(pos,size,color,additiveColor,angle,useWebGL,context);9===this.slices?drawNineSlice(pos,size,this.tileInfo,color,this.borderSize,additiveColor,this.extraSpace,angle,useWebGL,screenSpace,context):3===this.slices?drawThreeSlice(pos,size,this.tileInfo,color,this.borderSize,additiveColor,this.extraSpace,angle,useWebGL,screenSpace,context):drawTile(pos,size,this.tileInfo,color,angle,!1,additiveColor,useWebGL,screenSpace,context)}drawScreen(pos,size,color=WHITE,additiveColor,angle=0,useWebGL=!1,context){9===this.slices?drawNineSliceScreen(pos,size,this.tileInfo,color,this.borderSize,additiveColor,this.extraSpace,angle,useWebGL,context):3===this.slices?drawThreeSliceScreen(pos,size,this.tileInfo,color,this.borderSize,additiveColor,this.extraSpace,angle,useWebGL,context):drawTile(pos,size,this.tileInfo,color,angle,!1,additiveColor,useWebGL,!0,context)}}function drawSliceSnapped(pos,size,borderSize,pieceTile,color,additiveColor,useWebGL,context){var transform=(!useWebGL||!glEnable||!!context)&&(context||drawContext)?.getTransform?.(),ratio=getCanvasPixelRatio(),[transform,ratio,offsetX,offsetY]=transform?[transform.a||1,transform.d||1,transform.e||0,transform.f||0]:[ratio,ratio,0,0],edges=(center,length,scale,offset)=>{var border=max(1,round(borderSize*abs(scale))),center=round((center-length/2)*scale+offset),length=center+round(length*scale),start=min(center,length),center=max(center,length),length=min(start+border,floor((start+center)/2)),start=[start,length,max(center-border,length),center];return(scale<0?start.reverse():start).map(edge=>(edge-offset)/scale)},xs=edges(pos.x,size.x,transform,offsetX),ys=edges(pos.y,size.y,ratio,offsetY);for(let row=3;row--;)for(let col=3;col--;){var tileInfo,angle,turned,w=xs[col+1]-xs[col],h=ys[row+1]-ys[row];w<=0||h<=0||([tileInfo,angle]=pieceTile(col,row),turned=round(angle/(PI/2))%2,drawTile(vec2(xs[col]+w/2-.5,ys[row]+h/2-.5),turned?vec2(h,w):vec2(w,h),tileInfo,color,angle,!1,additiveColor,useWebGL,!0,context))}}function drawCrescent(pos,size=1,percent=0,color=WHITE,angle=0,invert=!1,lineWidth=0,lineColor=BLACK,useWebGL=glEnable,screenSpace=drawScreenSpace,context){drawPoly(getCrescentPoints(vec2(),size,percent,0,invert),color,lineWidth,lineColor,pos,angle,useWebGL,screenSpace,context)}function getCrescentPoints(pos,size=1,percent=0,angle=0,invert=!1,sides=glCircleSides){let p=mod(4*percent,4);2<=p&&(angle+=PI),p=p<=2?p-1:3-p,invert&&(p=-p,angle+=PI);var points=[],segs=max(3,sides>>1),radius=size/2;for(let i=0;i<=segs;i++){var t=i/segs*PI;points.push(vec2(radius*cos(t),radius*sin(t)).rotate(angle).add(pos))}for(let i=segs;0<=i;i--){const t=i/segs*PI;points.push(vec2(radius*cos(t),-radius*p*sin(t)).rotate(angle).add(pos))}return points}let textureSheetSize=2048,textureSheetPadding=1,textureSheets=[],textureSheetQueue=Promise.resolve(),textureSheetPendingCount=0;class TextureSheet{constructor(size=textureSheetSize){this.size=size,this.context=headlessMode?void 0:createCanvasContext(size),this.canvas=this.context?.canvas,this.textureInfo=new TextureInfo(this.canvas),this.cursor=vec2(),this.rowHeight=0,this.glDirty=!1,headlessMode&&(this.textureInfo.size=vec2(size),this.textureInfo.sizeInverse=vec2(1/size))}tryAdd(imageSize,frameSize,padding=textureSheetPadding,sourcePadding=0){var sourcePadding=isNumber(sourcePadding)?vec2(sourcePadding):sourcePadding,sourceCellWidth=(frameSize||=imageSize.subtract(sourcePadding.scale(2))).x+2*sourcePadding.x,sourcePadding=frameSize.y+2*sourcePadding.y,cellWidth=frameSize.x+2*padding,cellHeight=frameSize.y+2*padding,maxColumns=this.size/cellWidth|0,sourceCellWidth=imageSize.x/sourceCellWidth|0,imageSize=sourceCellWidth*(imageSize.y/sourcePadding|0);if(imageSize){sourcePadding=min(sourceCellWidth,maxColumns),sourceCellWidth=sourcePadding*cellWidth,maxColumns=ceil(imageSize/sourcePadding)*cellHeight;let x=this.cursor.x,y=this.cursor.y,rowHeight=this.rowHeight;if(x+sourceCellWidth>this.size&&(x=0,y+=rowHeight,rowHeight=0),!(y+maxColumns>this.size))return this.cursor.x=x+sourceCellWidth,this.cursor.y=y,this.rowHeight=max(rowHeight,maxColumns),new TileInfo(vec2(x+padding,y+padding),frameSize,this.textureInfo,padding,0,sourcePadding)}}drawImage(image,tileInfo,update=!0,sourcePadding=0){var sourcePad=isNumber(sourcePadding)?vec2(sourcePadding):sourcePadding,frameSize=tileInfo.size,sourceCellWidth=frameSize.x+2*sourcePad.x,sourceCellHeight=frameSize.y+2*sourcePad.y,sourceColumns=image.width/sourceCellWidth|0,sourcePadding=sourceColumns*(image.height/sourceCellHeight|0),columns=tileInfo.columns||sourcePadding,cellWidth=frameSize.x+2*tileInfo.padding,cellHeight=frameSize.y+2*tileInfo.padding;for(let i=sourcePadding;i--;){var sourceX=i%sourceColumns*sourceCellWidth+sourcePad.x,sourceY=(i/sourceColumns|0)*sourceCellHeight+sourcePad.y;this.context.drawImage(image,sourceX,sourceY,frameSize.x,frameSize.y,tileInfo.pos.x+i%columns*cellWidth,tileInfo.pos.y+(i/columns|0)*cellHeight,frameSize.x,frameSize.y)}this.glDirty=!0,update&&this.updateTexture()}updateTexture(){this.glDirty&&(this.glDirty=!1,this.textureInfo.createWebGLTexture())}}function loadSprite(src,frameSize,padding=textureSheetPadding,sourcePadding=0){isNumber(frameSize)&&(frameSize=vec2(frameSize));const tileInfo=new TileInfo(vec2(),vec2(),void 0,padding,0);if(!headlessMode){tileInfo.textureInfo=(textureSheets[0]||textureSheetCreate()).textureInfo;const image=new Image,imagePromise=new Promise(resolve=>{image.onerror=image.onload=resolve,image.crossOrigin="anonymous",image.src=src});textureSheetQueueJob("loadSprite "+src,async()=>{var added;await imagePromise,image.width&&(added=textureSheetAdd(vec2(image.width,image.height),frameSize,padding,sourcePadding))&&(Object.assign(tileInfo,added.tile),added.sheet.drawImage(image,tileInfo,!1,sourcePadding))})}return tileInfo}function loadAtlas(imageSrc,jsonSrc,padding=textureSheetPadding){const atlas={};if(!headlessMode){const jsonPromise="object"==typeof jsonSrc?Promise.resolve(jsonSrc):fetch(jsonSrc).then(r=>r.ok&&r.json()).catch(()=>{}),image=new Image,imagePromise=new Promise(resolve=>{image.onerror=image.onload=resolve,image.crossOrigin="anonymous",image.src=imageSrc});textureSheetQueueJob("loadAtlas "+imageSrc,async()=>{var data=await jsonPromise;if(await imagePromise,image.width&&data)for(const group of parseAtlas(data)){var sourceSize=group.frames[0].sourceSize,added=textureSheetAdd(vec2(sourceSize.x*group.frames.length,sourceSize.y),sourceSize,padding);if(added){const{sheet,tile}=added,context=sheet.context,cellWidth=sourceSize.x+2*padding,cellHeight=sourceSize.y+2*padding;group.frames.forEach((f,i)=>{var x=tile.pos.x+i%tile.columns*cellWidth+f.offset.x,i=tile.pos.y+(i/tile.columns|0)*cellHeight+f.offset.y;f.rotated?(context.save(),context.translate(x,i),context.rotate(-PI/2),context.drawImage(image,f.pos.x,f.pos.y,f.size.y,f.size.x,-f.size.y,0,f.size.y,f.size.x),context.restore()):context.drawImage(image,f.pos.x,f.pos.y,f.size.x,f.size.y,x,i,f.size.x,f.size.y)}),sheet.glDirty=!0,atlas[group.name]=tile}}})}return atlas}function textureSheetQueueJob(name,job){++textureSheetPendingCount,textureSheetQueue=textureSheetQueue.then(async()=>{try{await job()}catch(e){console.error(name+" failed:",e)}finally{if(!--textureSheetPendingCount)for(const sheet of textureSheets)try{sheet.updateTexture()}catch(e){console.error("texture sheet upload failed:",e)}}})}function parseAtlas(data){var frames=(isArray(data.frames)?data.frames.map(f=>[f.filename,f]):Object.entries(data.frames)).map(([name,f])=>({name:name.replace(/\.[^.\\/]+$/,""),pos:vec2(f.frame.x,f.frame.y),size:vec2(f.frame.w,f.frame.h),offset:vec2(f.spriteSourceSize?.x??0,f.spriteSourceSize?.y??0),sourceSize:vec2(f.sourceSize?.w??f.frame.w,f.sourceSize?.h??f.frame.h),rotated:!!f.rotated}));const groups=[];data=data.meta?.frameTags;if(data?.length){const tagged=new Set;for(const tag of data){groups.push({name:tag.name,frames:frames.slice(tag.from,tag.to+1)});for(let i=tag.from;i<=tag.to;++i)tagged.add(i)}frames.forEach((f,i)=>tagged.has(i)||groups.push({name:f.name,frames:[f]}))}else{var stems=new Map;for(const f of frames){let match=f.name.match(/^(.+?)([-_ /])?(\d+)$/);var stem=(match=match&&!match[2]&&/\d$/.test(match[1])?void 0:match)?match[1]:f.name;f.groupIndex=match?Number(match[3]):void 0,stems.has(stem)||stems.set(stem,[]),stems.get(stem).push(f)}for(const[stem,list]of stems)list.sort((a,b)=>a.groupIndex-b.groupIndex),1<list.length&&list.every((f,i)=>f.groupIndex===list[0].groupIndex+i)&&list.every(f=>f.sourceSize.x===list[0].sourceSize.x&&f.sourceSize.y===list[0].sourceSize.y)?groups.push({name:stem,frames:list}):list.forEach(f=>groups.push({name:f.name,frames:[f]}))}return groups}async function spritesReady(){for(;textureSheetPendingCount;)await textureSheetQueue}function textureSheetCreate(){var sheet=new TextureSheet;return textureSheets.push(sheet),sheet}function textureSheetAdd(imageSize,frameSize,padding,sourcePadding){let sheet,tile;for(sheet of textureSheets)if(tile=sheet.tryAdd(imageSize,frameSize,padding,sourcePadding))break;if(!tile){var emptySheet={size:textureSheetSize,cursor:vec2(),rowHeight:0};if(!TextureSheet.prototype.tryAdd.call(emptySheet,imageSize,frameSize,padding,sourcePadding))return;sheet=textureSheetCreate(),tile=sheet.tryAdd(imageSize,frameSize,padding,sourcePadding)}return{sheet:sheet,tile:tile}}function setTextureSheetSize(size){textureSheetSize=size}function setTextureSheetPadding(padding){textureSheetPadding=padding}const tweenActive=[],tweenUpdateList=[];let tweenUpdatePass=0;function tweenActivate(tween){tween.activePass=tweenUpdatePass,tween.active||(tween.active=!0,tweenActive.push(tween))}function tweenDeactivate(tween){tween.active&&(tween.active=!1,tweenActive.splice(tweenActive.indexOf(tween),1))}function tweenIsLerpable(v){return v&&"function"==typeof v.lerp}class Tween{constructor(callback,start=0,end=1,duration=1,options={}){tweenIsLerpable(start),this.callback=callback,this.start=start,this.end=end,this.duration=duration,this.life=duration,this.ease=options.ease||Ease.LINEAR,this.useRealTime=!!options.useRealTime,this.paused=!!options.paused,this.onComplete=void 0,this.thenCallback=void 0,this.loopRemaining=0,this.active=!1,this.activePass=0,this.lastTime=time,this.lastTimeReal=timeReal,this.target=void 0,tweenActivate(this),callback(this.interp(duration))}setEase(easeFn){return this.ease=easeFn,this}then(callback){return this.onComplete=callback,this}loop(count=1/0){return this.loopRemaining=count,this.thenCallback=()=>function tweenLoopContinuation(tween){const passed=tweenPassed(tween);if(tween.loopRemaining<=passed)return!1;return tweenNextIteration(tween,passed,()=>tweenLoopContinuation(tween))}(this),this}pingPong(count=1/0){return this.loopRemaining=count,this.thenCallback=()=>function tweenPingPongContinuation(tween){const passed=tweenPassed(tween);const done=tween.loopRemaining<=passed;const swaps=done?max(tween.loopRemaining-1,0):passed;if(1&swaps){const tmp=tween.start;tween.start=tween.end,tween.end=tmp}if(done)return 1&swaps&&tween.callback(tween.interp(0)),!1;return tweenNextIteration(tween,passed,()=>tweenPingPongContinuation(tween))}(this),this}pause(){this.paused=!0}resume(){this.paused=!1}restart(){this.life=this.duration,this.paused=!1,this.lastTime=time,this.lastTimeReal=timeReal,tweenActivate(this),this.callback(this.interp(this.duration))}isActive(){return!this.paused&&this.active}getPercent(){return percent(this.duration-this.life,0,this.duration)}getValue(){return this.interp(this.life)}interp(life){var y,s=this.start,e=this.end;return life<=0?"function"==typeof e.copy?e.copy():e:(y=1-(life=this.ease((this.duration-life)/this.duration)),s instanceof Vector2?vec2(e.x*life+s.x*y,e.y*life+s.y*y):void 0!==Vector3&&s instanceof Vector3?vec3(e.x*life+s.x*y,e.y*life+s.y*y,e.z*life+s.z*y):tweenIsLerpable(s)?s.lerp(e,life):s+(e-s)*life)}stop(){tweenDeactivate(this),this.thenCallback=void 0}}const Ease={LINEAR:x=>x,POWER:n=>x=>x**n,SINE:x=>1-cos(x*(PI/2)),CIRC:x=>1-(1-x*x)**.5,EXPO:x=>0===x?0:2**(10*x-10),BACK:x=>x*x*(2.70158*x-1.70158),ELASTIC:x=>0===x?0:1===x?1:-(2**(10*x-10))*sin((37-40*x)*PI/6),SPRING:x=>1-(sin(PI*(1-x)*(.2+2.5*(1-x)**3))*x**2.2+(1-x))*(1+1.2*x),BOUNCE:x=>{let t=1-x,f;return 1-(f=t<4/11?7.5625*t*t:t<8/11?7.5625*(t-=6/11)*t+.75:t<10/11?7.5625*(t-=9/11)*t+.9375:7.5625*(t-=10.5/11)*t+.984375)},IN:f=>f,OUT:f=>x=>1-f(1-x),IN_OUT:f=>Ease.PIECEWISE(f,Ease.OUT(f)),PIECEWISE:(...fns)=>{const n=fns.length;return x=>{var i=x*n-1e-9>>0;return(fns[i]((x-i/n)*n)+i)/n}},BEZIER:(x1,y1,x2,y2)=>{const curve=t=>{var u=1-t,c1=3*u*u*t,u=3*u*t*t,t=t**3;return[c1*x1+u*x2+t,c1*y1+u*y2+t]};return x=>{if(x<=0)return 0;if(1<=x)return 1;let t0=0,t1=1;for(let i=32;i--;){var tMid=(t0+t1)/2;curve(tMid)[0]<x?t0=tMid:t1=tMid}return curve((t0+t1)/2)[1]}}};function tweenProperty(target,propertyPath,start,end,duration=1,options={}){const parts=propertyPath.split("."),lastKey=parts.pop();let tween;return(tween=new Tween(value=>{if(target.destroyed)tween?.stop();else{let obj=target;for(const k of parts)obj=obj[k];obj[lastKey]=value}},start,end,duration,options)).target=target,tween}function tweenPassed(tween){var duration=tween.duration;return duration?1+floor(-min(tween.life,0)/duration):1}function tweenNextIteration(tween,passed,continuation){return tween.loopRemaining-=passed,function(tween){var duration=tween.duration;tween.life=duration?min(tween.life,0)%duration+duration:1e-9}(tween),tween.thenCallback=continuation,tweenActivate(tween),tween.callback(tween.interp(tween.life)),!0}function tweenUpdate(gameDelta,realDelta){var enginePath=void 0===gameDelta,list=(enginePath||void 0!==realDelta||(realDelta=gameDelta),tweenUpdateList.length?[]:tweenUpdateList),pass=++tweenUpdatePass;for(const t of tweenActive)list.push(t);for(let i=list.length;i--;){var next,t=list[i];if(t.active&&t.activePass!==pass){let dt;enginePath?(dt=t.useRealTime?timeReal-t.lastTimeReal:time-t.lastTime,t.lastTime=time,t.lastTimeReal=timeReal):dt=t.useRealTime?realDelta:gameDelta,t.target?.destroyed?t.stop():t.paused||dt<=0||(t.life-=dt,1e-9<t.life?t.callback(t.interp(t.life)):(t.callback(t.interp(0)),t.active&&t.activePass!==pass&&(tweenDeactivate(t),next=t.thenCallback,t.thenCallback=void 0,next&&next()||!t.onComplete||t.onComplete())))}}list.length=0}function tweenStopAll(){for(const t of tweenActive)t.thenCallback=void 0,t.active=!1;tweenActive.length=0}engineAddPlugin(tweenUpdate);const PATHFINDER_DIAGONAL_COST=Math.SQRT2,PATHFINDER_TILE_VEC=vec2(1);class PathFinderNode{constructor(x,y){this.pos=vec2(x,y),this.posWorld=vec2(),this.walkable=!1,this.cost=0,this.g=0,this.f=0,this.h=0,this.parent=null,this.isOpen=!1,this.isClosed=!1,this.heapIndex=0}resetSearch(){this.g=this.f=this.h=0,this.parent=null,this.isOpen=this.isClosed=!1}reset(){this.walkable=!1,this.cost=0,this.resetSearch()}isClear(){return this.walkable&&0===this.cost}}class PathFinder{constructor(source){isVector2(source)?(this.size=source.floor(),this.tileLayer=void 0):(this.size=(source=source).size,this.tileLayer=source),this.heuristicWeight=1,this.maxLoop=void 0,this.searchGaveUp=!1,this.smoothPath=!0,this.debug=!1,this.debugTime=1,this.nodes=new Array(this.size.x*this.size.y);for(let y=0;y<this.size.y;++y)for(let x=0;x<this.size.x;++x)this.nodes[x+y*this.size.x]=new PathFinderNode(x,y);this.nodeDataBuilt=!1,this.collisionScratch=vec2(),this.searchNodes=[]}isWalkable(x,y){return!(this.tileLayer&&0<this.tileLayer.getCollisionData(this.collisionScratch.set(x,y)))}getCost(x,y){return 0}getNode(x,y){return x<0||y<0||x>=this.size.x||y>=this.size.y?null:this.nodes[x+y*this.size.x]}worldToTile(worldPos){var ox=this.tileLayer?this.tileLayer.pos.x:0,oy=this.tileLayer?this.tileLayer.pos.y:0;return vec2(floor(worldPos.x-ox),floor(worldPos.y-oy))}tileToWorld(x,y){return vec2(x+.5+(this.tileLayer?this.tileLayer.pos.x:0),y+.5+(this.tileLayer?this.tileLayer.pos.y:0))}buildNodeData(){this.nodeDataBuilt=!0;var w=this.size.x,h=this.size.y,ox=this.tileLayer?this.tileLayer.pos.x:0,oy=this.tileLayer?this.tileLayer.pos.y:0;for(let y=0;y<h;++y)for(let x=0;x<w;++x){var node=this.nodes[x+y*w],walkable=(node.reset(),!!this.isWalkable(x,y)),cost=walkable?max(0,this.getCost(x,y)):0;node.walkable=walkable,node.cost=cost,node.posWorld.set(x+.5+ox,y+.5+oy),this.debug&&0<this.debugTime&&(walkable?0<cost&&(node.posWorld,PATHFINDER_TILE_VEC,rgb(1,0,0,min(.2,.05*cost)),this.debugTime):(node.posWorld,PATHFINDER_TILE_VEC,rgb(1,0,0,.25),this.debugTime))}}aStarSearch(startNode,endNode){var searchNodes=this.searchNodes;for(const n of searchNodes)n.resetSearch();searchNodes.length=0,searchNodes.push(startNode);const openList=[],isBetter=(a,b)=>a.f<b.f-1e-9||a.f<b.f+1e-9&&a.h<b.h;var siftUp=node=>{let i=node.heapIndex;for(;i;){var parent=i-1>>1;if(!isBetter(node,openList[parent]))break;(openList[i]=openList[parent]).heapIndex=i,i=parent}(openList[i]=node).heapIndex=i},maxLoop=(startNode.isOpen=!0,startNode.heapIndex=openList.length,siftUp(startNode),this.maxLoop??this.size.x*this.size.y);let loopCount=0;for(this.searchGaveUp=!1;0<openList.length;){var current=openList[0];if(current===endNode)break;if(++loopCount>maxLoop){this.searchGaveUp=!endNode.parent;break}right=left=last=best=void 0;var best=openList[0],last=openList.pop();if(last!==best){let i=0;for(;;){var left=2*i+1,right=1+left;if(left>=openList.length)break;right=right<openList.length&&isBetter(openList[right],openList[left])?right:left;if(!isBetter(openList[right],last))break;(openList[i]=openList[right]).heapIndex=i,i=right}(openList[i]=last).heapIndex=i}current.isOpen=!1,current.isClosed=!0,this.debug&&0<this.debugTime&&(current.posWorld,PATHFINDER_TILE_VEC,rgb(1,1,1,.05),this.debugTime);for(let dy=-1;dy<=1;++dy)for(let dx=-1;dx<=1;++dx)if(0!==dx||0!==dy){var neighbor=this.getNode(current.pos.x+dx,current.pos.y+dy);if(neighbor&&neighbor.walkable&&!neighbor.isClosed){let stepCost=1;if(0!==dx&&0!==dy){var card1=this.getNode(current.pos.x+dx,current.pos.y);if(!card1||!card1.walkable)continue;card1=this.getNode(current.pos.x,current.pos.y+dy);if(!card1||!card1.walkable)continue;stepCost=PATHFINDER_DIAGONAL_COST}card1=current.g+stepCost+neighbor.cost;if(neighbor.isOpen){if(card1>=neighbor.g)continue}else neighbor.isOpen=!0,neighbor.heapIndex=openList.length,openList.push(neighbor),searchNodes.push(neighbor);neighbor.parent=current,neighbor.g=card1;var adx=abs(endNode.pos.x-neighbor.pos.x),ady=abs(endNode.pos.y-neighbor.pos.y),adx=max(adx,ady)+(Math.SQRT2-1)*min(adx,ady);neighbor.h=adx,neighbor.f=neighbor.g+adx*this.heuristicWeight,siftUp(neighbor)}}}return null!==endNode.parent}getNearestClearNode(worldPos,searchRange=10,rebuild=!0){return!rebuild&&this.nodeDataBuilt||this.buildNodeData(),pathFinderNearestNode(this,worldPos,searchRange,node=>node.isClear())}smoothPathCorners(path){if(!(path.length<=2)){let i=1;for(;i<path.length-1;){var prev=path[i-1],node=path[i],next=path[i+1],dx=next.pos.x-prev.pos.x,dy=next.pos.y-prev.pos.y,dx=dx*dx+dy*dy,dy=node.pos.x-prev.pos.x,stepDy=node.pos.y-prev.pos.y,stepDxNext=next.pos.x-node.pos.x,stepDyNext=next.pos.y-node.pos.y;if(1==dx)this.debug&&0<this.debugTime&&(node.posWorld,rgb(.5,0,.5,.5),this.debugTime),path.splice(i,1),i=max(1,i-1);else{if(2==dx){this.debug&&0<this.debugTime&&(node.posWorld,rgb(1,0,0,.5),this.debugTime);let sx,sy;sy=(prev.pos.y===node.pos.y&&next.pos.x===node.pos.x?(sx=prev.pos.x,next):(sx=next.pos.x,prev)).pos.y;var shortcut=this.getNode(sx,sy);if(shortcut&&shortcut.isClear()){path.splice(i,1),i=max(1,i-1);continue}}else if(5==dx){this.debug&&0<this.debugTime&&(node.posWorld,rgb(1,1,0,.5),this.debugTime);var prevPrev=2<=i?path[i-2]:prev;let s1x,s1y,s2x,s2y;s2y=(0==dy||0==stepDxNext?(s1x=next.pos.x,s1y=node.pos.y,s2x=prev.pos.x,node):(s1x=node.pos.x,s1y=next.pos.y,s2x=node.pos.x,prev)).pos.y;var dd1x=s1x-prevPrev.pos.x,dd1y=s1y-prevPrev.pos.y,dd2x=s2x-prevPrev.pos.x,prevPrev=s2y-prevPrev.pos.y,dd1x=dd1x*dd1x+dd1y*dd1y<dd2x*dd2x+prevPrev*prevPrev,dd1y=dd1x?s1x:s2x,dd2x=dd1x?s1y:s2y;const shortcut=this.getNode(dd1y,dd2x);if(shortcut&&shortcut!==node&&shortcut.isClear()){prevPrev=next.pos.x+s2x-s1x,dd1x=next.pos.y+s2y-s1y,dd1y=this.getNode(prevPrev,dd1x);if(dd1y&&dd1y.isClear()){path[i]=shortcut,i=max(1,i-1);continue}}}else if(4==dx||8==dx){if(this.debug&&0<this.debugTime&&(node.posWorld,rgb(0,1,0,.5),this.debugTime),dy==stepDxNext&&stepDy==stepDyNext){++i;continue}{let sx,sy;sy=(prev.pos.y===next.pos.y?(sx=node.pos.x,prev):(sx=prev.pos.x,node)).pos.y;const shortcut=this.getNode(sx,sy);if(shortcut&&shortcut.isClear()){path[i]=shortcut,i=max(1,i-1);continue}}}++i}}}}smoothPathStringPull(path){if(!(path.length<=2)){var original=path.slice();path.length=0,path.push(original[0]);for(let k=0;k<original.length-1;){let j=original.length-1;if(original[k].isClear())for(;j>k+1&&(!original[j].isClear()||!this.isLineClear(original[k].pos,original[j].pos));)--j;else j=k+1;path.push(original[j]),k=j}}}dropCollinearNodes(path){for(let i=path.length-2;1<=i;--i){var a=path[i-1],b=path[i],c=path[i+1];(b.pos.x-a.pos.x)*(c.pos.y-a.pos.y)==(b.pos.y-a.pos.y)*(c.pos.x-a.pos.x)&&path.splice(i,1)}}isNodeClear(x,y){x=this.getNode(x,y);return null!==x&&x.isClear()}isLineClear(startPos,endPos){var dx=endPos.x-startPos.x,dy=endPos.y-startPos.y,adx=abs(dx),ady=abs(dy),sx=sign(dx),sy=sign(dy);let x=startPos.x,y=startPos.y;if(ady===adx){for(;x!==endPos.x;){if(x!==startPos.x){if(!this.isNodeClear(x,y))return!1;if(!this.isNodeClear(x,y-sy))return!1}if(!this.isNodeClear(x,y+sy))return!1;x+=sx,y+=sy}if(!this.isNodeClear(endPos.x,endPos.y-sy))return!1}else if(ady<adx)if(0==dy)for(x+=sx;x!==endPos.x;){if(!this.isNodeClear(x,y))return!1;x+=sx}else{let lastY=startPos.y;for(;x!==endPos.x;){if(y=startPos.y+Math.trunc(dy*(x-startPos.x)/dx),lastY!==y){if(!this.isNodeClear(x-sx,y+sy))return!1;if(!this.isNodeClear(x,y-sy))return!1}if(lastY=y,x!==startPos.x&&!this.isNodeClear(x,y))return!1;if(y+=sy,!this.isNodeClear(x,y))return!1;x+=sx}ady=endPos.y-sy;if(!this.isNodeClear(endPos.x,ady))return!1}else if(0==dx)for(y+=sy;y!==endPos.y;){if(!this.isNodeClear(x,y))return!1;y+=sy}else{let lastX=startPos.x;for(;y!==endPos.y;){if(x=startPos.x+Math.trunc(dx*(y-startPos.y)/dy),lastX!==x){if(!this.isNodeClear(x+sx,y-sy))return!1;if(!this.isNodeClear(x-sx,y))return!1}if(lastX=x,y!==startPos.y&&!this.isNodeClear(x,y))return!1;if(x+=sx,!this.isNodeClear(x,y))return!1;y+=sy}adx=endPos.x-sx;if(!this.isNodeClear(adx,endPos.y))return!1}return!0}findPath(startPos,endPos,rebuild=!0){this.searchGaveUp=!1,!rebuild&&this.nodeDataBuilt||this.buildNodeData();rebuild=node=>node.walkable,startPos=pathFinderNearestNode(this,startPos,10,rebuild),endPos=pathFinderNearestNode(this,endPos,10,rebuild);if(!startPos||!endPos)return[];if(startPos===endPos)return[startPos.posWorld.copy()];if(!this.aStarSearch(startPos,endPos))return[];var nodePath=[];for(let n=endPos;n;n=n.parent)nodePath.push(n);nodePath.reverse(),this.smoothPath&&(this.smoothPathCorners(nodePath),this.smoothPathStringPull(nodePath),this.dropCollinearNodes(nodePath));var result=nodePath.map(n=>n.posWorld.copy());if(this.debug&&0<this.debugTime&&0<result.length){for(let i=1;i<result.length;++i)result[i-1],result[i],RED,this.debugTime;for(const p of result)p,rgb(1,0,0,.3),this.debugTime;result[0],rgb(0,1,0,.5),this.debugTime,result[result.length-1],rgb(0,1,0,.5),this.debugTime}return result}}function pathFinderNearestNode(finder,worldPos,searchRange,test){var center=finder.worldToTile(worldPos),centerX=center.x,centerY=center.y;let nearest=null,nearestDistSq=0;for(let offset=0;offset<=searchRange;++offset){var node,ddx,bound=max(0,offset-.5);if(nearest&&bound*bound>=nearestDistSq)break;for(let dy=-offset;dy<=offset;++dy)for(let dx=-offset;dx<=offset;++dx)0<offset&&abs(dx)!==offset&&abs(dy)!==offset||(node=finder.getNode(centerX+dx,centerY+dy))&&test(node)&&(ddx=(ddx=node.posWorld.x-worldPos.x)*ddx+(ddx=node.posWorld.y-worldPos.y)*ddx,!nearest||ddx<nearestDistSq)&&(nearest=node,nearestDistSq=ddx)}return nearest}function vec3(x=0,y,z){return void 0===y?new Vector3(x,x,x):new Vector3(x,y,void 0===z?0:z)}function isVector3(v){return v instanceof Vector3&&v.isValid()}function randVector3(length=1,coneAngle=PI){var coneAngle=rand(cos(coneAngle),1),s=(1-coneAngle*coneAngle)**.5,a=rand(2*PI);return new Vector3(s*cos(a)*length,coneAngle*length,s*sin(a)*length)}function randInSphere(radius=1,minRadius=0){return radius<=0?new Vector3:randVector3(radius*rand(clamp(minRadius/radius)**3,1)**(1/3))}class Vector3{constructor(x=0,y=0,z=0){this.x=x,this.y=y,this.z=z}set(x=0,y=0,z=0){return this.x=x,this.y=y,this.z=z,this}setFrom(v){return this.set(v.x,v.y,v.z)}copy(){return new Vector3(this.x,this.y,this.z)}add(v){return new Vector3(this.x+v.x,this.y+v.y,this.z+v.z)}subtract(v){return new Vector3(this.x-v.x,this.y-v.y,this.z-v.z)}multiply(v){return new Vector3(this.x*v.x,this.y*v.y,this.z*v.z)}divide(v){return new Vector3(this.x/v.x,this.y/v.y,this.z/v.z)}scale(s){return new Vector3(this.x*s,this.y*s,this.z*s)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2+this.z**2}reflect(normal,restitution=1){return this.subtract(normal.scale((1+restitution)*this.dot(normal)))}distance(v){return this.distanceSquared(v)**.5}distanceSquared(v){return(this.x-v.x)**2+(this.y-v.y)**2+(this.z-v.z)**2}normalize(length=1){var l=this.length();return l?this.scale(length/l):new Vector3}clampLength(length=1){var l=this.length();return length<l?this.scale(length/l):this.copy()}dot(v){return this.x*v.x+this.y*v.y+this.z*v.z}cross(v){return new Vector3(this.y*v.z-this.z*v.y,this.z*v.x-this.x*v.z,this.x*v.y-this.y*v.x)}lerp(v,percent){return this.add(v.subtract(this).scale(clamp(percent)))}rotate(axis,angle){var c=cos(angle),angle=sin(angle),d=axis.dot(this)*(1-c);return this.scale(c).add(axis.cross(this).scale(angle)).add(axis.scale(d))}rotateX(angle){var c=cos(angle),angle=sin(angle);return new Vector3(this.x,this.y*c-this.z*angle,this.y*angle+this.z*c)}rotateY(angle){var c=cos(angle),angle=sin(angle);return new Vector3(this.x*c+this.z*angle,this.y,this.z*c-this.x*angle)}rotateZ(angle){var c=cos(angle),angle=sin(angle);return new Vector3(this.x*c-this.y*angle,this.x*angle+this.y*c,this.z)}abs(){return new Vector3(abs(this.x),abs(this.y),abs(this.z))}floor(){return new Vector3(floor(this.x),floor(this.y),floor(this.z))}round(){return new Vector3(round(this.x),round(this.y),round(this.z))}snap(grid){return new Vector3(floor(this.x*grid)/grid,floor(this.y*grid)/grid,floor(this.z*grid)/grid)}transform(matrix){return matrix.transformPoint(this)}transformDirection(matrix){return matrix.transformDirection(this)}isValid(){return isNumber(this.x)&&isNumber(this.y)&&isNumber(this.z)}toString(digits=3){var f;return this.isValid()?`(${(f=v=>(v<0?"":" ")+v.toFixed(digits))(this.x)},${f(this.y)},${f(this.z)} )`:`(${this.x},${this.y},${this.z})`}}const matrix4Scratch=new Float32Array(16),matrix4Identity=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);class Matrix4{constructor(m){this.m=new Float32Array(16),m?this.m.set(m):this.m[0]=this.m[5]=this.m[10]=this.m[15]=1}static identity(){return new Matrix4}static translation(v){var r=new Matrix4;return r.m[12]=v.x,r.m[13]=v.y,r.m[14]=v.z,r}static rotation(euler,matrix=new Matrix4){var cx=cos(euler.x),sx=sin(euler.x),cy=cos(euler.y),sy=sin(euler.y),cz=cos(euler.z),euler=sin(euler.z),m=matrix.m;return m[0]=cy*cz+sy*sx*euler,m[1]=cx*euler,m[2]=-sy*cz+cy*sx*euler,m[3]=0,m[4]=-cy*euler+sy*sx*cz,m[5]=cx*cz,m[6]=sy*euler+cy*sx*cz,m[7]=0,m[8]=sy*cx,m[9]=-sx,m[10]=cy*cx,m[11]=0,m[12]=m[13]=m[14]=0,m[15]=1,matrix}static scaling(v){var r=new Matrix4;return r.m[0]=v.x,r.m[5]=v.y,r.m[10]=v.z,r}static perspective(fov,aspect,near,far){var fov=1/tan(fov/2),r=new Matrix4,m=r.m;return m[0]=fov/aspect,m[5]=fov,m[10]=far==1/0?-1:(far+near)/(near-far),m[11]=-1,m[14]=far==1/0?-2*near:2*far*near/(near-far),m[15]=0,r}static orthographic(left,right,bottom,top,near,far){var r=new Matrix4,m=r.m;return m[0]=2/(right-left),m[5]=2/(top-bottom),m[10]=-2/(far-near),m[12]=-(right+left)/(right-left),m[13]=-(top+bottom)/(top-bottom),m[14]=-(far+near)/(far-near),r}static lookAt(eye,target,up=vec3(0,1,0)){let z=eye.subtract(target).normalize(),x=(z.lengthSquared()||(z=vec3(0,0,1)),up.cross(z).normalize());x.lengthSquared()||(x=(.99<abs(z.y)?vec3(0,0,0<z.y?-1:1):vec3(0,1,0)).cross(z).normalize());target=z.cross(x);return new Matrix4([x.x,x.y,x.z,0,target.x,target.y,target.z,0,z.x,z.y,z.z,0,eye.x,eye.y,eye.z,1])}copy(){return new Matrix4(this.m)}multiply(matrix){var a=this.m,b=matrix.m,r=matrix4Scratch;for(let j=0;j<4;++j)for(let i=0;i<4;++i)r[4*j+i]=a[i]*b[4*j]+a[4+i]*b[4*j+1]+a[8+i]*b[4*j+2]+a[12+i]*b[4*j+3];return this.m.set(r),this}translate(v){return this.multiply(Matrix4.translation(v))}rotate(euler){return this.multiply(Matrix4.rotation(euler))}scale(v){return this.multiply(Matrix4.scaling(v))}transpose(){var m=this.m;for(let i=0;i<4;++i)for(let j=i+1;j<4;++j){var t=m[4*i+j];m[4*i+j]=m[4*j+i],m[4*j+i]=t}return this}invert(){var m=this.m,[a00,a01,a02,a03,a10,a11,a12,a13,a20,a21,a22,a23,a30,a31,a32,a33]=m,b00=a00*a11-a01*a10,b01=a00*a12-a02*a10,b02=a00*a13-a03*a10,b03=a01*a12-a02*a11,b04=a01*a13-a03*a11,b05=a02*a13-a03*a12,b06=a20*a31-a21*a30,b07=a20*a32-a22*a30,b08=a20*a33-a23*a30,b09=a21*a32-a22*a31,b10=a21*a33-a23*a31,b11=a22*a33-a23*a32,det=b00*b11-b01*b10+b02*b09+b03*b08-b04*b07+b05*b06;return det&&(m[0]=(a11*b11-a12*b10+a13*b09)*(det=1/det),m[1]=(a02*b10-a01*b11-a03*b09)*det,m[2]=(a31*b05-a32*b04+a33*b03)*det,m[3]=(a22*b04-a21*b05-a23*b03)*det,m[4]=(a12*b08-a10*b11-a13*b07)*det,m[5]=(a00*b11-a02*b08+a03*b07)*det,m[6]=(a32*b02-a30*b05-a33*b01)*det,m[7]=(a20*b05-a22*b02+a23*b01)*det,m[8]=(a10*b10-a11*b08+a13*b06)*det,m[9]=(a01*b08-a00*b10-a03*b06)*det,m[10]=(a30*b04-a31*b02+a33*b00)*det,m[11]=(a21*b02-a20*b04-a23*b00)*det,m[12]=(a11*b07-a10*b09-a12*b06)*det,m[13]=(a00*b09-a01*b07+a02*b06)*det,m[14]=(a31*b01-a30*b03-a32*b00)*det,m[15]=(a20*b03-a21*b01+a22*b00)*det),this}transformPoint(v){var m=this.m;return new Vector3(m[0]*v.x+m[4]*v.y+m[8]*v.z+m[12],m[1]*v.x+m[5]*v.y+m[9]*v.z+m[13],m[2]*v.x+m[6]*v.y+m[10]*v.z+m[14])}transformDirection(v){var m=this.m;return new Vector3(m[0]*v.x+m[4]*v.y+m[8]*v.z,m[1]*v.x+m[5]*v.y+m[9]*v.z,m[2]*v.x+m[6]*v.y+m[10]*v.z)}getTranslation(){return new Vector3(this.m[12],this.m[13],this.m[14])}determinant(){var m=this.m;return m[0]*(m[5]*m[10]-m[6]*m[9])-m[4]*(m[1]*m[10]-m[2]*m[9])+m[8]*(m[1]*m[6]-m[2]*m[5])}getScale(){var m=this.m,x=hypot(m[0],m[1],m[2]),y=hypot(m[4],m[5],m[6]),m=hypot(m[8],m[9],m[10]);return new Vector3(this.determinant()<0?-x:x,y,m)}getRotation(){var m=this.m,s=this.getScale(),sx=s.x||1,sy=s.y||1,s=s.z||1,m1=m[1]/sx,m5=m[5]/sy,m8=m[8]/s,m9=m[9]/s,s=m[10]/s,pitch=Math.asin(clamp(-m9,-1,1));return abs(m9)<1-1e-6?new Vector3(pitch,atan2(m8,s),atan2(m1,m5)):new Vector3(pitch,atan2(m[4]/sy*-m9,m[0]/sx),0)}toString(){const m=this.m,f=i=>m[i].toFixed(2).padStart(7);let s="";for(let row=0;row<4;++row)s+=`[${f(row)} ${f(4+row)} ${f(8+row)} ${f(12+row)} ]
|
|
17
|
+
`;return s}}function buildMatrix(pos,rotation,scale,matrix=new Matrix4){var turned=rotation&&(rotation.x||rotation.y||rotation.z),m=matrix.m;return turned?Matrix4.rotation(rotation,matrix):m.set(matrix4Identity),scale&&(m[0]*=scale.x,m[1]*=scale.x,m[2]*=scale.x,m[4]*=scale.y,m[5]*=scale.y,m[6]*=scale.y,m[8]*=scale.z,m[9]*=scale.z,m[10]*=scale.z),pos&&(m[12]=pos.x,m[13]=pos.y,m[14]=pos.z),matrix}class Ray3D{constructor(origin=vec3(),direction=vec3(0,0,-1)){this.origin=origin,this.direction=direction}getPosition(distance){return this.origin.add(this.direction.scale(distance))}copy(){return new Ray3D(this.origin.copy(),this.direction.copy())}}function isPointInBox3D(point,pos,size){return abs(point.x-pos.x)<=size.x/2&&abs(point.y-pos.y)<=size.y/2&&abs(point.z-pos.z)<=size.z/2}function isOverlapping3D(posA,sizeA,posB,sizeB=vec3()){posA=posA.subtract(posB);return abs(posA.x)<(sizeA.x+sizeB.x)/2&&abs(posA.y)<(sizeA.y+sizeB.y)/2&&abs(posA.z)<(sizeA.z+sizeB.z)/2}function collideSphereSphere(posA,radiusA,posB,radiusB){posA=posA.subtract(posB),posB=radiusA+radiusB,radiusA=posA.length();if(!(posB<=radiusA))return radiusA?posA.normalize(posB-radiusA):vec3(0,posB,0)}function collideSphereBox(pos,radius,boxPos,boxSize){var boxSize=boxSize.scale(.5),closest=vec3(clamp(pos.x,boxPos.x-boxSize.x,boxPos.x+boxSize.x),clamp(pos.y,boxPos.y-boxSize.y,boxPos.y+boxSize.y),clamp(pos.z,boxPos.z-boxSize.z,boxPos.z+boxSize.z)),closest=pos.subtract(closest),distSq=closest.lengthSquared();return distSq?radius*radius<=distSq?void 0:closest.normalize(radius-distSq**.5):pushOutAxis3D(closest=pos.subtract(boxPos),boxSize.x-abs(closest.x),boxSize.y-abs(closest.y),boxSize.z-abs(closest.z),radius)}function collideSphereInBox(pos,radius,boxPos,boxSize){var x=max(0,boxSize.x/2-radius),y=max(0,boxSize.y/2-radius),boxSize=max(0,boxSize.z/2-radius),radius=vec3(clamp(pos.x,boxPos.x-x,boxPos.x+x)-pos.x,clamp(pos.y,boxPos.y-y,boxPos.y+y)-pos.y,clamp(pos.z,boxPos.z-boxSize,boxPos.z+boxSize)-pos.z);return radius.lengthSquared()?radius:void 0}function pushOutAxis3D(d,penX,penY,penZ,extra=0){var s=v=>0<=v?1:-1;return penX<=penY&&penX<=penZ?vec3(s(d.x)*(penX+extra),0,0):penY<=penZ?vec3(0,s(d.y)*(penY+extra),0):vec3(0,0,s(d.z)*(penZ+extra))}function collideSphereCylinder(pos,radius,cylinderPos,cylinderRadius,cylinderHeight){var cylinderHeight=cylinderHeight/2,offsetX=pos.x-cylinderPos.x,offsetZ=pos.z-cylinderPos.z,offsetY=pos.y-cylinderPos.y,radialDist=(offsetX**2+offsetZ**2)**.5,radialScale=radialDist?min(radialDist,cylinderRadius)/radialDist:0,cylinderPos=vec3(cylinderPos.x+offsetX*radialScale,clamp(pos.y,cylinderPos.y-cylinderHeight,cylinderPos.y+cylinderHeight),cylinderPos.z+offsetZ*radialScale),radialScale=pos.subtract(cylinderPos),pos=radialScale.lengthSquared();return pos?radius*radius<=pos?void 0:radialScale.normalize(radius-pos**.5):(cylinderPos=cylinderRadius-radialDist)<=(radialScale=cylinderHeight-abs(offsetY))?(radialDist?vec3(offsetX/radialDist,0,offsetZ/radialDist):vec3(1,0,0)).scale(cylinderPos+radius):vec3(0,(0<=offsetY?1:-1)*(radialScale+radius),0)}function collideBoxBox3D(posA,sizeA,posB,sizeB){var posA=posA.subtract(posB),posB=(sizeA.x+sizeB.x)/2-abs(posA.x),overlapY=(sizeA.y+sizeB.y)/2-abs(posA.y),sizeA=(sizeA.z+sizeB.z)/2-abs(posA.z);if(!(posB<=0||overlapY<=0||sizeA<=0))return pushOutAxis3D(posA,posB,overlapY,sizeA)}function raycastSphere(ray,pos,radius){var{origin:ray,direction}=ray,ray=ray.subtract(pos),pos=ray.dot(ray)-radius*radius;return pos<0?0:(radius=direction.dot(direction))&&!((direction=(ray=2*ray.dot(direction))*ray-4*radius*pos)<0)&&0<=(pos=(-ray-direction**.5)/(2*radius))?pos:void 0}function raycastPlane(ray,planePos,planeNormal){var{origin:ray,direction}=ray,direction=direction.dot(planeNormal);return abs(direction)<1e-9||(planePos=planePos.subtract(ray).dot(planeNormal)/direction)<0?void 0:planePos}function raycastBox(ray,pos,size){var{origin,direction}=ray,ray=size.scale(.5),boxMin=pos.subtract(ray),boxMax=pos.add(ray);let tMin=0,tMax=1/0;for(const axis of"xyz"){var o=origin[axis],d=direction[axis],mn=boxMin[axis],mx=boxMax[axis];if(d){let t0=(mn-o)/d,t1=(mx-o)/d;if(t0>t1&&([t0,t1]=[t1,t0]),tMin=max(tMin,t0),tMax=min(tMax,t1),tMin>tMax)return}else if(o<mn||mx<o)return}return tMin}let render3D;const RENDER3D_VERTEX_BYTES=36,RENDER3D_INSTANCE_FLOATS=24,RENDER3D_INSTANCE_BYTES=4*RENDER3D_INSTANCE_FLOATS,RENDER3D_INSTANCE_ATTRIBS=[[4,4,0],[5,4,16],[6,4,32],[7,4,48],[11,4,64],[12,4,80]],RENDER3D_VERTEX_INPUTS="layout(location=0) in vec3 p;layout(location=1) in vec3 n;layout(location=2) in vec2 t;layout(location=3) in vec4 c;layout(location=4) in vec4 m0;layout(location=5) in vec4 m1;layout(location=6) in vec4 m2;layout(location=7) in vec4 m3;layout(location=11) in vec4 tint;layout(location=12) in vec4 uvRect;",RENDER3D_MAX_LIGHTS=8,RENDER3D_QUAD_UVS=Object.freeze([vec2(0,0),vec2(0,1),vec2(1,0),vec2(1,1)].map(uv=>Object.freeze(uv))),RENDER3D_FULL_UV_RECT=Object.freeze({x:0,y:0,w:1,h:1}),RENDER3D_DEFAULT_NORMAL=Object.freeze(vec3(0,1,0)),RENDER3D_DEFAULT_UV=Object.freeze(vec2()),RENDER3D_SHADOW_COLOR=Object.freeze(hsl(0,0,0,.5)),RENDER3D_IDENTITY=new Matrix4;let render3DShadowCut;function render3DFaceNormal(a,b,c,d=a){c=c.subtract(a).cross(d.subtract(b));return c.lengthSquared()?c.normalize():RENDER3D_DEFAULT_NORMAL}function render3DQuadStrip(a,b,c,d){return[a,b,d,c]}function render3DQuadValues(v){return isArray(v)?render3DQuadStrip(...v):v}function render3DCanDraw(){return!!render3D.program&&render3D.isRendering}function render3DCaptureBatchState(){var r=render3D;return{blend:r.blend,additive:r.additive,depthTest:r.depthTest,depthWrite:r.depthWrite,cullBackFaces:r.cullBackFaces,mirrored:r.mirrored,lighting:r.lighting,emissive:r.emissive,receiveShadow:r.receiveShadow,specular:r.specular,pixelated:r.pixelated,shader:r.shader}}function render3DApplyBatchState(s){var r=render3D;r.blend=s.blend,r.additive=s.additive,r.depthTest=s.depthTest,r.depthWrite=s.depthWrite,r.cullBackFaces=s.cullBackFaces,r.mirrored=s.mirrored,r.lighting=s.lighting,r.emissive=s.emissive,r.receiveShadow=s.receiveShadow,r.specular=s.specular,r.pixelated=s.pixelated,r.shader=s.shader}function render3DStateChanged(s){var r=render3D;return r.blend!==s.blend||r.additive!==s.additive||r.depthTest!==s.depthTest||r.depthWrite!==s.depthWrite||r.cullBackFaces!==s.cullBackFaces||r.mirrored!==s.mirrored||r.lighting!==s.lighting||r.emissive!==s.emissive||r.receiveShadow!==s.receiveShadow||r.specular!==s.specular||r.pixelated!==s.pixelated||r.shader!==s.shader}function render3DSphereVisible(x,y,z,radius){var planes=render3D.shadowPass?render3D.shadowPlanes:render3D.frustumPlanes;for(let i=0;i<planes.length;++i){var p=planes[i];if(p[0]*x+p[1]*y+p[2]*z+p[3]<-radius)return!1}return!0}function render3DWithState(fields,fn){var r=render3D,saved={};for(const key in fields)saved[key]=r[key],r[key]=fields[key];try{fn()}finally{Object.assign(r,saved)}}function render3DSize3(size){return isNumber(size)?vec3(size):size}function render3DSize2(size){return isNumber(size)?vec2(size):size}function render3DMatrix(matrix){return matrix instanceof Vector3?buildMatrix(matrix):matrix}function render3DNormalMatrix(matrix){return matrix.copy().invert().transpose()}function render3DAxis(m,i){return vec3(m[i],m[i+1],m[i+2])}function render3DMaxScale(m){return max(m[0]*m[0]+m[1]*m[1]+m[2]*m[2],m[4]*m[4]+m[5]*m[5]+m[6]*m[6],m[8]*m[8]+m[9]*m[9]+m[10]*m[10])**.5}function render3DMaxStretch(m,k=0){var xx=m[k]*m[k]+m[k+1]*m[k+1]+m[k+2]*m[k+2],yy=m[k+4]*m[k+4]+m[k+5]*m[k+5]+m[k+6]*m[k+6],zz=m[k+8]*m[k+8]+m[k+9]*m[k+9]+m[k+10]*m[k+10],xy=abs(m[k]*m[k+4]+m[k+1]*m[k+5]+m[k+2]*m[k+6]),xz=abs(m[k]*m[k+8]+m[k+1]*m[k+9]+m[k+2]*m[k+10]),m=abs(m[k+4]*m[k+8]+m[k+5]*m[k+9]+m[k+6]*m[k+10]);return max(xx+xy+xz,yy+xy+m,zz+xz+m)**.5}function render3DQuadAxes(center,right,up){return[center.subtract(right).add(up),center.subtract(right).subtract(up),center.add(right).add(up),center.add(right).subtract(up)]}function render3DSetObjectState(o){var r=render3D,emissive=o?.emissive||0;r.lighting=!0,r.emissive=emissive,r.additive=!!o?.additive,r.specular=o?.specular||0,r.receiveShadow=!o||o.receiveShadow,r.cullBackFaces=r.mirrored=!1,r.pixelated=!!o?.pixelated,r.shader=o?.shader||void 0,r.depthTest=!0,r.shadowPass&&(r.blend=!!o?.transparent)}function render3DDrawObjects(objects){for(const o of objects)render3DSetObjectState(o),o.render3D();render3DSetObjectState()}function render3DInstanceSlot(mesh,textureInfo){var grown,r=render3D,textureInfo=(mesh.instanceCount&&(mesh.instanceTextureInfo!==textureInfo||render3DStateChanged(mesh.instanceState))&&render3DFlushInstances(mesh),mesh.instanceCount||(mesh.instanceTextureInfo=textureInfo,mesh.instanceState=render3DCaptureBatchState(),r.instanceMeshes.push(mesh)),mesh.instanceData),r=mesh.instanceCount++*RENDER3D_INSTANCE_FLOATS;return(!textureInfo||textureInfo.length<r+RENDER3D_INSTANCE_FLOATS)&&(grown=new Float32Array(max(64*RENDER3D_INSTANCE_FLOATS,textureInfo?2*textureInfo.length:0)),textureInfo&&grown.set(textureInfo),mesh.instanceData=grown),r}function render3DFlushInstances(only){var r=render3D,gl=glContext;for(const mesh of only?[only]:r.instanceMeshes){var buffers,count=mesh.instanceCount;mesh.instanceCount=0,count&&mesh.buffer&&(buffers=(buffers=r.instanceBuffers)[r.instanceBufferIndex=(r.instanceBufferIndex+1)%buffers.length],gl.bindBuffer(gl.ARRAY_BUFFER,buffers),gl.bufferData(gl.ARRAY_BUFFER,mesh.instanceData,gl.DYNAMIC_DRAW,0,count*RENDER3D_INSTANCE_FLOATS),render3DDrawInstanced(mesh,buffers,count,mesh.instanceTextureInfo,mesh.instanceState))}only?(only=r.instanceMeshes.indexOf(only))<0||r.instanceMeshes.splice(only,1):r.instanceMeshes.length=0}function render3DDrawInstanced(mesh,buffer,count,textureInfo,state){var location,size,offset,gl=glContext;gl.bindBuffer(gl.ARRAY_BUFFER,buffer);for([location,size,offset]of RENDER3D_INSTANCE_ATTRIBS)gl.vertexAttribPointer(location,size,gl.FLOAT,!1,RENDER3D_INSTANCE_BYTES,offset),gl.enableVertexAttribArray(location),gl.vertexAttribDivisor(location,1);render3DSetDrawUniforms(RENDER3D_IDENTITY,textureInfo,WHITE,RENDER3D_FULL_UV_RECT,state),render3DBindMesh(mesh),gl.drawElementsInstanced(gl.TRIANGLES,mesh.bufferCount,mesh.indexType,0,count);for(const[location]of RENDER3D_INSTANCE_ATTRIBS)gl.disableVertexAttribArray(location),gl.vertexAttribDivisor(location,0);++drawCount,primitiveCount+=mesh.bufferCount/3*count}function render3DClearInstances(){for(const mesh of render3D.instanceMeshes)mesh.instanceCount=0;render3D.instanceMeshes.length=0}function render3DLayerObjects(after2D){return engineObjects.filter(o=>!o.destroyed&&o instanceof EngineObject3D&&function(o){return!!(o.renderAfter2D??render3D.renderAfter2D)}(o)===after2D)}const render3DCircleCache=new Map;function render3DLookRotation(direction,current){direction=direction.normalize();return direction.lengthSquared()?abs(direction.x)+abs(direction.z)<1e-9?vec3(0<direction.y?PI/2:-PI/2,current.y,0):vec3(Math.asin(clamp(direction.y,-1,1)),atan2(-direction.x,-direction.z),0):current}class Render3DPlugin{constructor(){(render3D=this).camera=new Camera3D,this.sunDirection=vec3(-.3,1,.5),this.sunColor=WHITE.copy(),this.ambientColor=hsl(0,0,.3),this.ambientGroundColor=void 0,this.fogColor=void 0,this.fogStart=0,this.fogEnd=0,this.gravity=vec3(),this.softShadowHeight=0,this.smoothShading=!1,this.shadows=!1,this.shadowMapSize=1024,this.shadowRange=40,this.shadowCenter=void 0,this.shadowBias=.003,this.shadowSoftness=1,this.lighting=!0,this.emissive=0,this.additive=!1,this.depthTest=!0,this.depthWrite=!0,this.cullBackFaces=!1,this.mirrored=!1,this.specular=0,this.shader=void 0,this.receiveShadow=!0,this.onRenderOpaque=void 0,this.onRenderTransparent=void 0,this.sky=void 0,this.renderAfter2D=!1,this.sortTransparent=!0,this.frustumCulling=!0,this.instancing=!0,this.mipmaps=!0,this.pixelated=!1,this.anisotropy=4,this.boxMesh=buildBox(),this.sphereMesh=buildSphere(1,16,8,!0),this.planeMesh=buildGrid(),this.planeMesh.doubleSided=!1,this.planeMeshDoubleSided=buildGrid(),this.billboardMesh=(new Mesh).addStrip([vec3(-.5,.5,0),vec3(-.5,-.5,0),vec3(.5,.5,0),vec3(.5,-.5,0)],vec3(0,0,1),RENDER3D_QUAD_UVS),this.billboardMesh.doubleSided=!0,this.isRendering=!1,this.shadowPass=!1,this.viewMatrix=new Matrix4,this.projectionMatrix=new Matrix4,this.viewProjection=new Matrix4,this.shadowMatrix=new Matrix4,this.cameraRight=vec3(1,0,0),this.cameraUp=vec3(0,1,0),this.cameraForward=vec3(0,0,-1),this.cameraBack=vec3(0,0,1),this.blend=!1,this.frustumPlanes=[],this.shadowPlanes=[],this.program=void 0,this.currentProgram=void 0,this.lightCount=0,this.shadowShader=void 0,this.vao=void 0,this.whiteTexture=void 0,this.samplers=new Map,this.samplerKey=void 0,this.shadowTexture=void 0,this.shadowFramebuffer=void 0,this.shadowTextureSize=0,this.contextGeneration=0,this.uniforms=new Map,this.uniformValues={},this.shadowMapDrawn=!1,this.passIsDefault=!0,this.lightPositions=new Float32Array(4*RENDER3D_MAX_LIGHTS),this.lightColors=new Float32Array(4*RENDER3D_MAX_LIGHTS),this.streamBuffer=void 0,this.instanceBuffers=[],this.instanceBufferIndex=0,this.instanceMeshes=[],this.attribValues=[],this.streamData=new ArrayBuffer(32768*RENDER3D_VERTEX_BYTES),this.streamFloats=new Float32Array(this.streamData),this.streamInts=new Uint32Array(this.streamData),this.streamCount=0,this.streamTileInfo=void 0,this.streamState=void 0,this.capture=void 0,this.transparentQueue=void 0,render3DInitGL(),engineAddPlugin(void 0,render3DRender,render3DContextLost,render3DContextRestored,render3DPreRender)}updateMatrices(aspect=mainCanvasSize.y?mainCanvasSize.x/mainCanvasSize.y:1){var camera=this.camera,cameraMatrix=(camera.align2D&&camera.update2D(),camera.getMatrix()),camera=(this.viewMatrix=cameraMatrix.copy().invert(),this.projectionMatrix=camera.getProjectionMatrix(aspect),this.viewProjection=this.projectionMatrix.copy().multiply(this.viewMatrix),cameraMatrix.m);this.cameraRight=render3DAxis(camera,0),this.cameraUp=render3DAxis(camera,4),this.cameraBack=render3DAxis(camera,8),this.cameraForward=this.cameraBack.scale(-1),this.frustumPlanes=render3DFrustumPlanes(this.viewProjection)}worldToClip(pos){var m=this.viewProjection.m,w=m[3]*pos.x+m[7]*pos.y+m[11]*pos.z+m[15],z=(m[2]*pos.x+m[6]*pos.y+m[10]*pos.z+m[14])/w;if(!(w<=0||z<-1))return vec3((m[0]*pos.x+m[4]*pos.y+m[8]*pos.z+m[12])/w,(m[1]*pos.x+m[5]*pos.y+m[9]*pos.z+m[13])/w,z)}worldToScreen(pos,canvasSize=mainCanvasSize){pos=this.worldToClip(pos);if(pos)return vec2((pos.x+1)/2*canvasSize.x,(1-pos.y)/2*canvasSize.y)}screenToRay(screenPos,canvasSize=mainCanvasSize){var width=canvasSize.x||1,canvasSize=canvasSize.y||1,aspect=width/canvasSize,camera=this.camera,width=(this.updateMatrices(aspect),screenPos.x/width*2-1),screenPos=1-screenPos.y/canvasSize*2,canvasSize=camera.orthographic?camera.orthographic/2:tan(camera.fov/2),width=this.cameraRight.scale(width*canvasSize*aspect).add(this.cameraUp.scale(screenPos*canvasSize));return camera.orthographic?new Ray3D(camera.pos.add(width).add(this.cameraForward.scale(camera.near)),this.cameraForward.copy()):new Ray3D(camera.pos.copy(),this.cameraForward.add(width).normalize())}screenToGround(screenPos,groundHeight=0,canvasSize=mainCanvasSize){screenPos=this.screenToRay(screenPos,canvasSize),canvasSize=raycastPlane(screenPos,vec3(0,groundHeight,0),RENDER3D_DEFAULT_NORMAL);return void 0===canvasSize?void 0:screenPos.getPosition(canvasSize)}pick(from,objects=engineObjects){var ray=from instanceof Ray3D?from:this.screenToRay(from);let nearest;for(const o of objects){var distance=render3DRaycastObject(ray,o);void 0!==distance&&(!nearest||distance<nearest.distance)&&(nearest={object:o,distance:distance})}return nearest}playSound(sound,pos3D,volume=1,pitch=1,randomnessScale=1,loop=!1,paused=!1){var pos3D=pos3D.subtract(this.camera.pos),range=sound.range;if(range){var distance=pos3D.length();if(range<distance)return;var taperRange=range*sound.taper;taperRange<distance&&(volume*=percent(distance,range,taperRange))}distance=sound.play(void 0,volume,pitch,randomnessScale,loop,paused);return distance?.setPan(pos3D.normalize().dot(this.cameraRight)),distance}playSoundLoop(sound,pos3D,volume=1,pitch=1,randomnessScale=1){return this.playSound(sound,pos3D,volume,pitch,randomnessScale,!0)}isSphereVisible(center,radius){return render3DSphereVisible(center.x,center.y,center.z,radius)}drawMesh(mesh,matrix=RENDER3D_IDENTITY,tileInfo,color=WHITE){if(matrix=render3DMatrix(matrix),this.capture)this.capture.combine(mesh,matrix,color);else{if(this.transparentQueue){const m=matrix.copy(),c=color.copy();return this.queueTransparent(m.getTranslation(),()=>this.drawMesh(mesh,m,tileInfo,c))}if(render3DCanDraw()&&(!this.shadowPass||this.lighting)&&(render3DMeshUpload(mesh),mesh.bufferCount)){const m=matrix.m;var cullBackFaces,mirrored;this.frustumCulling&&!render3DSphereVisible(m[12],m[13],m[14],mesh.radius*render3DMaxStretch(m))||(cullBackFaces=this.cullBackFaces,mirrored=this.mirrored,this.cullBackFaces=!mesh.doubleSided,this.mirrored=matrix.determinant()<0,!this.blend&&this.depthTest&&(mesh.instanced??this.instancing)?function(mesh,matrix,tileInfo,color){var k=render3DInstanceSlot(mesh,render3DTextureOf(tileInfo)),matrix=((mesh=mesh.instanceData).set(matrix.m,k),mesh[k+16]=color.r,mesh[k+17]=color.g,mesh[k+18]=color.b,mesh[k+19]=color.a,render3DGetTileUVs(tileInfo));mesh[k+20]=matrix.x,mesh[k+21]=matrix.y,mesh[k+22]=matrix.w,mesh[k+23]=matrix.h}(mesh,matrix,tileInfo,color):(this.flush(),render3DFlushBeforeOverlay(),render3DSetDrawUniforms(matrix,tileInfo,color),render3DBindMesh(mesh),glContext.drawElements(glContext.TRIANGLES,mesh.bufferCount,mesh.indexType,0),++drawCount,primitiveCount+=mesh.bufferCount/3),this.cullBackFaces=cullBackFaces,this.mirrored=mirrored)}}}drawStrip(points,normals,uvs,colors,tileInfo){if(this.capture)this.capture.addStrip(points,normals,uvs,colors);else{if(this.transparentQueue){let x=0,y=0,z=0;for(const p of points)x+=p.x,y+=p.y,z+=p.z;return this.queueTransparent(vec3(x,y,z).scale(1/points.length),()=>this.drawStrip(points,normals,uvs,colors,tileInfo))}var n=points.length,count=render3DStripCount(n),uvRect=render3DBeginStrip(count,tileInfo);if(uvRect){var floats=this.streamFloats,ints=this.streamInts,normalArray=isArray(normals),uvArray=isArray(uvs),colorArray=isArray(colors),rgba=colorArray?0:(colors||WHITE).rgbaInt();for(let k=0;k<count;++k){var i=render3DStripIndex(k,n),p=points[i],uv=uvArray?uvs[i]:uvs||RENDER3D_DEFAULT_UV;render3DWriteVertex(floats,ints,9*this.streamCount++,p.x,p.y,p.z,normalArray?normals[i]:normals||RENDER3D_DEFAULT_NORMAL,uvRect.x+uv.x*uvRect.w,uvRect.y+uv.y*uvRect.h,colorArray?colors[i].rgbaInt():rgba)}}}}drawStripUnlit(points,normals,uvs,colors,tileInfo){render3DWithState({lighting:!1},()=>this.drawStrip(points,normals,uvs,colors,tileInfo))}flush(){var gl;this.streamCount&&render3DCanDraw()&&(gl=glContext,render3DSetDrawUniforms(RENDER3D_IDENTITY,this.streamTileInfo,WHITE,RENDER3D_FULL_UV_RECT,this.streamState),render3DBindVertexBuffer(this.streamBuffer),gl.bufferSubData(gl.ARRAY_BUFFER,0,this.streamFloats,0,9*this.streamCount),gl.drawArrays(gl.TRIANGLE_STRIP,0,this.streamCount),++drawCount,primitiveCount+=this.streamCount,this.streamCount=0)}bake(drawFunction){this.flush();var mesh=this.capture=new Mesh;try{drawFunction()}finally{this.capture=void 0}return mesh}renderStages(objects,isDefault=!0){var opaque=[],transparent=[];for(const o of objects)(o.transparent||o.additive?transparent:opaque).push(o);isDefault&&this.sky&&this.drawSky(),this.blend=!1,this.depthWrite=!0;var m,byOrder=(a,b)=>a.renderOrder-b.renderOrder;opaque.sort(byOrder),transparent.sort(byOrder),render3DDrawObjects(opaque),isDefault&&this.onRenderOpaque?.(),this.flush(),render3DFlushInstances(),this.blend=!0,this.depthWrite=!1,this.transparentQueue=this.sortTransparent?[]:void 0;try{render3DDrawObjects(transparent);for(const o of objects)o.softShadow&&(m=render3DObjectMatrix(o),this.drawSoftShadow(m.getTranslation(),o.softShadow*render3DMaxScale(m.m),this.softShadowHeight));isDefault&&this.onRenderTransparent?.()}finally{this.flushTransparentQueue()}this.flush(),render3DSetObjectState(),this.blend=!1,this.depthWrite=!0}queueTransparent(pos,draw){var f,c;this.transparentQueue?(f=this.cameraForward,c=this.camera.pos,pos=(pos.x-c.x)*f.x+(pos.y-c.y)*f.y+(pos.z-c.z)*f.z,this.transparentQueue.push({distance:pos,state:render3DCaptureBatchState(),draw:draw})):draw()}flushTransparentQueue(){var queue=this.transparentQueue;if(queue){this.transparentQueue=void 0,queue.sort((a,b)=>b.distance-a.distance);var state=render3DCaptureBatchState();try{for(const item of queue)render3DApplyBatchState(item.state),item.draw()}finally{render3DApplyBatchState(state)}}}drawSky(){this.flush();var{near,far}=this.camera;const radius=far==1/0?1e4*near:(near+far)/2;render3DWithState({lighting:!1,blend:!1,depthTest:!1,depthWrite:!1,fogEnd:0,shader:void 0},()=>this.drawMesh(this.sky,buildMatrix(this.camera.pos,void 0,vec3(radius))))}updateShadowMatrix(){var range=0<this.shadowRange?this.shadowRange:1,half=range/2,toSun=this.sunDirection.normalize(),center=this.shadowCenter||this.camera.pos.add(this.cameraForward.scale(.8*half)),toSun=Matrix4.lookAt(center.add(toSun.scale(range)),center).invert(),center=range/(this.shadowTextureSize||this.shadowMapSize),m=toSun.m;m[12]=round(m[12]/center)*center,m[13]=round(m[13]/center)*center,this.shadowMatrix=Matrix4.orthographic(-half,half,-half,half,0,2*range).multiply(toSun),this.shadowPlanes=render3DFrustumPlanes(this.shadowMatrix)}setSky(topColor=hsl(.6,.8,.55),horizonColor=hsl(.6,1,.9),bottomColor=horizonColor,ambient=.5){return this.sky?.dispose(),this.sky=buildSky(topColor,horizonColor,bottomColor),this.fogColor=horizonColor.copy(),this.ambientColor=topColor.scale(ambient,1),this.ambientGroundColor=bottomColor.scale(ambient,1),this.sky}setFog(fogStart,fogEnd,fogColor){this.fogStart=fogStart,this.fogEnd=fogEnd,fogColor&&(this.fogColor=fogColor.copy())}drawBox(pos,size=1,color=WHITE,rotation){this.drawMesh(this.boxMesh,buildMatrix(pos,rotation,render3DSize3(size)),void 0,color)}drawSphere(pos,size=1,color=WHITE){this.drawMesh(this.sphereMesh,buildMatrix(pos,void 0,vec3(size)),void 0,color)}drawBillboard(pos,size=vec2(1),tileInfo,color=WHITE,angle=0,upright=!1){if(this.capture)return this.drawStripUnlit(function(pos,size,angle,upright){var size=render3DBillboardAxes(size,angle,upright),angle=size[0],upright=size[1],rz=size[2],ux=size[3],uy=size[4],size=size[5];return[vec3(pos.x-angle+ux,pos.y-upright+uy,pos.z-rz+size),vec3(pos.x-angle-ux,pos.y-upright-uy,pos.z-rz-size),vec3(pos.x+angle+ux,pos.y+upright+uy,pos.z+rz+size),vec3(pos.x+angle-ux,pos.y+upright-uy,pos.z+rz-size)]}(pos,size,angle,upright),this.cameraBack,RENDER3D_QUAD_UVS,color.copy(),tileInfo);if(this.transparentQueue){const p=pos.copy(),s=size.copy(),c=color.copy();return this.queueTransparent(p,()=>this.drawBillboard(p,s,tileInfo,c,angle,upright))}var ry,rz,ux,uy,x,y,n,u0,v0,u1,v1,floats,ints,j,lit=this.lighting;this.lighting=this.shadowPass&&lit;let uvRect;try{uvRect=render3DBeginStrip(6,tileInfo)}finally{this.lighting=lit}uvRect&&(size=(lit=render3DBillboardAxes(size,angle,upright))[0],ry=lit[1],rz=lit[2],ux=lit[3],uy=lit[4],lit=lit[5],x=pos.x,y=pos.y,pos=pos.z,n=this.cameraBack,color=color.rgbaInt(),u0=uvRect.x,v0=uvRect.y,u1=u0+uvRect.w,v1=v0+uvRect.h,floats=this.streamFloats,ints=this.streamInts,j=9*this.streamCount,this.streamCount+=6,render3DWriteVertex(floats,ints,j,x-size+ux,y-ry+uy,pos-rz+lit,n,u0,v0,color),render3DWriteVertex(floats,ints,j+=9,x-size+ux,y-ry+uy,pos-rz+lit,n,u0,v0,color),render3DWriteVertex(floats,ints,j+=9,x-size-ux,y-ry-uy,pos-rz-lit,n,u0,v1,color),render3DWriteVertex(floats,ints,j+=9,x+size+ux,y+ry+uy,pos+rz+lit,n,u1,v0,color),render3DWriteVertex(floats,ints,j+=9,x+size-ux,y+ry-uy,pos+rz-lit,n,u1,v1,color),render3DWriteVertex(floats,ints,j+=9,x+size-ux,y+ry-uy,pos+rz-lit,n,u1,v1,color))}drawQuad(a,b,c,d,tileInfo,color=WHITE){this.transparentQueue&&(a=a.copy(),b=b.copy(),c=c.copy(),d=d.copy()),color=isArray(color)?color.map(k=>k.copy()):color.copy(),this.drawStrip([a,b,d,c],render3DFaceNormal(a,b,c,d),RENDER3D_QUAD_UVS,render3DQuadValues(color),tileInfo)}drawTriangle(a,b,c,color=WHITE){this.transparentQueue&&(a=a.copy(),b=b.copy(),c=c.copy(),color=color.copy()),this.drawStrip([a,b,c],render3DFaceNormal(a,b,c),void 0,color)}drawLine(posA,posB,width=.1,color=WHITE){this.drawRibbon([posA,posB],width,void 0,color)}drawRibbon(points,width=.1,tileInfo,color=WHITE,side){var count=points.length;const strip=[],uvs=tileInfo?[]:void 0,colors=[],forward=this.cameraForward;let across=vec3(1,0,0);var loop=2<count&&points[0].distanceSquared(points[count-1])<1e-12;for(let i=0;i<count;++i){var p=points[i],w=isArray(width)?width[i]:width;let c=isArray(color)?color[i]:color;this.transparentQueue&&(c=c.copy());var s=side&&(isArray(side)?side[i]:side),next=points[i<count-1?i+1:loop?1:i],last=points[0<i?i-1:loop?count-2:i],s=s||next.subtract(last).cross(forward),next=(across=1e-12<s.lengthSquared()?s.normalize():across).scale(w/2);strip.push(p.add(next),p.subtract(next)),uvs?.push(vec2(i/(count-1),0),vec2(i/(count-1),1)),colors.push(c,c)}render3DWithState({lighting:!1,cullBackFaces:!1},()=>this.drawStrip(strip,forward.scale(-1),uvs,colors,tileInfo))}drawSoftDisc(pos,size=1,color=WHITE,normal=this.cameraBack,sides=16){if(render3DAssertBlending(),this.transparentQueue&&!this.capture){const p=pos.copy(),c=color.copy(),n=normal.copy();return this.queueTransparent(p,()=>this.drawSoftDisc(p,size,c,n,sides))}const n=normal.normalize();const u=(abs(n.y)<.9?vec3(0,1,0):vec3(1,0,0)).cross(n).normalize(),w=u.cross(n);render3DDrawSoftDisc(size/2,color,sides,n,(c,s,r)=>vec3(pos.x+(u.x*c+w.x*s)*r,pos.y+(u.y*c+w.y*s)*r,pos.z+(u.z*c+w.z*s)*r))}drawSoftShadow(pos,size=1,floorHeight=0,color=RENDER3D_SHADOW_COLOR,lift=.02){render3DAssertBlending();const heightMap=floorHeight,height=isNumber(floorHeight)?()=>floorHeight:heightMap.getHeight?(x,z)=>heightMap.getHeight(x,z):floorHeight;if(this.transparentQueue&&!this.capture){const p=pos.copy(),c=color.copy();return this.queueTransparent(vec3(p.x,height(p.x,p.z)+lift,p.z),()=>this.drawSoftShadow(p,size,floorHeight,c,lift))}render3DDrawSoftDisc(size/2,color,16,RENDER3D_DEFAULT_NORMAL,(c,s,r)=>{c=pos.x+c*r,s=pos.z+s*r;return vec3(c,height(c,s)+lift,s)})}}function render3DAssertBlending(){render3D}function render3DDrawSoftDisc(radius,color,sides,normal,pointAt){var alpha=[1,.9,.7,0],circle=function(sides){sides|=0;let circle=render3DCircleCache.get(sides);if(!circle){circle=new Float32Array(2*sides+2);for(let i=0;i<=sides;++i){var a=i/sides*2*PI;circle[2*i]=cos(a),circle[2*i+1]=sin(a)}render3DCircleCache.set(sides,circle)}return circle}(sides);for(let k=0;k<3;++k){var points=[],colors=[],c0=color.withAlpha(color.a*alpha[k]),c1=color.withAlpha(color.a*alpha[k+1]),r0=radius*k/3,r1=radius*(k+1)/3;for(let i=0;i<=sides;++i){var c=circle[2*i],s=circle[2*i+1];points.push(pointAt(c,s,r1),pointAt(c,s,r0)),colors.push(c1,c0)}render3D.drawStripUnlit(points,normal,void 0,colors)}}class Camera3D{constructor(){this.pos=vec3(0,0,10),this.rotation=vec3(),this.fov=PI/3,this.near=.1,this.far=1e3,this.orthographic=0,this.align2D=!1}getMatrix(){return buildMatrix(this.pos,this.rotation)}getViewMatrix(){return this.getMatrix().invert()}getProjectionMatrix(aspect){var h=this.orthographic/2,w=h*aspect;return h?Matrix4.orthographic(-w,w,-h,h,this.near,this.far):Matrix4.perspective(this.fov,aspect,this.near,this.far)}getForward(){return render3DAxis(this.getMatrix().m,8).scale(-1)}getRight(){return render3DAxis(this.getMatrix().m,0)}getUp(){return render3DAxis(this.getMatrix().m,4)}lookAt(target){this.rotation=render3DLookRotation(target.subtract(this.pos),this.rotation)}orbit(target,distance,yaw,pitch=.5){var r=cos(pitch)*distance;this.pos=target.add(vec3(sin(yaw)*r,sin(pitch)*distance,cos(yaw)*r)),this.rotation=render3DLookRotation(target.subtract(this.pos),vec3(0,yaw,0))}follow(target,offset,percent=1){this.pos=this.pos.lerp(target.add(offset),percent),this.lookAt(target)}update2D(canvasHeight=mainCanvasSize.y){var canvasHeight=canvasHeight/2/cameraScale,distance=canvasHeight/tan(this.fov/2);this.orthographic&&=2*canvasHeight,this.pos=vec3(cameraPos.x,cameraPos.y,distance),this.rotation=vec3(0,0,-cameraAngle)}}const RENDER3D_ATTRIBS=[[0,3,5126,!1,0],[1,3,5126,!1,12],[2,2,5126,!1,24],[3,4,5121,!0,32]],RENDER3D_VERTEX_SOURCE="#version 300 es\nprecision highp float;uniform mat4 viewProj,lightViewProj;"+RENDER3D_VERTEX_INPUTS+"out vec3 P,N;out vec2 T,L;out vec4 C,S;void main(){vec4 w=mat4(m0,m1,m2,m3)*vec4(p,1.);gl_Position=viewProj*w;P=w.xyz;vec3 c0=m0.xyz,c1=m1.xyz,c2=m2.xyz;N=mat3(c0/dot(c0,c0),c1/dot(c1,c1),c2/dot(c2,c2))*n;T=uvRect.xy+t*uvRect.zw;L=t;C=c*tint;S=lightViewProj*w;}",RENDER3D_SNIPPET_NAMES="uniform float iTime;uniform vec3 iResolution;\n#define iChannel0 tex\n#define localUV L\n#define worldPos P\n#define worldNormal N\n#define sunDirection (-lightDir.xyz)\n#define sunColor lightColor.rgb\n#define ambientColor ambientFog.rgb\n#define ambientGroundColor ambientGround.rgb\n#define lightCount extraLightCount\n#define lights extraLights\n#define lightColors extraLightColors\n";function render3DFragmentSource(fragmentCode){return"#version 300 es\nprecision highp float;uniform vec4 lightDir,lightColor,ambientFog,ambientGround,fogColor,shadowParams;uniform vec4 extraLights["+RENDER3D_MAX_LIGHTS+"],extraLightColors["+RENDER3D_MAX_LIGHTS+"];uniform int extraLightCount;uniform vec3 cameraPos;uniform sampler2D tex;uniform bool premultipliedTexture;uniform highp sampler2DShadow shadowMap;in vec3 P,N;in vec2 T,L;in vec4 C,S;out vec4 o;float shadow(){if(shadowParams.x<=0.)return 1.;vec3 q=S.xyz/S.w*.5+.5;if(any(greaterThanEqual(abs(q-.5),vec3(.5))))return 1.;q.z-=shadowParams.y;float s=0.;for(int x=-1;x<=1;++x)for(int y=-1;y<=1;++y)s+=texture(shadowMap,vec3(q.xy+vec2(x,y)*shadowParams.z,q.z));return s/9.;}"+(fragmentCode?RENDER3D_SNIPPET_NAMES+fragmentCode+"\n":"")+"void main(){"+(fragmentCode?"vec4 t;mainImage(t,T);":"vec4 t=texture(tex,T);")+"if(premultipliedTexture&&t.a>0.)t.rgb/=t.a;if(shadowParams.w>0.&&t.a<.5)discard;vec4 c=C*t;float e=lightDir.w;if(e<1.){vec3 n=dot(N,N)>0.?normalize(N):vec3(0,1,0);if(!gl_FrontFacing)n=-n;float nl=dot(n,-lightDir.xyz);float s=shadow();vec3 l=(ambientGround.a>0.?mix(ambientGround.rgb,ambientFog.rgb,n.y*.5+.5):ambientFog.rgb)+lightColor.rgb*max(nl,0.)*s;vec3 eye=lightColor.a>0.?normalize(cameraPos-P):vec3(0),sp=vec3(0);for(int i=0;i<"+RENDER3D_MAX_LIGHTS+";++i){if(i>=extraLightCount)break;vec4 L=extraLights[i];bool directional=L.w<0.;vec3 v=directional?L.xyz:L.xyz-P;float d=length(v);float a=directional?1.:max(0.,1.-d/L.w);v/=max(d,1e-6);float ln=dot(n,v);vec3 lc=extraLightColors[i].rgb*extraLightColors[i].a*a*a;l+=lc*max(0.,ln);if(lightColor.a>0.)sp+=lc*pow(max(dot(reflect(-v,n),eye),0.),16.)*step(0.,ln);}c.rgb*=l*(1.-e)+e;if(lightColor.a>0.){vec3 r=reflect(lightDir.xyz,n);c.rgb+=lightColor.rgb*pow(max(dot(r,eye),0.),16.)*lightColor.a*step(0.,nl)*s*(1.-e)+sp*lightColor.a*(1.-e);}}else c.rgb*=e;if(ambientFog.a>0.){float z=distance(cameraPos,P);c.rgb=mix(c.rgb,shadowParams.w<0.?vec3(0):fogColor.rgb,smoothstep(fogColor.a,ambientFog.a,z));}o=vec4(c.rgb,shadowParams.w>0.?1.:c.a);}"}function render3DUseProgram(program){var gl=glContext,r=render3D,c=(gl.useProgram(r.currentProgram=program),r.uniformValues={},gl.uniformMatrix4fv(render3DUniform("viewProj"),!1,r.viewProjection.m),gl.uniformMatrix4fv(render3DUniform("lightViewProj"),!1,r.shadowMatrix.m),gl.uniform1i(render3DUniform("tex"),0),gl.uniform1i(render3DUniform("shadowMap"),1),r.camera.pos);gl.uniform3f(render3DUniform("cameraPos"),c.x,c.y,c.z),gl.uniform1i(render3DUniform("extraLightCount"),r.lightCount),r.lightCount&&(gl.uniform4fv(render3DUniform("extraLights"),r.lightPositions,0,4*r.lightCount),gl.uniform4fv(render3DUniform("extraLightColors"),r.lightColors,0,4*r.lightCount)),program!==r.program&&(gl.uniform1f(render3DUniform("iTime"),time),gl.uniform3f(render3DUniform("iResolution"),glCanvas.width,glCanvas.height,1))}function render3DInitGL(){if(!headlessMode)if(glEnable&&glContext){var location,gl=glContext,r=render3D;glFlush(),r.uniforms=new Map,r.uniformValues={},render3DShadowCut=void 0,r.attribValues=[],r.program=glCreateProgram(RENDER3D_VERTEX_SOURCE,render3DFragmentSource()),r.shadowShader=glCreateProgram("#version 300 es\nprecision highp float;uniform mat4 viewProj;"+RENDER3D_VERTEX_INPUTS+"out vec2 T;out float A;void main(){T=uvRect.xy+t*uvRect.zw;A=c.a*tint.a;gl_Position=viewProj*mat4(m0,m1,m2,m3)*vec4(p,1.);}","#version 300 es\nprecision highp float;uniform sampler2D tex;uniform float cut;in vec2 T;in float A;void main(){if(texture(tex,T).a*mix(1.,A,cut)<.5)discard;}"),r.vao=gl.createVertexArray(),gl.bindVertexArray(r.vao);for([location]of RENDER3D_ATTRIBS)gl.enableVertexAttribArray(location);r.streamBuffer=gl.createBuffer(),gl.bindBuffer(gl.ARRAY_BUFFER,r.streamBuffer),gl.bufferData(gl.ARRAY_BUFFER,r.streamData.byteLength,gl.DYNAMIC_DRAW),r.streamCount=0,r.instanceBuffers=[gl.createBuffer(),gl.createBuffer(),gl.createBuffer()],r.whiteTexture=glCreateTexture(),r.samplers=new Map,r.samplerKey=void 0,render3DUpdateShadowMap(1),gl.bindBuffer(gl.ARRAY_BUFFER,glArrayBuffer),glSetInstancedMode(!0)}else console.warn("Render3DPlugin: WebGL not enabled, construct the plugin in gameInit with glEnable set")}function render3DContextLost(){var r=render3D;r.program=r.currentProgram=r.shadowShader=r.vao=r.streamBuffer=r.whiteTexture=void 0;for(const shader of glShaderObjects)shader.program3D=void 0;r.lightCount=0,r.instanceBuffers=[],r.samplers=new Map,r.samplerKey=void 0,render3DClearInstances(),r.shadowFramebuffer=r.shadowTexture=void 0,r.shadowTextureSize=0,r.streamCount=0,++r.contextGeneration}function render3DContextRestored(){render3DInitGL()}function render3DUniform(name,program=render3D.currentProgram){var u=render3D.uniforms;let cache=u.get(program);return cache||u.set(program,cache={}),cache[name]??=glContext.getUniformLocation(program,name)}function render3DAttrib4f(location,x,y,z,w){var last=render3D.attribValues[location]||=[NaN,NaN,NaN,NaN];last[0]===x&&last[1]===y&&last[2]===z&&last[3]===w||(last[0]=x,last[1]=y,last[2]=z,last[3]=w,glContext.vertexAttrib4f(location,x,y,z,w))}function render3DBindTexture(tileInfo,state=render3D){var gl=glContext,r=render3D,tileInfo=render3DTextureOf(tileInfo),texture=tileInfo?.glTexture||r.whiteTexture;if(gl.bindTexture(gl.TEXTURE_2D,texture),texture===r.whiteTexture||!r.mipmaps&&!state.pixelated)return gl.bindSampler(0,null);gl.bindSampler(0,function(wrap,pixelated){var sharp,gl=glContext,r=render3D,[wrap,wrapT]=glWrapModes(wrap),key=1e5*wrap+2*wrapT+(pixelated?1:0),sampler=r.samplers.get(key);return sampler||(sampler=gl.createSampler(),sharp=pixelated||tilesPixelated,gl.samplerParameteri(sampler,gl.TEXTURE_MAG_FILTER,sharp?gl.NEAREST:gl.LINEAR),gl.samplerParameteri(sampler,gl.TEXTURE_MIN_FILTER,pixelated?gl.NEAREST:tilesPixelated?gl.NEAREST_MIPMAP_LINEAR:gl.LINEAR_MIPMAP_LINEAR),gl.samplerParameteri(sampler,gl.TEXTURE_WRAP_S,wrap),gl.samplerParameteri(sampler,gl.TEXTURE_WRAP_T,wrapT),(sharp=gl.getExtension("EXT_texture_filter_anisotropic"))&&!pixelated&&(wrap=gl.getParameter(sharp.MAX_TEXTURE_MAX_ANISOTROPY_EXT),gl.samplerParameterf(sampler,sharp.TEXTURE_MAX_ANISOTROPY_EXT,clamp(r.anisotropy,1,wrap))),r.samplers.set(key,sampler)),sampler}(tileInfo?.wrap,state.pixelated)),glUpdateMipmaps(texture),state.pixelated||glMipmappedTextures.has(texture)||(glMipmappedTextures.add(texture),gl.generateMipmap(gl.TEXTURE_2D))}function render3DUniform4f(name,x,y,z,w){var last=render3D.uniformValues[name]||=[NaN,NaN,NaN,NaN];last[0]===x&&last[1]===y&&last[2]===z&&last[3]===w||(last[0]=x,last[1]=y,last[2]=z,last[3]=w,glContext.uniform4f(render3DUniform(name),x,y,z,w))}function render3DBindVertexBuffer(buffer){var gl=glContext;gl.bindBuffer(gl.ARRAY_BUFFER,buffer);for(const a of RENDER3D_ATTRIBS)gl.vertexAttribPointer(a[0],a[1],a[2],a[3],RENDER3D_VERTEX_BYTES,a[4])}function render3DBindMesh(mesh){render3DBindVertexBuffer(mesh.buffer),glContext.bindBuffer(glContext.ELEMENT_ARRAY_BUFFER,mesh.indexBuffer)}function render3DTextureOf(tileInfo){return tileInfo instanceof TileInfo?tileInfo.textureInfo:tileInfo}const render3DLevel=[];function render3DLevelLeave(o){o=render3DLevel.indexOf(o);0<=o&&render3DLevel.splice(o,1)}const render3DTileUVRect={x:0,y:0,w:1,h:1},render3DWholeTiles=new WeakSet;function render3DGetTileUVs(tileInfo){var inv,rect,bleedX,bleedY;return tileInfo instanceof TileInfo&&tileInfo.textureInfo&&!render3DWholeTiles.has(tileInfo)?(inv=tileInfo.textureInfo.sizeInverse,rect=render3DTileUVRect,bleedX=inv.x*tileInfo.bleed,bleedY=inv.y*tileInfo.bleed,rect.x=tileInfo.pos.x*inv.x+bleedX,rect.y=tileInfo.pos.y*inv.y+bleedY,rect.w=tileInfo.size.x*inv.x-2*bleedX,rect.h=tileInfo.size.y*inv.y-2*bleedY,rect):RENDER3D_FULL_UV_RECT}function render3DSetDrawUniforms(matrix,tileInfo,tint,uvRect,state=render3D){var fc,gl=glContext,r=render3D;uvRect||=render3DGetTileUVs(tileInfo),function(m,tint,uvRect){var gl=glContext;gl.vertexAttrib4f(4,m[0],m[1],m[2],m[3]),gl.vertexAttrib4f(5,m[4],m[5],m[6],m[7]),gl.vertexAttrib4f(6,m[8],m[9],m[10],m[11]),gl.vertexAttrib4f(7,m[12],m[13],m[14],m[15]),render3DAttrib4f(11,tint.r,tint.g,tint.b,tint.a),render3DAttrib4f(12,uvRect.x,uvRect.y,uvRect.w,uvRect.h)}(matrix.m,tint,uvRect),render3DBindTexture(tileInfo,state),r.shadowPass?(matrix=state.blend?1:0,render3DShadowCut!==matrix&&glContext.uniform1f(render3DUniform("cut",r.shadowShader),render3DShadowCut=matrix)):((uvRect=state.shader?(tint=state.shader).program3D||=glCreateProgram(RENDER3D_VERTEX_SOURCE,render3DFragmentSource(tint.fragmentCode)):r.program)!==r.currentProgram&&render3DUseProgram(uvRect),state.blend?(gl.enable(gl.BLEND),matrix=state.additive?gl.ONE:gl.ONE_MINUS_SRC_ALPHA,gl.blendFuncSeparate(gl.SRC_ALPHA,matrix,gl.ONE,matrix)):gl.disable(gl.BLEND),state.depthTest?gl.enable(gl.DEPTH_TEST):gl.disable(gl.DEPTH_TEST),gl.depthMask(state.depthWrite),state.cullBackFaces?gl.enable(gl.CULL_FACE):gl.disable(gl.CULL_FACE),gl.frontFace(state.mirrored?gl.CCW:gl.CW),uvRect=-((tint=r.sunDirection).length()||1),matrix=r.sunColor,gl=r.ambientColor,fc=r.fogColor||canvasClearColor,render3DUniform4f("lightDir",tint.x/uvRect,tint.y/uvRect,tint.z/uvRect,state.lighting?state.emissive:1),render3DUniform4f("lightColor",matrix.r,matrix.g,matrix.b,state.specular),render3DUniform4f("ambientFog",gl.r,gl.g,gl.b,r.fogEnd),(tint=r.ambientGroundColor)?render3DUniform4f("ambientGround",tint.r,tint.g,tint.b,1):render3DUniform4f("ambientGround",0,0,0,0),render3DUniform4f("fogColor",fc.r,fc.g,fc.b,r.fogStart),uvRect=state.blend?state.additive?-1:0:1,render3DUniform4f("shadowParams",r.shadows&&r.passIsDefault&&state.receiveShadow?1:0,r.shadowBias,r.shadowSoftness/r.shadowTextureSize,uvRect),matrix=render3DTextureOf(tileInfo),gl="premultipliedTexture",tint=+!(!matrix?.glTexture||!glPremultipliedTextures.has(matrix.glTexture)),(fc=render3D.uniformValues)[gl]!==tint&&(fc[gl]=tint,glContext.uniform1i(render3DUniform(gl),tint)))}function render3DFrustumPlanes(matrix){var m=matrix.m,planes=[];for(let i=0;i<3;++i)for(const sign of[1,-1]){var p=[m[3]+sign*m[i],m[7]+sign*m[4+i],m[11]+sign*m[8+i],m[15]+sign*m[12+i]];const l=hypot(p[0],p[1],p[2])||1;planes.push(p.map(v=>v/l))}return planes}function render3DPreRender(){var r=render3D;r.updateMatrices(),render3DRenderPass(r.shadowMapDrawn=!1)}function render3DRender(){render3DRenderPass(!0)}function render3DRenderPass(after2D){var gl=glContext,r=render3D;if(r.program&&glEnable){!function(){var r=render3D,key=tilesPixelated+" "+r.anisotropy;if(r.samplerKey!==key){r.samplerKey=key;for(const sampler of r.samplers.values())glContext.deleteSampler(sampler);r.samplers.clear()}}();var isDefault=after2D===!!r.renderAfter2D,after2D=render3DLayerObjects(after2D);if(isDefault||after2D.length){r.passIsDefault=isDefault,glFlush(),r.streamCount=0,r.capture=r.transparentQueue=void 0,render3DClearInstances();var lights=function(){var lights=engineObjects.filter(o=>!o.destroyed&&o instanceof Light3D&&0<o.color.a&&0<o.intensity&&(o.directional||0<o.radius));if(lights.length>RENDER3D_MAX_LIGHTS){const cameraPos=render3D.camera.pos,distances=new Map;for(const light of lights)distances.set(light,light.directional?-1:light.getWorldPos3D().distanceSquared(cameraPos));lights.sort((a,b)=>distances.get(a)-distances.get(b)),lights.length=RENDER3D_MAX_LIGHTS}return lights}();r.lightCount=lights.length;const positions=r.lightPositions,colors=r.lightColors;lights.forEach((light,i)=>{var p=light.directional?light.getWorldPos3D().normalize():light.getWorldPos3D(),c=light.color,i=4*i;positions[i]=p.x,positions[1+i]=p.y,positions[2+i]=p.z,positions[3+i]=light.directional?-1:max(0,light.radius),colors[i]=c.r,colors[1+i]=c.g,colors[2+i]=c.b,colors[3+i]=c.a*light.intensity}),gl.bindVertexArray(r.vao),gl.frontFace(gl.CW),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,r.shadowTexture||null),gl.activeTexture(gl.TEXTURE0),gl.depthMask(!0),gl.clear(gl.DEPTH_BUFFER_BIT),r.isRendering=!0;try{r.shadows&&!r.shadowMapDrawn&&(!function(){var gl=glContext,r=render3D;render3DUpdateShadowMap(0|r.shadowMapSize),r.updateShadowMatrix(),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,null),gl.activeTexture(gl.TEXTURE0),gl.bindFramebuffer(gl.FRAMEBUFFER,r.shadowFramebuffer),gl.viewport(0,0,r.shadowTextureSize,r.shadowTextureSize),gl.clear(gl.DEPTH_BUFFER_BIT),gl.useProgram(r.shadowShader),gl.uniformMatrix4fv(render3DUniform("viewProj",r.shadowShader),!1,r.shadowMatrix.m),gl.enable(gl.DEPTH_TEST),gl.depthMask(!0),gl.disable(gl.BLEND),gl.disable(gl.CULL_FACE),r.shadowPass=!0;try{render3DDrawObjects(render3DLayerObjects(!!r.renderAfter2D).filter(o=>o.castShadow&&!o.additive&&(!o.transparent||o.tileInfo))),r.onRenderOpaque?.(),r.flush(),render3DFlushInstances()}finally{r.shadowPass=!1,gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,glCanvas.width,glCanvas.height),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,r.shadowTexture),gl.activeTexture(gl.TEXTURE0)}}(),r.shadowMapDrawn=!0),render3DUseProgram(r.program),r.renderStages(after2D,isDefault)}finally{r.isRendering=!1,r.currentProgram=void 0,r.streamCount=0,r.capture=r.transparentQueue=void 0,gl.disable(gl.DEPTH_TEST),gl.disable(gl.CULL_FACE),gl.depthMask(!0),gl.frontFace(gl.CCW),gl.bindSampler(0,null),glActiveTexture&&gl.bindTexture(gl.TEXTURE_2D,glActiveTexture),gl.bindBuffer(gl.ARRAY_BUFFER,glArrayBuffer),glSetInstancedMode(!0)}}}}function render3DUpdateShadowMap(size){var texture,framebuffer,gl=glContext,r=render3D;r.shadowTexture&&r.shadowTextureSize===size||(r.shadowTexture&&gl.deleteTexture(r.shadowTexture),r.shadowFramebuffer&&gl.deleteFramebuffer(r.shadowFramebuffer),texture=r.shadowTexture=gl.createTexture(),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,texture),gl.texImage2D(gl.TEXTURE_2D,0,gl.DEPTH_COMPONENT24,size,size,0,gl.DEPTH_COMPONENT,gl.UNSIGNED_INT,null),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_COMPARE_MODE,gl.COMPARE_REF_TO_TEXTURE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_COMPARE_FUNC,gl.LEQUAL),gl.activeTexture(gl.TEXTURE0),framebuffer=r.shadowFramebuffer=gl.createFramebuffer(),gl.bindFramebuffer(gl.FRAMEBUFFER,framebuffer),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.DEPTH_ATTACHMENT,gl.TEXTURE_2D,texture,0),gl.drawBuffers([gl.NONE]),gl.readBuffer(gl.NONE),gl.bindFramebuffer(gl.FRAMEBUFFER,null),r.shadowTextureSize=size)}function render3DBeginStrip(count,tileInfo){var textureInfo,r=render3D;if(render3DCanDraw()&&((!r.shadowPass||r.lighting)&&!(32768<count)))return textureInfo=render3DTextureOf(tileInfo),r.streamCount&&(textureInfo!==r.streamTileInfo||render3DStateChanged(r.streamState)||32768<r.streamCount+count)&&r.flush(),render3DFlushBeforeOverlay(),r.streamCount||(r.streamState=render3DCaptureBatchState()),r.streamTileInfo=textureInfo,render3DGetTileUVs(tileInfo)}function render3DFlushBeforeOverlay(){var r=render3D;r.depthTest||r.shadowPass||!r.instanceMeshes.length||(r.flush(),render3DFlushInstances())}function render3DMeshUpload(mesh){mesh.buffer&&!mesh.dirty&&mesh.contextGeneration===render3D.contextGeneration||mesh.upload()}function render3DWorldSize(o,m){o=o.size3D;return vec3(o.x*hypot(m[0],m[1],m[2]),o.y*hypot(m[4],m[5],m[6]),o.z*hypot(m[8],m[9],m[10]))}function render3DSlopeNormal(heightFunction,x,z,ex,ez,halfX,halfZ){var x0=max(x-ex,-halfX),ex=min(x+ex,halfX),halfX=max(z-ez,-halfZ),ez=min(z+ez,halfZ),halfZ=(heightFunction(ex,z)-heightFunction(x0,z))/(ex-x0||1),z=(heightFunction(x,ez)-heightFunction(x,halfX))/(ez-halfX||1),ex=1/hypot(halfZ,1,z);return vec3(-halfZ*ex,ex,-z*ex)}const render3DBillboardAxesScratch=new Float64Array(6);function render3DBillboardAxes(size,angle,upright){var r=render3D.cameraRight,u=render3D.cameraUp;let rx=r.x,ry=r.y,rz=r.z,ux=u.x,uy=u.y,uz=u.z;upright&&(r=hypot(rx,rz),rx=r?rx/r:1,ry=0,rz=r?rz/r:0,ux=0,uy=1,uz=0);u=cos(angle),upright=sin(angle),r=size.x/2,angle=size.y/2,size=render3DBillboardAxesScratch;return size[0]=(rx*u+ux*upright)*r,size[1]=(ry*u+uy*upright)*r,size[2]=(rz*u+uz*upright)*r,size[3]=(ux*u-rx*upright)*angle,size[4]=(uy*u-ry*upright)*angle,size[5]=(uz*u-rz*upright)*angle,size}function render3DStripCount(n){return n+2+(1&n)}function render3DStripIndex(k,n){return k<1?0:k<=n?k-1:n-1}function render3DWriteVertex(floats,ints,j,x,y,z,n,u,v,rgba){floats[j]=x,floats[j+1]=y,floats[j+2]=z,floats[j+3]=n.x,floats[j+4]=n.y,floats[j+5]=n.z,floats[j+6]=u,floats[j+7]=v,ints[j+8]=rgba}function render3DFlipTriangles(indices){for(let t=0;t<indices.length;t+=3){var b=indices[t+1];indices[t+1]=indices[t+2],indices[t+2]=b}return indices}function render3DStripTriangles(count,remap,place){var indices=[];for(let i=2;i<count;++i){var a=1&i?i-1:i-2,b=1&i?i-2:i-1;place[a]!=place[b]&&place[b]!=place[i]&&place[a]!=place[i]&&indices.push(remap[a],remap[b],remap[i])}return indices}function render3DMeshVertexData(mesh,vertices,data=new ArrayBuffer(vertices.length*RENDER3D_VERTEX_BYTES)){var count=vertices.length,floats=new Float32Array(data),ints=new Uint32Array(data);for(let j=0;j<count;++j){var i=vertices[j],p=mesh.points[i],uv=mesh.uvs[i]||RENDER3D_DEFAULT_UV;render3DWriteVertex(floats,ints,9*j,p.x,p.y,p.z,mesh.normals[i]||RENDER3D_DEFAULT_NORMAL,uv.x,uv.y,(mesh.colors[i]||WHITE).rgbaInt())}return data}function render3DPolygonStrip(points){var strip=[points[0]];for(let i=1,j=points.length-1;i<=j;++i,--j)strip.push(points[i]),i!==j&&strip.push(points[j]);return strip}function render3DFlipWinding(mesh){if(mesh.indices)return render3DFlipTriangles(mesh.indices);for(const key of["points","normals","uvs","colors"]){var a=mesh[key];a.length&&(a.unshift(a[0]),a.push(a[a.length-1]))}mesh.vertexKeys=void 0}const render3DMeshBuffers="undefined"==typeof FinalizationRegistry?void 0:new FinalizationRegistry(({buffer,indexBuffer,generation})=>{generation===render3D?.contextGeneration&&glContext&&(glContext.deleteBuffer(buffer),glContext.deleteBuffer(indexBuffer))});function render3DPlaceKey(p){return`${round(1e5*p.x)},${round(1e5*p.y)},`+round(1e5*p.z)}function render3DAddCornerNormal(sums,a,b,c,normal){b=b.subtract(a),c=c.subtract(a),b=Math.acos(clamp(b.dot(c)/(b.length()*c.length()||1),-1,1)),c=render3DPlaceKey(a);sums.set(c,(sums.get(c)||vec3()).add(normal.scale(b)))}class Mesh{constructor(){this.points=[],this.normals=[],this.uvs=[],this.colors=[],this.buffer=void 0,this.indexBuffer=void 0,this.bufferCount=0,this.indexType=0,this.dirty=!1,this.instanced=void 0,this.doubleSided=!1,this.dynamicDraw=!1,this.indices=void 0,this.vertexKeys=void 0,this.vertexLayout=void 0,this.instanceCount=0,this.instanceData=void 0,this.radius=0,this.bounds=void 0,this.contextGeneration=0}get vertexCount(){return this.points.length}addStrip(points,normals,uvs,colors){var part;return this.indices?(part=(new Mesh).addStrip(points,normals,uvs,colors).toIndexed(),this.addTriangles(part.points,part.indices,part.normals,part.uvs,part.colors)):(function(points,normals,uvs,colors,callback){var n=points.length,count=render3DStripCount(n),normalArray=isArray(normals),uvArray=isArray(uvs),colorArray=isArray(colors);for(let k=0;k<count;++k){var i=render3DStripIndex(k,n);callback(points[i],normalArray?normals[i]:normals||RENDER3D_DEFAULT_NORMAL,uvArray?uvs[i]:uvs||RENDER3D_DEFAULT_UV,colorArray?colors[i]:colors||WHITE)}}(points,normals,uvs,colors,(p,n,uv,c)=>{this.points.push(p),this.normals.push(n),this.uvs.push(uv),this.colors.push(c)}),this.vertexKeys=void 0,this.dirty=!0,this)}addTriangles(points,indices,normals,uvs,colors){this.toIndexed();var offset=this.points.length,normalArray=isArray(normals),uvArray=isArray(uvs),colorArray=isArray(colors);for(let i=0;i<points.length;++i)this.points.push(points[i]),this.normals.push(normalArray?normals[i]:normals||RENDER3D_DEFAULT_NORMAL),this.uvs.push(uvArray?uvs[i]:uvs||RENDER3D_DEFAULT_UV),this.colors.push(colorArray?colors[i]:colors||WHITE);for(const i of indices)this.indices.push(i+offset);return this.dirty=!0,this}toIndexed(){var vertices,indices;return this.indices||({vertices,indices}=this.getTriangles(),this.points=vertices.map(i=>this.points[i]),this.normals=vertices.map(i=>this.normals[i]||RENDER3D_DEFAULT_NORMAL),this.uvs=vertices.map(i=>this.uvs[i]||RENDER3D_DEFAULT_UV),this.colors=vertices.map(i=>this.colors[i]||WHITE),this.indices=render3DFlipTriangles(indices),this.vertexKeys=void 0,this.dirty=!0),this}addQuad(a,b,c,d,color,uvs){return this.addStrip([a,b,d,c],render3DFaceNormal(a,b,c,d),uvs?render3DQuadValues(uvs):RENDER3D_QUAD_UVS,render3DQuadValues(color))}combine(mesh,matrix=RENDER3D_IDENTITY,color=WHITE){var last,normalMatrix=render3DNormalMatrix(matrix=render3DMatrix(matrix)),mirrors=matrix.determinant()<0;let part=mesh,order;if(this.indices||mesh.indices){this.toIndexed(),part=mesh.indices?mesh:(new Mesh).combine(mesh).toIndexed();var offset=this.points.length,indices=part.indices;for(let t=0,n=indices.length;t<n;t+=3)this.indices.push(indices[t]+offset,indices[t+(mirrors?2:1)]+offset,indices[t+(mirrors?1:2)]+offset)}else mirrors&&part.points.length&&(last=part.points.length-1,order=[0,...part.points.keys(),last]);for(let j=0,count=(order||part.points).length;j<count;++j){var i=order?order[j]:j;this.points.push(matrix.transformPoint(part.points[i])),this.normals.push(normalMatrix.transformDirection(part.normals[i]||RENDER3D_DEFAULT_NORMAL).normalize()),this.uvs.push((part.uvs[i]||RENDER3D_DEFAULT_UV).copy()),this.colors.push((part.colors[i]||WHITE).multiply(color))}return this.doubleSided||=mesh.doubleSided,this.vertexKeys=void 0,this.dirty=!0,this}mirror(axis=vec3(1,0,0)){var axis=axis.normalize(),a=-2*axis.x,b=-2*axis.y,c=-2*axis.z,a=new Matrix4([1+a*axis.x,b*axis.x,c*axis.x,0,a*axis.y,1+b*axis.y,c*axis.y,0,a*axis.z,b*axis.z,1+c*axis.z,0,0,0,0,1]);return(new Mesh).combine(this).combine(this,a)}spin(count,axis=vec3(0,1,0)){var mesh=new Mesh,unit=axis.normalize();for(let i=0;i<count;++i)mesh.combine(this,function(axis,angle){var x=vec3(1,0,0).rotate(axis,angle),y=vec3(0,1,0).rotate(axis,angle),axis=vec3(0,0,1).rotate(axis,angle);return new Matrix4([x.x,x.y,x.z,0,y.x,y.y,y.z,0,axis.x,axis.y,axis.z,0,0,0,0,1])}(unit,i/count*2*PI));return mesh}union(mesh,matrix){return render3DMeshCSG(this,mesh,matrix,0)}subtract(mesh,matrix){return render3DMeshCSG(this,mesh,matrix,1)}intersect(mesh,matrix){return render3DMeshCSG(this,mesh,matrix,2)}scaleUVs(scale){const s=render3DSize2(scale);return this.uvs=this.uvs.map(uv=>vec2(uv.x*s.x,uv.y*s.y)),this.dirty=!0,this}transform(matrix){var normalMatrix=render3DNormalMatrix(matrix=render3DMatrix(matrix));for(let i=0;i<this.points.length;++i)this.points[i]=matrix.transformPoint(this.points[i]),this.normals[i]&&=normalMatrix.transformDirection(this.normals[i]).normalize();return matrix.determinant()<0&&render3DFlipWinding(this),this.dirty=!0,this}flipNormals(){return render3DFlipWinding(this),this.normals=this.normals.map(n=>n.scale(-1)),this.dirty=!0,this}setColor(color){return this.colors=this.points.map(()=>color),this.dirty=!0,this}getBounds(){if(!this.points.length)return{min:vec3(),max:vec3()};var lo=vec3(1/0),hi=vec3(-1/0);for(const p of this.points)lo.x=min(lo.x,p.x),lo.y=min(lo.y,p.y),lo.z=min(lo.z,p.z),hi.x=max(hi.x,p.x),hi.y=max(hi.y,p.y),hi.z=max(hi.z,p.z);return{min:lo,max:hi}}center(){var bounds=this.getBounds();return this.transform(bounds.min.add(bounds.max).scale(-.5))}fit(size=1){var bounds=this.getBounds(),bounds=bounds.max.subtract(bounds.min),size=size/(max(bounds.x,bounds.y,bounds.z)||1);return this.transform(Matrix4.scaling(vec3(size)))}computeRadius(){let r=0;for(const p of this.points)r=max(r,p.lengthSquared());return this.bounds=this.getBounds(),this.radius=r**.5}computeNormals(smooth=!1){if(this.indices){var split;smooth||(this.points=(split=a=>this.indices.map(i=>a[i]))(this.points),this.normals=split(this.normals),this.uvs=split(this.uvs),this.colors=split(this.colors),this.indices=this.indices.map((_,i)=>i));const points=this.points,indices=this.indices,sums=new Map,normals=points.map(()=>RENDER3D_DEFAULT_NORMAL);for(let t=0;t<indices.length;t+=3){var a=points[indices[t]],b=points[indices[t+1]],c=points[indices[t+2]],b=b.subtract(a).cross(c.subtract(a));if(b.lengthSquared()){var normal=b.normalize();if(smooth)for(let j=0;j<3;++j)render3DAddCornerNormal(sums,points[indices[t+j]],points[indices[t+(j+1)%3]],points[indices[t+(j+2)%3]],normal);else normals[indices[t]]=normals[indices[t+1]]=normals[indices[t+2]]=normal}}this.normals=smooth?points.map(p=>{p=sums.get(render3DPlaceKey(p));return p&&p.lengthSquared()?p.normalize():RENDER3D_DEFAULT_NORMAL}):normals}else{const points=this.points,n=points.length;var faceNormals=[];for(let i=0;i+2<n;++i){const a=points[i],b=points[i+1],c=points[i+2],normal=b.subtract(a).cross(c.subtract(a));faceNormals.push(normal.lengthSquared()?normal.normalize(1&i?1:-1):void 0)}const normals=points.map(()=>RENDER3D_DEFAULT_NORMAL);if(smooth){const sums=new Map;faceNormals.forEach((f,i)=>f&&[0,1,2].forEach(j=>render3DAddCornerNormal(sums,points[i+j],points[i+(j+1)%3],points[i+(j+2)%3],f)));for(let i=0;i<n;++i)normals[i]=(sums.get(render3DPlaceKey(points[i]))||RENDER3D_DEFAULT_NORMAL).normalize()}else faceNormals.forEach((f,i)=>f&&(normals[i]=normals[i+1]=normals[i+2]=f));this.normals=normals,this.vertexKeys=void 0}return this.dirty=!0,this}upload(){var gl,indices,wide,data,layout;return this.computeRadius(),render3D?.program&&glContext&&(gl=glContext,layout=this.vertexLayout,this.dynamicDraw&&layout&&this.buffer&&this.contextGeneration===render3D.contextGeneration&&layout.pointCount===this.points.length?(gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer),gl.bufferSubData(gl.ARRAY_BUFFER,0,render3DMeshVertexData(this,layout.vertices,layout.data)),gl.bindBuffer(gl.ARRAY_BUFFER,glArrayBuffer),this.dirty=!1):(this.dispose(),{vertices:layout,indices}=this.getTriangles(),wide=65535<layout.length,data=render3DMeshVertexData(this,layout),this.vertexLayout=this.dynamicDraw?{vertices:layout,pointCount:this.points.length,data:data}:void 0,this.vertexKeys=void 0,this.buffer=gl.createBuffer(),this.indexBuffer=gl.createBuffer(),this.bufferCount=indices.length,this.indexType=wide?gl.UNSIGNED_INT:gl.UNSIGNED_SHORT,this.dirty=!1,gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer),gl.bufferData(gl.ARRAY_BUFFER,data,this.dynamicDraw?gl.DYNAMIC_DRAW:gl.STATIC_DRAW),gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer),gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,new(wide?Uint32Array:Uint16Array)(indices),gl.STATIC_DRAW),this.contextGeneration=render3D.contextGeneration,layout={buffer:this.buffer,indexBuffer:this.indexBuffer,generation:this.contextGeneration},render3DMeshBuffers?.register(this,layout,this),gl.bindBuffer(gl.ARRAY_BUFFER,glArrayBuffer))),this}getTriangles(){var count=this.points.length,vertices=[];if(this.indices){for(let i=0;i<count;++i)vertices.push(i);return{vertices:vertices,indices:render3DFlipTriangles(this.indices.slice())}}var remap=new Int32Array(count),place=new Int32Array(count),keys=this.vertexKeys;if(keys&&keys.length===count){var first=new Int32Array(count).fill(-1);for(let i=0;i<count;++i){var key=keys[i],j=first[key];j<0?(remap[first[key]=i]=vertices.length,vertices.push(i)):remap[i]=remap[j],place[i]=first[key]}}else{var values=new Float64Array(9*count);let size=1;for(;size<2*count;)size*=2;var mask=size-1,seen=new Int32Array(size),places=new Int32Array(size),mix=(h,v)=>Math.imul(h^v,2654435761)>>>0;for(let i=0;i<count;++i){var p=this.points[i],n=this.normals[i]||RENDER3D_DEFAULT_NORMAL,uv=this.uvs[i]||RENDER3D_DEFAULT_UV,k=9*i,x=values[k]=round(1e6*p.x),y=values[1+k]=round(1e6*p.y),z=values[2+k]=round(1e6*p.z);values[3+k]=round(1e6*n.x),values[4+k]=round(1e6*n.y),values[5+k]=round(1e6*n.z),values[6+k]=round(1e6*uv.x),values[7+k]=round(1e6*uv.y),values[8+k]=(this.colors[i]||WHITE).rgbaInt();let h=mix(mix(mix(2166136261,x),y),z);for(let slot=h&mask;;slot=slot+1&mask){var o=places[slot]-1;if(o<0){places[slot]=i+1,place[i]=i;break}if(values[9*o]===x&&values[9*o+1]===y&&values[9*o+2]===z){place[i]=o;break}}for(let m=3;m<9;++m)h=mix(h,values[k+m]);for(let slot=h&mask;;slot=slot+1&mask){const o=seen[slot]-1;if(o<0){seen[slot]=i+1,remap[i]=vertices.length,vertices.push(i);break}let same=!0;for(let m=0;same&&m<9;++m)same=values[9*o+m]===values[k+m];if(same){remap[i]=remap[o];break}}}}return{vertices:vertices,indices:render3DStripTriangles(count,remap,place)}}render(matrix,tileInfo,color){render3D?.drawMesh(this,matrix,tileInfo,color)}dispose(){this.buffer&&(render3DMeshBuffers?.unregister(this),this.contextGeneration===render3D?.contextGeneration&&(glContext?.deleteBuffer(this.buffer),glContext?.deleteBuffer(this.indexBuffer)),this.buffer=this.indexBuffer=void 0,this.bufferCount=0)}}function buildLathe(profile,sides=16,smooth=render3D?.smoothShading,capped=!0){sides|=0;var mesh=new Mesh;const rings=profile.length;var point=(i,a)=>vec3(sin(a)*profile[i][0],profile[i][1],cos(a)*profile[i][0]);const segmentNormal=i=>{var[r0,y0]=profile[i],[i,y1]=profile[i+1],y1=vec2(y1-y0,r0-i);return y1.length()?y1.normalize():vec2(1,0)},closed=2<rings&&abs(profile[0][0]-profile[rings-1][0])<1e-9&&abs(profile[0][1]-profile[rings-1][1])<1e-9;var vertexNormal=i=>{if(!closed&&(!i||i==rings-1)&&abs(profile[i][0])<1e-9){var up=i?1:-1;if(segmentNormal(i?i-1:0).y*up>Math.SQRT1_2-1e-9)return vec2(0,up)}let n=vec2();up=s=>{return n=n.add(segmentNormal(s).scale((s=s,hypot(profile[s+1][0]-profile[s][0],profile[s+1][1]-profile[s][1]))))};return 0<i?up(i-1):closed&&up(rings-2),i<rings-1?up(i):closed&&up(0),n.length()?n.normalize():vec2(1,0)},normal3D=(n,a)=>vec3(sin(a)*n.x,n.y,cos(a)*n.x);const lengths=[0];for(let i=1;i<rings;++i)lengths[i]=lengths[i-1]+hypot(profile[i][0]-profile[i-1][0],profile[i][1]-profile[i-1][1]);const total=lengths[rings-1]||1;var v=i=>1-lengths[i]/total;for(let i=0;i+1<rings;++i)if(smooth){var points=[],normals=[],uvs=[],n0=vertexNormal(i),n1=vertexNormal(i+1),half=PI/sides,turn1=abs(profile[i+1][0])<1e-9?-half:0,turn0=abs(profile[i][0])<1e-9?half:0;for(let j=0;j<=sides;++j){var a=j/sides*2*PI,u=j/sides;points.push(point(i+1,a),point(i,a)),normals.push(normal3D(n1,a+turn1),normal3D(n0,a+turn0)),uvs.push(vec2(u,v(i+1)),vec2(u,v(i)))}mesh.addStrip(points,normals,uvs)}else{var n=segmentNormal(i);for(let j=0;j<sides;++j){var a0=j/sides*2*PI,a1=(j+1)/sides*2*PI,u0=j/sides,u1=(j+1)/sides;mesh.addStrip([point(i+1,a0),point(i,a0),point(i+1,a1),point(i,a1)],normal3D(n,(a0+a1)/2),[vec2(u0,v(i+1)),vec2(u0,v(i)),vec2(u1,v(i+1)),vec2(u1,v(i))])}}if(capped&&!closed)for(var[i,up]of[[0,!1],[rings-1,!0]])if(!(abs(profile[i][0])<1e-9)){const points=[],uvs=[];for(let j=0;j<sides;++j){const a=(up?j:-j)/sides*2*PI;points.push(point(i,a)),uvs.push(vec2(.5*sin(a)+.5,.5*cos(a)+.5))}mesh.addStrip(render3DPolygonStrip(points),vec3(0,up?1:-1,0),render3DPolygonStrip(uvs))}return mesh.doubleSided=!closed&&!capped&&(1e-9<abs(profile[0][0])||1e-9<abs(profile[rings-1][0])),mesh}function buildSphere(size=1,sides=16,rings=8,smooth=render3D?.smoothShading){var profile=[];for(let i=0;i<=rings;++i){var a=i/rings*PI-PI/2;profile.push([cos(a)*size/2,sin(a)*size/2])}return buildLathe(profile,sides,smooth)}function buildBox(size=1,bevel=0,bevelSegments=1){var half=render3DSize3(size).scale(.5),size=[[vec3(0,0,1),vec3(1,0,0),vec3(0,1,0)],[vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0)],[vec3(1,0,0),vec3(0,0,-1),vec3(0,1,0)],[vec3(-1,0,0),vec3(0,0,1),vec3(0,1,0)],[vec3(0,1,0),vec3(1,0,0),vec3(0,0,-1)],[vec3(0,-1,0),vec3(1,0,0),vec3(0,0,1)]],bevel=min(bevel,half.x,half.y,half.z);if(0<bevel)return function(half,t,segments,faces){const mesh=new Mesh,inner=half.subtract(vec3(t)),n=max(1,0|segments);var normal,r,u,triangle=(a,b,c,na,nb,nc)=>{let normal=b.subtract(a).cross(c.subtract(a));if(!(normal.lengthSquared()<1e-20)){normal.dot(a.add(b).add(c))<0&&([b,c,nb,nc,normal]=[c,b,nc,nb,normal.scale(-1)]),normal=normal.normalize();let face=faces[0];for(const f of faces)f[0].dot(normal)>face[0].dot(normal)&&(face=f);const[,r,u]=face,hr=abs(half.dot(r)),hu=abs(half.dot(u));var uv=p=>vec2(.5+p.dot(r)/(2*hr),.5-p.dot(u)/(2*hu));mesh.addStrip([a,b,c],1<n&&na?[na,nb,nc]:normal,[uv(a),uv(b),uv(c)])}};for([normal,r,u]of faces){var[a,b,c,d]=render3DQuadAxes(normal.multiply(half),r.multiply(inner),u.multiply(inner));triangle(a,b,c),triangle(c,b,d)}var axes=[vec3(1,0,0),vec3(0,1,0),vec3(0,0,1)];for(let k=0;k<3;++k)for(const si of[-1,1])for(const sj of[-1,1]){const n1=axes[(k+1)%3].scale(si),n2=axes[(k+2)%3].scale(sj);var middle=n1.multiply(inner).add(n2.multiply(inner)),along=axes[k].multiply(inner),start=middle.subtract(along),end=middle.add(along),dir=m=>n1.scale(cos(m/n*PI/2)).add(n2.scale(sin(m/n*PI/2)));for(let m=0;m<n;++m){var d0=dir(m),d1=dir(m+1);const a=start.add(d0.scale(t)),b=end.add(d0.scale(t)),c=start.add(d1.scale(t)),d=end.add(d1.scale(t));triangle(a,b,c,d0,d0,d1),triangle(c,b,d,d1,d0,d1)}}for(const sx of[-1,1])for(const sy of[-1,1])for(const sz of[-1,1]){const nx=vec3(sx,0,0),ny=vec3(0,sy,0),nz=vec3(0,0,sz),corner=inner.multiply(vec3(sx,sy,sz)),dir=(i,j)=>{var polar=i/n*PI/2,j=i?j/i*PI/2:0;return nz.scale(cos(polar)).add(nx.scale(cos(j)*sin(polar))).add(ny.scale(sin(j)*sin(polar)))};var point=(i,j)=>corner.add(dir(i,j).scale(t));for(let i=0;i<n;++i){for(let j=0;j<=i;++j)triangle(point(i,j),point(i+1,j),point(i+1,j+1),dir(i,j),dir(i+1,j),dir(i+1,j+1));for(let j=0;j<i;++j)triangle(point(i,j),point(i+1,j+1),point(i,j+1),dir(i,j),dir(i+1,j+1),dir(i,j+1))}}return mesh}(half,bevel,bevelSegments,size);var n,r,u,mesh=new Mesh;for([n,r,u]of size){var center=n.multiply(half),right=r.multiply(half),up=u.multiply(half);mesh.addStrip(render3DQuadAxes(center,right,up),n,RENDER3D_QUAD_UVS)}return mesh}function buildGrid(size=vec2(1),segments=1,color,heightFunction,smooth=heightFunction&&render3D?.smoothShading){heightFunction||=()=>0,size=render3DSize2(size),segments=render3DSize2(segments);var mesh=new Mesh;const segmentsX=segments.x,segmentsZ=segments.y,cellX=size.x/segmentsX,cellZ=size.y/segmentsZ,halfX=size.x/2,halfZ=size.y/2,ex=cellX/2,ez=cellZ/2,px=i=>i*cellX-halfX,pz=j=>j*cellZ-halfZ,colorAt=color,cellColor=(i,j)=>color?isColor(color)?color:colorAt(px(i),pz(j)):WHITE;var row=j=>{var points=[],normals=[],uvs=[],colors=[],z=pz(j);for(let i=0;i<=segmentsX;++i){var x=px(i);points.push(vec3(x,heightFunction(x,z),z)),uvs.push(vec2(i/segmentsX,j/segmentsZ)),smooth&&(normals.push(render3DSlopeNormal(heightFunction,x,z,ex,ez,halfX,halfZ)),colors.push(cellColor(i,j)))}return{points:points,normals:normals,uvs:uvs,colors:colors}};let above=row(0);for(let j=0;j<segmentsZ;++j){var below=row(j+1);if(smooth){var points=[],normals=[],uvs=[],colors=[];for(let i=0;i<=segmentsX;++i)points.push(above.points[i],below.points[i]),normals.push(above.normals[i],below.normals[i]),uvs.push(above.uvs[i],below.uvs[i]),colors.push(above.colors[i],below.colors[i]);mesh.addStrip(points,normals,uvs,colors)}else for(let i=0;i<segmentsX;++i)mesh.addQuad(above.points[i],below.points[i],below.points[i+1],above.points[i+1],cellColor(i+.5,j+.5),[above.uvs[i],below.uvs[i],below.uvs[i+1],above.uvs[i+1]]);above=below}if(smooth){var n=2*(segmentsX+1)+2,keys=mesh.vertexKeys=new Int32Array(mesh.points.length),id=(i,j)=>j*(segmentsX+1)+i;for(let j=0;j<segmentsZ;++j){var start=j*n;keys[start]=id(0,j);for(let i=0;i<=segmentsX;++i)keys[1+start+2*i]=id(i,j),keys[2+start+2*i]=id(i,j+1);keys[start+n-1]=id(segmentsX,j+1)}}return mesh.doubleSided=!0,mesh}function buildSky(topColor=hsl(.6,.8,.55),horizonColor=hsl(.6,1,.9),bottomColor=horizonColor,sides=16,rings=8){sides=buildSphere(2,sides,rings,!0).flipNormals();return sides.colors=sides.points.map(p=>p.y<0?horizonColor.lerp(bottomColor,-p.y):horizonColor.lerp(topColor,p.y)),sides}class EngineObject3D extends EngineObject{constructor(pos3D=vec3(),mesh,tileInfo,color=WHITE){tileInfo instanceof TextureInfo&&render3DWholeTiles.add(tileInfo=new TileInfo(vec2(),tileInfo.size,tileInfo,0,0)),super(vec2(pos3D.x,pos3D.y),vec2(),tileInfo,0,color),this.mass=0,this.pos3D=pos3D.copy(),this.rotation3D=vec3(),this.scale3D=vec3(1),this.velocity3D=vec3(),this.angleVelocity3D=vec3();color=(this.mesh=mesh)&&mesh.points.length?!mesh.dirty&&mesh.bounds||mesh.getBounds():void 0;this.size3D=color?color.max.subtract(color.min):vec3(1),this.softShadow=0,this.upright=!1,this.pixelated=!1,this.sync2D=!1,this.transparent=!mesh&&!!tileInfo,this.additive=!1,this.emissive=0,this.specular=0,this.castShadow=!0,this.collideAsSphere3D=!1,this.receiveShadow=!0,this.renderAfter2D=void 0,this.localMatrix=void 0,this.worldMatrix=new Matrix4,this.matrixBuilt=new Float64Array(9).fill(NaN),this.matrixVersion=0,this.matrixParent=void 0,this.matrixParentVersion=0,this.movePass=engineObjectsUpdateCount}updatePhysics(){this.sync2D&&super.updatePhysics();var oldPos=this.collideLevel&&this.mass&&!this.sync2D?this.pos3D.copy():void 0;if(render3DMove(this),oldPos&&function(o,oldPos){var ground=o.groundObject;o.groundObject=void 0;for(const level of render3DLevel)level.destroyed||level.levelCollide3D(o,oldPos,level===ground)}(this,oldPos),this.collideSolidObjects&&!this.sync2D){var a=this;let shapeA=render3DSolidShape(a);var q,dy,p,reach,dx,reachA=render3DSolidReach(a);for(const b of engineObjectsCollide){if(b===a)break;!b.destroyed&&b instanceof EngineObject3D&&!b.parent&&!b.sync2D&&(a.isSolid||b.isSolid)&&(p=shapeA.pos,q=b.pos3D,reach=reachA+render3DSolidReach(b),dx=p.x-q.x,dy=p.y-q.y,p=p.z-q.z,reach*reach<dx*dx+dy*dy+p*p||(q=function(a,b){if(!a.size)return b.size?collideSphereBox(a.pos,a.radius,b.pos,b.size):collideSphereSphere(a.pos,a.radius,b.pos,b.radius);if(b.size)return collideBoxBox3D(a.pos,a.size,b.pos,b.size);{b=collideSphereBox(b.pos,b.radius,a.pos,a.size);return b&&b.scale(-1)}}(shapeA,render3DSolidShape(b)))&&(reach=a.collideWithObject(b,q),dx=b.collideWithObject(a,q.scale(-1)),reach)&&dx&&(dy=a.mass+b.mass,p=a.mass?b.mass?b.mass/dy:1:0,reach=b.mass?a.mass?a.mass/dy:1:0,a.pos3D=a.pos3D.add(q.scale(p)),b.pos3D=b.pos3D.subtract(q.scale(reach)),p&&(shapeA=render3DSolidShape(a)),dx=q.normalize(),p&&a.velocity3D.dot(dx)<0&&(a.velocity3D=a.velocity3D.reflect(dx,a.restitution)),reach)&&0<b.velocity3D.dot(dx)&&(b.velocity3D=b.velocity3D.reflect(dx,b.restitution)))}}}updateTransforms(updateChildren=!0){if(paused||this.parent&&this.movePass!==engineObjectsUpdateCount&&(this.movePass=engineObjectsUpdateCount,render3DMove(this)),super.updateTransforms(!1),!paused&&this.sync2D&&(this.pos3D.x=this.pos.x,this.pos3D.y=this.pos.y,this.rotation3D.z=-this.angle),updateChildren)for(const child of this.children)child.updateTransforms()}setCollision(collideSolidObjects=!0,isSolid=!0,collideLevel=!0,collideRaycast=!1){super.setCollision(collideSolidObjects,isSolid,collideLevel,collideRaycast)}collideWithVoxel(type,cell){return!0}getWorldPos3D(){return render3DObjectMatrix(this).getTranslation()}getForward3D(){return render3DAxis(render3DObjectMatrix(this).m,8).normalize(-1)}getRight3D(){return render3DAxis(render3DObjectMatrix(this).m,0).normalize()}getUp3D(){return render3DAxis(render3DObjectMatrix(this).m,4).normalize()}getMatrix(){return render3DObjectMatrix(this).copy()}lookAt(target){var parent=this.parent instanceof EngineObject3D?this.parent:void 0,parent=parent?parent.getMatrix().invert().transformPoint(target):target;this.rotation3D=render3DLookRotation(parent.subtract(this.pos3D),this.rotation3D)}addChild(child,localPos,localAngle){return child instanceof EngineObject3D&&(child.movePass=engineObjectsUpdateCount),super.addChild(child,localPos,localAngle)}attach(child){if(!(child instanceof EngineObject3D))return super.attach(child);child.parent?.removeChild(child);var local=render3DObjectMatrix(this).copy().invert().multiply(render3DObjectMatrix(child));return super.attach(child),child.pos3D=local.getTranslation(),child.rotation3D=local.getRotation(),child.scale3D=local.getScale(),child.localMatrix&&(child.localMatrix=local),child}removeChild(child){var world;child instanceof EngineObject3D&&!child.destroyed&&(world=render3DObjectMatrix(child),child.pos3D=world.getTranslation(),child.rotation3D=world.getRotation(),child.scale3D=world.getScale(),child.localMatrix)&&(child.localMatrix=world.copy()),super.removeChild(child)}setMesh(mesh){const old=this.mesh;return this.mesh=mesh,old&&old!==mesh&&old.buffer&&!engineObjects.some(o=>o.mesh===old)&&old.dispose(),mesh}render(){}render3D(){var size,matrix=render3DObjectMatrix(this);this.mesh?render3D.drawMesh(this.mesh,matrix,this.tileInfo,this.color):this.tileInfo&&(size=render3DWorldSize(this,matrix=matrix.m),render3D.drawBillboard(vec3(matrix[12],matrix[13],matrix[14]),vec2(size.x,size.y),this.tileInfo,this.color,this.rotation3D.z,this.upright))}}const render3DLocalMatrix=new Matrix4;function render3DObjectMatrix(o){var parent=o.parent instanceof EngineObject3D?o.parent:void 0,parentMatrix=parent&&render3DObjectMatrix(parent),p=o.pos3D,r=o.rotation3D,s=o.scale3D,k=o.matrixBuilt,local=o.localMatrix;return local?(parent?(o.worldMatrix.m.set(parentMatrix.m),o.worldMatrix.multiply(local),o.matrixParentVersion=parent.matrixVersion):o.worldMatrix.m.set(local.m),k[0]=NaN,o.matrixParent=parent,++o.matrixVersion):(k[0]!==p.x||k[1]!==p.y||k[2]!==p.z||k[3]!==r.x||k[4]!==r.y||k[5]!==r.z||k[6]!==s.x||k[7]!==s.y||k[8]!==s.z||o.matrixParent!==parent||parent&&o.matrixParentVersion!==parent.matrixVersion)&&(k[0]=p.x,k[1]=p.y,k[2]=p.z,k[3]=r.x,k[4]=r.y,k[5]=r.z,k[6]=s.x,k[7]=s.y,k[8]=s.z,parent?(buildMatrix(p,r,s,render3DLocalMatrix),o.worldMatrix.m.set(parentMatrix.m),o.worldMatrix.multiply(render3DLocalMatrix),o.matrixParentVersion=parent.matrixVersion):buildMatrix(p,r,s,o.worldMatrix),o.matrixParent=parent,++o.matrixVersion),o.worldMatrix}function render3DMove(o){var g,s,p=o.pos3D,v=o.velocity3D,r=o.rotation3D,a=o.angleVelocity3D;o.mass&&!o.sync2D&&(g=render3D.gravity,s=o.gravityScale,o=o.damping,v.x=v.x*o+g.x*s,v.y=v.y*o+g.y*s,v.z=v.z*o+g.z*s),p.x+=v.x,p.y+=v.y,p.z+=v.z,r.x+=a.x,r.y+=a.y,r.z+=a.z}function render3DSolidShape(o){var s=o.size3D,k=o.scale3D,kx=abs(k.x),ky=abs(k.y),k=abs(k.z);return o.collideAsSphere3D?{pos:o.pos3D.copy(),radius:max(s.x,s.y,s.z)/2*max(kx,ky,k)}:{pos:o.pos3D.copy(),size:vec3(s.x*kx,s.y*ky,s.z*k)}}function render3DSolidReach(o){var s=o.size3D,k=o.scale3D,kx=abs(k.x),ky=abs(k.y),k=abs(k.z);return o.collideAsSphere3D?max(s.x,s.y,s.z)/2*max(kx,ky,k):hypot(s.x*kx,s.y*ky,s.z*k)/2}function engineObjectsCollect3D(pos,size,objects=engineObjects,testCenters=!1){var radiusSquared="number"==typeof size&&0<size?(size/2)**2:void 0,box=radiusSquared?void 0:"number"==typeof size?vec3():render3DSize3(size),collected=[];for(const o of objects)if(o instanceof EngineObject3D&&!o.destroyed){var m=render3DObjectMatrix(o).m,s=o.size3D;if(s.x||s.y||s.z){var dx,s=vec3(m[12],m[13],m[14]),m=testCenters?vec3():render3DWorldSize(o,m);let hit;(hit=box?isOverlapping3D(pos,box,s,m):(dx=max(abs(pos.x-s.x)-m.x/2,0))*dx+(dx=max(abs(pos.y-s.y)-m.y/2,0))*dx+(dx=max(abs(pos.z-s.z)-m.z/2,0))*dx<radiusSquared)&&collected.push(o)}}return collected}function render3DRaycastObject(ray,o){if(!o.destroyed&&o instanceof EngineObject3D){if(o instanceof HeightMap||o instanceof VoxelMap)return o.levelRaycast3D(ray);if((o.mesh||o.tileInfo)&&!(o instanceof InstancedMesh3D)){var matrix=render3DObjectMatrix(o),mesh=o.mesh;if(!mesh)return function(ray,o,matrix){var center=matrix.getTranslation(),matrix=render3DWorldSize(o,matrix.m),matrix=vec2(matrix.x,matrix.y);var right,t,p;if(0<matrix.x&&0<matrix.y)return ray.direction.lengthSquared()?(o=render3DBillboardAxes(matrix,o.rotation3D.z,o.upright),right=vec3(o[0],o[1],o[2]),o=vec3(o[3],o[4],o[5]),void 0!==(t=raycastPlane(ray,center,right.cross(o)))&&(p=ray.getPosition(t).subtract(center),abs(p.dot(right))<=right.lengthSquared())&&abs(p.dot(o))<=o.lengthSquared()?t:void 0):render3DRaycastDisc(ray,center,hypot(matrix.x,matrix.y)/2)}(ray,o,matrix);o=(mesh.dirty||!mesh.radius?mesh.computeRadius():mesh.radius)*render3DMaxStretch(matrix.m);if(0<o){var center=matrix.getTranslation(),distance=raycastSphere(ray,center,o);if(void 0!==distance){if(!matrix.determinant())return render3DRaycastDisc(ray,center,o);var distance=matrix.copy().invert(),bounds=mesh.bounds||mesh.getBounds(),local=new Ray3D(distance.transformPoint(ray.origin),distance.transformDirection(ray.direction)),center=raycastBox(local,bounds.min.add(bounds.max).scale(.5),bounds.max.subtract(bounds.min));if(0!==center)return center;let exit=1/0;for(const k of["x","y","z"]){var d=local.direction[k];d&&(exit=min(exit,((0<d?bounds.max:bounds.min)[k]-local.origin[k])/d))}return exit===1/0?0:exit}}}}}function render3DRaycastDisc(ray,center,radius){var d=ray.direction,center=center.subtract(ray.origin),ray=d.dot(d),ray=ray?center.dot(d)/ray:0;return 0<=ray&¢er.subtract(d.scale(ray)).lengthSquared()<=radius*radius?ray:void 0}function engineObjectsRaycast3D(ray,objects=engineObjects){var hits=[];for(const o of objects){var distance=render3DRaycastObject(ray,o);void 0!==distance&&hits.push({o:o,distance:distance})}return hits.sort((a,b)=>a.distance-b.distance).map(hit=>hit.o)}function engineObjectsCallback3D(pos,size,callback,objects=engineObjects,testCenters=!1){for(const o of engineObjectsCollect3D(pos,size,objects,testCenters))o.destroyed||callback(o)}class InstancedMesh3D extends EngineObject3D{constructor(mesh,count,tileInfo,color=WHITE){super(vec3(),mesh,tileInfo,color),this.size3D=vec3(),this.count=count,this.maxCount=count,this.instanceData=new Float32Array(count*RENDER3D_INSTANCE_FLOATS),this.radius=0,this.reach=0,this.maxScale=0,this.dirtyStart=0,this.dirtyEnd=count,this.buffer=void 0,this.bufferGeneration=-1;var uv=render3DGetTileUVs(this.uvTileInfo=tileInfo),data=this.instanceData;for(let i=0;i<count;++i){this.setMatrixAt(i,RENDER3D_IDENTITY);var k=i*RENDER3D_INSTANCE_FLOATS;data[16+k]=color.r,data[17+k]=color.g,data[18+k]=color.b,data[19+k]=color.a,data[20+k]=uv.x,data[21+k]=uv.y,data[22+k]=uv.w,data[23+k]=uv.h}}setMatrixAt(i,matrix){var data=this.instanceData,k=i*RENDER3D_INSTANCE_FLOATS,matrix=matrix.m;data.set(matrix,k),this.markDirty(i)}getMatrixAt(i){var i=i*RENDER3D_INSTANCE_FLOATS,matrix=new Matrix4;return matrix.m.set(this.instanceData.subarray(i,16+i)),matrix}setColorAt(i,color){var data=this.instanceData,k=i*RENDER3D_INSTANCE_FLOATS;data[16+k]=color.r,data[17+k]=color.g,data[18+k]=color.b,data[19+k]=color.a,this.markDirty(i)}markDirty(i){this.dirtyStart=min(this.dirtyStart,i),this.dirtyEnd=max(this.dirtyEnd,i+1);var d=this.instanceData,i=i*RENDER3D_INSTANCE_FLOATS,d=(this.reach=max(this.reach,hypot(d[12+i],d[13+i],d[14+i])),this.maxScale=max(this.maxScale,render3DMaxStretch(d,i)),this.mesh.radius||this.mesh.computeRadius());this.radius=this.reach+d*this.maxScale}render3D(){var r=render3D,gl=glContext,mesh=this.mesh;if(r.transparentQueue)return r.queueTransparent(this.getWorldPos3D(),()=>this.render3D());if(mesh&&this.count&&render3DCanDraw()&&(!r.shadowPass||r.lighting)&&(render3DMeshUpload(mesh),mesh.bufferCount)&&(this.radius=this.reach+mesh.radius*this.maxScale,!r.frustumCulling||render3DSphereVisible(0,0,0,this.radius))){if(this.uvTileInfo!==this.tileInfo){var uv=render3DGetTileUVs(this.tileInfo),data=this.instanceData;for(let k=20;k<data.length;k+=RENDER3D_INSTANCE_FLOATS)data[k]=uv.x,data[k+1]=uv.y,data[k+2]=uv.w,data[k+3]=uv.h;this.uvTileInfo=this.tileInfo,this.dirtyStart=0,this.dirtyEnd=this.maxCount}this.buffer&&this.bufferGeneration===r.contextGeneration?this.dirtyEnd>this.dirtyStart&&(start=this.dirtyStart*RENDER3D_INSTANCE_FLOATS,end=this.dirtyEnd*RENDER3D_INSTANCE_FLOATS,gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer),gl.bufferSubData(gl.ARRAY_BUFFER,4*start,this.instanceData,start,end-start)):(this.buffer=gl.createBuffer(),this.bufferGeneration=r.contextGeneration,gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer),gl.bufferData(gl.ARRAY_BUFFER,this.instanceData,gl.DYNAMIC_DRAW)),this.dirtyStart=1/0,this.dirtyEnd=0,r.flush(),render3DFlushBeforeOverlay();var end=r.cullBackFaces,start=this.tileInfo;r.cullBackFaces=!mesh.doubleSided,render3DDrawInstanced(mesh,this.buffer,this.count,render3DTextureOf(start),r),r.cullBackFaces=end}}destroy(immediate){this.buffer&&this.bufferGeneration===render3D?.contextGeneration&&glContext?.deleteBuffer(this.buffer),this.buffer=void 0,super.destroy(immediate)}}class Light3D extends EngineObject3D{constructor(pos3D=vec3(),radius=5,color=WHITE,intensity=1){super(pos3D,void 0,void 0,color),this.size3D=vec3(),this.radius=radius,this.intensity=intensity,this.directional=!1}render3D(){}}class DirectionalLight3D extends Light3D{constructor(pos3D=vec3(0,1,0),color=WHITE,intensity=1){super(pos3D,0,color,intensity),this.directional=!0}}function render3DDetach(o){o.parent?o.parent.removeChild(o):o.worldPos3D&&(o.pos3D=o.worldPos3D)}let render3DSoftDotTexture;function render3DBevelProfile(corner,d1,d2,t,segments){var p1=[corner[0]+d1[0]*t,corner[1]+d1[1]*t],p2=[corner[0]+d2[0]*t,corner[1]+d2[1]*t];if(1===(segments=max(1,0|segments)))return[p1,p1,p2,p2];var half=Math.acos(clamp(d1[0]*d2[0]+d1[1]*d2[1],-1,1))/2,bx=d1[0]+d2[0],d1=d1[1]+d2[1],d2=t/cos(half)/hypot(bx,d1),cx=corner[0]+bx*d2,cy=corner[1]+d1*d2,radius=t*tan(half),start=atan2(p1[1]-cy,p1[0]-cx),turn=mod(atan2(p2[1]-cy,p2[0]-cx)-start+PI,2*PI)-PI,points=[p1];for(let i=1;i<segments;++i){var a=start+turn*i/segments;points.push([cx+cos(a)*radius,cy+sin(a)*radius])}return points.push(p2),points}function buildCylinder(size=1,height=1,sides=16,smooth=render3D?.smoothShading,capped=!0,bevel=0,bevelSegments=1){size/=2,height/=2,bevel=min(bevel,size,height);return buildLathe(0<bevel?[...render3DBevelProfile([size,-height],[-1,0],[0,1],bevel,bevelSegments),...render3DBevelProfile([size,height],[0,-1],[-1,0],bevel,bevelSegments)]:[[size,-height],[size,height]],sides,smooth,capped)}function buildCone(size=1,height=1,sides=16,smooth=render3D?.smoothShading,capped=!0,bevel=0,bevelSegments=1){var size=size/2,h=height/2,slant=hypot(size,height),bevel=min(bevel,size,slant/2);return buildLathe(0<bevel?[...render3DBevelProfile([size,-h],[-1,0],[-size/slant,height/slant],bevel,bevelSegments),[0,h]]:[[size,-h],[0,h]],sides,smooth,capped)}function buildCapsule(size=1,height=1,sides=16,rings=4,smooth=render3D?.smoothShading){var profile=[],r=size/2,straight=max(0,height-size)/2;for(let i=0;i<=rings;++i){var a=i/rings*PI/2;profile.push([r*sin(a),-straight-r*cos(a)])}for(let i=0;i<=rings;++i){const a=i/rings*PI/2;profile.push([r*cos(a),straight+r*sin(a)])}return buildLathe(profile,sides,smooth)}function buildTorus(size=1,tubeSize=.3,sides=16,tubeSides=8,smooth=render3D?.smoothShading){var profile=[],radius=(size-tubeSize)/2,tubeRadius=tubeSize/2;for(let i=0;i<=tubeSides;++i){var a=i/tubeSides*2*PI;profile.push([radius+tubeRadius*cos(a),tubeRadius*sin(a)])}return buildLathe(profile,sides,smooth)}function buildRibbon(points,width=1,color=WHITE,closed=!1,up=vec3(0,1,0)){var mesh=new Mesh,count=points.length,edges=[];let across=(abs(up.y)<.9?vec3(0,1,0):vec3(1,0,0)).cross(up).normalize();for(let i=0;i<count;++i){var next=points[closed?(i+1)%count:min(i+1,count-1)],last=points[closed?(i+count-1)%count:max(i-1,0)],next=next.subtract(last).cross(up),last=(across=1e-12<next.lengthSquared()?next.normalize():across).scale((isArray(width)?width[i]:width)/2);edges.push([points[i].subtract(last),points[i].add(last)])}for(let i=0;i+1<count+(closed?1:0);++i){var j=(i+1)%count,a=edges[i],b=edges[j],j=isArray(color)?[color[i],color[i],color[j],color[j]]:color;mesh.addQuad(a[0],a[1],b[1],b[0],j)}return mesh.doubleSided=!0,mesh}function buildLoft(stations){var mesh=new Mesh,section=([z,w,t,b,m=.5])=>[vec3(-w/2,lerp(b,t,m),z),vec3(0,t,z),vec3(w/2,lerp(b,t,m),z),vec3(0,b,z)];for(let i=0;i+1<stations.length;++i){var s1=section(stations[i]),s2=section(stations[i+1]);for(let k=0;k<4;++k)mesh.addQuad(s1[k],s1[(k+1)%4],s2[(k+1)%4],s2[k])}var tail=section(stations[stations.length-1]),nose=section(stations[0]);return mesh.addQuad(tail[0],tail[1],tail[2],tail[3]),mesh.addQuad(nose[3],nose[2],nose[1],nose[0]),mesh}function buildExtrude(pixels,size=vec2(1),depth=1){let rows=pixels,width,height;if(pixels instanceof TileInfo){var image=function(textureInfo){textureInfo=textureInfo.image;let pixels=render3DPixelCache.get(textureInfo);pixels||render3DPixelCache.set(textureInfo,pixels=render3DImageData(textureInfo));return pixels}(pixels.textureInfo),data=image.data,x0=0|pixels.pos.x,y0=0|pixels.pos.y;width=0|pixels.size.x,height=0|pixels.size.y,rows=[];for(let y=0;y<height;++y){var row=rows[y]=[];for(let x=0;x<width;++x){var k=4*((y0+y)*image.width+x0+x);row.push(127<data[3+k]?rgb(data[k]/255,data[1+k]/255,data[2+k]/255):void 0)}}}else height=rows.length,width=rows[0].length;const solid=(x,y)=>{if(!(x<0||y<0||x>=width||y>=height)){y=rows[y]&&rows[y][x];if(y)return isColor(y)?.5<y.a?y:void 0:WHITE}};var runs=(count,colorAt,emit)=>{let start=0,color;for(let i=0;i<=count;++i){var c=i<count?colorAt(i):void 0;a=c,b=color,a!==b&&(!a||!b||a.rgbaInt()!==b.rgbaInt())&&(color&&emit(start,i,color),start=i,color=c)}var a,b};const mesh=new Mesh,sx=size.x/width,sy=size.y/height,hz=depth/2,px=x=>x*sx-size.x/2,py=y=>size.y/2-y*sy,quad=(origin,right,up,normal,color)=>mesh.addStrip(render3DQuadAxes(origin.add(right.scale(.5)).add(up.scale(.5)),right.scale(.5),up.scale(.5)),normal,RENDER3D_QUAD_UVS,color),X=vec3(1,0,0),Y=vec3(0,1,0),Z=vec3(0,0,1);for(let y=0;y<height;++y)runs(width,x=>solid(x,y),(a,b,c)=>{var w=X.scale((b-a)*sx),h=Y.scale(sy);quad(vec3(px(a),py(y+1),hz),w,h,Z,c),quad(vec3(px(b),py(y+1),-hz),w.scale(-1),h,Z.scale(-1),c)}),runs(width,x=>solid(x,y-1)?void 0:solid(x,y),(a,b,c)=>quad(vec3(px(a),py(y),hz),X.scale((b-a)*sx),Z.scale(-depth),Y,c)),runs(width,x=>solid(x,y+1)?void 0:solid(x,y),(a,b,c)=>quad(vec3(px(a),py(y+1),-hz),X.scale((b-a)*sx),Z.scale(depth),Y.scale(-1),c));for(let x=0;x<width;++x)runs(height,y=>solid(x-1,y)?void 0:solid(x,y),(a,b,c)=>quad(vec3(px(x),py(b),-hz),Z.scale(depth),Y.scale((b-a)*sy),X.scale(-1),c)),runs(height,y=>solid(x+1,y)?void 0:solid(x,y),(a,b,c)=>quad(vec3(px(x+1),py(b),hz),Z.scale(-depth),Y.scale((b-a)*sy),X,c));return mesh}function buildText3D(text,size=1,depth=.2,font=engineImageFont){const tileInfo=font.tileInfo;let glyphs=render3DGlyphCache.get(font);glyphs||render3DGlyphCache.set(font,glyphs=new Map);const charSize=vec2(size*tileInfo.size.x/tileInfo.size.y,size),mesh=new Mesh,lines=(text+"").split("\n");return lines.forEach((line,j)=>{var y=((lines.length-1)/2-j)*charSize.y*1.3;for(let i=0;i<line.length;++i){var charCode=line.charCodeAt(i),charCode=charCode<32||127<charCode?95:charCode-32;if(charCode){let glyph=glyphs.get(charCode);glyph||(pos=font.getGlyphPos(charCode),glyphs.set(charCode,glyph=buildExtrude(new TileInfo(pos,tileInfo.size,tileInfo.textureInfo))));var pos,charCode=(i-(line.length-1)/2)*charSize.x;mesh.combine(glyph,buildMatrix(vec3(charCode,y,0),void 0,vec3(charSize.x,charSize.y,depth)))}}}),mesh}class HeightMap extends EngineObject3D{constructor(heights,mapSize=vec2(1),height=1,colors,pos3D=vec3(),smooth=render3D?.smoothShading){super(pos3D),isArray(heights)||(heights=render3DImageToArray(heights,r=>r/255)),colors&&!isArray(colors)&&(colors=render3DImageToArray(colors,(r,g,b,a)=>rgb(r/255,g/255,b/255,a/255))),this.heights=heights,this.colors=colors,this.mapSize=mapSize.copy(),this.height=height,this.smooth=!!smooth,this.size3D=vec3(mapSize.x,height,mapSize.y),this.rebuild(),render3DLevel.push(this)}get rows(){return this.heights.length}get columns(){return this.heights[0].length}getHeight(x,z){x instanceof Vector3&&(z=x.z,x=x.x);var p=this.pos3D,columns=(x-=p.x,z-=p.z,this.columns),rows=this.rows,h=this.heights,x=clamp((x/this.mapSize.x+.5)*(columns-1),0,columns-1),z=clamp((z/this.mapSize.y+.5)*(rows-1),0,rows-1),columns=min(floor(x),columns-2),rows=min(floor(z),rows-2),x=x-columns,z=z-rows,a=h[rows][columns],b=h[rows+1][columns],c=h[rows+1][columns+1],h=h[rows][columns+1];return(x+z<=1?a+x*(h-a)+z*(b-a):c+(1-x)*(b-c)+(1-z)*(h-c))*this.height+p.y}getNormal(x,z){x instanceof Vector3&&(z=x.z,x=x.x);const p=this.pos3D,size=this.mapSize;var ex=size.x/(this.columns-1)/2,ez=size.y/(this.rows-1)/2;return render3DSlopeNormal((lx,lz)=>this.getHeight(lx+p.x,lz+p.z),x-p.x,z-p.z,ex,ez,size.x/2,size.y/2)}getColor(x,z){x instanceof Vector3&&(z=x.z,x=x.x);var c=this.colors;if(!c)return WHITE;x-=this.pos3D.x,z-=this.pos3D.z;var columns=c[0].length,rows=c.length,x=clamp(round((x/this.mapSize.x+.5)*(columns-1)),0,columns-1);return c[clamp(round((z/this.mapSize.y+.5)*(rows-1)),0,rows-1)][x]}raycast(ray){const p=this.pos3D,origin=ray.origin.subtract(p),direction=ray.direction;var ray=this.mapSize,height=this.height,length=direction.length();if(length){var start=raycastBox(new Ray3D(origin,direction),vec3(0,height/2,0),vec3(ray.x,abs(height)+.001,ray.y));if(void 0!==start&&(0<ray.x&&0<ray.y)){let end=start+hypot(ray.x,ray.y,height)/length;direction.x&&(end=min(end,(sign(direction.x)*ray.x/2-origin.x)/direction.x)),direction.z&&(end=min(end,(sign(direction.z)*ray.y/2-origin.z)/direction.z));var f0,f1,height=(this.columns-1)/ray.x,length=(this.rows-1)/ray.y,u0=(origin.x/ray.x+.5)*(this.columns-1),height=direction.x*height,ray=(origin.z/ray.y+.5)*(this.rows-1),length=direction.z*length,breaks=[start,end];for([f0,f1]of[[u0,height],[ray,length],[u0+ray,height+length]])if(f1){const a=f0+f1*start,b=f0+f1*end;for(let k=ceil(min(a,b));k<=max(a,b);++k)breaks.push((k-f0)/f1)}breaks.sort((a,b)=>a-b);var above=at=>{at=origin.add(direction.scale(at));return at.y+p.y-this.getHeight(at.x+p.x,at.z+p.z)},touching=d=>abs(d)<=1e-9;let a=start,da=above(a);if(touching(da))return start;var startUnder=da<=0;for(const b of breaks)if(!(b<=a||b>end)){var db=above(b);if(touching(db))return b;if(db<=0!=startUnder)return da===db?b:a+(b-a)*da/(da-db);a=b,da=db}}}}buildMesh(smooth=render3D?.smoothShading){const p=this.pos3D,size=this.mapSize,nudgeX=smooth?0:size.x/(this.columns-1)/1e3,nudgeZ=smooth?0:size.y/(this.rows-1)/1e3;return buildGrid(size,vec2(this.columns-1,this.rows-1),this.colors&&((x,z)=>this.getColor(x-nudgeX+p.x,z-nudgeZ+p.z)),(x,z)=>this.getHeight(x+p.x,z+p.z)-p.y,smooth)}rebuild(){this.setMesh(this.buildMesh(this.smooth))}levelRaycast3D(ray){return this.raycast(ray)}levelCollide3D(o,oldPos,wasOn){var p=o.pos3D,m=this.pos3D,size=this.mapSize;abs(p.x-m.x)>size.x/2||abs(p.z-m.z)>size.y/2||(m=o.size3D.y*abs(o.scale3D.y)/2,size=this.getHeight(p.x,p.z),wasOn=wasOn&&o.velocity3D.y<=0?hypot(p.x-oldPos.x,p.z-oldPos.z):0,p.y-m>size+wasOn)||(p.y=size+m,(oldPos=o.velocity3D).y<0&&(oldPos.y*=-max(o.restitution,this.restitution)),o.groundObject=this)}update(){super.update()}destroy(immediate){render3DLevelLeave(this),super.destroy(immediate)}}function render3DImageData(image){var width=(image=image instanceof TextureInfo?image.image:image).width,height=image.height;return workReadCanvas.width=width,workReadCanvas.height=height,workReadContext.drawImage(image,0,0),workReadContext.getImageData(0,0,width,height)}function render3DImageToArray(image,sample){var{data,width,height}=render3DImageData(image),rows=[];for(let y=0;y<height;++y){var row=rows[y]=[];for(let x=0;x<width;++x){var k=4*(y*width+x);row.push(sample(data[k],data[1+k],data[2+k],data[3+k]))}}return rows}const render3DGlyphCache=new WeakMap,render3DPixelCache=new WeakMap;class CameraControl3D extends EngineObject3D{constructor(target=vec3(),distance=10,pitch=.4,idleSpin=0){super(target),this.size3D=vec3(),this.distance=distance,this.pitch=pitch,this.idleSpin=idleSpin,this.yaw=0,this.dragButton=0,this.dragSpeed=.01,this.zoomSpeed=.1,this.zoomRange=vec2(distance/4,3*distance),this.pitchRange=vec2(min(-.2,pitch),max(1.4,pitch))}update(){mouseIsDown(this.dragButton)?(this.yaw-=mouseDeltaScreen.x*this.dragSpeed,this.pitch+=mouseDeltaScreen.y*this.dragSpeed):this.yaw+=this.idleSpin,this.pitch=clamp(this.pitch,this.pitchRange.x,this.pitchRange.y),this.zoomSpeed&&mouseWheel&&(this.distance=clamp(this.distance*(1+sign(mouseWheel)*this.zoomSpeed),this.zoomRange.x,this.zoomRange.y)),render3D.camera.orbit(this.getWorldPos3D(),this.distance,this.yaw,this.pitch)}render3D(){}}class FirstPersonCamera3D extends EngineObject3D{constructor(pos3D=render3D.camera.pos,yaw=render3D.camera.rotation.y,pitch=render3D.camera.rotation.x){super(pos3D),this.size3D=vec3(),this.mass=1,this.yaw=yaw,this.pitch=pitch,this.moveSpeed=.1,this.lookSpeed=.003,this.pitchRange=vec2(-1.5,1.5),this.fly=!1,this.lockPointer=!0,this.jumpSpeed=0,this.eyeHeight=0}updatePhysics(){var gravityScale=this.gravityScale;this.fly&&(this.gravityScale=0),super.updatePhysics(),this.gravityScale=gravityScale}update(){this.lockPointer&&mouseWasPressed(0)&&pointerLockRequest(),(pointerLockIsActive()||mouseIsDown(0))&&(this.yaw-=mouseDeltaScreen.x*this.lookSpeed,this.pitch-=mouseDeltaScreen.y*this.lookSpeed),this.pitch=clamp(this.pitch,this.pitchRange.x,this.pitchRange.y);var input=keyDirection(),input=vec3(input.x,0,-input.y).clampLength(1).scale(this.moveSpeed).rotateX(this.fly?this.pitch:0).rotateY(this.yaw),input=(this.velocity3D=this.fly?input:vec3(input.x,this.velocity3D.y,input.z),this.jumpSpeed&&!this.fly&&this.groundObject&&(keyWasPressed("Space")||gamepadWasPressed(0))&&(this.velocity3D.y=this.jumpSpeed),vec3(this.pitch,this.yaw,0));render3D.camera.pos=render3DObjectMatrix(this).transformPoint(vec3(0,this.eyeHeight,0)),render3D.camera.rotation=this.parent instanceof EngineObject3D?this.parent.getMatrix().multiply(Matrix4.rotation(input)).getRotation():input}destroy(immediate){this.lockPointer&&pointerLockIsActive()&&pointerLockExit(),super.destroy(immediate)}render3D(){}}const render3DParticlePos=vec3(),render3DParticleColor=new Color,render3DParticleSize=vec2();class ParticleEmitter3D extends EngineObject3D{constructor(pos3D=vec3(),emitSize=0,emitTime=0,emitRate=100,emitConeAngle=PI,tileInfo,colorStartA=WHITE,colorStartB=WHITE,colorEndA=CLEAR_WHITE,colorEndB=CLEAR_WHITE,particleTime=.5,sizeStart=.1,sizeEnd=1,speed=.1,damping=1,gravity=0,fadeRate=.1,randomness=.2,additive=!1){super(pos3D,void 0,tileInfo),this.transparent=!0,this.castShadow=!1,this.size3D=vec3(),this.emitSize=emitSize,this.emitTime=emitTime,this.emitRate=emitRate,this.emitConeAngle=emitConeAngle,this.colorStartA=colorStartA.copy(),this.colorStartB=colorStartB.copy(),this.colorEndA=colorEndA.copy(),this.colorEndB=colorEndB.copy(),this.particleTime=particleTime,this.sizeStart=sizeStart,this.sizeEnd=sizeEnd,this.speed=speed,this.damping=damping,this.gravity=gravity,this.fadeRate=fadeRate,this.randomness=randomness,this.additive=additive,this.trailTime=0,this.angleSpeed=0,this.angleDamping=1,this.particleData=new Float32Array(1344),this.particleCount=0,this.trailData=void 0,this.trailMax=0,this.worldPos3D=void 0,this.emitTimeBuffer=0}update(){var matrix=render3DObjectMatrix(this),matrix=(this.worldPos3D=matrix.getTranslation(),render3DMaxScale(matrix.m));if(!this.emitTime||this.getAliveTime()<=this.emitTime){if(this.emitRate&&particleEmitRateScale)for(this.emitTimeBuffer+=this.emitRate*particleEmitRateScale*timeDelta;1<=this.emitTimeBuffer;--this.emitTimeBuffer)this.emitParticle()}else this.particleCount||this.destroy();var trailMax=this.trailTime?max(1,round(this.trailTime/timeDelta)):0;if(trailMax!==this.trailMax){this.trailMax=trailMax,this.trailData=trailMax?new Float32Array(this.particleData.length/21*trailMax*3):void 0;for(let k=20;k<this.particleData.length;k+=21)this.particleData[k]=0}var data=this.particleData,trail=this.trailData,damping=this.damping,gravity=this.gravity*matrix,angleDamping=this.angleDamping;for(let i=this.particleCount;i--;){var k=21*i,vx=data[3+k]*=damping,vy=data[4+k]=data[4+k]*damping+gravity,vz=data[5+k]*=damping,vx=(data[k]+=vx,data[1+k]+=vy,data[2+k]+=vz,data[18+k]+=data[19+k]*=angleDamping,i*trailMax*3);if(trailMax){let n=data[20+k];n===trailMax&&(trail.copyWithin(vx,3+vx,vx+3*n),--n);var vy=vx+3*n;trail[vy]=data[k],trail[1+vy]=data[1+k],trail[2+vy]=data[2+k],data[20+k]=n+1}(data[17+k]+=timeDelta)>=data[16+k]&&(vy=21*(vz=--this.particleCount),i!==vz)&&(data.copyWithin(k,vy,21+vy),trailMax)&&trail.copyWithin(vx,vz*trailMax*3,(1+vz)*trailMax*3)}}destroy(immediate){if(immediate||!this.particleCount||this.destroyed)return super.destroy(immediate);this.emitTime=-1,render3DDetach(this)}emitParticle(){var random=()=>rand(1-this.randomness,1+this.randomness),matrix=render3DObjectMatrix(this),scale=render3DMaxScale(matrix.m),size=this.emitSize,box=size,box=isVector3(size)?vec3(rand(-.5,.5)*box.x,rand(-.5,.5)*box.y,rand(-.5,.5)*box.z):randInSphere(size/2),size=matrix.transformDirection(randVector3(1,this.emitConeAngle)).normalize(),matrix=matrix.transformPoint(box),box=this.speed*random()*scale,colorStart=randColor(this.colorStartA,this.colorStartB,!0),colorEnd=randColor(this.colorEndA,this.colorEndB,!0),k=21*this.particleCount++,grown=(21+k>this.particleData.length&&((grown=new Float32Array(2*this.particleData.length)).set(this.particleData),this.particleData=grown,this.trailData)&&((grown=new Float32Array(2*this.trailData.length)).set(this.trailData),this.trailData=grown),this.particleData);grown[k]=matrix.x,grown[1+k]=matrix.y,grown[2+k]=matrix.z,grown[3+k]=size.x*box,grown[4+k]=size.y*box,grown[5+k]=size.z*box,grown[6+k]=colorStart.r,grown[7+k]=colorStart.g,grown[8+k]=colorStart.b,grown[9+k]=colorStart.a,grown[10+k]=colorEnd.r,grown[11+k]=colorEnd.g,grown[12+k]=colorEnd.b,grown[13+k]=colorEnd.a,grown[14+k]=this.sizeStart*random()*scale,grown[15+k]=this.sizeEnd*random()*scale,grown[16+k]=this.particleTime*random(),grown[17+k]=0,grown[18+k]=this.angleSpeed?rand(2*PI):0,grown[19+k]=this.angleSpeed?this.angleSpeed*random()*randSign():0,grown[20+k]=0}render3D(){var r=render3D;if(r.transparentQueue)return r.queueTransparent(this.getWorldPos3D(),()=>this.render3D());var fade=this.fadeRate/2,texture=this.tileInfo||function(){if(render3DSoftDotTexture||!glContext||"undefined"==typeof OffscreenCanvas)return render3DSoftDotTexture;var stop,alpha,context=createCanvasContext(32),gradient=context.createRadialGradient(16,16,0,16,16,16);for([stop,alpha]of[[0,1],[.33,.9],[.67,.7],[1,0]])gradient.addColorStop(stop,"rgba(255,255,255,"+alpha+")");return context.fillStyle=gradient,context.fillRect(0,0,32,32),render3DSoftDotTexture=new TextureInfo(context.canvas)}(),color=render3DParticleColor,size=render3DParticleSize,quad=r.billboardMesh,instanced=texture&&r.instancing&&!r.capture&&render3DCanDraw();let data,textureInfo,uv,lit;instanced&&(render3DMeshUpload(quad),textureInfo=render3DTextureOf(texture),uv=render3DGetTileUVs(texture),lit=r.lighting,r.lighting=r.shadowPass&&lit,r.cullBackFaces=r.mirrored=!1);var cb=r.cameraBack,particles=this.particleData,trailMax=this.trailMax,pos=render3DParticlePos;try{for(let i=0,count=this.particleCount;i<count;++i){var a,k,p=21*i,t=particles[17+p]/particles[16+p],alpha=t<fade?t/fade:1-fade<t?(1-t)/fade:1,s=(color.r=particles[6+p]+(particles[10+p]-particles[6+p])*t,color.g=particles[7+p]+(particles[11+p]-particles[7+p])*t,color.b=particles[8+p]+(particles[12+p]-particles[8+p])*t,color.a=(particles[9+p]+(particles[13+p]-particles[9+p])*t)*alpha,size.x=size.y=lerp(particles[14+p],particles[15+p],t)),angle=particles[18+p],trailCount=particles[20+p];if(1<trailCount){var trail=this.trailData,points=[],widths=[],colors=[];for(let j=0;j<trailCount;++j){var f=(j+1)/trailCount,q=3*(i*trailMax+j);points.push(vec3(trail[q],trail[1+q],trail[2+q])),widths.push(s*f),colors.push(color.scale(1,f))}r.drawRibbon(points,widths,this.tileInfo,colors)}else instanced?(a=render3DBillboardAxes(size,angle,!1),k=render3DInstanceSlot(quad,textureInfo),(data=quad.instanceData)[k]=2*a[0],data[k+1]=2*a[1],data[k+2]=2*a[2],data[k+3]=0,data[k+4]=2*a[3],data[k+5]=2*a[4],data[k+6]=2*a[5],data[k+7]=0,data[k+8]=cb.x,data[k+9]=cb.y,data[k+10]=cb.z,data[k+11]=0,data[k+12]=particles[p],data[k+13]=particles[1+p],data[k+14]=particles[2+p],data[k+15]=1,data[k+16]=color.r,data[k+17]=color.g,data[k+18]=color.b,data[k+19]=color.a,data[k+20]=uv.x,data[k+21]=uv.y,data[k+22]=uv.w,data[k+23]=uv.h):(pos.x=particles[p],pos.y=particles[1+p],pos.z=particles[2+p],texture?r.drawBillboard(pos,size,texture,color,angle):r.drawSoftDisc(pos,s,color,void 0,8))}}finally{instanced&&(r.lighting=lit,r.flush(),render3DFlushInstances(quad))}}}class Trail3D extends EngineObject3D{constructor(pos3D=vec3(),lifeTime=1,width=.2,tileInfo,color=WHITE,colorEnd=CLEAR_WHITE,additive=!1){super(pos3D,void 0,tileInfo,color),this.transparent=!0,this.additive=additive,this.castShadow=!1,this.size3D=vec3(),this.finishing=!1,this.lifeTime=lifeTime,this.width=width,this.colorEnd=colorEnd.copy(),this.side=void 0,this.samples=[]}clear(){this.samples.length=0}destroy(immediate){if(immediate||!this.samples.length||this.destroyed||this.lifeTime==1/0)return super.destroy(immediate);this.finishing=!0,render3DDetach(this)}update(){var pos,last,samples=this.samples;for(!this.finishing&&(pos=this.worldPos3D=this.getWorldPos3D(),!(last=samples[samples.length-1])||1e-8<pos.distanceSquared(last.pos))&&samples.push({pos:pos,side:this.side?.copy(),time:time});samples.length&&time-samples[0].time>this.lifeTime;)samples.shift();this.finishing&&!samples.length&&this.destroy()}render3D(){var samples=this.samples;if(!(samples.length<2)){var points=[],widths=[],colors=[],sides=this.side?[]:void 0;for(const s of samples){var age=clamp((time-s.time)/this.lifeTime);points.push(s.pos),widths.push(this.width*(1-age)),colors.push(this.color.lerp(this.colorEnd,age)),sides?.push(s.side)}render3D.drawRibbon(points,widths,this.tileInfo,colors,sides)}}}function parseOBJ(text,smooth=render3D?.smoothShading){const positions=[],normals=[],uvs=[],points=[],vertexNormals=[],vertexUVs=[],indices=[],seen=new Map,fromFile=[];let missingNormals=!1,face=0;const index=(s,list)=>{s=parseInt(s);return s<0?list.length+s:s-1},lookup=(s,list)=>list[index(s,list)];for(const line of text.split("\n")){var parts=line.trim().split(/\s+/);switch(parts[0]){case"v":positions.push(vec3(+parts[1],+parts[2],+parts[3]));break;case"vn":normals.push(vec3(+parts[1],+parts[2],+parts[3]));break;case"vt":uvs.push(vec2(+parts[1],1-(+parts[2]||0)));break;case"f":var corners=parts.slice(1).map(c=>c.split("/"));if(!(corners.length<3)){const facePoints=corners.map(c=>lookup(c[0],positions)),hasNormals=corners.every(c=>c[2]),faceNormal=(missingNormals||=!hasNormals,hasNormals?void 0:render3DFaceNormal(facePoints[0],facePoints[1],facePoints[2],facePoints[3]));var ids=corners.map((c,i)=>{var key=index(c[0],positions)+"/"+(c[1]?index(c[1],uvs):"")+"/"+(hasNormals?index(c[2],normals):smooth?"":"f"+face);let id=seen.get(key);return void 0===id&&(seen.set(key,id=points.length),points.push(facePoints[i]),vertexUVs.push(c[1]?lookup(c[1],uvs):RENDER3D_DEFAULT_UV),vertexNormals.push(hasNormals?lookup(c[2],normals):faceNormal),fromFile.push(hasNormals)),id});for(let k=2;k<ids.length;++k)indices.push(ids[1],ids[k],ids[(k+1)%ids.length]);++face}}}const mesh=(new Mesh).addTriangles(points,indices,vertexNormals,vertexUVs);if(missingNormals&&smooth){const given=mesh.normals;mesh.computeNormals(!0),fromFile.forEach((f,i)=>f&&(mesh.normals[i]=given[i]))}return mesh}async function loadOBJ(url,smooth=render3D?.smoothShading){var response=await fetch(url);if(response.ok)return parseOBJ(await response.text(),smooth);throw new Error("loadOBJ failed: "+url)}const RENDER3D_CSG_EPSILON=1e-5;class Render3DCSGVertex{constructor(pos,normal,uv,color){this.pos=pos,this.normal=normal,this.uv=uv,this.color=color}lerp(v,t){return new Render3DCSGVertex(this.pos.lerp(v.pos,t),this.normal.lerp(v.normal,t),this.uv.lerp(v.uv,t),this.color.lerp(v.color,t))}flipped(){return new Render3DCSGVertex(this.pos,this.normal.scale(-1),this.uv,this.color)}}class Render3DCSGPolygon{constructor(vertices,plane){this.vertices=vertices,this.plane=plane}flipped(){var plane={normal:this.plane.normal.scale(-1),w:-this.plane.w};return new Render3DCSGPolygon(this.vertices.map(v=>v.flipped()).reverse(),plane)}}class Render3DCSGNode{constructor(){this.plane=void 0,this.front=void 0,this.back=void 0,this.polygons=[]}}function render3DCSGSplit(plane,polygon,coplanarFront,coplanarBack,front,back){var{normal,w}=plane,vertices=polygon.vertices,types=[];let polygonType=0;for(const v of vertices){var t=normal.dot(v.pos)-w,t=t<-RENDER3D_CSG_EPSILON?2:t>RENDER3D_CSG_EPSILON?1:0;polygonType|=t,types.push(t)}if(0===polygonType)(0<normal.dot(polygon.plane.normal)?coplanarFront:coplanarBack).push(polygon);else if(1===polygonType)front.push(polygon);else if(2===polygonType)back.push(polygon);else{var f=[],b=[];for(let i=0;i<vertices.length;++i){var j=(i+1)%vertices.length,ti=types[i],tj=types[j],vi=vertices[i],j=vertices[j];2!==ti&&f.push(vi),1!==ti&&b.push(vi),3==(ti|tj)&&(ti=vi.lerp(j,(w-normal.dot(vi.pos))/normal.dot(j.pos.subtract(vi.pos))),f.push(ti),b.push(ti))}3<=f.length&&front.push(new Render3DCSGPolygon(f,polygon.plane)),3<=b.length&&back.push(new Render3DCSGPolygon(b,polygon.plane))}}function render3DCSGBuild(root,polygons){for(var stack=[[root,polygons]];stack.length;){var[node,list]=stack.pop();if(list.length){node.plane||=list[0].plane;var front=[],back=[];for(const polygon of list)render3DCSGSplit(node.plane,polygon,node.polygons,node.polygons,front,back);front.length&&stack.push([node.front||=new Render3DCSGNode,front]),back.length&&stack.push([node.back||=new Render3DCSGNode,back])}}}function render3DCSGInvert(root){for(const node of function(root){for(var nodes=[],stack=[root];stack.length;){var node=stack.pop();nodes.push(node),node.front&&stack.push(node.front),node.back&&stack.push(node.back)}return nodes}(root))node.plane&&={normal:node.plane.normal.scale(-1),w:-node.plane.w},[node.front,node.back]=[node.back,node.front]}function render3DCSGClipPolygons(root,polygons){for(var result=[],stack=[[root,polygons]];stack.length;){var[node,list]=stack.pop();if(node.plane){var front=[],back=[];for(const polygon of list)render3DCSGSplit(node.plane,polygon,front,back,front,back);if(node.front)stack.push([node.front,front]);else for(const polygon of front)result.push(polygon);node.back&&stack.push([node.back,back])}else for(const polygon of list)result.push(polygon)}return result}function render3DCSGNear(polygons,polygonsOther){var lo=vec3(1/0),hi=vec3(-1/0);for(const polygon of polygonsOther)for(var{pos}of polygon.vertices)lo.x=min(lo.x,pos.x),lo.y=min(lo.y,pos.y),lo.z=min(lo.z,pos.z),hi.x=max(hi.x,pos.x),hi.y=max(hi.y,pos.y),hi.z=max(hi.z,pos.z);var near=[],far=[],faces=[[vec3(1,0,0),hi.x],[vec3(-1,0,0),-lo.x],[vec3(0,1,0),hi.y],[vec3(0,-1,0),-lo.y],[vec3(0,0,1),hi.z],[vec3(0,0,-1),-lo.z]];for(const polygon of polygons){let inside=[polygon];for(var[normal,w]of faces){var next=[];for(const part of inside)render3DCSGSplit({normal:normal,w:w},part,next,next,far,next);inside=next}for(const part of inside)near.push(part)}return[near,far]}function render3DCSGPolygons(mesh,matrix){const list=(new Mesh).combine(mesh,matrix).toIndexed();var vertex=i=>new Render3DCSGVertex(list.points[i],list.normals[i],list.uvs[i],list.colors[i]),polygons=[];for(let t=0;t<list.indices.length;t+=3){var i=list.indices[t],j=list.indices[t+1],k=list.indices[t+2],plane=function(a,b,c){if(!((b=b.subtract(a).cross(c.subtract(a))).lengthSquared()<1e-18))return{normal:c=b.normalize(),w:c.dot(a)}}(list.points[i],list.points[j],list.points[k]);plane&&polygons.push(new Render3DCSGPolygon([vertex(i),vertex(j),vertex(k)],plane))}return polygons}function render3DCSGMesh(polygons){const reach=4*RENDER3D_CSG_EPSILON,placer=function(distance){const cells=new Map;return p=>{var cx=floor(p.x/distance),cy=floor(p.y/distance),cz=floor(p.z/distance);for(let x=cx-1;x<=cx+1;++x)for(let y=cy-1;y<=cy+1;++y)for(let z=cz-1;z<=cz+1;++z)for(const q of cells.get(x+","+y+","+z)||[])if(q.distanceSquared(p)<distance**2)return q;var k=cx+","+cy+","+cz;return cells.has(k)||cells.set(k,[]),cells.get(k).push(p),p}}(RENDER3D_CSG_EPSILON),placed=new Set;var polygons=polygons.map(polygon=>{var loop=[];for(const v of polygon.vertices){var pos=(p=>{p=placer(p);return placed.add(p),p})(v.pos);loop.length&&loop[loop.length-1].pos===pos||loop.push(new Render3DCSGVertex(pos,v.normal,v.uv,v.color))}return 1<loop.length&&loop[0].pos===loop[loop.length-1].pos&&loop.pop(),loop}),places=[...placed];let lo=vec3(1/0),hi=vec3(-1/0);for(const p of places)lo=vec3(min(lo.x,p.x),min(lo.y,p.y),min(lo.z,p.z)),hi=vec3(max(hi.x,p.x),max(hi.y,p.y),max(hi.z,p.z));var span=places.length?max(hi.x-lo.x,hi.y-lo.y,hi.z-lo.z):0;const size=max(span/Math.cbrt(places.length||1),reach),grid=new Map;for(const p of places){var k=(p=>floor((p.x-lo.x)/size)+","+floor((p.y-lo.y)/size)+","+floor((p.z-lo.z)/size))(p);grid.has(k)||grid.set(k,[]),grid.get(k).push(p)}const mesh=new Mesh,shared=new Map;mesh.indices=[];for(const loop of polygons)if(!(loop.length<3)){const full=[];for(let i=0;i<loop.length;++i){var t,p,a=loop[i],b=loop[(i+1)%loop.length];full.push(a);for([t,p]of((a,b)=>{var t,found=[],ab=b.subtract(a),length2=ab.lengthSquared(),x0=floor((min(a.x,b.x)-lo.x-reach)/size),x1=floor((max(a.x,b.x)-lo.x+reach)/size),y0=floor((min(a.y,b.y)-lo.y-reach)/size),y1=floor((max(a.y,b.y)-lo.y+reach)/size),z0=floor((min(a.z,b.z)-lo.z-reach)/size),z1=floor((max(a.z,b.z)-lo.z+reach)/size);for(let x=x0;x<=x1;++x)for(let y=y0;y<=y1;++y)for(let z=z0;z<=z1;++z)for(const p of grid.get(x+","+y+","+z)||[])p===a||p===b||(t=p.subtract(a).dot(ab)/length2)<=0||1<=t||a.add(ab.scale(t)).distanceSquared(p)<reach**2&&found.push([t,p]);return found.sort((f,g)=>f[0]-g[0])})(a.pos,b.pos)){var v=a.lerp(b,t);full.push(new Render3DCSGVertex(p,v.normal,v.uv,v.color))}}const n=full.length,pos=i=>full[i%n].pos;let area=vec3();for(let j=1;j<n-1;++j)area=area.add(pos(j).subtract(pos(0)).cross(pos(j+1).subtract(pos(0))));if(!(area.lengthSquared()<1e-18)){const ring=full.map(v=>({pos:v.pos,id:(v=>{var n=v.normal.lengthSquared()?v.normal.normalize():RENDER3D_DEFAULT_NORMAL,c=v.color,key=[n.x,n.y,n.z,v.uv.x,v.uv.y,c.r,c.g,c.b,c.a].map(x=>round(1e6*x)).join();let atPlace=shared.get(v.pos),i;return atPlace||shared.set(v.pos,atPlace=new Map),void 0===(i=atPlace.get(key))&&(atPlace.set(key,i=mesh.points.length),mesh.points.push(v.pos.copy()),mesh.normals.push(n),mesh.uvs.push(v.uv.copy()),mesh.colors.push(c.copy())),i})(v)})),earArea=i=>{var a=ring[(i+ring.length-1)%ring.length].pos,b=ring[i].pos,i=ring[(i+1)%ring.length].pos;return b.subtract(a).cross(i.subtract(a))};for(;3<ring.length;){let i=ring.findIndex((_,i)=>{i=earArea(i);return 1e-18<=i.lengthSquared()&&1e-18<=area.subtract(i).lengthSquared()});i<0&&(i=0),area=area.subtract(earArea(i)),mesh.indices.push(ring[(i+ring.length-1)%ring.length].id,ring[i].id,ring[(i+1)%ring.length].id),ring.splice(i,1)}mesh.indices.push(ring[0].id,ring[1].id,ring[2].id)}}return mesh.dirty=!0,mesh}function render3DMeshCSG(a,b,matrix,operation){var a=render3DCSGPolygons(a),b=render3DCSGPolygons(b,matrix),[matrix,farA]=render3DCSGNear(a,b),[nearB,farB]=render3DCSGNear(b,a),treeA=new Render3DCSGNode,treeB=new Render3DCSGNode,a=(render3DCSGBuild(treeA,a),render3DCSGBuild(treeB,b),render3DCSGClipPolygons),b=list=>list.map(polygon=>polygon.flipped());let polygons;return render3DCSGMesh(polygons=0===operation?[...farA,...a(treeB,matrix),...farB,...b(a(treeA,b(a(treeA,nearB))))]:1===operation?(render3DCSGInvert(treeA),[...farA,...b(a(treeB,b(matrix))),...a(treeA,b(a(treeA,nearB)))]):(render3DCSGInvert(treeA),render3DCSGInvert(treeB),[...b(a(treeB,b(matrix))),...b(a(treeA,b(a(treeA,nearB))))]))}const RENDER3D_VOXEL_FACES=[[vec3(1,0,0),vec3(0,0,-1),vec3(0,1,0)],[vec3(-1,0,0),vec3(0,0,1),vec3(0,1,0)],[vec3(0,1,0),vec3(1,0,0),vec3(0,0,-1)],[vec3(0,-1,0),vec3(1,0,0),vec3(0,0,1)],[vec3(0,0,1),vec3(1,0,0),vec3(0,1,0)],[vec3(0,0,-1),vec3(-1,0,0),vec3(0,1,0)]],RENDER3D_VOXEL_SHADES=[.55,.7,.85,1].map(v=>Object.freeze(rgb(v,v,v))),RENDER3D_VOXEL_CORNERS=RENDER3D_VOXEL_FACES.map(([normal,right,up])=>render3DQuadAxes(normal.scale(.5),right.scale(.5),up.scale(.5))),RENDER3D_VOXEL_CORNER_SIDES=[[-1,1],[-1,-1],[1,1],[1,-1]],RENDER3D_VOXEL_ORDER=[0,1,2,3],RENDER3D_VOXEL_FLIPPED=[1,3,0,2];class VoxelMap extends EngineObject3D{constructor(pos3D=vec3(),mapSize=vec3(16),tileInfo=tile()){super(pos3D),this.mapSize=mapSize.floor(),this.tileInfo=tileInfo,this.data=new Uint8Array(this.mapSize.x*this.mapSize.y*this.mapSize.z),this.ambientOcclusion=!0,this.size3D=vec3();pos3D=this.mapSize,this.chunkCount=vec3(ceil(pos3D.x/16),ceil(pos3D.y/16),ceil(pos3D.z/16)),this.chunkMeshes=[],this.chunkTransparentMeshes=[],this.chunkCenters=[],this.chunksChanged=new Set,this.blockTypes=[],this.tiles=new Map,mapSize=new EngineObject3D;mapSize.size3D=vec3(),mapSize.transparent=!0,mapSize.render3D=()=>this.renderChunks(!0),this.addChild(mapSize),render3DLevel.push(this)}getVoxel(cell){return this.voxelAt(floor(cell.x),floor(cell.y),floor(cell.z))}voxelAt(x,y,z){var s=this.mapSize;return x<0||y<0||z<0||x>=s.x||y>=s.y||z>=s.z?0:this.data[x+s.x*(y+s.y*z)]}setVoxel(cell,type){var x=floor(cell.x),y=floor(cell.y),z=floor(cell.z),cell=this.mapSize;if(!(x<0||y<0||z<0||x>=cell.x||y>=cell.y||z>=cell.z)){cell=x+cell.x*(y+cell.y*z);if(this.data[cell]!==type){this.data[cell]=type,this.markChunk(x,y,z);for(let dz=-1;dz<=1;++dz)for(let dy=-1;dy<=1;++dy)for(let dx=-1;dx<=1;++dx)(this.ambientOcclusion||abs(dx)+abs(dy)+abs(dz)===1)&&this.voxelAt(x+dx,y+dy,z+dz)&&this.markChunk(x+dx,y+dy,z+dz)}}}markChunk(x,y,z){var s=this.mapSize,c=this.chunkCount;x<0||y<0||z<0||x>=s.x||y>=s.y||z>=s.z||this.chunksChanged.add(floor(x/16)+c.x*(floor(y/16)+c.y*floor(z/16)))}setBlockType(type,faces,{seeThrough=!1,transparent=!1}={}){faces=isNumber(faces)?[faces,faces,faces,faces,faces,faces]:isArray(faces)?faces:[faces.side,faces.side,faces.top??faces.side,faces.bottom??faces.side,faces.side,faces.side];this.blockTypes[type]={faces:faces,seeThrough:seeThrough||transparent,transparent:transparent},this.rebuild()}blockType(type){return this.blockTypes[type]||={faces:[type,type,type,type,type,type],seeThrough:!1,transparent:!1}}rebuild(){this.tiles.clear();var c=this.chunkCount;for(let i=c.x*c.y*c.z;i--;)this.chunksChanged.add(i)}buildChunks(){for(const index of this.chunksChanged)this.buildChunk(index);this.chunksChanged.clear()}buildChunk(index){var c=this.chunkCount,n=16,s=this.mapSize,data=this.data,x0=index%c.x*n,y0=floor(index/c.x)%c.y*n,c=floor(index/(c.x*c.y))*n,x1=min(x0+n,s.x),y1=min(y0+n,s.y),z1=min(c+n,s.z),center=vec3((x0+x1)/2,(y0+y1)/2,(c+z1)/2),opaque=new Mesh,transparent=new Mesh,ao=this.ambientOcclusion,solid=(x,y,z)=>{x=this.voxelAt(x,y,z);return x&&!this.blockType(x).seeThrough?1:0},points=[],uvs=[],colors=[],shade=[];for(let z=c;z<z1;++z)for(let y=y0;y<y1;++y)for(let x=x0;x<x1;++x){var type=data[x+s.x*(y+s.y*z)];if(type){var block=this.blockType(type),mesh=block.transparent?transparent:opaque,cx=x+.5-center.x,cy=y+.5-center.y,cz=z+.5-center.z;for(let f=0;f<6;++f){var[normal,right,up]=RENDER3D_VOXEL_FACES[f],bx=x+normal.x,by=y+normal.y,bz=z+normal.z,next=this.voxelAt(bx,by,bz);if(!next||next!==type&&this.blockType(next).seeThrough){for(let k=0;k<4;++k){var[r,u]=RENDER3D_VOXEL_CORNER_SIDES[k],side1=ao&&solid(bx+right.x*r,by+right.y*r,bz+right.z*r),side2=ao&&solid(bx+up.x*u,by+up.y*u,bz+up.z*u),r=ao&&solid(bx+right.x*r+up.x*u,by+right.y*r+up.y*u,bz+right.z*r+up.z*u);shade[k]=side1&&side2?0:3-+side1-+side2-+r}var corners=RENDER3D_VOXEL_CORNERS[f],quad=this.tileUVs(block.faces[f]),order=shade[1]+shade[2]<shade[0]+shade[3]?RENDER3D_VOXEL_FLIPPED:RENDER3D_VOXEL_ORDER;for(let k=0;k<4;++k){var i=order[k],p=corners[i];points[k]=vec3(cx+p.x,cy+p.y,cz+p.z),uvs[k]=quad[i],colors[k]=ao?RENDER3D_VOXEL_SHADES[shade[i]]:WHITE}mesh.addStrip(points,normal,uvs,colors)}}}}this.chunkMeshes[index]?.dispose(),this.chunkTransparentMeshes[index]?.dispose(),this.chunkMeshes[index]=opaque.points.length?opaque:void 0,this.chunkTransparentMeshes[index]=transparent.points.length?transparent:void 0,this.chunkCenters[index]=center}tileUVs(index){let uvs=this.tiles.get(index);if(!uvs){var first=this.tileInfo;const rect=render3DGetTileUVs(first.columns?first.frame(index):first.index(index));uvs=RENDER3D_QUAD_UVS.map(q=>Object.freeze(vec2(rect.x+q.x*rect.w,rect.y+q.y*rect.h))),this.tiles.set(index,uvs)}return uvs}boxBlocked(pos,size,o){const m=this.pos3D,s=this.mapSize;var cells=(center,half,corner,count)=>{var first=floor(center-half-corner+1e-9),center=max(ceil(center+half-corner-1e-9)-1,first);return[max(first,0),min(center,count-1)]},[x0,x1]=cells(pos.x,size.x/2,m.x,s.x),[y0,y1]=cells(pos.y,size.y/2,m.y,s.y),[cells,z1]=cells(pos.z,size.z/2,m.z,s.z);for(let z=cells;z<=z1;++z)for(let y=y0;y<=y1;++y)for(let x=x0;x<=x1;++x){var type=this.data[x+s.x*(y+s.y*z)];if(type&&o.collideWithVoxel(type,vec3(x,y,z)))return!0}return!1}levelCollide3D(o,oldPos){var k=o.scale3D,size=vec3(o.size3D.x*abs(k.x),o.size3D.y*abs(k.y),o.size3D.z*abs(k.z)),p=o.pos3D,v=o.velocity3D,m=this.pos3D;if(this.boxBlocked(p,size,o))if(this.boxBlocked(oldPos,size,o)){var k=size.y/2,flat=vec3(size.x,0,size.z);let top;for(let y=floor(oldPos.y-k-m.y);y<=floor(oldPos.y-m.y);++y)this.boxBlocked(vec3(oldPos.x,m.y+y+.5,oldPos.z),flat,o)&&(top=y+1);var from,lead,first,last,slab,slabSize,k=void 0===top?void 0:vec3(oldPos.x,m.y+top+k+1e-4,oldPos.z);if(k&&!this.boxBlocked(k,size,o))return p.set(k.x,k.y,k.z),v.y=max(v.y,0),void(o.groundObject=this);const tiny=1e-9,place=oldPos.copy();for(const axis of["y","x","z"]){const move=p[axis]-place[axis],halfSize=size[axis]/2,corner=m[axis];move&&(from=(lead=at=>0<move?ceil(at+halfSize-corner-tiny)-1:floor(at-halfSize-corner+tiny))(place[axis]),lead=lead(p[axis]),first=min(from,lead)+(0<move?1:0),last=max(from,lead)-(0<move?0:1),slab=place.copy(),slabSize=size.copy(),slab[axis]=corner+(first+last+1)/2,slabSize[axis]=last-first+1,from!==lead&&this.boxBlocked(slab,slabSize,o)?("y"===axis&&move<0&&(o.groundObject=this),v[axis]=0):place[axis]=p[axis])}void p.set(place.x,place.y,place.z)}else{var restitution=max(o.restitution,this.restitution);const place=oldPos.copy();for(const axis of["y","x","z"]){var moved=place.copy();if(moved[axis]=p[axis],this.boxBlocked(moved,size,o)){const half=size[axis]/2,move=p[axis]-oldPos[axis];move&&(moved[axis]=move<0?m[axis]+floor(p[axis]-half-m[axis])+1+half+1e-4:m[axis]+ceil(p[axis]+half-m[axis])-1-half-1e-4,this.boxBlocked(moved,size,o)||(place[axis]=moved[axis])),"y"===axis&&move<0&&(o.groundObject=this),v[axis]*=-restitution}else place[axis]=p[axis]}p.set(place.x,place.y,place.z)}}raycast(ray,maxDistance=1/0,test=()=>!0){var s=this.mapSize,o=ray.origin.subtract(this.pos3D),d=ray.direction,axes=["x","y","z"];if(d.lengthSquared()){let enter=0,exit=maxDistance,entryAxis=-1;for(let k=0;k<3;++k){var a=axes[k],dk=d[a],ok=o[a];if(dk){let t0=-ok/dk,t1=(s[a]-ok)/dk;t0>t1&&([t0,t1]=[t1,t0]),t0>enter&&(enter=t0,entryAxis=k),exit=min(exit,t1)}else if(ok<0||ok>s[a])return}if(!(enter>exit)){const start=o.add(d.scale(enter)),cell=[],step=[],next=[],delta=[];for(let k=0;k<3;++k){const a=axes[k],dk=d[a];cell[k]=clamp(floor(start[a]),0,s[a]-1),step[k]=sign(dk),next[k]=dk?enter+(cell[k]+(0<dk?1:0)-start[a])/dk:1/0,delta[k]=dk?abs(1/dk):1/0}var normalOn=k=>vec3(0===k?-step[0]:0,1===k?-step[1]:0,2===k?-step[2]:0);let normal,t=enter;for(normal=0<=entryAxis?normalOn(entryAxis):normalOn(abs(d.x)>=abs(d.y)&&abs(d.x)>=abs(d.z)?0:abs(d.y)>=abs(d.z)?1:2);t<=maxDistance;){var type=this.data[cell[0]+s.x*(cell[1]+s.y*cell[2])];if(type){var at=vec3(cell[0],cell[1],cell[2]);if(test(type,at))return{distance:t,cell:at,normal:normal,type:type}}const k=next[0]<next[1]?next[0]<next[2]?0:2:next[1]<next[2]?1:2;if(t=next[k],cell[k]+=step[k],cell[k]<0||cell[k]>=s[axes[k]])return;next[k]+=delta[k],normal=normalOn(k)}}}}levelRaycast3D(ray){return this.raycast(ray)?.distance}update(){super.update()}render3D(){this.renderChunks(!1)}renderChunks(transparent){this.buildChunks();var meshes=transparent?this.chunkTransparentMeshes:this.chunkMeshes,p=this.pos3D,texture=this.tileInfo.textureInfo;for(let i=0;i<meshes.length;++i){var mesh=meshes[i],c=this.chunkCenters[i];mesh&&render3D.drawMesh(mesh,vec3(p.x+c.x,p.y+c.y,p.z+c.z),texture,this.color)}}destroy(immediate){render3DLevelLeave(this);for(const mesh of[...this.chunkMeshes,...this.chunkTransparentMeshes])mesh?.dispose();super.destroy(immediate)}}class GLTFPart{constructor(name,mesh,color,textureInfo,transparent){this.name=name,this.mesh=mesh,this.color=color,this.textureInfo=textureInfo,this.transparent=transparent,this.pixelated=!1,this.node=0,this.unlit=!1}}class GLTFAnimation{constructor(name,channels){this.name=name,this.channels=channels,this.duration=channels.reduce((d,c)=>max(d,c.times[c.times.length-1]||0),0)}}class GLTFModel{constructor(parts,animations=[],nodeTree){this.parts=parts,this.animations=animations,this.nodeTree=nodeTree,this.modelMatrix=new Matrix4,this.mesh=new Mesh;for(const part of parts)this.mesh.combine(part.mesh,nodeTree?.restPose?.[part.node]||RENDER3D_IDENTITY,part.color);animations=new Set(parts.map(p=>p.textureInfo));this.textureInfo=1===animations.size?animations.values().next().value:void 0,this.bounds=void 0}getBounds(){var bounds=this.bounds||=this.mesh.getBounds();return{min:bounds.min.copy(),max:bounds.max.copy()}}center(){var bounds=this.getBounds();return this.transform(bounds.min.add(bounds.max).scale(-.5))}fit(size=1){var bounds=this.getBounds(),bounds=bounds.max.subtract(bounds.min);return this.transform(Matrix4.scaling(vec3(size/(max(bounds.x,bounds.y,bounds.z)||1))))}transform(matrix){matrix=render3DMatrix(matrix);for(const part of this.parts)part.mesh.transform(matrix);return this.mesh.transform(matrix),this.bounds=void 0,this.modelMatrix=matrix.copy().multiply(this.modelMatrix),this}getAnimation(animation){return animation instanceof GLTFAnimation?animation:isNumber(animation)?this.animations[animation]:this.animations.find(a=>a.name===animation)}getPose(animation,time){const tree=this.nodeTree;if(!tree)return this.parts.map(()=>new Matrix4);const moved=new Map;for(const channel of animation.channels){let node=moved.get(channel.node);node||moved.set(channel.node,node=function(node){return{translation:[...node.translation||[0,0,0]],rotation:[...node.rotation||[0,0,0,1]],scale:[...node.scale||[1,1,1]]}}(tree.nodes[channel.node])),!function(channel,time,out){var{times,values,components:n,interpolation:channel}=channel,last=times.length-1,cubic="CUBICSPLINE"===channel,stride=cubic?3*n:n,at=cubic?n:0;let k=0,high=last;for(;k<high;){var mid=k+high+1>>1;times[mid]<=time?k=mid:high=mid-1}if(k>=last||time<=times[0]||"STEP"===channel)for(let j=0;j<n;++j)out[j]=values[k*stride+at+j];else{var dt=times[k+1]-times[k],t=(time-times[k])/dt,a=k*stride,b=a+stride;if(cubic){var last=t*t,channel=last*t,h00=2*channel-3*last+1,h10=channel-2*last+t,h01=-2*channel+3*last,h11=channel-last;for(let j=0;j<n;++j)out[j]=h00*values[a+n+j]+h10*dt*values[a+2*n+j]+h01*values[b+n+j]+h11*dt*values[b+j]}else if(4===n){let dot=0;for(let j=0;j<4;++j)dot+=values[a+j]*values[b+j];cubic=dot<0?-1:1;dot*=cubic;let wa=1-t,wb=t*cubic;dot<.9995&&(channel=Math.acos(dot),last=sin(channel),wa=sin((1-t)*channel)/last,wb=sin(t*channel)/last*cubic);for(let j=0;j<4;++j)out[j]=wa*values[a+j]+wb*values[b+j]}else for(let j=0;j<n;++j)out[j]=values[a+j]+(values[b+j]-values[a+j])*t;if(4===n){var l=hypot(out[0],out[1],out[2],out[3])||1;for(let j=0;j<4;++j)out[j]/=l}}}(channel,time,node[channel.path])}const world=[],worldOf=i=>{var local,parent;return world[i]||(local=gltfNodeMatrix(moved.get(i)||tree.nodes[i]),parent=tree.parents[i],world[i]=void 0===parent?local:worldOf(parent).copy().multiply(local))},model=this.modelMatrix,modelInverse=model.copy().invert();return this.parts.map(part=>model.copy().multiply(worldOf(part.node)).multiply(tree.restInverse[part.node]).multiply(modelInverse))}dispose(){for(const part of this.parts)part.mesh.dispose();this.mesh.dispose();for(const textureInfo of new Set(this.parts.map(p=>p.textureInfo)))textureInfo?.destroyWebGLTexture()}createObject(pos3D=vec3()){return new GLTFObject(this,pos3D)}}class GLTFObject extends EngineObject3D{constructor(model,pos3D=vec3()){super(pos3D);pos3D=model.mesh.points.length?model.getBounds():void 0;pos3D&&(this.size3D=pos3D.max.subtract(pos3D.min)),this.model=model,this.animation=void 0,this.animationTime=0,this.animationSpeed=1,this.animationLoop=!0,this.animationPlaying=!1,this.parts=[];for(const part of model.parts){var modelMatrix,o=new EngineObject3D(vec3(),part.mesh,part.textureInfo,part.color),rest=(o.transparent=part.transparent,o.pixelated=part.pixelated,o.emissive=part.unlit?1:0,model.nodeTree?.restPose?.[part.node]);rest&&(rest=(modelMatrix=model.modelMatrix).copy().multiply(rest).multiply(modelMatrix.copy().invert()),o.localMatrix=rest,o.pos3D=rest.getTranslation(),o.rotation3D=rest.getRotation(),o.scale3D=rest.getScale()),this.addChild(o),this.parts.push(o)}}play(animation=0,loop=!0,speed=1){animation=this.model.getAnimation(animation);animation&&(this.animation=animation,this.animationLoop=loop,this.animationSpeed=speed,this.animationPlaying=!0,this.setAnimationTime(speed<0?animation.duration:0))}stop(){this.animationPlaying=!1}setAnimationTime(time){if(this.animationTime=time,this.animation){var pose=this.model.getPose(this.animation,time),parts=this.parts;for(let i=0;i<pose.length&&i<parts.length;++i){var o=parts[i],m=pose[i];o.destroyed||o.parent!==this||(o.localMatrix=m,o.pos3D=m.getTranslation(),o.rotation3D=m.getRotation(),o.scale3D=m.getScale())}}}update(){super.update();var animation=this.animation;if(this.animationPlaying&&animation){animation=animation.duration;let t=this.animationTime+timeDelta*this.animationSpeed;this.animationLoop?t=animation?mod(t,animation):0:(t>=animation||t<=0)&&(t=clamp(t,0,animation),this.animationPlaying=!1),this.setAnimationTime(t)}}}async function loadGLTF(url){var response=await fetch(url);if(response.ok)return parseGLTF(await response.arrayBuffer(),url.slice(0,url.lastIndexOf("/")+1));throw new Error("loadGLTF failed: "+url)}async function parseGLTF(data,baseUrl=""){let json=data,glbBuffer;if(data instanceof ArrayBuffer){var view=new DataView(data);if(12<=data.byteLength&&1179937895===view.getUint32(0,!0)){if(2!==view.getUint32(4,!0))throw new Error("only GLB version 2 is read");for(let offset=12;offset+8<=data.byteLength;){var length=view.getUint32(offset,!0),type=view.getUint32(offset+4,!0),chunk=data.slice(offset+8,offset+8+length);1313821514===type?json=(new TextDecoder).decode(chunk):5130562===type&&(glbBuffer=chunk),offset+=8+length}}else json=(new TextDecoder).decode(data)}if(!(json="string"==typeof json?JSON.parse(json):json).asset||!String(json.asset.version).startsWith("2"))throw new Error("only glTF 2.0 is read");for(const name of json.extensionsRequired||[])if("KHR_draco_mesh_compression"===name||"EXT_meshopt_compression"===name)throw new Error(`glTF with ${name} is not read, export it uncompressed`);const buffers=await Promise.all((json.buffers||[]).map((buffer,i)=>buffer.uri?gltfFetch(buffer.uri,baseUrl).then(r=>r.arrayBuffer()):glbBuffer)),baseColorTextures=new Set((json.materials||[]).map(m=>m.pbrMetallicRoughness?.baseColorTexture?.index)),opaqueTextures=function(json){var opaque=new Set,cut=new Set;for(const material of json.materials||[]){var index=material.pbrMetallicRoughness?.baseColorTexture?.index;void 0!==index&&(material.alphaMode&&"OPAQUE"!==material.alphaMode?cut:opaque).add(index)}for(const index of cut)opaque.delete(index);return opaque}(json),textures=await Promise.all((json.textures||[]).map(async(texture,index)=>{if(glContext&&"undefined"!=typeof createImageBitmap&&baseColorTextures.has(index))try{var view,source=texture.source??texture.extensions?.EXT_texture_webp?.source??texture.extensions?.EXT_texture_avif?.source,image=json.images[source],sampler=json.samplers?.[texture.sampler]||{};let blob;blob=image.uri?await gltfFetch(image.uri,baseUrl).then(r=>r.blob()):(view=json.bufferViews[image.bufferView],new Blob([new Uint8Array(buffers[view.buffer],view.byteOffset||0,view.byteLength)],{type:image.mimeType}));var bitmap=opaqueTextures.has(index)&&"image/jpeg"!==blob.type?await createImageBitmap(blob,{premultiplyAlpha:"none"}).then(gltfOpaqueImage):await createImageBitmap(blob);return new TextureInfo(bitmap,!0,[sampler.wrapS??10497,sampler.wrapT??10497])}catch(e){}})),parts=[],parents=[],restInverse=[],restPose=[],visit=(index,parentMatrix,parentIndex,parentRest)=>{var node=json.nodes[index],local=gltfNodeMatrix(node);let matrix=parentMatrix?parentMatrix.copy().multiply(local):local,rest=parentRest&&parentRest.copy().multiply(local);if(!node.matrix&&node.scale?.includes(0)&&(rest||=matrix,parentRest=gltfNodeMatrix({...node,scale:node.scale.map(s=>s||1)}),matrix=parentMatrix?parentMatrix.copy().multiply(parentRest):parentRest),parents[index]=parentIndex,restInverse[index]=matrix.copy().invert(),rest&&(restPose[index]=rest.copy().multiply(restInverse[index])),void 0!==node.mesh){var mesh=json.meshes[node.mesh];for(const primitive of mesh.primitives){var part=function(json,buffers,textures,primitive,matrix,name){var mode=primitive.mode??4;if(!(mode<4)){var attributes=primitive.attributes,read=(index,make)=>{var{data,components,count}=gltfAccessor(json,buffers,index),list=[];for(let i=0;i<count;++i)list.push(make(data,i*components,components));return list},points=read(attributes.POSITION,(d,k)=>vec3(d[k],d[k+1],d[k+2])),normals=void 0!==attributes.NORMAL?read(attributes.NORMAL,(d,k)=>vec3(d[k],d[k+1],d[k+2])):void 0,material=json.materials?.[primitive.material]||{},pbr=material.pbrMetallicRoughness||{},textureRef=pbr.baseColorTexture,uvTransform=textureRef?.extensions?.KHR_texture_transform,uvAccessor=attributes["TEXCOORD_"+(uvTransform?.texCoord??textureRef?.texCoord??0)];const[ox,oy]=uvTransform?.offset||[0,0],[sx,sy]=uvTransform?.scale||[1,1],r=uvTransform?.rotation||0,c=cos(r),s=sin(r);uvTransform=void 0!==uvAccessor?read(uvAccessor,(d,k)=>vec2(c*sx*d[k]+s*sy*d[k+1]+ox,c*sy*d[k+1]-s*sx*d[k]+oy)):void 0,uvAccessor=void 0!==attributes.COLOR_0?read(attributes.COLOR_0,(d,k,n)=>rgb(gltfSRGB(d[k]),gltfSRGB(d[k+1]),gltfSRGB(d[k+2]),3<n?d[k+3]:1)):void 0;let indices=void 0!==primitive.indices?Array.from(gltfAccessor(json,buffers,primitive.indices).data):points.map((_,i)=>i);5===mode?indices=indices.flatMap((_,i,s)=>i<2?[]:1&i?[s[i-1],s[i-2],s[i]]:[s[i-2],s[i-1],s[i]]):6===mode&&(indices=indices.flatMap((_,i,s)=>i<2?[]:[s[0],s[i-1],s[i]]));read=(new Mesh).addTriangles(points,indices,normals,uvTransform,uvAccessor),attributes=(normals||read.computeNormals(!1),read.transform(matrix),pbr.baseColorFactor||[1,1,1,1]),primitive=(read.doubleSided=!!material.doubleSided,material.extensions?.KHR_materials_transmission?.transmissionFactor||0),mode="BLEND"===material.alphaMode,points=(mode?attributes[3]:1)*(1-.8*primitive),uvTransform=textureRef&&json.textures?.[textureRef.index],uvAccessor=new GLTFPart(name,read,rgb(gltfSRGB(attributes[0]),gltfSRGB(attributes[1]),gltfSRGB(attributes[2]),points),textureRef?textures[textureRef.index]:void 0,mode||0<primitive);return uvAccessor.pixelated=9728===json.samplers?.[uvTransform?.sampler]?.magFilter,uvAccessor.unlit=!!material.extensions?.KHR_materials_unlit,uvAccessor}}(json,buffers,textures,primitive,matrix,node.name||mesh.name||"part "+parts.length);part&&(part.node=index,parts.push(part))}}for(const child of node.children||[])visit(child,matrix,index,rest)};let animations;try{var scene=json.scenes?.[json.scene??0];if(scene)(scene.nodes||[]).forEach(i=>visit(i));else if(json.nodes){const children=new Set(json.nodes.flatMap(n=>n.children||[]));json.nodes.forEach((n,i)=>children.has(i)||visit(i))}animations=(json.animations||[]).map((animation,i)=>new GLTFAnimation(animation.name||"animation "+i,animation.channels.filter(c=>void 0!==c.target.node&&["translation","rotation","scale"].includes(c.target.path)&&!json.nodes[c.target.node].matrix).map(c=>{var sampler=animation.samplers[c.sampler];return{node:c.target.node,path:c.target.path,interpolation:sampler.interpolation||"LINEAR",times:gltfAccessor(json,buffers,sampler.input).data,values:gltfAccessor(json,buffers,sampler.output).data,components:"rotation"===c.target.path?4:3}})))}catch(error){for(const texture of textures)texture?.destroyWebGLTexture();throw error}var used=new Set(parts.map(p=>p.textureInfo));for(const texture of textures)texture&&!used.has(texture)&&texture.destroyWebGLTexture();return new GLTFModel(parts,animations,{nodes:json.nodes,parents:parents,restInverse:restInverse,restPose:restPose})}function gltfOpaqueImage(image){var gl=glContext,{width,height}=image;if(gl.isContextLost())return image;var texture=gl.createTexture(),framebuffer=gl.createFramebuffer(),bound=gl.getParameter(gl.FRAMEBUFFER_BINDING),data=(gl.bindTexture(gl.TEXTURE_2D,texture),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,image),gl.bindFramebuffer(gl.FRAMEBUFFER,framebuffer),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,texture,0),new Uint8ClampedArray(width*height*4));gl.readPixels(0,0,width,height,gl.RGBA,gl.UNSIGNED_BYTE,data),gl.bindFramebuffer(gl.FRAMEBUFFER,bound),gl.bindTexture(gl.TEXTURE_2D,glActiveTexture),gl.deleteFramebuffer(framebuffer),gl.deleteTexture(texture);for(let i=3;i<data.length;i+=4)data[i]=255;return image.close(),createImageBitmap(new ImageData(data,width,height))}function gltfFetch(uri,baseUrl){if(uri.startsWith("data:")){var comma=uri.indexOf(","),bytes=atob(uri.slice(comma+1)),data=new Uint8Array(bytes.length);for(let i=0;i<bytes.length;++i)data[i]=bytes.charCodeAt(i);return Promise.resolve(new Response(data.buffer,{headers:{"Content-Type":uri.slice(5,uri.indexOf(";"))}}))}comma="undefined"!=typeof location&&location.href,comma=comma?new URL(baseUrl,comma):/^[a-z][a-z0-9+.-]*:/i.test(baseUrl)?baseUrl:void 0;const url=comma?new URL(uri,comma).href:baseUrl+uri;return fetch(url).then(r=>{if(r.ok)return r;throw new Error("glTF file not found: "+url)})}function gltfNodeMatrix(node){var x,y,z,w,sx,sy,sz;return node.matrix?new Matrix4(node.matrix):([x,y,z,w]=node.rotation||[0,0,0,1],[sx,sy,sz]=node.scale||[1,1,1],node=node.translation||[0,0,0],new Matrix4([(1-2*(y*y+z*z))*sx,2*(x*y+z*w)*sx,2*(x*z-y*w)*sx,0,2*(x*y-z*w)*sy,(1-2*(x*x+z*z))*sy,2*(y*z+x*w)*sy,0,2*(x*z+y*w)*sz,2*(y*z-x*w)*sz,(1-2*(x*x+y*y))*sz,0,node[0],node[1],node[2],1]))}function gltfAccessor(json,buffers,index){var a=json.accessors[index],index=json.bufferViews?.[a.bufferView],components={SCALAR:1,VEC2:2,VEC3:3,VEC4:4,MAT4:16}[a.type],types={5120:Int8Array,5121:Uint8Array,5122:Int16Array,5123:Uint16Array,5125:Uint32Array,5126:Float32Array},Type=types[a.componentType];if(!components||!Type)throw new Error(`glTF accessor of ${a.type} ${a.componentType} is not read`);var size=Type.BYTES_PER_ELEMENT,scales=[[Int8Array,127],[Uint8Array,255],[Int16Array,32767],[Uint16Array,65535]],scale=a.normalized&&new Map(scales).get(Type)||1,out=new Float32Array(a.count*components);if(index){var buffer=buffers[index.buffer],offset=(index.byteOffset||0)+(a.byteOffset||0),stride=index.byteStride||components*size;if(stride===components*size)out.set(new Type(buffer,offset,a.count*components));else for(let i=0;i<a.count;++i)out.set(new Type(buffer,offset+i*stride,components),i*components)}var sparse=a.sparse;if(sparse){var{indices:scales,values:index}=sparse,size=types[scales.componentType],types=json.bufferViews?.[scales.bufferView],json=json.bufferViews?.[index.bufferView];if(!size||!types||!json)throw new Error("glTF sparse accessor is missing its indices or values");var at=new size(buffers[types.buffer],(types.byteOffset||0)+(scales.byteOffset||0),sparse.count),data=new Type(buffers[json.buffer],(json.byteOffset||0)+(index.byteOffset||0),sparse.count*components);for(let i=0;i<sparse.count;++i)out.set(data.subarray(i*components,(i+1)*components),at[i]*components)}if(1!==scale)for(let i=0;i<out.length;++i)out[i]/=scale;return{data:out,components:components,count:a.count}}function gltfSRGB(c){return c<=.0031308?12.92*c:1<=c?c:1.055*c**(1/2.4)-.055}let threeJS;class ThreeJSPlugin{constructor(THREE,cameraFOV=60){threeJS=this,headlessMode||(this.THREE=THREE,this.renderer=new THREE.WebGLRenderer({antialias:!0}),this.scene=new THREE.Scene,this.camera=new THREE.PerspectiveCamera(cameraFOV,1,.1,1e3),this.cameraAlign2D=!0,this.cameraNear=void 0,this.cameraFar=void 0,THREE=this.renderer.domElement,(cameraFOV=mainCanvas.parentElement).insertBefore(THREE,cameraFOV.firstChild),THREE.style.cssText=mainCanvas.style.cssText,setBackgroundCanvas(THREE),engineAddPlugin(void 0,()=>this.render()))}alignCamera2D(){var distance=mainCanvasSize.y/2/cameraScale/tan(this.camera.fov/2*PI/180),baseNear=this.cameraNear??=this.camera.near,baseFar=this.cameraFar??=this.camera.far,scale=max(1,2*distance/baseFar),baseNear=baseNear*scale,baseFar=baseFar*scale;baseNear==this.camera.near&&baseFar==this.camera.far||(this.camera.near=baseNear,this.camera.far=baseFar,this.camera.updateProjectionMatrix()),this.camera.position.set(cameraPos.x,cameraPos.y,distance),this.camera.rotation.set(0,0,-cameraAngle)}render(){var threeCanvas,dpr,bufferSizeX,bufferSizeY;this.renderer&&(threeCanvas=this.renderer.domElement,dpr=getCanvasPixelRatio(),bufferSizeX=mainCanvasSize.x*dpr|0,bufferSizeY=mainCanvasSize.y*dpr|0,threeCanvas.width==bufferSizeX&&threeCanvas.height==bufferSizeY||(this.renderer.setPixelRatio(dpr),this.renderer.setSize(mainCanvasSize.x,mainCanvasSize.y,!1),this.camera.aspect=mainCanvasSize.x/mainCanvasSize.y,this.camera.updateProjectionMatrix()),threeCanvas.style.cssText!=mainCanvas.style.cssText&&(threeCanvas.style.cssText=mainCanvas.style.cssText),this.cameraAlign2D&&this.alignCamera2D(),this.renderer.render(this.scene,this.camera))}}class ThreeJSObject extends EngineObject{constructor(pos,size,mesh,z=0){super(pos,size),this.mesh=mesh,this.z=z,mesh&&(threeJS.scene?.add(mesh),this.syncMesh())}updateTransforms(updateChildren=!0){super.updateTransforms(updateChildren),this.syncMesh()}syncMesh(){this.mesh&&(this.mesh.position.set(this.pos.x,this.pos.y,this.z),this.mesh.rotation.z=-this.angle)}render(){}destroy(immediate){this.destroyed||(this.mesh&&threeJS.scene?.remove(this.mesh),super.destroy(immediate))}}export{engineName,engineVersion,frameRate,timeDelta,engineObjects,engineObjectsCollide,frame,time,timeReal,paused,getPaused,setPaused,engineInit,engineStep,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCollect,engineObjectsCallback,engineObjectsRaycast,engineAddPlugin,debug,debugOverlay,debugWatermark,debugKey,debugKeysAlways,debugTweakables,levelEditor,ASSERT,LOG,debugPointSize,debugRect,debugPoly,debugCircle,debugPoint,debugLine,debugOverlap,debugText,debugClear,debugScreenshot,debugShowErrors,debugVideoCaptureStart,debugVideoCaptureStop,debugVideoCaptureIsActive,cameraPos,cameraAngle,cameraScale,timeScale,canvasColorTiles,canvasClearColor,canvasMaxSize,canvasMinAspect,canvasMaxAspect,canvasFixedSize,canvasPixelated,tilesPixelated,canvasPixelRatio,fontDefault,showSplashScreen,headlessMode,engineManualStep,tileDefaultSize,tileDefaultPadding,tileDefaultBleed,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultRestitution,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,glEnable,drawScreenSpace,glCircleSides,gamepadsEnable,gamepadDirectionEmulateStick,gamepadAxisFilterEnable,inputWASDEmulateDirection,touchInputEnable,touchGamepadEnable,touchGamepadPassthrough,touchGamepadCenterButtonSize,touchGamepadButtonCount,touchGamepadLeftStick,touchGamepadLeftButtonCount,touchGamepadRightStick,touchGamepadAnalog,touchGamepadFloating,touchGamepadSize,touchGamepadAlpha,touchGamepadDisplayTime,touchGamepadVibration,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,soundPauseWhenHidden,setCameraPos,setCameraAngle,setCameraScale,setTimeScale,setCanvasColorTiles,setCanvasClearColor,setCanvasMaxSize,setCanvasMinAspect,setCanvasMaxAspect,setCanvasFixedSize,setCanvasPixelated,setTilesPixelated,setCanvasPixelRatio,getCanvasPixelRatio,setFontDefault,setShowSplashScreen,setHeadlessMode,setEngineManualStep,setGLEnable,setDrawScreenSpace,setTileDefaultSize,setTileDefaultPadding,setTileDefaultBleed,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultRestitution,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setGLCircleSides,setTouchInputEnable,setGamepadsEnable,setGamepadDirectionEmulateStick,setGamepadAxisFilterEnable,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadPassthrough,setTouchGamepadCenterButtonSize,setTouchGamepadButtonCount,setTouchGamepadLeftStick,setTouchGamepadLeftButtonCount,setTouchGamepadRightStick,setTouchGamepadAnalog,setTouchGamepadFloating,setTouchGamepadSize,setTouchGamepadAlpha,setTouchGamepadDisplayTime,setTouchGamepadVibration,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setSoundPauseWhenHidden,setDebugWatermark,setDebugKey,setDebugKeysAlways,setDebugTweakables,setDebugOverlay,PI,abs,floor,ceil,round,min,max,sign,hypot,log2,sin,cos,tan,atan2,mod,clamp,percent,distanceWrap,lerpWrap,distanceAngle,lerpAngle,lerp,percentLerp,smoothStep,nearestPowerOfTwo,isPowerOfTwo,isOverlapping,isIntersecting,collideCircleCircle,collideCircleBox,collideBoxBox,lineTest,oscillate,formatTime,fetchJSON,saveText,createCanvasContext,saveCanvas,saveDataURL,shareURL,readSaveData,writeSaveData,noise1D,noise2D,rand,randInt,randBool,randSign,randInCircle,randVec2,randColor,RandomGenerator,Vector2,Color,Timer,vec2,rgb,hsl,isColor,isVector2,isNumber,isStringLike,isArray,WHITE,CLEAR_WHITE,BLACK,CLEAR_BLACK,GRAY,RED,ORANGE,YELLOW,GREEN,CYAN,BLUE,PURPLE,MAGENTA,tile,TileInfo,TextureInfo,loadTexture,SpriteAnimation,Shader,mainCanvas,mainContext,drawContext,workCanvas,workContext,workReadCanvas,workReadContext,backgroundCanvas,mainCanvasSize,textureInfos,drawCount,primitiveCount,screenToWorld,worldToScreen,screenToWorldDelta,worldToScreenDelta,screenToWorldTransform,drawTile,drawRect,drawRectGradient,drawTextureWrapped,drawLineList,drawLine,drawPoly,drawRegularPoly,drawEllipse,drawCircle,drawEllipseGradient,drawCircleGradient,drawCanvas2D,drawText,drawTextScreen,setAdditiveBlendMode,setShader,setBackgroundCanvas,combineCanvases,engineImageFont,ImageFont,isFullscreen,toggleFullscreen,setCursor,getCameraSize,cameraFit,isOnScreen,glCanvas,glContext,glAntialias,glClearCanvas,glSetTexture,glSetTextureWrap,glCompileShader,glCreateProgram,glCreateTexture,glDeleteTexture,glSetTextureData,glFlush,glCopyToContext,glSetAntialias,glDraw,glDrawUntextured,glDrawPointsTransform,glDrawOutlineTransform,glDrawPoints,glDrawColoredPoints,glSetRenderTarget,glClearRect,keyIsDown,keyWasPressed,keyWasReleased,keyDirection,inputClear,inputClearKey,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseDelta,mouseDeltaScreen,mouseWheel,mouseInWindow,isUsingGamepad,lastInputDevice,inputMouseMoveThreshold,inputPreventDefault,gamepadPrimary,isTouchDevice,setInputPreventDefault,setInputMouseMoveThreshold,usingMouseInput,usingKeyboardInput,usingGamepadInput,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,gamepadStickCount,gamepadDpad,gamepadConnected,gamepadVibrate,gamepadVibrateStop,vibrate,vibrateStop,pointerLockRequest,pointerLockExit,pointerLockIsActive,audioContext,audioMasterGain,setAudioMasterEffect,audioDefaultSampleRate,audioIsRunning,Sound,SoundInstance,speak,speakStop,getNoteFrequency,playSamples,playAudioBuffer,createAudioBuffer,zzfx,zzfxG,EngineObject,tileCollisionLayers,tileCollisionGetData,tileCollisionTest,tileCollisionRaycast,tileLayersLoad,objectLayersAddType,objectLayersLoad,TileLayerData,CanvasLayer,TileLayer,TileCollisionLayer,ParticleEmitter,Particle,medals,medalsPreventUnlock,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,medalsInit,medalsForEach,medalsReset,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalsPreventUnlock,Medal,newgrounds,NewgroundsPlugin,NewgroundsMedal,postProcess,PostProcessPlugin,postProcessBloom,postProcessBloomShader,lightSystem,LightSystemPlugin,Light,ZzFXMusic,zzfxM,AudioEffect,AudioFilter,AudioReverb,AudioDelay,AudioDistortion,AudioCompressor,uiSystem,uiDebug,uiSetDebug,UISystemPlugin,UIObject,UIText,UITextInput,UITile,UIButton,UICheckbox,UISlider,UIVideo,UILayout,box2d,box2dDebug,box2dSetDebug,box2dInit,Box2dPlugin,Box2dObject,Box2dStaticObject,Box2dKinematicObject,Box2dTileLayer,Box2dRaycastResult,Box2dJoint,Box2dTargetJoint,Box2dDistanceJoint,Box2dPinJoint,Box2dRopeJoint,Box2dRevoluteJoint,Box2dGearJoint,Box2dPrismaticJoint,Box2dWheelJoint,Box2dWeldJoint,Box2dFrictionJoint,Box2dPulleyJoint,Box2dMotorJoint,drawNineSlice,drawNineSliceScreen,drawThreeSlice,drawThreeSliceScreen,TileSlice,drawCrescent,getCrescentPoints,Tween,tweenProperty,tweenStopAll,tweenUpdate,Ease,PathFinder,PathFinderNode,tweak,tweakDivider,tweakButton,tweakEngineDefaults,vec3,isVector3,randVector3,randInSphere,Vector3,Matrix4,Ray3D,buildMatrix,isPointInBox3D,isOverlapping3D,collideSphereSphere,collideSphereBox,collideSphereInBox,collideSphereCylinder,collideBoxBox3D,raycastSphere,raycastPlane,raycastBox,render3D,Render3DPlugin,Camera3D,EngineObject3D,Mesh,InstancedMesh3D,buildLathe,buildCylinder,buildSphere,buildCone,buildCapsule,buildTorus,buildBox,buildGrid,buildRibbon,buildLoft,buildSky,buildExtrude,buildText3D,HeightMap,VoxelMap,Light3D,DirectionalLight3D,CameraControl3D,FirstPersonCamera3D,ParticleEmitter3D,Trail3D,engineObjectsCollect3D,engineObjectsCallback3D,engineObjectsRaycast3D,parseOBJ,loadOBJ,GLTFModel,GLTFPart,GLTFObject,GLTFAnimation,parseGLTF,loadGLTF,debugBox3D,debugSphere3D,debugLine3D,debugPoint3D,threeJS,ThreeJSPlugin,ThreeJSObject,textureSheetSize,textureSheetPadding,setTextureSheetSize,setTextureSheetPadding,textureSheets,TextureSheet,loadSprite,loadAtlas,parseAtlas,spritesReady};
|