UNPKG

littlejsengine

Version:

LittleJS - Tiny and Fast HTML5 Game Engine

4 lines (3 loc) 182 kB
// LittleJS Engine - MIT License - Copyright 2021 Frank Force // https://github.com/KilledByAPixel/LittleJS const engineName="LittleJS",engineVersion="1.18.21",frameRate=60,timeDelta=1/frameRate;let engineObjects=[],engineObjectsCollide=[],frame=0,time=0,timeReal=0,paused=!1;function getPaused(){return paused}function setPaused(a=!0){paused=a}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0,showEngineVersion=!0;const pluginList=[];class EnginePlugin{constructor(a,b,c,d){this.update=a;this.render=b;this.glContextLost=c;this.glContextRestored=d}}function engineAddPlugin(a,b,c,d){ASSERT(!pluginList.find(f=>f.update===a&&f.render===b&&f.glContextLost===c&&f.glContextRestored===d));const e=new EnginePlugin(a,b,c,d);pluginList.push(e)}async function engineInit(a,b,c,d,e,f=[],g){function h(m=0){function n(){if(!headlessMode){r||k();mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);mainContext.imageSmoothingEnabled=!tilesPixelated;glPreRender();d();engineObjects.sort((t,u)=>t.renderOrder-u.renderOrder);for(const t of engineObjects)t.destroyed||t.render();e();pluginList.forEach(t=>t.render?.());inputRender();debugRender();glFlush();debugRenderPost();primitiveCount=drawCount=0}}var p=m-frameTimeLastMS;frameTimeLastMS||(p=0);frameTimeLastMS=m;if(debug||debugWatermark)averageFPS=lerp(averageFPS,1e3/(p||1),.05);m=debug&&keyIsDown("Equal");const q=debug&&keyIsDown("Minus");m=m?10:q?.1:1;timeReal+=p*m/1e3;m*=timeScale;frameTimeBufferMS+=paused?0:p*m;1>=m&&(frameTimeBufferMS=min(frameTimeBufferMS,50));let r=!1;if(paused){r=!0;k();inputUpdate();pluginList.forEach(t=>t.update?.());for(const t of engineObjects)t.parent||t.updateTransforms();debugUpdate();c();inputUpdatePost();debugVideoCaptureIsActive()&&n()}else{p=0;0>frameTimeBufferMS&&-9<frameTimeBufferMS&&(p=frameTimeBufferMS,frameTimeBufferMS=0);for(;0<=frameTimeBufferMS;frameTimeBufferMS-=1e3/frameRate)time=frame++/frameRate,r=!0,k(),inputUpdate(),b(),pluginList.forEach(t=>t.update?.()),engineObjectsUpdate(),debugUpdate(),c(),inputUpdatePost(),debugVideoCaptureIsActive()&&n();frameTimeBufferMS+=p}debugVideoCaptureIsActive()||n();requestAnimationFrame(h)}function k(){if(!headlessMode){if(canvasFixedSize.x){mainCanvasSize=canvasFixedSize.copy();var m=innerWidth/innerHeight,n=canvasFixedSize.x/canvasFixedSize.y,p=m<n?"100%":"";m=m<n?"":"100%";mainCanvas.style.width=p;mainCanvas.style.height=m;glCanvas&&(glCanvas.style.width=p,glCanvas.style.height=m)}else p=canvasPixelRatio??(devicePixelRatio||1),m=innerWidth*p|0,n=innerHeight*p|0,mainCanvasSize.x=min(m,canvasMaxSize.x),mainCanvasSize.y=min(n,canvasMaxSize.y),m/=n,ASSERT(canvasMinAspect<=canvasMaxAspect),canvasMaxAspect&&m>canvasMaxAspect?mainCanvasSize.x=min(mainCanvasSize.y*canvasMaxAspect|0,canvasMaxSize.x):m<canvasMinAspect&&(mainCanvasSize.y=min(mainCanvasSize.x/canvasMinAspect|0,canvasMaxSize.y)),m=(mainCanvasSize.x/p|0)+"px",p=(mainCanvasSize.y/p|0)+"px",mainCanvas.style.width=m,mainCanvas.style.height=p,glCanvas&&(glCanvas.style.width=m,glCanvas.style.height=p);mainCanvas.width=mainCanvasSize.x;mainCanvas.height=mainCanvasSize.y;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"}}async function l(){await a();h()}showEngineVersion&&console.log(`${engineName} Engine v${engineVersion}`);ASSERT(!mainContext,"engine already initialized");if(!mainContext){ASSERT(isArray(f),"pass in images as array");document.body||document.documentElement.appendChild(document.createElement("body"));g||=document.body;a||=()=>{};b||=()=>{};c||=()=>{};d||=()=>{};e||=()=>{};if(headlessMode)return l();glInit(g);g.style.cssText="margin:0;overflow:hidden;background:#000;user-select:none;-webkit-user-select:none;touch-action:none;-webkit-touch-callout:none";mainCanvas=g.appendChild(document.createElement("canvas"));drawContext=mainContext=mainCanvas.getContext("2d");inputInit();audioInit();debugInit();mainCanvas.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)";glCanvas&&(glCanvas.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)");setCanvasPixelated(canvasPixelated);k();glPreRender();workCanvas=new OffscreenCanvas(64,64);workContext=workCanvas.getContext("2d");workReadCanvas=new OffscreenCanvas(64,64);workReadContext=workReadCanvas.getContext("2d",{willReadFrequently:!0});g=f.map((m,n)=>loadTexture(n,m));f.length||g.push(loadTexture(0));g.push(imageFontInit());showSplashScreen&&g.push(new Promise(m=>{function n(){inputClear();drawEngineLogo(p+=.01);1<p?m():setTimeout(n,16)}let p=0;n()}));await Promise.all(g);return l()}}function engineObjectsUpdate(){function a(b){if(!b.destroyed){b.update();for(const c of b.children)a(c)}}engineObjectsCollide=engineObjects.filter(b=>b.collideSolidObjects);for(const b of engineObjects)b.parent||b.destroyed||b.updatePhysics();for(const b of engineObjects)if(!b.parent&&!b.destroyed){b.update();for(const c of b.children)a(c);b.updateTransforms()}engineObjects=engineObjects.filter(b=>!b.destroyed)}function engineObjectsDestroy(a=!0){for(const b of engineObjects)b.parent||b.destroy(a);engineObjects=engineObjects.filter(b=>!b.destroyed)}function engineObjectsCollect(a,b,c=engineObjects){const d=[];if(a)if(b instanceof Vector2)for(const e of c)e.isOverlapping(a,b)&&d.push(e);else{b*=b;for(const e of c)a.distanceSquared(e.pos)<b&&d.push(e)}else for(const e of c)d.push(e);return d}function engineObjectsCallback(a,b,c,d=engineObjects){engineObjectsCollect(a,b,d).forEach(e=>c(e))}function engineObjectsRaycast(a,b,c=engineObjects){const d=[];for(const e of c)e.collideRaycast&&isIntersecting(a,b,e.pos,e.size)&&(debugRaycast&&debugRect(e.pos,e.size,"#f00"),d.push(e));debugRaycast&&debugLine(a,b,d.length?"#f00":"#00f",.02);return d}let debugWatermark=0,debugKey="";const debug=0,debugOverlay=0,debugPhysics=0,debugParticles=0,debugRaycast=0,debugGamepads=0,debugSound=0,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!1}function debugVideoCaptureStart(){}function debugVideoCaptureStop(){}function debugVideoCaptureUpdate(){}function debugProtectConstant(a){return a}const PI=Math.PI,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,min=Math.min,max=Math.max,sign=a=>Math.sign(a),hypot=(...a)=>Math.hypot(...a),log2=a=>Math.log2(a),sin=Math.sin,cos=Math.cos,tan=Math.tan,atan2=Math.atan2;function mod(a,b=1){return(a%b+b)%b}function clamp(a,b=0,c=1){return a<b?b:a>c?c:a}function percent(a,b,c){return(c-=b)?clamp((a-b)/c):0}function lerp(a,b,c){return a+clamp(c)*(b-a)}function percentLerp(a,b,c,d,e){return lerp(d,e,percent(a,b,c))}function distanceWrap(a,b,c=1){ASSERT(0<c,"distanceWrap wrapSize must be > 0");a=(a-b)%c;return 2*a%c-a}function lerpWrap(a,b,c,d=1){return a+clamp(c)*distanceWrap(b,a,d)}function distanceAngle(a,b){return distanceWrap(a,b,2*PI)}function lerpAngle(a,b,c){return lerpWrap(a,b,c,2*PI)}function smoothStep(a){return a*a*(3-2*a)}function isPowerOfTwo(a){return 0<a&&!(a&a-1)}function nearestPowerOfTwo(a){return 2**ceil(log2(a))}function isOverlapping(a,b,c,d=vec2()){const e=2*(a.y-c.y),f=b.x+d.x;b=b.y+d.y;return abs(2*(a.x-c.x))<f&&abs(e)<b}function isIntersecting(a,b,c,d){c=c.subtract(d.scale(.5));d=c.add(d);b=b.subtract(a);c=a.subtract(c);d=a.subtract(d);a=[-b.x,b.x,-b.y,b.y];b=[c.x,-d.x,c.y,-d.y];c=0;d=1;for(let e=4;e--;)if(a[e]){const f=b[e]/a[e];if(0>a[e]){if(f>d)return!1;c=max(f,c)}else{if(f<c)return!1;d=min(f,d)}}else if(0>b[e])return!1;return!0}function oscillate(a=1,b=1,c=time,d=0,e=0){a=mod(d+c*a,1);e=1===e?2*abs(2*a-1)-1:2===e?.5>a?-1:1:3===e?2*a-1:-cos(2*a*PI);return b/2*(e+1)}function isNumber(a){return"number"===typeof a&&!isNaN(a)}function isStringLike(a){return null!=a&&"string"===typeof a?.toString()}function isArray(a){return Array.isArray(a)}function lineTest(a,b,c,d){ASSERT(isVector2(a),"posStart must be a vec2");ASSERT(isVector2(b),"posEnd must be a vec2");ASSERT("function"===typeof c,"testFunction must be a function");ASSERT(!d||isVector2(d),"normal must be a vec2");var e=b.x-a.x,f=b.y-a.y;const g=(e*e+f*f)**.5;if(g){b=a.floor();var h=e/g,k=f/g;e=sign(h);f=sign(k);var l=h?abs(1/h):Infinity,m=k?abs(1/k):Infinity,n=0<e?b.x+1:b.x,p=0<f?b.y+1:b.y,q=0;n=h?(n-a.x)/h:Infinity;var r=k?(p-a.y)/k:Infinity;for(p=l<m;q<g;){if(c(b))return a=vec2(a.x+h*q,a.y+k*q),c=a.floor(),c.x<b.x?a.x=b.x:c.x>b.x&&(a.x=b.x+1-1e-9),c.y<b.y?a.y=b.y:c.y>b.y&&(a.y=b.y+1-1e-9),d&&(p?d.set(-e,0):d.set(0,-f)),a;(p=n<r)?(b.x+=e,q=n,n+=l):(b.y+=f,q=r,r+=m)}}}function rand(a=1,b=0){return b+Math.random()*(a-b)}function randInt(a,b=0){return floor(rand(a,b))}function randBool(a=.5){return rand()<a}function randSign(){return 2*randInt(2)-1}function randVec2(a=1){return(new Vector2).setAngle(rand(2*PI),a)}function randInCircle(a=1,b=0){if(0>=a)return new Vector2;b=clamp(b/a);return randVec2(a*rand(b*b,1)**.5)}function randColor(a=new Color,b=new Color(0,0,0,1),c=!1){return c?a.lerp(b,rand()):new Color(rand(a.r,b.r),rand(a.g,b.g),rand(a.b,b.b),rand(a.a,b.a))}class RandomGenerator{constructor(a=123456789){ASSERT(0!==a,"RandomGenerator seed must be non-zero (xorshift is fixed at 0)");this.seed=a}float(a=1,b=0){this.seed^=this.seed<<13;this.seed^=this.seed>>>17;this.seed^=this.seed<<5;return b+(this.seed>>>0)/2**32*(a-b)}int(a,b=0){return floor(this.float(a,b))}bool(a=.5){return this.float()<a}sign(){return.5<this.float()?1:-1}floatSign(a=1,b=0){const c=min(a,b);a=max(a,b)-c;b=this.float(2*a);return b<a?c+b:a-c-b}angle(){return this.float(-PI,PI)}vec2(a=1,b=0){return vec2(this.float(a,b),this.float(a,b))}randColor(a=new Color,b=new Color(0,0,0,1),c=!1){return c?a.lerp(b,this.float()):new Color(this.float(a.r,b.r),this.float(a.g,b.g),this.float(a.b,b.b),this.float(a.a,b.a))}mutateColor(a,b=.05,c=0){ASSERT_NUMBER_VALID(b);ASSERT_NUMBER_VALID(c);return new Color(a.r+this.float(b,-b),a.g+this.float(b,-b),a.b+this.float(b,-b),a.a+this.float(c,-c)).clamp()}}function vec2(a=0,b){return new Vector2(a,b??a)}function isVector2(a){return a instanceof Vector2&&a.isValid()}function ASSERT_VECTOR2_VALID(a){ASSERT(isVector2(a),"Vector2 is invalid.",a)}function ASSERT_NUMBER_VALID(a){ASSERT(isNumber(a),"Number is invalid.",a)}function ASSERT_VECTOR2_NORMAL(a){ASSERT_VECTOR2_VALID(a);ASSERT(.01>abs(a.lengthSquared()-1),"Vector2 is not normal.",a)}class Vector2{constructor(a=0,b=0){this.x=a;this.y=b;ASSERT(this.isValid(),"Constructed Vector2 is invalid.",this)}set(a=0,b=0){this.x=a;this.y=b;ASSERT_VECTOR2_VALID(this);return this}setFrom(a){return this.set(a.x,a.y)}copy(){return new Vector2(this.x,this.y)}add(a){return new Vector2(this.x+a.x,this.y+a.y)}subtract(a){return new Vector2(this.x-a.x,this.y-a.y)}multiply(a){return new Vector2(this.x*a.x,this.y*a.y)}divide(a){return new Vector2(this.x/a.x,this.y/a.y)}scale(a){return new Vector2(this.x*a,this.y*a)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(a){return this.distanceSquared(a)**.5}distanceSquared(a){return(this.x-a.x)**2+(this.y-a.y)**2}normalize(a=1){const b=this.length();return b?this.scale(a/b):new Vector2(0,a)}clampLength(a=1){const b=this.length();return b>a?this.scale(a/b):this.copy()}dot(a){return this.x*a.x+this.y*a.y}cross(a){return this.x*a.y-this.y*a.x}reflect(a,b=1){return this.subtract(a.scale((1+b)*this.dot(a)))}angle(){return atan2(this.x,this.y)}setAngle(a=0,b=1){ASSERT_NUMBER_VALID(a);ASSERT_NUMBER_VALID(b);this.x=b*sin(a);this.y=b*cos(a);return this}rotate(a){ASSERT_NUMBER_VALID(a);const b=cos(-a);a=sin(-a);return new Vector2(this.x*b-this.y*a,this.x*a+this.y*b)}setDirection(a,b=1){ASSERT_NUMBER_VALID(a);ASSERT_NUMBER_VALID(b);a=mod(a,4);ASSERT(0===a||1===a||2===a||3===a,"Vector2.setDirection() direction must be an integer between 0 and 3.");this.x=a%2?a-1?-b:b:0;this.y=a%2?0:a?-b:b;return this}direction(){return abs(this.x)>abs(this.y)?0>this.x?3:1:0>this.y?2:0}abs(){return new Vector2(abs(this.x),abs(this.y))}floor(){return new Vector2(floor(this.x),floor(this.y))}snap(a){ASSERT_NUMBER_VALID(a);return new Vector2(floor(this.x*a)/a,floor(this.y*a)/a)}mod(a=1){return new Vector2(mod(this.x,a),mod(this.y,a))}area(){return abs(this.x*this.y)}lerp(a,b){ASSERT_VECTOR2_VALID(a);ASSERT_NUMBER_VALID(b);b=clamp(b);return new Vector2(a.x*b+this.x*(1-b),a.y*b+this.y*(1-b))}arrayCheck(a){return 0<=this.x&&0<=this.y&&this.x<a.x&&this.y<a.y}toString(a=3){ASSERT_NUMBER_VALID(a);return this.isValid()?`(${(0>this.x?"":" ")+this.x.toFixed(a)},${(0>this.y?"":" ")+this.y.toFixed(a)} )`:`(${this.x}, ${this.y})`}isValid(){return isNumber(this.x)&&isNumber(this.y)}}function rgb(a,b,c,d){return new Color(a,b,c,d)}function hsl(a,b,c,d){return(new Color).setHSLA(a,b,c,d)}function isColor(a){return a instanceof Color&&a.isValid()}function ASSERT_COLOR_VALID(a){ASSERT(isColor(a),"Color is invalid.",a)}class Color{constructor(a=1,b=1,c=1,d=1){this.r=a;this.g=b;this.b=c;this.a=d;ASSERT(this.isValid(),"Constructed Color is invalid.",this)}set(a=1,b=1,c=1,d=1){this.r=a;this.g=b;this.b=c;this.a=d;ASSERT_COLOR_VALID(this);return this}setFrom(a){return this.set(a.r,a.g,a.b,a.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(a){return new Color(this.r+a.r,this.g+a.g,this.b+a.b,this.a+a.a)}subtract(a){return new Color(this.r-a.r,this.g-a.g,this.b-a.b,this.a-a.a)}multiply(a){return new Color(this.r*a.r,this.g*a.g,this.b*a.b,this.a*a.a)}divide(a){return new Color(this.r/a.r,this.g/a.g,this.b/a.b,this.a/a.a)}scale(a,b=a){return new Color(this.r*a,this.g*a,this.b*a,this.a*b)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(a,b){ASSERT_COLOR_VALID(a);ASSERT_NUMBER_VALID(b);b=clamp(b);return new Color(a.r*b+this.r*(1-b),a.g*b+this.g*(1-b),a.b*b+this.b*(1-b),a.a*b+this.a*(1-b))}setHSLA(a=0,b=0,c=1,d=1){a=mod(a,1);b=clamp(b);c=clamp(c);b=.5>c?c*(1+b):c+b-c*b;c=2*c-b;const e=(f,g,h)=>1>6*(h=mod(h,1))?f+6*(g-f)*h:1>2*h?g:2>3*h?f+(g-f)*(4-6*h):f;this.r=e(c,b,a+1/3);this.g=e(c,b,a);this.b=e(c,b,a-1/3);this.a=d;ASSERT_COLOR_VALID(this);return this}HSLA(){const a=clamp(this.r),b=clamp(this.g),c=clamp(this.b),d=clamp(this.a),e=max(a,b,c),f=min(a,b,c),g=(e+f)/2;let h=0,k=0;if(e!==f){let l=e-f;k=.5<g?l/(2-e-f):l/(e+f);a===e?h=(b-c)/l+(b<c?6:0):b===e?h=(c-a)/l+2:c===e&&(h=(a-b)/l+4)}return[h/6,k,g,d]}mutate(a=.05,b=0){ASSERT_NUMBER_VALID(a);ASSERT_NUMBER_VALID(b);return new Color(this.r+rand(a,-a),this.g+rand(a,-a),this.b+rand(a,-a),this.a+rand(b,-b)).clamp()}toString(a=!0){if(debug&&!this.isValid())return"#000";const b=c=>(16>(c=255*clamp(c)|0)?"0":"")+c.toString(16);return"#"+b(this.r)+b(this.g)+b(this.b)+(a?b(this.a):"")}setHex(a){ASSERT(isStringLike(a),"Color hex code must be a string");ASSERT("#"===a[0],"Color hex code must start with #");ASSERT([4,5,7,9].includes(a.length),"Invalid hex");6>a.length?(this.r=clamp(parseInt(a[1],16)/15),this.g=clamp(parseInt(a[2],16)/15),this.b=clamp(parseInt(a[3],16)/15),this.a=5===a.length?clamp(parseInt(a[4],16)/15):1):(this.r=clamp(parseInt(a.slice(1,3),16)/255),this.g=clamp(parseInt(a.slice(3,5),16)/255),this.b=clamp(parseInt(a.slice(5,7),16)/255),this.a=9===a.length?clamp(parseInt(a.slice(7,9),16)/255):1);ASSERT_COLOR_VALID(this);return this}rgbaInt(){const a=255*clamp(this.r)|0,b=255*clamp(this.g)<<8,c=255*clamp(this.b)<<16,d=255*clamp(this.a)<<24;return a+b+c+d}isValid(){return isNumber(this.r)&&isNumber(this.g)&&isNumber(this.b)&&isNumber(this.a)}}const WHITE=debugProtectConstant(rgb()),CLEAR_WHITE=debugProtectConstant(rgb(1,1,1,0)),BLACK=debugProtectConstant(rgb(0,0,0)),CLEAR_BLACK=debugProtectConstant(rgb(0,0,0,0)),GRAY=debugProtectConstant(rgb(.5,.5,.5)),RED=debugProtectConstant(rgb(1,0,0)),ORANGE=debugProtectConstant(rgb(1,.5,0)),YELLOW=debugProtectConstant(rgb(1,1,0)),GREEN=debugProtectConstant(rgb(0,1,0)),CYAN=debugProtectConstant(rgb(0,1,1)),BLUE=debugProtectConstant(rgb(0,0,1)),PURPLE=debugProtectConstant(rgb(.5,0,1)),MAGENTA=debugProtectConstant(rgb(1,0,1));class Timer{constructor(a,b=!1){ASSERT(void 0===a||isNumber(a),"Constructed Timer is invalid.",a);this.useRealTime=b;b=this.getGlobalTime();this.time=void 0===a?void 0:b+a;this.setTime=a}set(a=0){ASSERT(isNumber(a),"Timer is invalid.",a);this.time=this.getGlobalTime()+a;this.setTime=a}setUseRealTime(a=!0){ASSERT(!this.isSet(),"Cannot change global time setting while timer is set.");this.useRealTime=a}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 "+(0>this.get()?"before":"after"):"unset"}valueOf(){return this.get()}}function formatTime(a){const b=0>a?"-":"";a=abs(a)|0;return b+(a/60|0)+":"+(10>a%60?"0":"")+a%60}async function fetchJSON(a){const b=await fetch(a);if(!b.ok)throw Error(`Failed to fetch JSON from ${a}: ${b.status} ${b.statusText}`);return b.json()}function saveText(a,b="text",c="text/plain"){saveDataURL(URL.createObjectURL(new Blob([a],{type:c})),b)}function saveCanvas(a,b="screenshot",c="image/png"){if(a instanceof OffscreenCanvas){const d=document.createElement("canvas");d.width=a.width;d.height=a.height;d.getContext("2d").drawImage(a,0,0);saveDataURL(d.toDataURL(c),b)}else saveDataURL(a.toDataURL(c),b)}function saveDataURL(a,b="download",c){ASSERT(isStringLike(a),"saveDataURL requires url string");ASSERT(isStringLike(b),"saveDataURL requires filename string");const d=document.createElement("a");d.download=b;d.href=a;d.click();void 0!==c&&setTimeout(()=>URL.revokeObjectURL(a),c)}function shareURL(a,b,c){ASSERT(isStringLike(a),"shareURL requires title string");ASSERT(isStringLike(b),"shareURL requires url string");navigator.share?.({title:a,url:b}).then(()=>c?.())}function readSaveData(a,b){ASSERT(isStringLike(a),"loadData requires saveName string");let c={};try{const d=localStorage[a];if(d)try{c=JSON.parse(d)}catch{LOG("readSaveData: corrupt JSON for",a,"— using defaults")}}catch{LOG("readSaveData: localStorage unavailable — using defaults")}return{...b,...c}}function writeSaveData(a,b){ASSERT(isStringLike(a),"saveData requires saveName string");try{localStorage[a]=JSON.stringify(b)}catch{LOG("writeSaveData: failed to write",a)}}function noiseHash(a){a=(a|0)^2654435769;a=Math.imul(a^a>>>16,2246822507);a=Math.imul(a^a>>>13,3266489909);return((a^a>>>16)>>>0)/2**32}function noise1D(a){const b=floor(a);return lerp(noiseHash(b),noiseHash(b+1),smoothStep(a-b))}function noise2D(a,b){const c=floor(a),d=floor(b);a=smoothStep(a-c);b=smoothStep(b-d);return lerp(lerp(noiseHash(c+374761393*d),noiseHash(c+1+374761393*d),a),lerp(noiseHash(c+374761393*(d+1)),noiseHash(c+1+374761393*(d+1)),a),b)}let cameraPos=vec2(),cameraAngle=0,cameraScale=32,timeScale=1,canvasColorTiles=!0,canvasClearColor=CLEAR_BLACK,canvasMaxSize=vec2(1920,1080),canvasMinAspect=0,canvasMaxAspect=0,canvasFixedSize=vec2(),canvasPixelated=!1,tilesPixelated=!0,canvasPixelRatio=1,fontDefault="arial",showSplashScreen=!1,headlessMode=!1,glEnable=!0,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,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;function setCameraPos(a){cameraPos=a.copy()}function setCameraAngle(a){cameraAngle=a}function setCameraScale(a){cameraScale=a}function setTimeScale(a){timeScale=a}function setCanvasColorTiles(a){canvasColorTiles=a}function setCanvasClearColor(a){canvasClearColor=a.copy()}function setCanvasMaxSize(a){canvasMaxSize=a.copy()}function setCanvasMinAspect(a){canvasMinAspect=a}function setCanvasMaxAspect(a){canvasMaxAspect=a}function setCanvasFixedSize(a){canvasFixedSize=a.copy()}function setCanvasPixelated(a){canvasPixelated=a;mainCanvas&&(mainCanvas.style.imageRendering=a?"pixelated":"");glCanvas&&(glCanvas.style.imageRendering=a?"pixelated":"")}function setTilesPixelated(a){tilesPixelated=a}function setCanvasPixelRatio(a){canvasPixelRatio=a}function setFontDefault(a){fontDefault=a}function setShowSplashScreen(a){showSplashScreen=a}function setHeadlessMode(a){headlessMode=a}function setGLEnable(a){a&&!glCanBeEnabled?console.warn("Can not enable WebGL if it was disabled on start."):(glEnable=a,glCanvas&&(glCanvas.style.display=a?"":"none"))}function setGLCircleSides(a){glCircleSides=a}function setTileDefaultSize(a){tileDefaultSize=a.copy()}function setTileDefaultPadding(a){tileDefaultPadding=a}function setTileDefaultBleed(a){tileDefaultBleed=a}function setEnablePhysicsSolver(a){enablePhysicsSolver=a}function setObjectDefaultMass(a){objectDefaultMass=a}function setObjectDefaultDamping(a){objectDefaultDamping=a}function setObjectDefaultAngleDamping(a){objectDefaultAngleDamping=a}function setObjectDefaultRestitution(a){objectDefaultRestitution=a}function setObjectDefaultFriction(a){objectDefaultFriction=a}function setObjectMaxSpeed(a){objectMaxSpeed=a}function setGravity(a){gravity=a.copy()}function setParticleEmitRateScale(a){particleEmitRateScale=a}function setGamepadsEnable(a){gamepadsEnable=a}function setGamepadDirectionEmulateStick(a){gamepadDirectionEmulateStick=a}function setInputWASDEmulateDirection(a){inputWASDEmulateDirection=a}function setTouchInputEnable(a){touchInputEnable=a}function setTouchGamepadEnable(a){touchGamepadEnable=a}function setTouchGamepadPassthrough(a){touchGamepadPassthrough=a}function setTouchGamepadCenterButtonSize(a){touchGamepadCenterButtonSize=a}function setTouchGamepadButtonCount(a){touchGamepadButtonCount=a;0<a&&(touchGamepadRightStick=!1)}function setTouchGamepadLeftStick(a){(touchGamepadLeftStick=a)&&(touchGamepadLeftButtonCount=0)}function setTouchGamepadLeftButtonCount(a){touchGamepadLeftButtonCount=a;0<a&&(touchGamepadLeftStick=!1)}function setTouchGamepadRightStick(a){(touchGamepadRightStick=a)&&(touchGamepadButtonCount=0)}function setTouchGamepadAnalog(a){touchGamepadAnalog=a}function setTouchGamepadFloating(a){touchGamepadFloating=a}function setTouchGamepadSize(a){touchGamepadSize=a}function setTouchGamepadAlpha(a){touchGamepadAlpha=a}function setTouchGamepadDisplayTime(a){touchGamepadDisplayTime=a}function setTouchGamepadVibration(a){touchGamepadVibration=a}function setVibrateEnable(a){vibrateEnable=a}function setSoundEnable(a){soundEnable=a}function setSoundVolume(a){soundVolume=a;soundEnable&&!headlessMode&&audioMasterGain&&(audioMasterGain.gain.value=a)}function setSoundDefaultRange(a){soundDefaultRange=a}function setSoundDefaultTaper(a){soundDefaultTaper=a}function setDebugWatermark(a){debugWatermark=a}function setDebugKey(a){debugKey=a}class EngineObject{constructor(a=vec2(),b=vec2(1),c,d=0,e=WHITE,f=0){ASSERT(isVector2(a),"object pos must be a vec2");ASSERT(isVector2(b),"object size must be a vec2");ASSERT(!c||c instanceof TileInfo,"object tileInfo should be a TileInfo or undefined");ASSERT("number"===typeof d&&isFinite(d),"object angle should be a number");ASSERT(isColor(e),"object color should be a valid rgba color");ASSERT("number"===typeof f,"object renderOrder should be a number");this.pos=a.copy();this.size=b.copy();this.drawSize=void 0;this.tileInfo=c;this.angle=d;this.color=e.copy();this.additiveColor=void 0;this.destroyed=this.mirror=!1;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.restitution=objectDefaultRestitution;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=f;this.velocity=vec2();this.angleVelocity=0;this.spawnTime=time;this.children=[];this.clampSpeed=!0;this.parent=this.groundObject=void 0;this.localPos=vec2();this.localAngle=0;this.collideRaycast=this.isSolid=this.collideSolidObjects=this.collideTiles=!1;engineObjects.push(this)}updateTransforms(){var a=this.parent;if(a){const c=a.getMirrorSign();var b=this.localPos;const d=a.pos,e=b.x*c;b=b.y;if(a=a.angle){const f=cos(-a),g=sin(-a);this.pos.set(e*f-b*g+d.x,e*g+b*f+d.y)}else this.pos.set(e+d.x,b+d.y);this.angle=c*this.localAngle+a}for(const c of this.children)c.updateTransforms()}updatePhysics(){ASSERT(!this.parent);if(!this.destroyed){this.clampSpeed&&(this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed),this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed));var a=this.pos.copy();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;ASSERT(0<=this.angleDamping&&1>=this.angleDamping);ASSERT(0<=this.damping&&1>=this.damping);if(enablePhysicsSolver&&this.mass){var b=0>this.velocity.y&&0>gravity.y||0<this.velocity.y&&0<gravity.y;if(this.groundObject){var c=max(this.friction,this.groundObject.friction),d=this.groundObject.velocity.x;this.velocity.x=d+(this.velocity.x-d)*c;this.groundObject=void 0}if(this.collideSolidObjects)for(var e of engineObjectsCollide)if(!e.destroyed&&!e.parent&&e!==this&&(this.isSolid||e.isSolid)&&this.isOverlappingObject(e)&&(c=this.collideWithObject(e),d=e.collideWithObject(this),c&&d))if(isOverlapping(a,this.size,e.pos,e.size))c=a.subtract(e.pos),d=c.length(),c=.001>d?vec2(0,1):c.scale(.001/d),this.velocity=this.velocity.add(c),e.mass&&(e.velocity=e.velocity.subtract(c)),debugPhysics&&debugOverlap(this.pos,this.size,e.pos,e.size,"#f00");else{d=this.size.add(e.size);var f=2*(a.y-e.pos.y)>d.y+gravity.y,g=2*abs(a.y-e.pos.y)<d.y,h=2*abs(a.x-e.pos.x)<d.x;c=max(this.restitution,e.restitution);if(f||h||!g)if(this.pos.y=e.pos.y+(d.y/2+.001)*sign(a.y-e.pos.y),e.groundObject&&b||!e.mass)b&&(this.groundObject=e),this.velocity.y*=-c;else if(e.mass){h=(this.mass*this.velocity.y+e.mass*e.velocity.y)/(this.mass+e.mass);const k=e.velocity.y*(e.mass-this.mass)/(this.mass+e.mass)+2*this.velocity.y*this.mass/(this.mass+e.mass);this.velocity.y=lerp(h,this.velocity.y*(this.mass-e.mass)/(this.mass+e.mass)+2*e.velocity.y*e.mass/(this.mass+e.mass),c);e.velocity.y=lerp(h,k,c)}!f&&g&&(this.pos.x=e.pos.x+(d.x/2+.001)*sign(a.x-e.pos.x),e.mass?(d=(this.mass*this.velocity.x+e.mass*e.velocity.x)/(this.mass+e.mass),f=e.velocity.x*(e.mass-this.mass)/(this.mass+e.mass)+2*this.velocity.x*this.mass/(this.mass+e.mass),this.velocity.x=lerp(d,this.velocity.x*(this.mass-e.mass)/(this.mass+e.mass)+2*e.velocity.x*e.mass/(this.mass+e.mass),c),e.velocity.x=lerp(d,f,c)):this.velocity.x*=-c);debugPhysics&&debugOverlap(this.pos,this.size,e.pos,e.size,"#f0f")}if(this.collideTiles&&(e=tileCollisionTest(this.pos,this.size,this))&&!tileCollisionTest(a,this.size,this)){d=tileCollisionTest(vec2(this.pos.x,a.y),this.size,this);f=tileCollisionTest(vec2(a.x,this.pos.y),this.size,this);c=max(this.restitution,e.restitution);if(d){g=0<(0<gravity.y?-1:1)?floor(a.y-this.size.y/2+1)+this.size.y/2+.001:ceil(a.y+this.size.y/2-1)-this.size.y/2-.001;if(.1>abs(g-this.pos.y)&&!tileCollisionTest(vec2(this.pos.x,g),this.size,this)){this.pos.y=g;debugPhysics&&debugRect(this.pos,this.size,"#ff0");return}this.pos.x=a.x;this.velocity.x*=-c}if(f||!d)b?(b=this.size.y/2+1e-4,this.pos.y=0>gravity.y?floor(a.y-this.size.y/2)+b:ceil(a.y+this.size.y/2)-b,this.groundObject=e):(this.pos.y=a.y,this.groundObject=void 0),this.velocity.y*=-c;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(a=!1){if(!this.destroyed){this.destroyed=!0;this.parent?.removeChild(this);for(const b of this.children)b.parent=void 0,b.destroy(a)}}localToWorld(a){return this.pos.add(a.rotate(this.angle))}worldToLocal(a){return a.subtract(this.pos).rotate(-this.angle)}localToWorldVector(a){return a.rotate(this.angle)}worldToLocalVector(a){return a.rotate(-this.angle)}collideWithTile(a,b){return 0<a}collideWithObject(a){return!0}getUp(a=1){return vec2().setAngle(this.angle,a)}getRight(a=1){return vec2().setAngle(this.angle+PI/2,a)}getAliveTime(){return time-this.spawnTime}getSpeed(){return this.velocity.length()}applyAcceleration(a){this.mass&&(this.velocity=this.velocity.add(a))}applyAngularAcceleration(a){this.mass&&(this.angleVelocity+=a)}applyForce(a){this.mass&&this.applyAcceleration(a.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(a,b=vec2(),c=0){ASSERT(!this.destroyed,"cannot add child to destroyed object");if(this.destroyed)return a;ASSERT(!a.parent&&!this.children.includes(a));ASSERT(a instanceof EngineObject,"child must be an EngineObject");ASSERT(a!==this,"cannot add self as child");this.children.push(a);a.parent=this;a.localPos=b.copy();a.localAngle=c;a.updateTransforms();return a}removeChild(a){ASSERT(a.parent===this&&this.children.includes(a));this.children.splice(this.children.indexOf(a),1);a.parent=void 0}isOverlappingObject(a){return this.isOverlapping(a.pos,a.size)}isOverlapping(a,b=vec2()){return isOverlapping(this.pos,this.size,a,b)}setCollision(a=!0,b=!0,c=!0,d=!0){ASSERT(a||!b,"solid objects must be set to collide");this.collideSolidObjects=a;this.isSolid=b;this.collideTiles=c;this.collideRaycast=d}toString(){let a="type = "+this.constructor.name;if(this.pos.x||this.pos.y)a+="\npos = "+this.pos;if(this.velocity.x||this.velocity.y)a+="\nvelocity = "+this.velocity;if(this.size.x||this.size.y)a+="\nsize = "+this.size;this.angle&&(a+="\nangle = "+this.angle.toFixed(3));this.color&&(a+="\ncolor = "+this.color);return a}renderDebugInfo(){if(debug){var a=this.collideTiles||this.collideSolidObjects||this.isSolid;if(a||this.parent){var b=vec2(max(this.size.x,.2),max(this.size.y,.2)),c=rgb(this.collideTiles?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,.5);debugRect(this.pos,b,c,0,this.angle,a);this.parent&&debugRect(this.pos,b.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,mainContext,drawContext,workCanvas,workContext,workReadCanvas,workReadContext,mainCanvasSize=vec2(),textureInfos=[],drawCount,primitiveCount;function isWhite(a){return 1<=a.r&&1<=a.g&&1<=a.b}function isBlack(a){return 0>=a.r&&0>=a.g&&0>=a.b&&0>=a.a}function tile(a=0,b=tileDefaultSize,c=0,d=tileDefaultPadding,e=tileDefaultBleed){ASSERT(isVector2(a)||"number"===typeof a,"index must be a vec2 or number");ASSERT(isVector2(b)||"number"===typeof b,"size must be a vec2 or number");ASSERT(isNumber(c)||c instanceof TextureInfo,"texture must be a number or TextureInfo");ASSERT(isNumber(d),"padding must be a number");if(headlessMode)return new TileInfo;"number"===typeof b&&(ASSERT(0<b),b=new Vector2(b,b));c="number"===typeof c?textureInfos[c]:c;ASSERT(c instanceof TextureInfo,"tile texture is not loaded");ASSERT(0<c.size.x,"tile texture is not loaded");const f=b.x+2*d,g=b.y+2*d;let h;if("number"===typeof a){const k=c.size.x/f|0;h=a%k;a=a/k|0}else h=a.x,a=a.y;a=new Vector2(h*f+d,a*g+d);return new TileInfo(a,b,c,d,e)}class TileInfo{constructor(a=vec2(),b=tileDefaultSize,c=textureInfos[0],d=tileDefaultPadding,e=tileDefaultBleed){this.pos=a.copy();this.size=b.copy();this.padding=d;this.textureInfo=c;this.bleed=e}offset(a){return new TileInfo(this.pos.add(a),this.size,this.textureInfo,this.padding,this.bleed)}frame(a){ASSERT("number"===typeof a);a*=this.size.x+2*this.padding;ASSERT(a+this.size.x<=this.textureInfo.size.x,"frame extends beyond texture width!");return this.offset(new Vector2(a))}index(a){return tile(a,this.size,this.textureInfo,this.padding,this.bleed)}setFullImage(a=this.textureInfo){this.textureInfo=a;this.pos=new Vector2;this.size=a.size.copy();this.bleed=this.padding=0;return this}}class TextureInfo{constructor(a,b=!0,c=!1){this.size=(this.image=a)?vec2(a.width,a.height):vec2();this.sizeInverse=a?vec2(1/a.width,1/a.height):vec2();this.glTexture=void 0;this.wrap=c;b&&this.createWebGLTexture()}createWebGLTexture(){glRegisterTextureInfo(this)}destroyWebGLTexture(){glUnregisterTextureInfo(this)}hasWebGL(){return!!this.glTexture}setWrap(a=!0){this.wrap=a;glSetTextureWrap(this.glTexture,a)}}function drawTile(a,b=vec2(1),c,d=WHITE,e=0,f,g,h=glEnable,k=!1,l){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isColor(d),"color is invalid");ASSERT(isNumber(e),"angle must be a number");ASSERT(!g||isColor(g),"additiveColor must be a color");ASSERT(!l||!h,"context only supported in canvas 2D mode");const m=c?.textureInfo,n=c?.bleed??0;if(h&&glEnable)if(ASSERT(!!glContext,"WebGL is not enabled!"),k&&([a,b,e]=screenToWorldTransform(a,b,e)),m){var p=m.sizeInverse;h=c.pos.x*p.x;k=c.pos.y*p.y;l=c.size.x*p.x;const q=c.size.y*p.y;glSetTexture(m.glTexture);if(n){const r=p.x*n;p=p.y*n;glDraw(a.x,a.y,f?-b.x:b.x,b.y,e,h+r,k+p,h-r+l,k-p+q,d.rgbaInt(),g&&g.rgbaInt())}else glDraw(a.x,a.y,f?-b.x:b.x,b.y,e,h,k,h+l,k+q,d.rgbaInt(),g&&g.rgbaInt())}else f=g?d.add(g):d,glDrawUntextured(a.x,a.y,b.x,b.y,e,f.rgbaInt());else++drawCount,++primitiveCount,drawCanvas2D(a,b,e,f,q=>{if(m)q.scale(1,-1),drawImageColor(q,m.image,c.pos.x,c.pos.y,c.size.x,c.size.y,-.5,-.5,1,1,d,g,n);else{const r=g?d.add(g):d;q.fillStyle=r.toString();q.fillRect(-.5,-.5,1,1)}},k,l)}function drawRect(a,b,c,d,e,f,g){drawTile(a,b,void 0,c,d,!1,void 0,e,f,g)}function drawRectGradient(a,b,c=WHITE,d=CLEAR_WHITE,e=0,f=glEnable,g=!1,h){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isColor(c)&&isColor(d),"color is invalid");ASSERT(isNumber(e),"angle must be a number");ASSERT(!h||!f,"context only supported in canvas 2D mode");if(f&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");g&&(a=screenToWorld(a),b=b.scale(1/cameraScale),e+=cameraAngle);f=[];g=[];h=b.x/2;b=b.y/2;const k=c.rgbaInt(),l=d.rgbaInt(),m=cos(-e);e=sin(-e);for(let n=4;n--;){const p=n&1?h:-h,q=n&2?b:-b,r=n&2?k:l;f.push(vec2(a.x+(p*m-q*e),a.y+(p*e+q*m)));g.push(r)}glDrawColoredPoints(f,g)}else++drawCount,++primitiveCount,drawCanvas2D(a,b,e,!1,k=>{const l=k.createLinearGradient(0,.5,0,-.5);l.addColorStop(0,c.toString());l.addColorStop(1,d.toString());k.fillStyle=l;k.fillRect(-.5,-.5,1,1)},g,h)}function drawTextureWrapped(a,b,c,d=0,e=WHITE,f=0,g,h=glEnable,k=!1,l){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isVector2(c),"wrapCount must be a vec2");ASSERT(isColor(e),"color is invalid");ASSERT(isNumber(f),"angle must be a number");ASSERT(!g||isColor(g),"additiveColor must be a color");ASSERT(!l||!h,"context only supported in canvas 2D mode");ASSERT(!(d instanceof TileInfo),"pass a TextureInfo or texture index, not a TileInfo — use tileInfo.textureInfo");headlessMode||(d="number"===typeof d?textureInfos[d]:d,ASSERT(d instanceof TextureInfo,"texture not loaded"),ASSERT(0<d.size.x,"texture not loaded"),ASSERT(d.wrap,"drawTextureWrapped requires a wrap-enabled texture; call textureInfo.setWrap(true) first"),h&&glEnable?(ASSERT(!!glContext,"WebGL is not enabled!"),k&&([a,b,f]=screenToWorldTransform(a,b,f)),glSetTexture(d.glTexture),glDraw(a.x,a.y,b.x,b.y,f,0,0,c.x,c.y,e.rgbaInt(),g&&g.rgbaInt())):(++drawCount,++primitiveCount,k||(a=worldToScreen(a),b=b.scale(cameraScale),f-=cameraAngle),k=!canvasColorTiles||(g?isWhite(e.add(g))&&0>=g.a:isWhite(e)),h=!k&&g&&!isBlack(g),g=k?d.image:bakeTintedImage(d.image,e,g),l=l||drawContext,l.save(),l.translate(a.x+.5,a.y+.5),l.rotate(f),l.globalAlpha=h?1:e.a,a=l.createPattern(g,"repeat"),c=(new DOMMatrix).translate(-b.x/2,-b.y/2).scale(b.x/(c.x*g.width),b.y/(c.y*g.height)),a.setTransform(c),l.fillStyle=a,l.fillRect(-b.x/2,-b.y/2,b.x,b.y),l.globalAlpha=1,l.restore()))}function drawLineList(a,b=.1,c=WHITE,d=!1,e=vec2(),f=0,g=glEnable,h=!1,k){ASSERT(isArray(a),"points must be an array");ASSERT(isNumber(b),"width must be a number");ASSERT(isColor(c),"color is invalid");ASSERT(isVector2(e),"pos must be a vec2");ASSERT(isNumber(f),"angle must be a number");ASSERT(!k||!g,"context only supported in canvas 2D mode");g&&glEnable?(ASSERT(!!glContext,"WebGL is not enabled!"),g=vec2(1),h&&([e,g,f]=screenToWorldTransform(e,g,f)),glDrawOutlineTransform(a,c.rgbaInt(),b,e.x,e.y,g.x,g.y,f,d)):(++drawCount,++primitiveCount,drawCanvas2D(e,vec2(1),f,!1,l=>{l.strokeStyle=c.toString();l.lineWidth=b;l.beginPath();for(let m=0;m<a.length;++m){const n=a[m];l.lineTo(n.x,n.y)}d&&l.closePath();l.stroke()},h,k))}function drawLine(a,b,c=.1,d=WHITE,e=vec2(),f=0,g=glEnable,h=!1,k){b=vec2((b.x-a.x)/2,(b.y-a.y)/2);c=vec2(c,2*b.length());e=e.add(a.add(b));h&&(b.y*=-1);f+=b.angle();drawRect(e,c,d,f,g,h,k)}function drawRegularPoly(a,b=vec2(1),c=3,d=WHITE,e=0,f=BLACK,g=0,h=glEnable,k=!1,l){ASSERT(isVector2(b),"size must be a vec2");ASSERT(isNumber(c),"sides must be a number");const m=[],n=b.x/2;b=b.y/2;for(let p=c;p--;){const q=p/c*PI*2;m.push(vec2(sin(q)*n,cos(q)*b))}drawPoly(m,d,e,f,a,g,h,k,l)}function drawPoly(a,b=WHITE,c=0,d=BLACK,e=vec2(),f=0,g=glEnable,h=!1,k){ASSERT(isVector2(e),"pos must be a vec2");ASSERT(isArray(a),"points must be an array");ASSERT(isColor(b)&&isColor(d),"color is invalid");ASSERT(isNumber(c),"lineWidth must be a number");ASSERT(isNumber(f),"angle must be a number");ASSERT(!k||!g,"context only supported in canvas 2D mode");g&&glEnable?(ASSERT(!!glContext,"WebGL is not enabled!"),g=vec2(1),h&&([e,g,f]=screenToWorldTransform(e,g,f)),glDrawPointsTransform(a,b.rgbaInt(),e.x,e.y,g.x,g.y,f),0<c&&glDrawOutlineTransform(a,d.rgbaInt(),c,e.x,e.y,g.x,g.y,f)):drawCanvas2D(e,vec2(1),f,!1,l=>{l.fillStyle=b.toString();l.beginPath();for(const m of a)l.lineTo(m.x,m.y);l.closePath();l.fill();c&&(l.strokeStyle=d.toString(),l.lineWidth=c,l.stroke())},h,k)}function drawEllipse(a,b=vec2(1),c=WHITE,d=0,e=0,f=BLACK,g=glEnable,h=!1,k){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isColor(c)&&isColor(f),"color is invalid");ASSERT(isNumber(d),"angle must be a number");ASSERT(isNumber(e),"lineWidth must be a number");ASSERT(0<=e,"lineWidth must be a positive value or 0");ASSERT(!k||!g,"context only supported in canvas 2D mode");e=clamp(e,0,min(b.x,b.y));g&&glEnable?drawRegularPoly(a,b,glCircleSides,c,e,f,d,g,h,k):drawCanvas2D(a,vec2(1),d,!1,l=>{l.fillStyle=c.toString();l.beginPath();l.ellipse(0,0,b.x/2,b.y/2,0,0,9);l.fill();e&&(l.strokeStyle=f.toString(),l.lineWidth=e,l.stroke())},h,k)}function drawCircle(a,b=1,c=WHITE,d=0,e=BLACK,f=glEnable,g=!1,h){ASSERT(isNumber(b),"size must be a number");drawEllipse(a,vec2(b),c,0,d,e,f,g,h)}let drawEllipseGradientOffset=0;function drawEllipseGradient(a,b=vec2(1),c=WHITE,d=CLEAR_WHITE,e=0,f=glEnable,g=!1,h){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isColor(c)&&isColor(d),"color is invalid");ASSERT(isNumber(e),"angle must be a number");ASSERT(!h||!f,"context only supported in canvas 2D mode");if(!headlessMode)if(f&&glEnable){ASSERT(!!glContext,"WebGL is not enabled!");g&&(a=screenToWorld(a),b=b.scale(1/cameraScale),e+=cameraAngle);f=glCircleSides;const k=b.x/2,l=b.y/2;b=c.rgbaInt();g=d.rgbaInt();h=drawEllipseGradientOffset++;const m=cos(-e),n=sin(-e);e=r=>{const t=sin(r)*k;r=cos(r)*l;return vec2(a.x+t*m-r*n,a.y+t*n+r*m)};const p=[e(h%f/f*PI*2)],q=[g];for(let r=f;r--;){const t=(r+h)%f/f*PI*2;p.push(a);q.push(b);p.push(e(t));q.push(g)}glDrawColoredPoints(p,q)}else++drawCount,++primitiveCount,drawCanvas2D(a,b,e,!1,k=>{const l=k.createRadialGradient(0,0,0,0,0,.5);l.addColorStop(0,c.toString());l.addColorStop(1,d.toString());k.fillStyle=l;k.beginPath();k.ellipse(0,0,.5,.5,0,0,9);k.fill()},g,h)}function drawCircleGradient(a,b=1,c=WHITE,d=CLEAR_WHITE,e=glEnable,f=!1,g){ASSERT(isNumber(b),"size must be a number");drawEllipseGradient(a,vec2(b),c,d,0,e,f,g)}function drawCanvas2D(a,b,c=0,d=!1,e,f=!1,g=drawContext){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b),"size must be a vec2");ASSERT(isNumber(c),"angle must be a number");ASSERT("function"===typeof e,"drawFunction must be a function");f||(a=worldToScreen(a),b=b.scale(cameraScale),c-=cameraAngle);g.save();g.translate(a.x+.5,a.y+.5);g.rotate(c);g.scale(d?-b.x:b.x,-b.y);e(g);g.restore()}function drawText(a,b,c=1,d=WHITE,e=0,f=BLACK,g="center",h=fontDefault,k="",l,m=0,n=drawContext){b=worldToScreen(b);c*=cameraScale;e*=cameraScale;m-=cameraAngle;drawTextScreen(a,b,c,d,e,f,g,h,k,l,-1*m,n)}function drawTextScreen(a,b,c,d=WHITE,e=0,f=BLACK,g="center",h=fontDefault,k="",l,m=0,n=drawContext){ASSERT(isStringLike(a),"text must be a string");ASSERT(isVector2(b),"pos must be a vec2");ASSERT(isNumber(c),"size must be a number");ASSERT(isColor(d),"color must be a color");ASSERT(isNumber(e),"lineWidth must be a number");ASSERT(isColor(f),"lineColor must be a color");ASSERT(["left","center","right"].includes(g),"align must be left, center, or right");ASSERT(isStringLike(h),"font must be a string");ASSERT(isStringLike(k),"fontStyle must be a string");ASSERT(isNumber(m),"angle must be a number");a=(a+"").split("\n");const p=b.y-(a.length-1)*c/2;n.save();n.fillStyle=d.toString();n.strokeStyle=f.toString();n.lineWidth=e;n.textAlign=g;n.font=k+" "+c+"px "+h;n.textBaseline="middle";n.translate(b.x,p);n.rotate(-m);let q=0;a.forEach(r=>{e&&n.strokeText(r,0,q,l);n.fillText(r,0,q,l);q+=c});n.restore()}async function loadTexture(a,b){ASSERT(isNumber(a),"textureIndex must be a number");ASSERT(!textureInfos[a],"textureIndex is already loaded!");ASSERT(!b||isStringLike(b),"image src must be a string");const c=new Image;b&&await new Promise(d=>{c.onerror=c.onload=d;c.crossOrigin="anonymous";c.src=b});textureInfos[a]=new TextureInfo(c)}function screenToWorld(a){ASSERT(isVector2(a),"screenPos must be a vec2");let b=(a.x-mainCanvasSize.x/2+.5)/cameraScale;a=(a.y-mainCanvasSize.y/2+.5)/-cameraScale;if(cameraAngle){const c=cos(-cameraAngle),d=sin(-cameraAngle),e=b*d+a*c;b=b*c-a*d;a=e}return new Vector2(b+cameraPos.x,a+cameraPos.y)}function worldToScreen(a){ASSERT(isVector2(a),"worldPos must be a vec2");let b=a.x-cameraPos.x;a=a.y-cameraPos.y;if(cameraAngle){const c=cos(cameraAngle),d=sin(cameraAngle),e=b*d+a*c;b=b*c-a*d;a=e}return new Vector2(b*cameraScale+mainCanvasSize.x/2-.5,a*-cameraScale+mainCanvasSize.y/2-.5)}function screenToWorldDelta(a){ASSERT(isVector2(a),"screenDelta must be a vec2");let b=a.x/cameraScale;a=a.y/-cameraScale;if(cameraAngle){const c=cos(-cameraAngle),d=sin(-cameraAngle),e=b*d+a*c;b=b*c-a*d;a=e}return new Vector2(b,a)}function worldToScreenDelta(a){ASSERT(isVector2(a),"worldDelta must be a vec2");let b=a.x;a=a.y;if(cameraAngle){const c=cos(cameraAngle),d=sin(cameraAngle),e=b*d+a*c;b=b*c-a*d;a=e}return new Vector2(b*cameraScale,a*-cameraScale)}function screenToWorldTransform(a,b,c=0){ASSERT(isVector2(a),"screenPos must be a vec2");ASSERT(isVector2(b),"screenSize must be a vec2");ASSERT(isNumber(c),"screenAngle must be a number");return[screenToWorld(a),b.scale(1/cameraScale),c+cameraAngle]}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function cameraFit(a,b,c,d){function e(k){if(void 0===k||isNumber(k))k=vec2(k);return isVector2(k)?{top:k.y,right:k.x,bottom:k.y,left:k.x}:{top:k.top||0,right:k.right||0,bottom:k.bottom||0,left:k.left||0}}ASSERT(isVector2(a),"center must be a vec2");ASSERT(isVector2(b),"size must be a vec2");c=e(c);d=e(d);const f=b.x+c.left+c.right;b=b.y+c.top+c.bottom;const g=mainCanvasSize.x-d.left-d.right,h=mainCanvasSize.y-d.top-d.bottom;if(!(0<f&&0<b&&0<g&&0<h))return cameraScale;cameraScale=min(g/f,h/b);c=vec2(c.right-c.left,c.top-c.bottom).scale(.5);d=vec2(d.right-d.left,d.top-d.bottom).scale(.5/cameraScale);cameraPos=a.add(c).add(d);return cameraScale}function isOnScreen(a,b=0){ASSERT(isVector2(a),"pos must be a vec2");ASSERT(isVector2(b)||isNumber(b),"size must be a vec2 or number");if(!cameraScale)return!1;let c=a.x-cameraPos.x;a=a.y-cameraPos.y;if(cameraAngle){var d=cos(cameraAngle),e=sin(cameraAngle);const f=c*e+a*d;c=c*d-a*e;a=f}c*=2*cameraScale;a*=2*-cameraScale;b instanceof Vector2&&(b=b.length());b*=cameraScale;d=mainCanvasSize.x;e=mainCanvasSize.y;return c+b>-d&&c-b<d&&a+b>-e&&a-b<e}function setAdditiveBlendMode(a=!0){glAdditive=a;drawContext.globalCompositeOperation=a?"lighter":"source-over"}function combineCanvases(){const a=mainCanvasSize.x,b=mainCanvasSize.y;workCanvas.width=a;workCanvas.height=b;workContext.fillStyle="#000";workContext.fillRect(0,0,a,b);glCopyToContext(workContext);workContext.drawImage(mainCanvas,0,0);mainContext.drawImage(workCanvas,0,0)}function bakeTintedImage(a,b,c){var d=a.width|0,e=a.height|0;workReadCanvas.width=d;workReadCanvas.height=e;workReadContext.drawImage(a,0,0);a=workReadContext.getImageData(0,0,d,e);d=a.data;if(c&&!isBlack(c))for(b=[b.r,b.g,b.b,b.a],c=[255*c.r,255*c.g,255*c.b,255*c.a],e=0;e<d.length;++e)d[e]=d[e]*b[e&3]+c[e&3]|0;else for(c=0;c<d.length;c+=4)d[c]*=b.r,d[c+1]*=b.g,d[c+2]*=b.b;workReadContext.putImageData(a,0,0);return workReadCanvas}function drawImageColor(a,b,c,d,e,f,g,h,k,l,m,n,p=0){e=max(1,e|0);f=max(1,f|0);const q=e-2*p,r=f-2*p;if(!canvasColorTiles||(n?isWhite(m.add(n))&&0>=n.a:isWhite(m)))a.globalAlpha=m.a,a.drawImage(b,c+p,d+p,q,r,g,h,k,l),a.globalAlpha=1;else if(workReadCanvas.width=e,workReadCanvas.height=f,workReadContext.drawImage(b,c|0,d|0,e,f,0,0,e,f),b=workReadContext.getImageData(0,0,e,f),c=b.data,n&&!isBlack(n)){m=[m.r,m.g,m.b,m.a];n=[255*n.r,255*n.g,255*n.b,255*n.a];for(d=0;d<c.length;++d)c[d]=c[d]*m[d&3]+n[d&3]|0;workReadContext.putImageData(b,0,0);a.drawImage(workReadCanvas,p,p,q,r,g,h,k,l)}else{for(n=0;n<c.length;n+=4)c[n]*=m.r,c[n+1]*=m.g,c[n+2]*=m.b;workReadContext.putImageData(b,0,0);a.globalAlpha=m.a;a.drawImage(workReadCanvas,p,p,q,r,g,h,k,l);a.globalAlpha=1}}function isFullscreen(){return!!document.fullscreenElement}function toggleFullscreen(){const a=mainCanvas.parentElement;isFullscreen()?document.exitFullscreen&&document.exitFullscreen():a.requestFullscreen&&a.requestFullscreen()}function setCursor(a="auto"){mainCanvas.parentElement.style.cursor=a}let engineImageFont;class ImageFont{constructor(a){ASSERT(!!a,"tileInfo is required for ImageFont");this.tileInfo=a.frame(0)}drawText(a,b,c=1,d,e,f,g){ASSERT(isVector2(c)||"number"===typeof c,"size must be a vec2 or number");"number"===typeof c?(ASSERT(0<c),c*=cameraScale,c=new Vector2(c,c)):c=c.scale(cameraScale);this.drawTextScreen(a,worldToScreen(b),c,d,e,f,g)}drawTextScreen(a,b,c,d=!0,e=WHITE,f=glEnable,g){ASSERT(isStringLike(a),"text must be a string");ASSERT(isVector2(b),"pos must be a vec2");ASSERT(isVector2(c)||"number"===typeof c,"size must be a vec2 or number");ASSERT(isColor(e),"color must be a color");c="number"===typeof c?new Vector2(c,c):c;const h=new Vector2,k=this.tileInfo,l=k.padding,m=k.size.x+2*l,n=k.size.y+2*l,p=k.textureInfo.size.x/m|0;(a+"").split("\n").forEach((q,r)=>{const t=d?(q.length-1)*c.x/2:0;for(let w=q.length;w--;){var u=q.charCodeAt(w);u=32>u||127<u?95:u-32;const x=u/p|0;k.pos.x=u%p*m+l;k.pos.y=x*n+l;h.x=b.x+w*c.x-t|0;h.y=b.y+r*c.y|0;drawTile(h,c,k,e,0,!1,void 0,f,!0,g)}})}}async function imageFontInit(){const a=new Image;await new Promise(e=>{a.onerror=a.onload=e;a.crossOrigin="anonymous";a.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=="});var b=vec2();const c=vec2(8),d=new TextureInfo(a);b=new TileInfo(b,c,d,1,0);engineImageFont=new ImageFont(b)}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&&void 0!==window.ontouchstart;function setInputPreventDefault(a=!0){inputPreventDefault=a}function setInputMouseMoveThreshold(a){inputMouseMoveThreshold=a}function usingMouseInput(){return"mouse"===lastInputDevice}function usingKeyboardInput(){return"keyboard"===lastInputDevice}function usingGamepadInput(){return"gamepad"===lastInputDevice}function inputClearKey(a,b=0,c=!0,d=!0,e=!0){inputData[b]&&(inputData[b][a]&=~((c?1:0)|(d?2:0)|(e?4:0)))}function inputClear(){inputData.length=0;inputData[0]=[];touchGamepadButtons.length=0;touchGamepadSticks.length=0;tou