littlejsengine 1.12.4 → 1.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/box2d.wasm.js +630 -0
- package/dist/box2d.wasm.wasm +0 -0
- package/dist/littlejs.d.ts +52 -44
- package/dist/littlejs.esm.js +100 -97
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +98 -96
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +98 -96
- package/examples/box2d/game.js +36 -41
- package/examples/box2d/gameObjects.js +38 -35
- package/examples/box2d/index.html +1 -1
- package/examples/box2d/scenes.js +17 -13
- package/examples/box2d/tiles.png +0 -0
- package/examples/index.html +22 -14
- package/examples/platformer/game.js +6 -15
- package/examples/shorts/base.html +1 -0
- package/examples/shorts/box2d.js +46 -0
- package/examples/shorts/box2dCar.js +49 -0
- package/examples/shorts/postProcess.js +45 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/uiSystem.js +39 -0
- package/examples/starter/game.js +2 -0
- package/examples/uiSystem/game.js +1 -1
- package/package.json +1 -1
- package/plugins/box2d.js +23 -29
- package/plugins/pluginExport.js +1 -1
- package/plugins/uiSystem.js +9 -3
- package/src/engine.js +10 -11
- package/src/engineAudio.js +24 -15
- package/src/engineBuild.js +12 -2
- package/src/engineExport.js +1 -0
- package/src/engineInput.js +3 -3
- package/src/engineUtilities.js +16 -0
- package/src/engineWebGL.js +13 -1
package/dist/littlejs.esm.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";let showWatermark=0;let debugKey="";const debug=0;const debugOverlay=0;const debugPhysics=0;const debugParticles=0;const debugRaycast=0;const debugGamepads=0;const debugMedals=0;function ASSERT(){}function debugInit(){}function debugUpdate(){}function debugRender(){}function debugRect(){}function debugPoly(){}function debugCircle(){}function debugPoint(){}function debugLine(){}function debugOverlap(){}function debugText(){}function debugClear(){}function debugScreenshot(){}function debugSaveCanvas(){}function debugSaveText(){}function debugSaveDataURL(){}function debugShowErrors(){}function debugVideoCaptureIsActive(){return false}function debugVideoCaptureStart(){}function debugVideoCaptureStop(){}function debugVideoCaptureUpdate(){}const PI=Math.PI;function abs(value){return Math.abs(value)}function min(valueA,valueB){return Math.min(valueA,valueB)}function max(valueA,valueB){return Math.max(valueA,valueB)}function sign(value){return Math.sign(value)}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(percent,valueA,valueB){return valueA+clamp(percent)*(valueB-valueA)}function distanceWrap(valueA,valueB,wrapSize=1){const d=(valueA-valueB)%wrapSize;return d*2%wrapSize-d}function lerpWrap(percent,valueA,valueB,wrapSize=1){return valueA+clamp(percent)*distanceWrap(valueB,valueA,wrapSize)}function distanceAngle(angleA,angleB){return distanceWrap(angleA,angleB,2*PI)}function lerpAngle(percent,angleA,angleB){return lerpWrap(percent,angleA,angleB,2*PI)}function smoothStep(percent){return percent*percent*(3-2*percent)}function nearestPowerOfTwo(value){return 2**Math.ceil(Math.log2(value))}function isOverlapping(posA,sizeA,posB,sizeB=vec2()){return abs(posA.x-posB.x)*2<sizeA.x+sizeB.x&&abs(posA.y-posB.y)*2<sizeA.y+sizeB.y}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 wave(frequency=1,amplitude=1,t=time){return amplitude/2*(1-Math.cos(t*frequency*2*PI))}function formatTime(t){return(t/60|0)+":"+(t%60<10?"0":"")+(t%60|0)}function rand(valueA=1,valueB=0){return valueB+Math.random()*(valueA-valueB)}function randInt(valueA,valueB=0){return Math.floor(rand(valueA,valueB))}function randSign(){return randInt(2)*2-1}function randVector(length=1){return(new Vector2).setAngle(rand(2*PI),length)}function randInCircle(radius=1,minRadius=0){return radius>0?randVector(radius*rand(minRadius/radius,1)**.5):new Vector2}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){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 Math.floor(this.float(valueA,valueB))}sign(){return this.float()>.5?1:-1}}function vec2(x=0,y){return new Vector2(x,y==undefined?x:y)}function isVector2(v){return v instanceof Vector2}class Vector2{constructor(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid())}set(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid());return this}copy(){return new Vector2(this.x,this.y)}add(v){ASSERT(isVector2(v));return new Vector2(this.x+v.x,this.y+v.y)}subtract(v){ASSERT(isVector2(v));return new Vector2(this.x-v.x,this.y-v.y)}multiply(v){ASSERT(isVector2(v));return new Vector2(this.x*v.x,this.y*v.y)}divide(v){ASSERT(isVector2(v));return new Vector2(this.x/v.x,this.y/v.y)}scale(s){ASSERT(!isVector2(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){ASSERT(isVector2(v));return this.distanceSquared(v)**.5}distanceSquared(v){ASSERT(isVector2(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}dot(v){ASSERT(isVector2(v));return this.x*v.x+this.y*v.y}cross(v){ASSERT(isVector2(v));return this.x*v.y-this.y*v.x}angle(){return Math.atan2(this.x,this.y)}setAngle(angle=0,length=1){this.x=length*Math.sin(angle);this.y=length*Math.cos(angle);return this}rotate(angle){const c=Math.cos(-angle),s=Math.sin(-angle);return new Vector2(this.x*c-this.y*s,this.x*s+this.y*c)}setDirection(direction,length=1){direction=mod(direction,4);ASSERT(direction==0||direction==1||direction==2||direction==3);return vec2(direction%2?direction-1?-length:length:0,direction%2?0:direction?-length:length)}direction(){return abs(this.x)>abs(this.y)?this.x<0?3:1:this.y<0?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(v,percent){ASSERT(isVector2(v));return this.add(v.subtract(this).scale(clamp(percent)))}arrayCheck(arraySize){ASSERT(isVector2(arraySize));return this.x>=0&&this.y>=0&&this.x<arraySize.x&&this.y<arraySize.y}toString(digits=3){if(debug)return`(${(this.x<0?"":" ")+this.x.toFixed(digits)},${(this.y<0?"":" ")+this.y.toFixed(digits)} )`}isValid(){return typeof this.x=="number"&&!isNaN(this.x)&&typeof this.y=="number"&&!isNaN(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}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())}set(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;ASSERT(this.isValid());return this}copy(){return new Color(this.r,this.g,this.b,this.a)}add(c){ASSERT(isColor(c));return new Color(this.r+c.r,this.g+c.g,this.b+c.b,this.a+c.a)}subtract(c){ASSERT(isColor(c));return new Color(this.r-c.r,this.g-c.g,this.b-c.b,this.a-c.a)}multiply(c){ASSERT(isColor(c));return new Color(this.r*c.r,this.g*c.g,this.b*c.b,this.a*c.a)}divide(c){ASSERT(isColor(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(isColor(c));return this.add(c.subtract(this).scale(clamp(percent)))}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(this.isValid());return this}HSLA(){const r=clamp(this.r);const g=clamp(this.g);const b=clamp(this.b);const a=clamp(this.a);const max=Math.max(r,g,b);const min=Math.min(r,g,b);const l=(max+min)/2;let h=0,s=0;if(max!=min){let d=max-min;s=l>.5?d/(2-max-min):d/(max+min);if(r==max)h=(g-b)/d+(g<b?6:0);else if(g==max)h=(b-r)/d+2;else if(b==max)h=(r-g)/d+4}return[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=true){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(typeof hex=="string"&&hex[0]=="#");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(this.isValid());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 typeof this.r=="number"&&!isNaN(this.r)&&typeof this.g=="number"&&!isNaN(this.g)&&typeof this.b=="number"&&!isNaN(this.b)&&typeof this.a=="number"&&!isNaN(this.a)}}const WHITE=rgb();const BLACK=rgb(0,0,0);const GRAY=rgb(.5,.5,.5);const RED=rgb(1,0,0);const ORANGE=rgb(1,.5,0);const YELLOW=rgb(1,1,0);const GREEN=rgb(0,1,0);const CYAN=rgb(0,1,1);const BLUE=rgb(0,0,1);const PURPLE=rgb(.5,0,1);const MAGENTA=rgb(1,0,1);class Timer{constructor(timeLeft){this.time=timeLeft==undefined?undefined:time+timeLeft;this.setTime=timeLeft}set(timeLeft=0){this.time=time+timeLeft;this.setTime=timeLeft}unset(){this.time=undefined}isSet(){return this.time!=undefined}active(){return time<this.time}elapsed(){return time>=this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?1-percent(this.time-time,0,this.setTime):0}toString(){if(debug){return this.isSet()?Math.abs(this.get())+" seconds "+(this.get()<0?"before":"after"):"unset"}}valueOf(){return this.get()}}let cameraPos=vec2();let cameraScale=32;let canvasMaxSize=vec2(1920,1080);let canvasFixedSize=vec2();let canvasPixelated=true;let tilesPixelated=true;let fontDefault="arial";let showSplashScreen=false;let headlessMode=false;let glEnable=true;let glOverlay=true;let tileSizeDefault=vec2(16);let tileFixBleedScale=0;let enablePhysicsSolver=true;let objectDefaultMass=1;let objectDefaultDamping=1;let objectDefaultAngleDamping=1;let objectDefaultElasticity=0;let objectDefaultFriction=.8;let objectMaxSpeed=1;let gravity=vec2();let particleEmitRateScale=1;let gamepadsEnable=true;let gamepadDirectionEmulateStick=true;let inputWASDEmulateDirection=true;let touchInputEnable=true;let touchGamepadEnable=false;let touchGamepadAnalog=true;let touchGamepadSize=99;let touchGamepadAlpha=.3;let vibrateEnable=true;let soundEnable=true;let soundVolume=.3;let soundDefaultRange=40;let soundDefaultTaper=.7;let medalDisplayTime=5;let medalDisplaySlideTime=.5;let medalDisplaySize=vec2(640,80);let medalsPreventUnlock=false;function setCameraPos(pos){cameraPos=pos}function setCameraScale(scale){cameraScale=scale}function setCanvasMaxSize(size){canvasMaxSize=size}function setCanvasFixedSize(size){canvasFixedSize=size}function setCanvasPixelated(pixelated){canvasPixelated=pixelated}function setTilesPixelated(pixelated){tilesPixelated=pixelated}function setFontDefault(font){fontDefault=font}function setShowSplashScreen(show){showSplashScreen=show}function setHeadlessMode(headless){headlessMode=headless}function setGlEnable(enable){glEnable=enable}function setGlOverlay(overlay){glOverlay=overlay}function setTileSizeDefault(size){tileSizeDefault=size}function setTileFixBleedScale(scale){tileFixBleedScale=scale}function setEnablePhysicsSolver(enable){enablePhysicsSolver=enable}function setObjectDefaultMass(mass){objectDefaultMass=mass}function setObjectDefaultDamping(damp){objectDefaultDamping=damp}function setObjectDefaultAngleDamping(damp){objectDefaultAngleDamping=damp}function setObjectDefaultElasticity(elasticity){objectDefaultElasticity=elasticity}function setObjectDefaultFriction(friction){objectDefaultFriction=friction}function setObjectMaxSpeed(speed){objectMaxSpeed=speed}function setGravity(newGravity){gravity=newGravity}function setParticleEmitRateScale(scale){particleEmitRateScale=scale}function setGamepadsEnable(enable){gamepadsEnable=enable}function setGamepadDirectionEmulateStick(enable){gamepadDirectionEmulateStick=enable}function setInputWASDEmulateDirection(enable){inputWASDEmulateDirection=enable}function setTouchInputEnable(enable){touchInputEnable=enable}function setTouchGamepadEnable(enable){touchGamepadEnable=enable}function setTouchGamepadAnalog(analog){touchGamepadAnalog=analog}function setTouchGamepadSize(size){touchGamepadSize=size}function setTouchGamepadAlpha(alpha){touchGamepadAlpha=alpha}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 setMedalDisplayTime(time){medalDisplayTime=time}function setMedalDisplaySlideTime(time){medalDisplaySlideTime=time}function setMedalDisplaySize(size){medalDisplaySize=size}function setMedalsPreventUnlock(preventUnlock){medalsPreventUnlock=preventUnlock}function setShowWatermark(show){showWatermark=show}function setDebugKey(key){debugKey=key}class EngineObject{constructor(pos=vec2(),size=vec2(1),tileInfo,angle=0,color=new Color,renderOrder=0){ASSERT(isVector2(pos)&&isVector2(size),"ensure pos and size are vec2s");ASSERT(typeof tileInfo!=="number"||!tileInfo,"old style tile setup");this.pos=pos.copy();this.size=size;this.drawSize=undefined;this.tileInfo=tileInfo;this.angle=angle;this.color=color;this.additiveColor=undefined;this.mirror=false;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.elasticity=objectDefaultElasticity;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=renderOrder;this.velocity=vec2();this.angleVelocity=0;this.spawnTime=time;this.children=[];this.clampSpeedLinear=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();this.pos=this.localPos.multiply(vec2(mirror,1)).rotate(parent.angle).add(parent.pos);this.angle=mirror*this.localAngle+parent.angle}for(const child of this.children)child.updateTransforms()}update(){if(this.parent)return;if(this.clampSpeedLinear){this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed);this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed)}else{const length2=this.velocity.lengthSquared();if(length2>objectMaxSpeed*objectMaxSpeed){const s=objectMaxSpeed/length2**.5;this.velocity.x*=s;this.velocity.y*=s}}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 wasMovingDown=this.velocity.y<0;if(this.groundObject){const friction=max(this.friction,this.groundObject.friction);const groundSpeed=this.groundObject.velocity?this.groundObject.velocity.x:0;this.velocity.x=groundSpeed+(this.velocity.x-groundSpeed)*friction;this.groundObject=undefined}if(this.collideSolidObjects){const epsilon=.001;for(const o of engineObjectsCollide){if(!this.isSolid&&!o.isSolid||o.destroyed||o.parent||o==this)continue;if(!isOverlapping(this.pos,this.size,o.pos,o.size))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<.01?randVector(pushAwayAccel):deltaPos.scale(pushAwayAccel/length);this.velocity=this.velocity.add(velocity);if(o.mass)o.velocity=o.velocity.subtract(velocity);debugOverlay&&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 elasticity=max(this.elasticity,o.elasticity);if(smallStepUp||isBlockedY||!isBlockedX){this.pos.y=o.pos.y+(sizeBoth.y/2+epsilon)*sign(oldPos.y-o.pos.y);if(o.groundObject&&wasMovingDown||!o.mass){if(wasMovingDown)this.groundObject=o;this.velocity.y*=-elasticity}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(elasticity,inelastic,elastic0);o.velocity.y=lerp(elasticity,inelastic,elastic1)}}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(elasticity,inelastic,elastic0);o.velocity.x=lerp(elasticity,inelastic,elastic1)}else this.velocity.x*=-elasticity}debugOverlay&&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 blockedLayerY=tileCollisionTest(vec2(oldPos.x,this.pos.y),this.size,this);const blockedLayerX=tileCollisionTest(vec2(this.pos.x,oldPos.y),this.size,this);if(blockedLayerY||!blockedLayerX){const elasticity=max(this.elasticity,hitLayer.elasticity);this.velocity.y*=-elasticity;if(wasMovingDown){const epsilon=1e-4;this.pos.y=(oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;this.groundObject=hitLayer}else{this.pos.y=oldPos.y;this.groundObject=undefined}}if(blockedLayerX){this.pos.x=oldPos.x;this.velocity.x*=-this.elasticity}debugOverlay&&debugPhysics&&debugRect(this.pos,this.size,"#f00")}}}}render(){drawTile(this.pos,this.drawSize||this.size,this.tileInfo,this.color,this.angle,this.mirror,this.additiveColor)}destroy(){if(this.destroyed)return;this.destroyed=1;this.parent&&this.parent.removeChild(this);for(const child of this.children)child.destroy(child.parent=0)}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}getAliveTime(){return time-this.spawnTime}applyAcceleration(acceleration){if(this.mass)this.velocity=this.velocity.add(acceleration)}applyForce(force){this.applyAcceleration(force.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(child,localPos=vec2(),localAngle=0){ASSERT(!child.parent&&!this.children.includes(child));this.children.push(child);child.parent=this;child.localPos=localPos.copy();child.localAngle=localAngle}removeChild(child){ASSERT(child.parent==this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=0}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(){if(debug){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){const size=vec2(max(this.size.x,.2),max(this.size.y,.2));const color1=rgb(this.collideTiles?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,this.parent?.2:.5);const color2=this.parent?rgb(1,1,1,.5):rgb(0,0,0,.8);drawRect(this.pos,size,color1,this.angle,false);drawRect(this.pos,size.scale(.8),color2,this.angle,false);this.parent&&drawLine(this.pos,this.parent.pos,.1,rgb(0,0,1,.5),false)}}}let mainCanvas;let mainContext;let overlayCanvas;let overlayContext;let mainCanvasSize=vec2();let textureInfos=[];let drawCount;function tile(pos=vec2(),size=tileSizeDefault,textureIndex=0,padding=0){if(headlessMode)return new TileInfo;if(typeof size==="number"){ASSERT(size>0);size=vec2(size)}const textureInfo=textureInfos[textureIndex];ASSERT(!!textureInfo,"Texture not loaded");const sizePadded=size.add(vec2(padding*2));if(typeof pos==="number"){const cols=textureInfo.size.x/sizePadded.x|0;pos=cols>0?vec2(pos%cols,pos/cols|0):vec2()}pos=vec2(pos.x*sizePadded.x+padding,pos.y*sizePadded.y+padding);return new TileInfo(pos,size,textureIndex,padding)}class TileInfo{constructor(pos=vec2(),size=tileSizeDefault,textureIndex=0,padding=0){this.pos=pos.copy();this.size=size.copy();this.textureIndex=textureIndex;this.padding=padding}offset(offset){return new TileInfo(this.pos.add(offset),this.size,this.textureIndex)}frame(frame){ASSERT(typeof frame=="number");return this.offset(vec2(frame*(this.size.x+this.padding*2),0))}getTextureInfo(){return textureInfos[this.textureIndex]}}class TextureInfo{constructor(image){this.image=image;this.size=vec2(image.width,image.height);this.sizeInverse=vec2(1/image.width,1/image.height);this.glTexture=glEnable&&glCreateTexture(image)}}function screenToWorld(screenPos){return new Vector2((screenPos.x-mainCanvasSize.x/2+.5)/cameraScale+cameraPos.x,(screenPos.y-mainCanvasSize.y/2+.5)/-cameraScale+cameraPos.y)}function worldToScreen(worldPos){return new Vector2((worldPos.x-cameraPos.x)*cameraScale+mainCanvasSize.x/2-.5,(worldPos.y-cameraPos.y)*-cameraScale+mainCanvasSize.y/2-.5)}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle=0,mirror,additiveColor,useWebGL=glEnable,screenSpace,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");ASSERT(typeof tileInfo!=="number"||!tileInfo,"this is an old style calls, to fix replace it with tile(tileIndex, tileSize)");ASSERT(isVector2(pos)&&isVector2(size));ASSERT(isColor(color)&&(!additiveColor||isColor(additiveColor)));const textureInfo=tileInfo&&tileInfo.getTextureInfo();if(useWebGL){if(screenSpace){pos=screenToWorld(pos);size=size.scale(1/cameraScale)}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(tileFixBleedScale){const tileImageFixBleedX=sizeInverse.x*tileFixBleedScale;const tileImageFixBleedY=sizeInverse.y*tileFixBleedScale;glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x+tileImageFixBleedX,y+tileImageFixBleedY,x-tileImageFixBleedX+w,y-tileImageFixBleedY+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{glDraw(pos.x,pos.y,size.x,size.y,angle,0,0,0,0,0,color.rgbaInt())}}else{showWatermark&&++drawCount;size=vec2(size.x,-size.y);drawCanvas2D(pos,size,angle,mirror,context=>{if(textureInfo){const x=tileInfo.pos.x+tileFixBleedScale;const y=tileInfo.pos.y+tileFixBleedScale;const w=tileInfo.size.x-2*tileFixBleedScale;const h=tileInfo.size.y-2*tileFixBleedScale;context.globalAlpha=color.a;context.drawImage(textureInfo.image,x,y,w,h,-.5,-.5,1,1);context.globalAlpha=1}else{context.fillStyle=color.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 drawLine(posA,posB,thickness=.1,color,useWebGL,screenSpace,context){const halfDelta=vec2((posB.x-posA.x)/2,(posB.y-posA.y)/2);const size=vec2(thickness,halfDelta.length()*2);drawRect(posA.add(halfDelta),size,color,halfDelta.angle(),useWebGL,screenSpace,context)}function drawPoly(points,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){ASSERT(isColor(color)&&isColor(lineColor));context.fillStyle=color.toString();context.beginPath();for(const point of screenSpace?points:points.map(worldToScreen))context.lineTo(point.x,point.y);context.closePath();context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=screenSpace?lineWidth:lineWidth*cameraScale;context.stroke()}}function drawEllipse(pos,width=1,height=1,angle=0,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){ASSERT(isColor(color)&&isColor(lineColor));if(!screenSpace){pos=worldToScreen(pos);width*=cameraScale;height*=cameraScale;lineWidth*=cameraScale}context.fillStyle=color.toString();context.beginPath();context.ellipse(pos.x,pos.y,width,height,angle,0,9);context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}}function drawCircle(pos,radius=1,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){drawEllipse(pos,radius,radius,0,color,lineWidth,lineColor,screenSpace,context)}function drawCanvas2D(pos,size,angle,mirror,drawFunction,screenSpace,context=mainContext){if(!screenSpace){pos=worldToScreen(pos);size=size.scale(cameraScale)}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,lineWidth=0,lineColor,textAlign,font,maxWidth,context=mainContext){drawTextScreen(text,worldToScreen(pos),size*cameraScale,color,lineWidth*cameraScale,lineColor,textAlign,font,maxWidth,context)}function drawTextOverlay(text,pos,size=1,color,lineWidth=0,lineColor,textAlign,font,maxWidth){drawText(text,pos,size,color,lineWidth,lineColor,textAlign,font,maxWidth,overlayContext)}function drawTextScreen(text,pos,size=1,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),textAlign="center",font=fontDefault,maxWidth=undefined,context=overlayContext){context.fillStyle=color.toString();context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.textAlign=textAlign;context.font=size+"px "+font;context.textBaseline="middle";context.lineJoin="round";const lines=(text+"").split("\n");pos=pos.copy();pos.y-=(lines.length-1)*size/2;lines.forEach(line=>{lineWidth&&context.strokeText(line,pos.x,pos.y,maxWidth);context.fillText(line,pos.x,pos.y,maxWidth);pos.y+=size})}function setBlendMode(additive,useWebGL=glEnable,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL)glAdditive=additive;else{if(!context)context=mainContext;context.globalCompositeOperation=additive?"lighter":"source-over"}}function combineCanvases(){glCopyToContext(mainContext,true);mainContext.drawImage(overlayCanvas,0,0);glClearCanvas();overlayCanvas.width|=0}let engineFontImage;class FontImage{constructor(image,tileSize=vec2(8),paddingSize=vec2(0,1),context=overlayContext){if(!engineFontImage){engineFontImage=new Image;engineFontImage.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC"}this.image=image||engineFontImage;this.tileSize=tileSize;this.paddingSize=paddingSize;this.context=context}drawText(text,pos,scale=1,center){this.drawTextScreen(text,worldToScreen(pos).floor(),scale*cameraScale|0,center)}drawTextScreen(text,pos,scale=4,center){const context=this.context;context.save();const size=this.tileSize;const drawSize=size.add(this.paddingSize).scale(scale);const cols=this.image.width/this.tileSize.x|0;(text+"").split("\n").forEach((line,i)=>{const centerOffset=center?line.length*size.x*scale/2|0:0;for(let j=line.length;j--;){let charCode=line[j].charCodeAt(0);if(charCode<32||charCode>127)charCode=127;const tile=charCode-32;const x=tile%cols;const y=tile/cols|0;const drawPos=pos.add(vec2(j,i).multiply(drawSize));context.drawImage(this.image,x*size.x,y*size.y,size.x,size.y,drawPos.x-centerOffset,drawPos.y,size.x*scale,size.y*scale)}});context.restore()}}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}function keyIsDown(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&1)}function keyWasPressed(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&2)}function keyWasReleased(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&4)}function keyDirection(up="ArrowUp",down="ArrowDown",left="ArrowLeft",right="ArrowRight"){const k=key=>keyIsDown(key)?1:0;return vec2(k(right)-k(left),k(up)-k(down))}function clearInput(){inputData=[[]];touchGamepadButtons=[]}const mouseIsDown=keyIsDown;const mouseWasPressed=keyWasPressed;const mouseWasReleased=keyWasReleased;let mousePos=vec2();let mousePosScreen=vec2();let mouseWheel=0;let isUsingGamepad=false;let inputPreventDefault=true;function setInputPreventDefault(preventDefault){inputPreventDefault=preventDefault}function gamepadIsDown(button,gamepad=0){return keyIsDown(button,gamepad+1)}function gamepadWasPressed(button,gamepad=0){return keyWasPressed(button,gamepad+1)}function gamepadWasReleased(button,gamepad=0){return keyWasReleased(button,gamepad+1)}function gamepadStick(stick,gamepad=0){return gamepadStickData[gamepad]?gamepadStickData[gamepad][stick]||vec2():vec2()}let inputData=[[]];function inputUpdate(){if(headlessMode)return;if(!(touchInputEnable&&isTouchDevice)&&!document.hasFocus())clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){if(headlessMode)return;for(const deviceInputData of inputData)for(const i in deviceInputData)deviceInputData[i]&=1;mouseWheel=0}function inputInit(){if(headlessMode)return;onkeydown=e=>{if(!e.repeat){isUsingGamepad=false;inputData[0][e.code]=3;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=3}};onkeyup=e=>{inputData[0][e.code]=4;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=4};function remapKey(c){return inputWASDEmulateDirection?c=="KeyW"?"ArrowUp":c=="KeyS"?"ArrowDown":c=="KeyA"?"ArrowLeft":c=="KeyD"?"ArrowRight":c:c}onmousedown=e=>{if(soundEnable&&!headlessMode&&audioContext&&audioContext.state!="running")audioContext.resume();isUsingGamepad=false;inputData[0][e.button]=3;mousePosScreen=mouseEventToScreen(e);inputPreventDefault&&e.button&&e.preventDefault()};onmouseup=e=>inputData[0][e.button]=inputData[0][e.button]&2|4;onmousemove=e=>mousePosScreen=mouseEventToScreen(e);onwheel=e=>mouseWheel=e.ctrlKey?0:sign(e.deltaY);oncontextmenu=e=>false;onblur=e=>clearInput();if(isTouchDevice&&touchInputEnable)touchInputInit()}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)}const gamepadStickData=[];function gamepadsUpdate(){const applyDeadZones=v=>{const min=.3,max=.8;const deadZone=v=>v>min?percent(v,min,max):v<-min?-percent(-v,min,max):0;return vec2(deadZone(v.x),deadZone(-v.y)).clampLength()};if(touchGamepadEnable&&isTouchDevice){ASSERT(touchGamepadButtons,"set touchGamepadEnable before calling init!");if(touchGamepadTimer.isSet()){const sticks=gamepadStickData[0]||(gamepadStickData[0]=[]);sticks[0]=vec2();if(touchGamepadAnalog)sticks[0]=applyDeadZones(touchGamepadStick);else if(touchGamepadStick.lengthSquared()>.3){sticks[0].x=Math.round(touchGamepadStick.x);sticks[0].y=-Math.round(touchGamepadStick.y);sticks[0]=sticks[0].clampLength()}const data=inputData[1]||(inputData[1]=[]);for(let i=10;i--;){const j=i==3?2:i==2?3:i;const wasDown=gamepadIsDown(j,0);data[j]=touchGamepadButtons[i]?wasDown?1:3:wasDown?4:0}}}if(!gamepadsEnable||!navigator||!navigator.getGamepads)return;if(!debug&&!document.hasFocus())return;const gamepads=navigator.getGamepads();for(let i=gamepads.length;i--;){const gamepad=gamepads[i];const data=inputData[i+1]||(inputData[i+1]=[]);const sticks=gamepadStickData[i]||(gamepadStickData[i]=[]);if(gamepad){for(let j=0;j<gamepad.axes.length-1;j+=2)sticks[j>>1]=applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));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.value||button.value>.9)if(!i&&button.pressed)isUsingGamepad=true}if(gamepadDirectionEmulateStick){const dpad=vec2((gamepadIsDown(15,i)&&1)-(gamepadIsDown(14,i)&&1),(gamepadIsDown(12,i)&&1)-(gamepadIsDown(13,i)&&1));if(dpad.lengthSquared())sticks[0]=dpad.clampLength()}touchGamepadEnable&&isUsingGamepad&&touchGamepadTimer.unset()}}}function vibrate(pattern=100){vibrateEnable&&!headlessMode&&navigator&&navigator.vibrate&&navigator.vibrate(pattern)}function vibrateStop(){vibrate(0)}const isTouchDevice=!headlessMode&&window.ontouchstart!==undefined;let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchInputInit(){let handleTouch=handleTouchDefault;if(touchGamepadEnable){handleTouch=handleTouchGamepad;touchGamepadButtons=[];touchGamepadStick=vec2()}document.addEventListener("touchstart",e=>handleTouch(e),{passive:false});document.addEventListener("touchmove",e=>handleTouch(e),{passive:false});document.addEventListener("touchend",e=>handleTouch(e),{passive:false});onmousedown=onmouseup=()=>0;let wasTouching;function handleTouchDefault(e){if(soundEnable&&!headlessMode&&audioContext&&audioContext.state!="running")audioContext.resume();const touching=e.touches.length;const button=0;if(touching){const p=vec2(e.touches[0].clientX,e.touches[0].clientY);mousePosScreen=mouseEventToScreen(p);wasTouching?isUsingGamepad=touchGamepadEnable:inputData[0][button]=3}else if(wasTouching)inputData[0][button]=inputData[0][button]&2|4;wasTouching=touching;if(inputPreventDefault&&document.hasFocus())e.preventDefault();return true}function handleTouchGamepad(e){touchGamepadStick=vec2();touchGamepadButtons=[];isUsingGamepad=true;const touching=e.touches.length;if(touching){touchGamepadTimer.set();if(paused&&!wasTouching){touchGamepadButtons[9]=1;handleTouchDefault(e);return}}const stickCenter=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);const buttonCenter=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize));const startCenter=mainCanvasSize.scale(.5);for(const touch of e.touches){const touchPos=mouseEventToScreen(vec2(touch.clientX,touch.clientY));if(touchPos.distance(stickCenter)<touchGamepadSize){touchGamepadStick=touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength()}else if(touchPos.distance(buttonCenter)<touchGamepadSize){const button=touchPos.subtract(buttonCenter).direction();touchGamepadButtons[button]=1}else if(touchPos.distance(startCenter)<touchGamepadSize&&!wasTouching){touchGamepadButtons[9]=1}}handleTouchDefault(e);return true}}function touchGamepadRender(){if(!touchInputEnable||!isTouchDevice||headlessMode)return;if(!touchGamepadEnable||!touchGamepadTimer.isSet())return;const alpha=percent(touchGamepadTimer.get(),4,3);if(!alpha||paused)return;const context=overlayContext;context.save();context.globalAlpha=alpha*touchGamepadAlpha;context.strokeStyle="#fff";context.lineWidth=3;context.fillStyle=touchGamepadStick.lengthSquared()>0?"#fff":"#000";context.beginPath();const leftCenter=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);if(touchGamepadAnalog){context.arc(leftCenter.x,leftCenter.y,touchGamepadSize/2,0,9);context.fill();context.stroke()}else{for(let i=10;i--;){const angle=i*PI/4;context.arc(leftCenter.x,leftCenter.y,touchGamepadSize*.6,angle+PI/8,angle+PI/8);i%2&&context.arc(leftCenter.x,leftCenter.y,touchGamepadSize*.33,angle,angle);i==1&&context.fill()}context.stroke()}const rightCenter=vec2(mainCanvasSize.x-touchGamepadSize,mainCanvasSize.y-touchGamepadSize);for(let i=4;i--;){const pos=rightCenter.add(vec2().setDirection(i,touchGamepadSize/2));context.fillStyle=touchGamepadButtons[i]?"#fff":"#000";context.beginPath();context.arc(pos.x,pos.y,touchGamepadSize/4,0,9);context.fill();context.stroke()}context.restore()}let audioContext=new AudioContext;let audioMasterGain;function audioInit(){if(!soundEnable||headlessMode)return;audioMasterGain=audioContext.createGain();audioMasterGain.connect(audioContext.destination);audioMasterGain.gain.value=soundVolume}class Sound{constructor(zzfxSound,range=soundDefaultRange,taper=soundDefaultTaper){if(!soundEnable||headlessMode)return;this.range=range;this.taper=taper;this.randomness=0;if(zzfxSound){const defaultRandomness=.05;this.randomness=zzfxSound[1]!=undefined?zzfxSound[1]:defaultRandomness;zzfxSound[1]=0;this.sampleChannels=[zzfxG(...zzfxSound)];this.sampleRate=zzfxR}}play(pos,volume=1,pitch=1,randomnessScale=1,loop=false){if(!soundEnable||headlessMode)return;if(!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 playbackRate=pitch+pitch*this.randomness*randomnessScale*rand(-1,1);this.gainNode=audioContext.createGain();this.source=playSamples(this.sampleChannels,volume,playbackRate,pan,loop,this.sampleRate,this.gainNode);return this.source}setVolume(volume=1){if(this.gainNode)this.gainNode.gain.value=volume}stop(){if(this.source)this.source.stop();this.source=undefined}getSource(){return this.source}playNote(semitoneOffset,pos,volume){return this.play(pos,volume,2**(semitoneOffset/12),0)}getDuration(){return this.sampleChannels&&this.sampleChannels[0].length/this.sampleRate}isLoading(){return!this.sampleChannels}}class SoundWave extends Sound{constructor(filename,randomness=0,range,taper,onloadCallback){super(undefined,range,taper);if(!soundEnable||headlessMode)return;this.randomness=randomness;fetch(filename).then(response=>response.arrayBuffer()).then(arrayBuffer=>audioContext.decodeAudioData(arrayBuffer)).then(audioBuffer=>{this.sampleChannels=[];for(let i=audioBuffer.numberOfChannels;i--;)this.sampleChannels[i]=Array.from(audioBuffer.getChannelData(i));this.sampleRate=audioBuffer.sampleRate}).then(()=>onloadCallback&&onloadCallback(this))}}function playAudioFile(filename,volume=1,loop=false){if(!soundEnable||headlessMode)return;return new SoundWave(filename,0,0,0,s=>s.play(undefined,volume,1,1,loop))}function speak(text,language="",volume=1,rate=1,pitch=1){if(!soundEnable||headlessMode)return;if(!speechSynthesis)return;const utterance=new SpeechSynthesisUtterance(text);utterance.lang=language;utterance.volume=2*volume*soundVolume;utterance.rate=rate;utterance.pitch=pitch;speechSynthesis.speak(utterance);return utterance}function speakStop(){speechSynthesis&&speechSynthesis.cancel()}function getNoteFrequency(semitoneOffset,rootFrequency=220){return rootFrequency*2**(semitoneOffset/12)}function playSamples(sampleChannels,volume=1,rate=1,pan=0,loop=false,sampleRate=zzfxR,gainNode){if(!soundEnable||headlessMode)return;const channelCount=sampleChannels.length;const sampleLength=sampleChannels[0].length;const buffer=audioContext.createBuffer(channelCount,sampleLength,sampleRate);const source=audioContext.createBufferSource();sampleChannels.forEach((c,i)=>buffer.getChannelData(i).set(c));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);if(audioContext.state!="running"){audioContext.resume().then(()=>source.start())}else source.start();return source}function zzfx(...zzfxSound){return playSamples([zzfxG(...zzfxSound)])}const zzfxR=44100;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=zzfxR,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,cos=Math.cos(w),alpha=Math.sin(w)/2/quality,a0=1+alpha,a1=-2*cos/a0,a2=(1-alpha)/a0,b0=(1+sign(filter)*cos)/2/a0,b1=-(sign(filter)+cos)/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:Math.sin(t**3):Math.max(Math.min(Math.tan(t),1),-1):1-(2*t/PI2%2+2)%2:1-4*abs(Math.round(t/PI2)-t/PI2):Math.sin(t);s=(repeatTime?1-tremolo+tremolo*Math.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)*Math.cos(modulation*modOffset++);t+=f+f*noise*Math.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}let tileCollisionLayers=[];function getTileCollisionData(pos){for(const layer of tileCollisionLayers)if(pos.arrayCheck(layer.size))return layer.getCollisionData(pos);return 0}function tileCollisionTest(pos,size=vec2(),object){for(const layer of tileCollisionLayers)if(layer.collisionTest(pos,size,object))return layer}function tileCollisionRaycast(posStart,posEnd,object){for(const layer of tileCollisionLayers){const hitPos=layer.collisionRaycast(posStart,posEnd,object);if(hitPos)return hitPos}}class TileLayerData{constructor(tile,direction=0,mirror=false,color=new Color){this.tile=tile;this.direction=direction;this.mirror=mirror;this.color=color}clear(){this.tile=this.direction=0;this.mirror=false;this.color=new Color}}class TileLayer extends EngineObject{constructor(position,size,tileInfo=tile(),scale=vec2(1),renderOrder=0){super(position,size,tileInfo,0,undefined,renderOrder);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=scale;this.isOverlay=false;this.friction=0;this.elasticity=0;this.data=[];for(let j=this.size.area();j--;)this.data.push(new TileLayerData);if(headlessMode){this.redraw=()=>{};this.render=()=>{};this.redrawStart=()=>{};this.redrawEnd=()=>{};this.drawTileData=()=>{};this.drawCanvas2D=()=>{}}}setData(layerPos,data,redraw=false){if(layerPos.arrayCheck(this.size)){this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]=data;redraw&&this.drawTileData(layerPos)}}getData(layerPos){return layerPos.arrayCheck(this.size)&&this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]}update(){}render(){ASSERT(mainContext!=this.context,"must call redrawEnd() after drawing tiles");!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);let pos=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));pos=pos.floor();(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,pos.x,pos.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}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.redrawEnd()}redrawStart(clear=false){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;mainCanvasSize=this.size.multiply(this.tileInfo.size);cameraPos=this.size.scale(.5);cameraScale=this.tileInfo.size.x;if(clear){mainCanvas.width=mainCanvasSize.x;mainCanvas.height=mainCanvasSize.y}this.context.imageSmoothingEnabled=!tilesPixelated;glPreRender()}redrawEnd(){ASSERT(mainContext==this.context,"must call redrawStart() before drawing tiles");glCopyToContext(mainContext,true);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(layerPos,clear=true){const s=this.tileInfo.size;if(clear){const pos=layerPos.multiply(s);this.context.clearRect(pos.x,this.canvas.height-pos.y,s.x,-s.y)}const d=this.getData(layerPos);if(d.tile!=undefined){ASSERT(mainContext==this.context,"must call redrawStart() before drawing tiles");const pos=layerPos.add(vec2(.5));const tileInfo=tile(d.tile,s,this.tileInfo.textureIndex,this.tileInfo.padding);drawTile(pos,vec2(1),tileInfo,d.color,d.direction*PI/2,d.mirror)}}drawCanvas2D(pos,size,angle,mirror,drawFunction){const context=this.context;context.save();pos=pos.subtract(this.pos).multiply(this.tileInfo.size);size=size.multiply(this.tileInfo.size);context.translate(pos.x,this.canvas.height-pos.y);context.rotate(angle);context.scale(mirror?-size.x:size.x,size.y);drawFunction(context);context.restore()}drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle,mirror){this.drawCanvas2D(pos,size,angle,mirror,context=>{const textureInfo=tileInfo&&tileInfo.getTextureInfo();if(textureInfo){context.globalAlpha=color.a;context.drawImage(textureInfo.image,tileInfo.pos.x,tileInfo.pos.y,tileInfo.size.x,tileInfo.size.y,-.5,-.5,1,1);context.globalAlpha=1}else{context.fillStyle=color;context.fillRect(-.5,-.5,1,1)}})}drawRect(pos,size,color,angle){this.drawTile(pos,size,undefined,color,angle)}}class TileCollisionLayer extends TileLayer{constructor(position,size,tileInfo=tile(),renderOrder=0){const scale=vec2(1);super(position,size.floor(),tileInfo,scale,renderOrder);this.collisionData=[];this.initCollision(this.size);tileCollisionLayers.push(this)}destroy(){if(this.destroyed)return;const index=tileCollisionLayers.indexOf(this);ASSERT(index>=0,"tile collision layer not found in array");tileCollisionLayers.splice(index,1);super.destroy()}initCollision(size){this.size=size.floor();this.collisionData=[];this.collisionData.length=size.area();this.collisionData.fill(0)}setCollisionData(pos,data=1){const i=(pos.y|0)*this.size.x+pos.x|0;pos.arrayCheck(this.size)&&(this.collisionData[i]=data)}getCollisionData(pos){const i=(pos.y|0)*this.size.x+pos.x|0;return pos.arrayCheck(this.size)?this.collisionData[i]:0}collisionTest(pos,size=vec2(),object){const minX=max(pos.x-size.x/2|0,0);const minY=max(pos.y-size.y/2|0,0);const maxX=min(pos.x+size.x/2,this.size.x);const maxY=min(pos.y+size.y/2,this.size.y);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&&(!object||object.collideWithTile(tileData,vec2(x,y))))return true}return false}collisionRaycast(posStart,posEnd,object){const delta=posEnd.subtract(posStart);const totalLength=delta.length();const normalizedDelta=delta.normalize();const unit=vec2(abs(1/normalizedDelta.x),abs(1/normalizedDelta.y));const flooredPosStart=posStart.floor();let pos=flooredPosStart;let xi=unit.x*(delta.x<0?posStart.x-pos.x:pos.x-posStart.x+1);let yi=unit.y*(delta.y<0?posStart.y-pos.y:pos.y-posStart.y+1);while(true){const tileData=this.getCollisionData(pos);if(tileData&&(!object||object.collideWithTile(tileData,pos))){debugRaycast&&debugLine(posStart,posEnd,"#f00",.02);debugRaycast&&debugPoint(pos.add(vec2(.5)),"#ff0");return pos.add(vec2(.5))}if(xi>totalLength&&yi>totalLength)break;if(xi>yi)pos.y+=sign(delta.y),yi+=unit.y;else pos.x+=sign(delta.x),xi+=unit.x}debugRaycast&&debugLine(posStart,posEnd,"#00f",.02)}}class ParticleEmitter extends EngineObject{constructor(position,angle,emitSize=0,emitTime=0,emitRate=100,emitConeAngle=PI,tileInfo,colorStartA=new Color,colorStartB=new Color,colorEndA=new Color(1,1,1,0),colorEndB=new Color(1,1,1,0),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(position,vec2(),tileInfo,angle,undefined,renderOrder);this.emitSize=emitSize;this.emitTime=emitTime;this.emitRate=emitRate;this.emitConeAngle=emitConeAngle;this.colorStartA=colorStartA;this.colorStartB=colorStartB;this.colorEndA=colorEndA;this.colorEndB=colorEndB;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.particleDestroyCallback=undefined;this.particleCreateCallback=undefined;this.emitTimeBuffer=0}update(){this.parent&&super.update();if(!this.emitTime||this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const rate=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;this.emitTimeBuffer>0;this.emitTimeBuffer-=rate)this.emitParticle()}}else this.destroy();if(debugParticles){const emitSize=typeof this.emitSize==="number"?vec2(this.emitSize):this.emitSize;debugRect(this.pos,emitSize,"#0f0",0,this.angle)}}emitParticle(){let pos=typeof this.emitSize==="number"?randInCircle(this.emitSize/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=this.pos.add(pos);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 particle=new Particle(pos,this.tileInfo,angle,colorStart,colorEnd,particleTime,sizeStart,sizeEnd,this.fadeRate,this.additive,this.trailScale,this.localSpace&&this,this.particleDestroyCallback);particle.velocity=vec2().setAngle(velocityAngle,speed);particle.angleVelocity=angleSpeed;particle.fadeRate=this.fadeRate;particle.damping=this.damping;particle.angleDamping=this.angleDamping;particle.elasticity=this.elasticity;particle.friction=this.friction;particle.gravityScale=this.gravityScale;particle.collideTiles=this.collideTiles;particle.renderOrder=this.renderOrder;particle.mirror=!!randInt(2);this.particleCreateCallback&&this.particleCreateCallback(particle);return particle}render(){}}class Particle extends EngineObject{constructor(position,tileInfo,angle,colorStart,colorEnd,lifeTime,sizeStart,sizeEnd,fadeRate,additive,trailScale,localSpaceEmitter,destroyCallback){super(position,vec2(),tileInfo,angle);this.colorStart=colorStart;this.colorEndDelta=colorEnd.subtract(colorStart);this.lifeTime=lifeTime;this.sizeStart=sizeStart;this.sizeEndDelta=sizeEnd-sizeStart;this.fadeRate=fadeRate;this.additive=additive;this.trailScale=trailScale;this.localSpaceEmitter=localSpaceEmitter;this.destroyCallback=destroyCallback;this.clampSpeedLinear=false}render(){const p=this.lifeTime>0?min((time-this.spawnTime)/this.lifeTime,1):1;const radius=this.sizeStart+p*this.sizeEndDelta;const size=vec2(radius);const fadeRate=this.fadeRate/2;const color=new Color(this.colorStart.r+p*this.colorEndDelta.r,this.colorStart.g+p*this.colorEndDelta.g,this.colorStart.b+p*this.colorEndDelta.b,(this.colorStart.a+p*this.colorEndDelta.a)*(p<fadeRate?p/fadeRate:p>1-fadeRate?(1-p)/fadeRate:1));this.additive&&setBlendMode(true);let pos=this.pos,angle=this.angle;if(this.localSpaceEmitter){pos=this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));angle+=this.localSpaceEmitter.angle}if(this.trailScale){let velocity=this.velocity;if(this.localSpaceEmitter)velocity=velocity.rotate(-this.localSpaceEmitter.angle);const speed=velocity.length();if(speed){const direction=velocity.scale(1/speed);const trailLength=speed*this.trailScale;size.y=max(size.x,trailLength);angle=direction.angle();drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))),size,this.tileInfo,color,angle,this.mirror)}}else drawTile(pos,size,this.tileInfo,color,angle,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(pos,size,"#f005",0,angle);if(p==1){this.color=color;this.size=size;this.destroyCallback&&this.destroyCallback(this);this.destroyed=1}}}const medals={};let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(saveName){medalsSaveName=saveName;if(!debugMedals)medalsForEach(medal=>medal.unlocked=!!localStorage[medal.storageKey()]);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))}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;if(src)(this.image=new Image).src=src;medals[id]=this}unlock(){if(medalsPreventUnlock||this.unlocked)return;ASSERT(medalsSaveName,"save name must be set");localStorage[this.storageKey()]=this.unlocked=true;medalsDisplayQueue.push(this)}render(hidePercent=0){const context=overlayContext;const width=min(medalDisplaySize.x,mainCanvas.width);const height=medalDisplaySize.y;const x=overlayCanvas.width-width;const y=-height*hidePercent;context.save();context.beginPath();context.fillStyle=new Color(.9,.9,.9).toString();context.strokeStyle=new Color(0,0,0).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,new Color(0,0,0),0,undefined,"left",undefined,textWidth);pos.y=y+height-gap.y*2-descriptionSize/2;drawTextScreen(this.description,pos,descriptionSize,new Color(0,0,0),0,undefined,"left",undefined,textWidth);context.restore()}renderIcon(pos,size){if(this.image)overlayContext.drawImage(this.image,pos.x-size/2,pos.y-size/2,size,size);else drawTextScreen(this.icon,pos,size*.7,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}let glCanvas;let glContext;let glAntialias=true;let glShader,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glInstanceCount,glAdditive,glBatchAdditive;const gl_MAX_INSTANCES=1e4;const gl_INDICES_PER_INSTANCE=11;const gl_INSTANCE_BYTE_STRIDE=gl_INDICES_PER_INSTANCE*4;const gl_INSTANCE_BUFFER_SIZE=gl_MAX_INSTANCES*gl_INSTANCE_BYTE_STRIDE;function glInit(){if(!glEnable||headlessMode)return;glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl2",{antialias:glAntialias});const rootElement=mainCanvas.parentElement;glOverlay&&rootElement.appendChild(glCanvas);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;"+"}");const glInstanceData=new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);glPositionData=new Float32Array(glInstanceData);glColorData=new Uint32Array(glInstanceData);glArrayBuffer=glContext.createBuffer();glGeometryBuffer=glContext.createBuffer();const geometry=new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,geometry,glContext.STATIC_DRAW)}function glPreRender(){if(!glEnable||headlessMode)return;glClearCanvas();glContext.useProgram(glShader);glContext.activeTexture(glContext.TEXTURE0);if(textureInfos[0])glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture=textureInfos[0].glTexture);let offset=glAdditive=glBatchAdditive=0;const initVertexAttribArray=(name,type,typeSize,size)=>{const location=glContext.getAttribLocation(glShader,name);const stride=typeSize&&gl_INSTANCE_BYTE_STRIDE;const divisor=typeSize&&1;const normalize=typeSize==1;glContext.enableVertexAttribArray(location);glContext.vertexAttribPointer(location,size,type,normalize,stride,offset);glContext.vertexAttribDivisor(location,divisor);offset+=size*typeSize};glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);initVertexAttribArray("g",glContext.FLOAT,0,2);glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,gl_INSTANCE_BUFFER_SIZE,glContext.DYNAMIC_DRAW);initVertexAttribArray("p",glContext.FLOAT,4,4);initVertexAttribArray("u",glContext.FLOAT,4,4);initVertexAttribArray("c",glContext.UNSIGNED_BYTE,1,4);initVertexAttribArray("a",glContext.UNSIGNED_BYTE,1,4);initVertexAttribArray("r",glContext.FLOAT,4,1);const s=vec2(2*cameraScale).divide(mainCanvasSize);const p=vec2(-1).subtract(cameraPos.multiply(s));glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),false,[s.x,0,0,0,0,s.y,0,0,1,1,1,1,p.x,p.y,0,0])}function glClearCanvas(){glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height);glContext.clear(glContext.COLOR_BUFFER_BIT)}function glSetTexture(texture){if(headlessMode||texture==glActiveTexture)return;glFlush();glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture=texture)}function glCompileShader(source,type){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){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){const texture=glContext.createTexture();glContext.bindTexture(glContext.TEXTURE_2D,texture);if(image&&image.width)glSetTextureData(texture,image);else{const whitePixel=new Uint8Array([255,255,255,255]);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,1,1,0,glContext.RGBA,glContext.UNSIGNED_BYTE,whitePixel)}const filter=tilesPixelated?glContext.NEAREST:glContext.LINEAR;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,filter);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,filter);return texture}function glSetTextureData(texture,image){ASSERT(!!image&&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)}function glFlush(){if(!glInstanceCount)return;const destBlend=glBatchAdditive?glContext.ONE:glContext.ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(glContext.SRC_ALPHA,destBlend,glContext.ONE,destBlend);glContext.enable(glContext.BLEND);glContext.bufferSubData(glContext.ARRAY_BUFFER,0,glPositionData);glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP,0,4,glInstanceCount);if(showWatermark)drawCount+=glInstanceCount;glInstanceCount=0;glBatchAdditive=glAdditive}function glCopyToContext(context,forceDraw=false){if(!glEnable||!glInstanceCount&&!forceDraw)return;glFlush();if(!glOverlay||forceDraw)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,uv0X,uv0Y,uv1X,uv1Y,rgba=-1,rgbaAdditive=0){ASSERT(typeof rgba=="number"&&typeof rgbaAdditive=="number","invalid color");if(glInstanceCount>=gl_MAX_INSTANCES||glBatchAdditive!=glAdditive)glFlush();let offset=glInstanceCount++*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}const engineName="LittleJS";const engineVersion="1.12.4";const frameRate=60;const timeDelta=1/frameRate;let engineObjects=[];let engineObjectsCollide=[];let frame=0;let time=0;let timeReal=0;let paused=false;function setPaused(isPaused){paused=isPaused}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0;const pluginUpdateList=[],pluginRenderList=[];function engineAddPlugin(updateFunction,renderFunction){ASSERT(!pluginUpdateList.includes(updateFunction));ASSERT(!pluginRenderList.includes(renderFunction));updateFunction&&pluginUpdateList.push(updateFunction);renderFunction&&pluginRenderList.push(renderFunction)}function engineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources=[],rootElement=document.body){ASSERT(!mainContext,"engine already initialized");ASSERT(Array.isArray(imageSources),"pass in images as array");gameInit||=()=>{};gameUpdate||=()=>{};gameUpdatePost||=()=>{};gameRender||=()=>{};gameRenderPost||=()=>{};function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);overlayContext.imageSmoothingEnabled=mainContext.imageSmoothingEnabled=!tilesPixelated;glPreRender()}function engineUpdate(frameTimeMS=0){let frameTimeDeltaMS=frameTimeMS-frameTimeLastMS;frameTimeLastMS=frameTimeMS;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS,1e3/(frameTimeDeltaMS||1));const debugSpeedUp=debug&&keyIsDown("Equal");const debugSpeedDown=debug&&keyIsDown("Minus");if(debug)frameTimeDeltaMS*=debugSpeedUp?10:debugSpeedDown?.1:1;timeReal+=frameTimeDeltaMS/1e3;frameTimeBufferMS+=paused?0:frameTimeDeltaMS;if(!debugSpeedUp)frameTimeBufferMS=min(frameTimeBufferMS,50);if(debug&&debugVideoCaptureIsActive())frameTimeBufferMS=0;updateCanvas();if(paused){for(const o of engineObjects)o.parent||o.updateTransforms();inputUpdate();pluginUpdateList.forEach(f=>f());debugUpdate();gameUpdatePost();inputUpdatePost()}else{let deltaSmooth=0;if(frameTimeBufferMS<0&&frameTimeBufferMS>-9){deltaSmooth=frameTimeBufferMS;frameTimeBufferMS=0}for(;frameTimeBufferMS>=0;frameTimeBufferMS-=1e3/frameRate){time=frame++/frameRate;inputUpdate();gameUpdate();pluginUpdateList.forEach(f=>f());engineObjectsUpdate();debugUpdate();gameUpdatePost();inputUpdatePost()}frameTimeBufferMS+=deltaSmooth}if(!headlessMode){enginePreRender();gameRender();engineObjects.sort((a,b)=>a.renderOrder-b.renderOrder);for(const o of engineObjects)o.destroyed||o.render();gameRenderPost();pluginRenderList.forEach(f=>f());touchGamepadRender();debugRender();glCopyToContext(mainContext);if(showWatermark){overlayContext.textAlign="right";overlayContext.textBaseline="top";overlayContext.font="1em monospace";overlayContext.fillStyle="#000";const text=engineName+" "+"v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+(glEnable?" GL":" 2D");overlayContext.fillText(text,mainCanvas.width-3,3);overlayContext.fillStyle="#fff";overlayContext.fillText(text,mainCanvas.width-2,2);drawCount=0}}debugVideoCaptureUpdate();requestAnimationFrame(engineUpdate)}function updateCanvas(){if(headlessMode)return;if(canvasFixedSize.x){mainCanvas.width=canvasFixedSize.x;mainCanvas.height=canvasFixedSize.y;const aspect=innerWidth/innerHeight;const fixedAspect=mainCanvas.width/mainCanvas.height;(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=aspect<fixedAspect?"100%":"";(glCanvas||mainCanvas).style.height=mainCanvas.style.height=overlayCanvas.style.height=aspect<fixedAspect?"":"100%"}else{mainCanvas.width=min(innerWidth,canvasMaxSize.x);mainCanvas.height=min(innerHeight,canvasMaxSize.y)}overlayCanvas.width=mainCanvas.width;overlayCanvas.height=mainCanvas.height;mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height)}function startEngine(){new Promise(resolve=>resolve(gameInit())).then(engineUpdate)}if(headlessMode){startEngine();return}const styleRoot="margin:0;"+"background:#000;"+(canvasPixelated?"image-rendering:pixelated;":"")+"user-select:none;"+"-webkit-user-select:none;"+(!touchInputEnable?"":"touch-action:none;"+"-webkit-touch-callout:none");rootElement.style.cssText=styleRoot;rootElement.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");inputInit();audioInit();debugInit();glInit();rootElement.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");const styleCanvas="position:absolute;"+"top:50%;left:50%;transform:translate(-50%,-50%)";mainCanvas.style.cssText=overlayCanvas.style.cssText=styleCanvas;if(glCanvas)glCanvas.style.cssText=styleCanvas;updateCanvas();const promises=imageSources.map((src,textureIndex)=>new Promise(resolve=>{const image=new Image;image.onerror=image.onload=()=>{textureInfos[textureIndex]=new TextureInfo(image);resolve()};image.crossOrigin="anonymous";image.src=src}));if(!imageSources.length){promises.push(new Promise(resolve=>{textureInfos[0]=new TextureInfo(new Image);resolve()}))}if(showSplashScreen){promises.push(new Promise(resolve=>{let t=0;console.log(`${engineName} Engine v${engineVersion}`);updateSplash();function updateSplash(){clearInput();drawEngineSplashScreen(t+=.01);t>1?resolve():setTimeout(updateSplash,16)}}))}Promise.all(promises).then(startEngine)}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(o=>o.collideSolidObjects);function updateObject(o){if(!o.destroyed){o.update();for(const child of o.children)updateObject(child)}}for(const o of engineObjects){if(!o.parent){updateObject(o);o.updateTransforms()}}engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsDestroy(){for(const o of engineObjects)o.parent||o.destroy();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)isOverlapping(pos,size,o.pos,o.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}function drawEngineSplashScreen(t){const x=overlayContext;const w=overlayCanvas.width=innerWidth;const h=overlayCanvas.height=innerHeight;{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,Math.hypot(w,h)*.7);g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());g.addColorStop(1,hsl(0,0,0,p3).toString());x.save();x.fillStyle=g;x.fillRect(0,0,w,h)}const rect=(X,Y,W,H,C)=>{x.beginPath();x.rect(X,Y,W,C?H*p:H);x.fillStyle=C;C?x.fill():x.stroke()};const line=(X,Y,Z,W)=>{x.beginPath();x.lineTo(X,Y);x.lineTo(Z,W);x.stroke()};const circle=(X,Y,R,A=0,B=2*PI,C,F)=>{const D=(A+B)/2,E=p*(B-A)/2;x.beginPath();F&&x.lineTo(X,Y);x.arc(X,Y,R,D-E,D+E);x.fillStyle=C;C?x.fill():x.stroke()};const color=(c=0,l=0)=>hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();const alpha=wave(1,1,t);const p=percent(alpha,.1,.5);x.translate(w/2,h/2);const size=min(6,min(w,h)/99);x.scale(size,size);x.translate(-40,-35);x.lineJoin=x.lineCap="round";x.lineWidth=.1+p*1.9;const p2=percent(alpha,.1,1);x.setLineDash([99*p2,99]);rect(7,16,18,-8,color(2,2));rect(7,8,18,4,color(2,3));rect(25,8,8,8,color(2,1));rect(25,8,-18,8);rect(25,8,8,8);rect(25,16,7,23,color());rect(11,39,14,-23,color(1,1));rect(11,16,14,18,color(1,2));rect(11,16,14,8,color(1,3));rect(25,16,-14,24);rect(15,29,6,-9,color(2,2));circle(15,21,5,0,PI/2,color(2,4),1);rect(21,21,-6,9);rect(37,14,9,6,color(3,2));rect(37,14,4.5,6,color(3,3));rect(37,14,9,6);rect(50,20,10,-8,color(0,1));rect(50,20,6.5,-8,color(0,2));rect(50,20,3.5,-8,color(0,3));rect(50,20,10,-8);circle(55,2,11.4,.5,PI-.5,color(3,3));circle(55,2,11.4,.5,PI/2,color(3,2),1);circle(55,2,11.4,.5,PI-.5);rect(45,7,20,-7,color(0,2));rect(45,-1,20,4,color(0,3));rect(45,-1,20,8);for(let i=5;i--;){circle(60-i*6,30,9.9,0,2*PI,color(i+2,3));circle(60-i*6,30,10,-.5,PI+.5,color(i+2,2));circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1))}circle(36,30,10,PI/2,PI*3/2);circle(48,30,10,PI/2,PI*3/2);circle(60,30,10);line(36,20,60,20);circle(60,30,4,PI,3*PI,color(3,2));circle(60,30,4,PI,2*PI,color(3,3));circle(60,30,4,PI,3*PI);for(let i=6;i--;){x.beginPath();x.lineTo(53,54);x.lineTo(53,40);x.lineTo(53+(1+i*2.9)*p,40);x.lineTo(53+(4+i*3.5)*p,54);x.fillStyle=color(0,i%2+2);x.fill();i%2&&x.stroke()}rect(6,40,5,5);rect(6,40,5,5,color());rect(15,54,38,-14,color());for(let i=3;i--;)for(let j=2;j--;){circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));x.stroke();circle(15*i+15,47,j?7:1,0,PI,color(i,2));x.stroke()}line(6,40,68,40);line(77,54,4,54);const s=engineName;x.font="900 16px arial";x.textAlign="center";x.textBaseline="top";x.lineWidth=.1+p*3.9;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=41-w2/2;i<s.length;++i){x.fillStyle=color(i,2);const w=x.measureText(s[i]).width;x[j?"strokeText":"fillText"](s[i],X+w/2,55.5,17*p);X+=w}x.restore()}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");this.medals=medalsResult?medalsResult.result.data["medals"]:[];debugMedals&&console.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?scoreboardResult.result.data.scoreboards:[];debugMedals&&console.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&&console.log("newgrounds call failed",e);return}debugMedals&&console.log(xmlHttp.responseText);return xmlHttp.responseText&&JSON.parse(xmlHttp.responseText)}}let postProcess;class PostProcessPlugin{constructor(shaderCode,includeOverlay=false){ASSERT(!postProcess,"Post process already initialized");postProcess=this;if(headlessMode)return;if(!shaderCode)shaderCode="void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}";this.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.;"+"}");this.texture=glCreateTexture();this.includeOverlay=includeOverlay;engineAddPlugin(undefined,postProcessRender);function postProcessRender(){if(headlessMode)return;if(glEnable){glFlush();mainContext.drawImage(glCanvas,0,0)}else{glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height)}if(postProcess.includeOverlay){mainContext.drawImage(overlayCanvas,0,0);overlayCanvas.width|=0}glContext.useProgram(postProcess.shader);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,1);glContext.disable(glContext.BLEND);glContext.activeTexture(glContext.TEXTURE0);glContext.bindTexture(glContext.TEXTURE_2D,postProcess.texture);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,mainCanvas);const vertexByteStride=8;const pLocation=glContext.getAttribLocation(postProcess.shader,"p");glContext.enableVertexAttribArray(pLocation);glContext.vertexAttribPointer(pLocation,2,glContext.FLOAT,false,vertexByteStride,0);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)}}}class ZzFXMusic extends Sound{constructor(zzfxMusic){super(undefined);if(!soundEnable||headlessMode)return;this.randomness=0;this.sampleChannels=zzfxM(...zzfxMusic);this.sampleRate=zzfxR}playMusic(volume,loop=false){return super.play(undefined,volume,1,1,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=zzfxR/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;class UISystemPlugin{constructor(context=overlayContext){ASSERT(!uiSystem,"UI system already initialized");uiSystem=this;this.defaultColor=WHITE;this.defaultLineColor=BLACK;this.defaultTextColor=BLACK;this.defaultButtonColor=hsl(0,0,.5);this.defaultHoverColor=hsl(0,0,.7);this.defaultLineWidth=4;this.defaultFont="arial";this.uiObjects=[];this.uiContext=context;engineAddPlugin(uiUpdate,uiRender);function uiUpdate(){function updateObject(o){if(!o.visible)return;if(o.parent)o.pos=o.localPos.add(o.parent.pos);o.update();for(const c of o.children)updateObject(c)}uiSystem.uiObjects.forEach(o=>o.parent||updateObject(o))}function uiRender(){function renderObject(o){if(!o.visible)return;if(o.parent)o.pos=o.localPos.add(o.parent.pos);o.render();for(const c of o.children)renderObject(c)}uiSystem.uiObjects.forEach(o=>o.parent||renderObject(o))}}drawRect(pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){uiSystem.uiContext.fillStyle=color.toString();uiSystem.uiContext.beginPath();uiSystem.uiContext.rect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y);uiSystem.uiContext.fill();if(lineWidth){uiSystem.uiContext.strokeStyle=lineColor.toString();uiSystem.uiContext.lineWidth=lineWidth;uiSystem.uiContext.stroke()}}drawLine(posA,posB,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){uiSystem.uiContext.strokeStyle=lineColor.toString();uiSystem.uiContext.lineWidth=lineWidth;uiSystem.uiContext.beginPath();uiSystem.uiContext.lineTo(posA.x,posA.y);uiSystem.uiContext.lineTo(posB.x,posB.y);uiSystem.uiContext.stroke()}drawTile(pos,size,tileInfo,color=uiSystem.defaultColor,angle=0,mirror=false){drawTile(pos,size,tileInfo,color,angle,mirror,BLACK,false,true,uiSystem.uiContext)}drawText(text,pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor,align="center",font=uiSystem.defaultFont){drawTextScreen(text,pos,size.y,color,lineWidth,lineColor,align,font,size.x,uiSystem.uiContext)}}class UIObject{constructor(pos=vec2(),size=vec2()){this.localPos=pos.copy();this.pos=pos.copy();this.size=size.copy();this.color=uiSystem.defaultColor;this.lineColor=uiSystem.defaultLineColor;this.textColor=uiSystem.defaultTextColor;this.hoverColor=uiSystem.defaultHoverColor;this.lineWidth=uiSystem.defaultLineWidth;this.font=uiSystem.defaultFont;this.visible=true;this.children=[];this.parent=undefined;uiSystem.uiObjects.push(this)}addChild(child){ASSERT(!child.parent&&!this.children.includes(child));this.children.push(child);child.parent=this}removeChild(child){ASSERT(child.parent==this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=undefined}update(){const mouseWasOver=this.mouseIsOver;const mouseDown=mouseIsDown(0);if(!mouseDown||isTouchDevice){this.mouseIsOver=isOverlapping(this.pos,this.size,mousePosScreen);if(!mouseDown&&isTouchDevice)this.mouseIsOver=false;if(this.mouseIsOver&&!mouseWasOver)this.onEnter();if(!this.mouseIsOver&&mouseWasOver)this.onLeave()}if(mouseWasPressed(0)&&this.mouseIsOver){this.mouseIsHeld=true;this.onPress();if(isTouchDevice)this.mouseIsOver=false}else if(this.mouseIsHeld&&!mouseDown){this.mouseIsHeld=false;this.onRelease()}}render(){if(this.size.x&&this.size.y)uiSystem.drawRect(this.pos,this.size,this.color,this.lineWidth,this.lineColor)}onEnter(){}onLeave(){}onPress(){}onRelease(){}onChange(){}}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.lineWidth=0}render(){uiSystem.drawText(this.text,this.pos,this.size,this.textColor,this.lineWidth,this.lineColor,this.align,this.font)}}class UITile extends UIObject{constructor(pos,size,tileInfo,color=WHITE,angle=0,mirror=false){super(pos,size);this.tileInfo=tileInfo;this.angle=angle;this.mirror=mirror;this.color=color}render(){uiSystem.drawTile(this.pos,this.size,this.tileInfo,this.color,this.angle,this.mirror)}}class UIButton extends UIObject{constructor(pos,size,text="",color=uiSystem.defaultButtonColor){super(pos,size);this.text=text;this.color=color}render(){const lineColor=this.mouseIsHeld?this.color:this.lineColor;const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,lineColor);const textSize=vec2(this.size.x,this.size.y*.8);uiSystem.drawText(this.text,this.pos,textSize,this.textColor,0,undefined,this.align,this.font)}}class UICheckbox extends UIObject{constructor(pos,size,checked=false){super(pos,size);this.checked=checked}onPress(){this.checked=!this.checked;this.onChange()}render(){const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,this.lineColor);if(this.checked){uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,-.5))),this.pos.add(this.size.multiply(vec2(.5,.5))),this.lineWidth,this.lineColor);uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,.5))),this.pos.add(this.size.multiply(vec2(.5,-.5))),this.lineWidth,this.lineColor)}}}class UIScrollbar extends UIObject{constructor(pos,size,value=.5,text="",color=uiSystem.defaultButtonColor,handleColor=WHITE){super(pos,size);this.value=value;this.text=text;this.color=color;this.handleColor=handleColor}update(){super.update();if(this.mouseIsHeld){const handleSize=vec2(this.size.y);const handleWidth=this.size.x-handleSize.x;const p1=this.pos.x-handleWidth/2;const p2=this.pos.x+handleWidth/2;const oldValue=this.value;this.value=percent(mousePosScreen.x,p1,p2);this.value==oldValue||this.onChange()}}render(){const lineColor=this.mouseIsHeld?this.color:this.lineColor;const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,lineColor);const handleSize=vec2(this.size.y);const handleWidth=this.size.x-handleSize.x;const p1=this.pos.x-handleWidth/2;const p2=this.pos.x+handleWidth/2;const handlePos=vec2(lerp(this.value,p1,p2),this.pos.y);const barColor=this.mouseIsHeld?this.color:this.handleColor;uiSystem.drawRect(handlePos,handleSize,barColor,this.lineWidth,this.lineColor);const textSize=vec2(this.size.x,this.size.y*.8);uiSystem.drawText(this.text,this.pos,textSize,this.textColor,0,undefined,this.align,this.font)}}let box2d;let box2dDebug=false;function box2dSetDebug(enable){box2dDebug=enable}class Box2dObject extends EngineObject{constructor(pos=vec2(),size,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.body.object=this;this.outlineColor=BLACK}destroy(){this.body&&box2d.world.DestroyBody(this.body);this.body=0;super.destroy()}update(){this.pos=box2d.vec2From(this.body.GetPosition());this.angle=-this.body.GetAngle()}render(){if(this.tileInfo)super.render();else this.drawFixtures(this.color,this.outlineColor,this.lineWidth,mainContext)}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,outlineColor,lineWidth=.1,context){this.getFixtureList().forEach(fixture=>box2d.drawFixture(fixture,this.pos,this.angle,color,outlineColor,lineWidth,context))}beginContact(otherObject){}endContact(otherObject){}addShape(shape,density=1,friction=.2,restitution=0,isSensor=false){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){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){function box2dCreatePolygonShape(points){function box2dCreatePointList(points){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}return box2d.instance.wrapPointer(buffer,box2d.instance.b2Vec2)}ASSERT(3<=points.length&&points.length<=8);const shape=new box2d.instance.b2PolygonShape;const box2dPoints=box2dCreatePointList(points);shape.Set(box2dPoints,points.length);return shape}const shape=box2dCreatePolygonShape(points);return this.addShape(shape,density,friction,restitution,isSensor)}addRegularPoly(diameter=1,sides=8,density,friction,restitution,isSensor){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){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){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){const shape=new box2d.instance.b2EdgeShape;shape.Set(box2d.vec2dTo(point1),box2d.vec2dTo(point2));return this.addShape(shape,density,friction,restitution,isSensor)}addEdgeLoop(points,density,friction,restitution,isSensor){const fixtures=[];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)}return fixtures}addEdgeList(points,density,friction,restitution,isSensor){const fixtures=[];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)}return fixtures}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()}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);localCenter&&data.set_center(box2d.vec2dTo(localCenter));mass&&data.set_mass(mass);momentOfInertia&&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)})}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();this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration),box2d.vec2dTo(pos))}applyTorque(torque){this.setAwake();this.body.ApplyTorque(torque)}applyAngularAcceleration(acceleration){this.setAwake();this.body.ApplyAngularImpulse(acceleration)}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 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.castObjectType(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(objectA.body.GetAngle()-objectB.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=objectB.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(objectA.body.GetAngle()-objectB.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=objectB.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(objectA.body.GetAngle()-objectB.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.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;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;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,outlineColor=BLACK,lineWidth=.1,context=mainContext){const shape=box2d.castObjectType(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)));box2d.drawPoly(pos,angle,points,color,outlineColor,lineWidth,context);break}case box2d.instance.b2Shape.e_circle:{const radius=shape.get_m_radius();box2d.drawCircle(pos,radius,color,outlineColor,lineWidth,context);break}case box2d.instance.b2Shape.e_edge:{const v1=box2d.vec2From(shape.get_m_vertex1());const v2=box2d.vec2From(shape.get_m_vertex2());box2d.drawLine(pos,angle,v1,v2,color,lineWidth,context);break}}}drawCircle(pos,radius,color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),0,0,context=>{context.beginPath();context.arc(0,0,radius,0,9);box2d.drawFillStroke(color,outlineColor,lineWidth,context)},0,context)}drawPoly(pos,angle,points,color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),angle,0,context=>{context.beginPath();points.forEach(p=>context.lineTo(p.x,p.y));context.closePath();box2d.drawFillStroke(color,outlineColor,lineWidth,context)},0,context)}drawLine(pos,angle,posA,posB,color=WHITE,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),angle,0,context=>{context.beginPath();context.lineTo(posA.x,posA.y);context.lineTo(posB.x,posB.y);box2d.drawFillStroke(0,color,lineWidth,context)},0,context)}drawFillStroke(color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){if(color){context.fillStyle=color.toString();context.fill()}if(outlineColor&&lineWidth){context.lineWidth=lineWidth;context.lineJoin=context.lineCap="round";context.strokeStyle=outlineColor.toString();context.stroke()}}vec2From(v){ASSERT(v instanceof box2d.instance.b2Vec2);return new Vector2(v.get_x(),v.get_y())}vec2FromPointer(v){return box2d.vec2From(box2d.instance.wrapPointer(v,box2d.instance.b2Vec2))}vec2dTo(v){ASSERT(v instanceof Vector2);return new box2d.instance.b2Vec2(v.x,v.y)}isNull(o){return!box2d.instance.getPointer(o)}castObjectType(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);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")}}function box2dEngineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources,rootElement){Box2D().then(box2dInstance=>{new Box2dPlugin(box2dInstance);setupDebugDraw();engineAddPlugin(box2dUpdate,box2dRender);engineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources,rootElement)});function box2dUpdate(){if(!paused)box2d.step()}function box2dRender(){if(box2dDebug||debugPhysics&&debugOverlay)box2d.world.DrawDebugData()}function setupDebugDraw(){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);box2d.drawLine(vec2(),0,point1,point2,color,undefined,overlayContext)};debugDraw.DrawPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);box2d.drawPoly(vec2(),0,points,undefined,color,undefined,overlayContext)};debugDraw.DrawSolidPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);box2d.drawPoly(vec2(),0,points,color,color,undefined,overlayContext)};debugDraw.DrawCircle=function(center,radius,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);box2d.drawCircle(center,radius,undefined,color,undefined,overlayContext)};debugDraw.DrawSolidCircle=function(center,radius,axis,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);axis=box2d.vec2FromPointer(axis).scale(radius);box2d.drawCircle(center,radius,color,color,undefined,overlayContext);box2d.drawLine(center,0,vec2(),axis,color,undefined,overlayContext)};debugDraw.DrawTransform=function(transform){transform=box2d.instance.wrapPointer(transform,box2d.instance.b2Transform);const pos=vec2(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);box2d.drawLine(pos,angle,vec2(),p1,c1,undefined,overlayContext);box2d.drawLine(pos,angle,vec2(),p2,c2,undefined,overlayContext)};debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);box2d.world.SetDebugDraw(debugDraw)}}export{engineName,engineVersion,frameRate,timeDelta,engineObjects,frame,time,timeReal,paused,setPaused,engineInit,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCollect,engineObjectsCallback,engineObjectsRaycast,engineAddPlugin,debug,debugOverlay,showWatermark,ASSERT,debugRect,debugPoly,debugCircle,debugPoint,debugLine,debugOverlap,debugText,debugClear,debugScreenshot,debugSaveCanvas,debugSaveText,debugSaveDataURL,debugShowErrors,debugVideoCaptureIsActive,debugVideoCaptureStart,debugVideoCaptureStop,cameraPos,cameraScale,canvasMaxSize,canvasFixedSize,canvasPixelated,tilesPixelated,fontDefault,showSplashScreen,headlessMode,tileSizeDefault,tileFixBleedScale,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultElasticity,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,glEnable,glOverlay,gamepadsEnable,gamepadDirectionEmulateStick,inputWASDEmulateDirection,touchGamepadEnable,touchGamepadAnalog,touchGamepadSize,touchGamepadAlpha,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,setCameraPos,setCameraScale,setCanvasMaxSize,setCanvasFixedSize,setCanvasPixelated,setTilesPixelated,setFontDefault,setShowSplashScreen,setHeadlessMode,setGlEnable,setGlOverlay,setTileSizeDefault,setTileFixBleedScale,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultElasticity,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setTouchInputEnable,setGamepadsEnable,setGamepadDirectionEmulateStick,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadAnalog,setTouchGamepadSize,setTouchGamepadAlpha,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalsPreventUnlock,setShowWatermark,setDebugKey,PI,abs,min,max,sign,mod,clamp,percent,distanceWrap,lerpWrap,distanceAngle,lerpAngle,lerp,smoothStep,nearestPowerOfTwo,isOverlapping,isIntersecting,wave,formatTime,rand,randInt,randSign,randInCircle,randVector,randColor,RandomGenerator,Vector2,Color,Timer,vec2,rgb,hsl,isColor,WHITE,BLACK,GRAY,RED,ORANGE,YELLOW,GREEN,CYAN,BLUE,PURPLE,MAGENTA,textureInfos,tile,TileInfo,TextureInfo,mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize,screenToWorld,worldToScreen,drawTile,drawRect,drawLine,drawPoly,drawEllipse,drawCircle,drawCanvas2D,drawText,drawTextOverlay,drawTextScreen,setBlendMode,combineCanvases,engineFontImage,FontImage,isFullscreen,toggleFullscreen,setCursor,getCameraSize,glCanvas,glContext,glCompileShader,glCopyToContext,glCreateProgram,glCreateTexture,glSetTextureData,glDraw,glFlush,glSetTexture,glSetAntialias,glClearCanvas,glAntialias,glShader,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glInstanceCount,glAdditive,glBatchAdditive,keyIsDown,keyWasPressed,keyWasReleased,keyDirection,clearInput,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseWheel,isUsingGamepad,inputPreventDefault,setInputPreventDefault,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,gamepadsUpdate,vibrate,vibrateStop,isTouchDevice,Sound,SoundWave,playAudioFile,speak,speakStop,getNoteFrequency,playSamples,zzfx,zzfxG,zzfxR,audioContext,EngineObject,tileCollisionLayers,getTileCollisionData,tileCollisionTest,tileCollisionRaycast,TileLayerData,TileLayer,TileCollisionLayer,ParticleEmitter,Particle,medals,medalsPreventUnlock,medalsInit,Medal};export{newgrounds,NewgroundsPlugin,NewgroundsMedal,postProcess,PostProcessPlugin,ZzFXMusic,uiSystem,UISystemPlugin,UIObject,UIText,UITile,UIButton,UICheckbox,UIScrollbar,box2d,box2dDebug,box2dSetDebug,box2dEngineInit,Box2dPlugin,Box2dObject,Box2dRaycastResult,Box2dJoint,Box2dTargetJoint,Box2dDistanceJoint,Box2dPinJoint,Box2dRopeJoint,Box2dRevoluteJoint,Box2dGearJoint,Box2dPrismaticJoint,Box2dWheelJoint,Box2dWeldJoint,Box2dFrictionJoint,Box2dPulleyJoint,Box2dMotorJoint};
|
|
1
|
+
"use strict";let showWatermark=0;let debugKey="";const debug=0;const debugOverlay=0;const debugPhysics=0;const debugParticles=0;const debugRaycast=0;const debugGamepads=0;const debugMedals=0;function ASSERT(){}function debugInit(){}function debugUpdate(){}function debugRender(){}function debugRect(){}function debugPoly(){}function debugCircle(){}function debugPoint(){}function debugLine(){}function debugOverlap(){}function debugText(){}function debugClear(){}function debugScreenshot(){}function debugSaveCanvas(){}function debugSaveText(){}function debugSaveDataURL(){}function debugShowErrors(){}function debugVideoCaptureIsActive(){return false}function debugVideoCaptureStart(){}function debugVideoCaptureStop(){}function debugVideoCaptureUpdate(){}const PI=Math.PI;function abs(value){return Math.abs(value)}function min(valueA,valueB){return Math.min(valueA,valueB)}function max(valueA,valueB){return Math.max(valueA,valueB)}function sign(value){return Math.sign(value)}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(percent,valueA,valueB){return valueA+clamp(percent)*(valueB-valueA)}function distanceWrap(valueA,valueB,wrapSize=1){const d=(valueA-valueB)%wrapSize;return d*2%wrapSize-d}function lerpWrap(percent,valueA,valueB,wrapSize=1){return valueA+clamp(percent)*distanceWrap(valueB,valueA,wrapSize)}function distanceAngle(angleA,angleB){return distanceWrap(angleA,angleB,2*PI)}function lerpAngle(percent,angleA,angleB){return lerpWrap(percent,angleA,angleB,2*PI)}function smoothStep(percent){return percent*percent*(3-2*percent)}function nearestPowerOfTwo(value){return 2**Math.ceil(Math.log2(value))}function isOverlapping(posA,sizeA,posB,sizeB=vec2()){return abs(posA.x-posB.x)*2<sizeA.x+sizeB.x&&abs(posA.y-posB.y)*2<sizeA.y+sizeB.y}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 wave(frequency=1,amplitude=1,t=time){return amplitude/2*(1-Math.cos(t*frequency*2*PI))}function formatTime(t){return(t/60|0)+":"+(t%60<10?"0":"")+(t%60|0)}async function fetchJSON(url){const response=await fetch(url);return response.json()}function rand(valueA=1,valueB=0){return valueB+Math.random()*(valueA-valueB)}function randInt(valueA,valueB=0){return Math.floor(rand(valueA,valueB))}function randSign(){return randInt(2)*2-1}function randVector(length=1){return(new Vector2).setAngle(rand(2*PI),length)}function randInCircle(radius=1,minRadius=0){return radius>0?randVector(radius*rand(minRadius/radius,1)**.5):new Vector2}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){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 Math.floor(this.float(valueA,valueB))}sign(){return this.float()>.5?1:-1}floatSign(valueA=1,valueB=0){return this.float(valueA,valueB)*this.sign()}}function vec2(x=0,y){return new Vector2(x,y==undefined?x:y)}function isVector2(v){return v instanceof Vector2}class Vector2{constructor(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid())}set(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid());return this}copy(){return new Vector2(this.x,this.y)}add(v){ASSERT(isVector2(v));return new Vector2(this.x+v.x,this.y+v.y)}subtract(v){ASSERT(isVector2(v));return new Vector2(this.x-v.x,this.y-v.y)}multiply(v){ASSERT(isVector2(v));return new Vector2(this.x*v.x,this.y*v.y)}divide(v){ASSERT(isVector2(v));return new Vector2(this.x/v.x,this.y/v.y)}scale(s){ASSERT(!isVector2(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){ASSERT(isVector2(v));return this.distanceSquared(v)**.5}distanceSquared(v){ASSERT(isVector2(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}dot(v){ASSERT(isVector2(v));return this.x*v.x+this.y*v.y}cross(v){ASSERT(isVector2(v));return this.x*v.y-this.y*v.x}angle(){return Math.atan2(this.x,this.y)}setAngle(angle=0,length=1){this.x=length*Math.sin(angle);this.y=length*Math.cos(angle);return this}rotate(angle){const c=Math.cos(-angle),s=Math.sin(-angle);return new Vector2(this.x*c-this.y*s,this.x*s+this.y*c)}setDirection(direction,length=1){direction=mod(direction,4);ASSERT(direction==0||direction==1||direction==2||direction==3);return vec2(direction%2?direction-1?-length:length:0,direction%2?0:direction?-length:length)}direction(){return abs(this.x)>abs(this.y)?this.x<0?3:1:this.y<0?2:0}invert(){return new Vector2(this.y,-this.x)}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}area(){return abs(this.x*this.y)}lerp(v,percent){ASSERT(isVector2(v));return this.add(v.subtract(this).scale(clamp(percent)))}arrayCheck(arraySize){ASSERT(isVector2(arraySize));return this.x>=0&&this.y>=0&&this.x<arraySize.x&&this.y<arraySize.y}toString(digits=3){if(debug)return`(${(this.x<0?"":" ")+this.x.toFixed(digits)},${(this.y<0?"":" ")+this.y.toFixed(digits)} )`}isValid(){return typeof this.x=="number"&&!isNaN(this.x)&&typeof this.y=="number"&&!isNaN(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}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())}set(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;ASSERT(this.isValid());return this}copy(){return new Color(this.r,this.g,this.b,this.a)}add(c){ASSERT(isColor(c));return new Color(this.r+c.r,this.g+c.g,this.b+c.b,this.a+c.a)}subtract(c){ASSERT(isColor(c));return new Color(this.r-c.r,this.g-c.g,this.b-c.b,this.a-c.a)}multiply(c){ASSERT(isColor(c));return new Color(this.r*c.r,this.g*c.g,this.b*c.b,this.a*c.a)}divide(c){ASSERT(isColor(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(isColor(c));return this.add(c.subtract(this).scale(clamp(percent)))}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(this.isValid());return this}HSLA(){const r=clamp(this.r);const g=clamp(this.g);const b=clamp(this.b);const a=clamp(this.a);const max=Math.max(r,g,b);const min=Math.min(r,g,b);const l=(max+min)/2;let h=0,s=0;if(max!=min){let d=max-min;s=l>.5?d/(2-max-min):d/(max+min);if(r==max)h=(g-b)/d+(g<b?6:0);else if(g==max)h=(b-r)/d+2;else if(b==max)h=(r-g)/d+4}return[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=true){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(typeof hex=="string"&&hex[0]=="#");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(this.isValid());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 typeof this.r=="number"&&!isNaN(this.r)&&typeof this.g=="number"&&!isNaN(this.g)&&typeof this.b=="number"&&!isNaN(this.b)&&typeof this.a=="number"&&!isNaN(this.a)}}const WHITE=rgb();const BLACK=rgb(0,0,0);const GRAY=rgb(.5,.5,.5);const RED=rgb(1,0,0);const ORANGE=rgb(1,.5,0);const YELLOW=rgb(1,1,0);const GREEN=rgb(0,1,0);const CYAN=rgb(0,1,1);const BLUE=rgb(0,0,1);const PURPLE=rgb(.5,0,1);const MAGENTA=rgb(1,0,1);class Timer{constructor(timeLeft){this.time=timeLeft==undefined?undefined:time+timeLeft;this.setTime=timeLeft}set(timeLeft=0){this.time=time+timeLeft;this.setTime=timeLeft}unset(){this.time=undefined}isSet(){return this.time!=undefined}active(){return time<this.time}elapsed(){return time>=this.time}get(){return this.isSet()?time-this.time:0}getPercent(){return this.isSet()?1-percent(this.time-time,0,this.setTime):0}toString(){if(debug){return this.isSet()?Math.abs(this.get())+" seconds "+(this.get()<0?"before":"after"):"unset"}}valueOf(){return this.get()}}let cameraPos=vec2();let cameraScale=32;let canvasMaxSize=vec2(1920,1080);let canvasFixedSize=vec2();let canvasPixelated=true;let tilesPixelated=true;let fontDefault="arial";let showSplashScreen=false;let headlessMode=false;let glEnable=true;let glOverlay=true;let tileSizeDefault=vec2(16);let tileFixBleedScale=0;let enablePhysicsSolver=true;let objectDefaultMass=1;let objectDefaultDamping=1;let objectDefaultAngleDamping=1;let objectDefaultElasticity=0;let objectDefaultFriction=.8;let objectMaxSpeed=1;let gravity=vec2();let particleEmitRateScale=1;let gamepadsEnable=true;let gamepadDirectionEmulateStick=true;let inputWASDEmulateDirection=true;let touchInputEnable=true;let touchGamepadEnable=false;let touchGamepadAnalog=true;let touchGamepadSize=99;let touchGamepadAlpha=.3;let vibrateEnable=true;let soundEnable=true;let soundVolume=.3;let soundDefaultRange=40;let soundDefaultTaper=.7;let medalDisplayTime=5;let medalDisplaySlideTime=.5;let medalDisplaySize=vec2(640,80);let medalsPreventUnlock=false;function setCameraPos(pos){cameraPos=pos}function setCameraScale(scale){cameraScale=scale}function setCanvasMaxSize(size){canvasMaxSize=size}function setCanvasFixedSize(size){canvasFixedSize=size}function setCanvasPixelated(pixelated){canvasPixelated=pixelated}function setTilesPixelated(pixelated){tilesPixelated=pixelated}function setFontDefault(font){fontDefault=font}function setShowSplashScreen(show){showSplashScreen=show}function setHeadlessMode(headless){headlessMode=headless}function setGlEnable(enable){glEnable=enable}function setGlOverlay(overlay){glOverlay=overlay}function setTileSizeDefault(size){tileSizeDefault=size}function setTileFixBleedScale(scale){tileFixBleedScale=scale}function setEnablePhysicsSolver(enable){enablePhysicsSolver=enable}function setObjectDefaultMass(mass){objectDefaultMass=mass}function setObjectDefaultDamping(damp){objectDefaultDamping=damp}function setObjectDefaultAngleDamping(damp){objectDefaultAngleDamping=damp}function setObjectDefaultElasticity(elasticity){objectDefaultElasticity=elasticity}function setObjectDefaultFriction(friction){objectDefaultFriction=friction}function setObjectMaxSpeed(speed){objectMaxSpeed=speed}function setGravity(newGravity){gravity=newGravity}function setParticleEmitRateScale(scale){particleEmitRateScale=scale}function setGamepadsEnable(enable){gamepadsEnable=enable}function setGamepadDirectionEmulateStick(enable){gamepadDirectionEmulateStick=enable}function setInputWASDEmulateDirection(enable){inputWASDEmulateDirection=enable}function setTouchInputEnable(enable){touchInputEnable=enable}function setTouchGamepadEnable(enable){touchGamepadEnable=enable}function setTouchGamepadAnalog(analog){touchGamepadAnalog=analog}function setTouchGamepadSize(size){touchGamepadSize=size}function setTouchGamepadAlpha(alpha){touchGamepadAlpha=alpha}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 setMedalDisplayTime(time){medalDisplayTime=time}function setMedalDisplaySlideTime(time){medalDisplaySlideTime=time}function setMedalDisplaySize(size){medalDisplaySize=size}function setMedalsPreventUnlock(preventUnlock){medalsPreventUnlock=preventUnlock}function setShowWatermark(show){showWatermark=show}function setDebugKey(key){debugKey=key}class EngineObject{constructor(pos=vec2(),size=vec2(1),tileInfo,angle=0,color=new Color,renderOrder=0){ASSERT(isVector2(pos)&&isVector2(size),"ensure pos and size are vec2s");ASSERT(typeof tileInfo!=="number"||!tileInfo,"old style tile setup");this.pos=pos.copy();this.size=size;this.drawSize=undefined;this.tileInfo=tileInfo;this.angle=angle;this.color=color;this.additiveColor=undefined;this.mirror=false;this.mass=objectDefaultMass;this.damping=objectDefaultDamping;this.angleDamping=objectDefaultAngleDamping;this.elasticity=objectDefaultElasticity;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=renderOrder;this.velocity=vec2();this.angleVelocity=0;this.spawnTime=time;this.children=[];this.clampSpeedLinear=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();this.pos=this.localPos.multiply(vec2(mirror,1)).rotate(parent.angle).add(parent.pos);this.angle=mirror*this.localAngle+parent.angle}for(const child of this.children)child.updateTransforms()}update(){if(this.parent)return;if(this.clampSpeedLinear){this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed);this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed)}else{const length2=this.velocity.lengthSquared();if(length2>objectMaxSpeed*objectMaxSpeed){const s=objectMaxSpeed/length2**.5;this.velocity.x*=s;this.velocity.y*=s}}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 wasMovingDown=this.velocity.y<0;if(this.groundObject){const friction=max(this.friction,this.groundObject.friction);const groundSpeed=this.groundObject.velocity?this.groundObject.velocity.x:0;this.velocity.x=groundSpeed+(this.velocity.x-groundSpeed)*friction;this.groundObject=undefined}if(this.collideSolidObjects){const epsilon=.001;for(const o of engineObjectsCollide){if(!this.isSolid&&!o.isSolid||o.destroyed||o.parent||o==this)continue;if(!isOverlapping(this.pos,this.size,o.pos,o.size))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<.01?randVector(pushAwayAccel):deltaPos.scale(pushAwayAccel/length);this.velocity=this.velocity.add(velocity);if(o.mass)o.velocity=o.velocity.subtract(velocity);debugOverlay&&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 elasticity=max(this.elasticity,o.elasticity);if(smallStepUp||isBlockedY||!isBlockedX){this.pos.y=o.pos.y+(sizeBoth.y/2+epsilon)*sign(oldPos.y-o.pos.y);if(o.groundObject&&wasMovingDown||!o.mass){if(wasMovingDown)this.groundObject=o;this.velocity.y*=-elasticity}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(elasticity,inelastic,elastic0);o.velocity.y=lerp(elasticity,inelastic,elastic1)}}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(elasticity,inelastic,elastic0);o.velocity.x=lerp(elasticity,inelastic,elastic1)}else this.velocity.x*=-elasticity}debugOverlay&&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 blockedLayerY=tileCollisionTest(vec2(oldPos.x,this.pos.y),this.size,this);const blockedLayerX=tileCollisionTest(vec2(this.pos.x,oldPos.y),this.size,this);if(blockedLayerY||!blockedLayerX){const elasticity=max(this.elasticity,hitLayer.elasticity);this.velocity.y*=-elasticity;if(wasMovingDown){const epsilon=1e-4;this.pos.y=(oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;this.groundObject=hitLayer}else{this.pos.y=oldPos.y;this.groundObject=undefined}}if(blockedLayerX){this.pos.x=oldPos.x;this.velocity.x*=-this.elasticity}debugOverlay&&debugPhysics&&debugRect(this.pos,this.size,"#f00")}}}}render(){drawTile(this.pos,this.drawSize||this.size,this.tileInfo,this.color,this.angle,this.mirror,this.additiveColor)}destroy(){if(this.destroyed)return;this.destroyed=1;this.parent&&this.parent.removeChild(this);for(const child of this.children)child.destroy(child.parent=0)}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}getAliveTime(){return time-this.spawnTime}applyAcceleration(acceleration){if(this.mass)this.velocity=this.velocity.add(acceleration)}applyForce(force){this.applyAcceleration(force.scale(1/this.mass))}getMirrorSign(){return this.mirror?-1:1}addChild(child,localPos=vec2(),localAngle=0){ASSERT(!child.parent&&!this.children.includes(child));this.children.push(child);child.parent=this;child.localPos=localPos.copy();child.localAngle=localAngle}removeChild(child){ASSERT(child.parent==this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=0}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(){if(debug){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){const size=vec2(max(this.size.x,.2),max(this.size.y,.2));const color1=rgb(this.collideTiles?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,this.parent?.2:.5);const color2=this.parent?rgb(1,1,1,.5):rgb(0,0,0,.8);drawRect(this.pos,size,color1,this.angle,false);drawRect(this.pos,size.scale(.8),color2,this.angle,false);this.parent&&drawLine(this.pos,this.parent.pos,.1,rgb(0,0,1,.5),false)}}}let mainCanvas;let mainContext;let overlayCanvas;let overlayContext;let mainCanvasSize=vec2();let textureInfos=[];let drawCount;function tile(pos=vec2(),size=tileSizeDefault,textureIndex=0,padding=0){if(headlessMode)return new TileInfo;if(typeof size==="number"){ASSERT(size>0);size=vec2(size)}const textureInfo=textureInfos[textureIndex];ASSERT(!!textureInfo,"Texture not loaded");const sizePadded=size.add(vec2(padding*2));if(typeof pos==="number"){const cols=textureInfo.size.x/sizePadded.x|0;pos=cols>0?vec2(pos%cols,pos/cols|0):vec2()}pos=vec2(pos.x*sizePadded.x+padding,pos.y*sizePadded.y+padding);return new TileInfo(pos,size,textureIndex,padding)}class TileInfo{constructor(pos=vec2(),size=tileSizeDefault,textureIndex=0,padding=0){this.pos=pos.copy();this.size=size.copy();this.textureIndex=textureIndex;this.padding=padding}offset(offset){return new TileInfo(this.pos.add(offset),this.size,this.textureIndex)}frame(frame){ASSERT(typeof frame=="number");return this.offset(vec2(frame*(this.size.x+this.padding*2),0))}getTextureInfo(){return textureInfos[this.textureIndex]}}class TextureInfo{constructor(image){this.image=image;this.size=vec2(image.width,image.height);this.sizeInverse=vec2(1/image.width,1/image.height);this.glTexture=glEnable&&glCreateTexture(image)}}function screenToWorld(screenPos){return new Vector2((screenPos.x-mainCanvasSize.x/2+.5)/cameraScale+cameraPos.x,(screenPos.y-mainCanvasSize.y/2+.5)/-cameraScale+cameraPos.y)}function worldToScreen(worldPos){return new Vector2((worldPos.x-cameraPos.x)*cameraScale+mainCanvasSize.x/2-.5,(worldPos.y-cameraPos.y)*-cameraScale+mainCanvasSize.y/2-.5)}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle=0,mirror,additiveColor,useWebGL=glEnable,screenSpace,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");ASSERT(typeof tileInfo!=="number"||!tileInfo,"this is an old style calls, to fix replace it with tile(tileIndex, tileSize)");ASSERT(isVector2(pos)&&isVector2(size));ASSERT(isColor(color)&&(!additiveColor||isColor(additiveColor)));const textureInfo=tileInfo&&tileInfo.getTextureInfo();if(useWebGL){if(screenSpace){pos=screenToWorld(pos);size=size.scale(1/cameraScale)}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(tileFixBleedScale){const tileImageFixBleedX=sizeInverse.x*tileFixBleedScale;const tileImageFixBleedY=sizeInverse.y*tileFixBleedScale;glDraw(pos.x,pos.y,mirror?-size.x:size.x,size.y,angle,x+tileImageFixBleedX,y+tileImageFixBleedY,x-tileImageFixBleedX+w,y-tileImageFixBleedY+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{glDraw(pos.x,pos.y,size.x,size.y,angle,0,0,0,0,0,color.rgbaInt())}}else{showWatermark&&++drawCount;size=vec2(size.x,-size.y);drawCanvas2D(pos,size,angle,mirror,context=>{if(textureInfo){const x=tileInfo.pos.x+tileFixBleedScale;const y=tileInfo.pos.y+tileFixBleedScale;const w=tileInfo.size.x-2*tileFixBleedScale;const h=tileInfo.size.y-2*tileFixBleedScale;context.globalAlpha=color.a;context.drawImage(textureInfo.image,x,y,w,h,-.5,-.5,1,1);context.globalAlpha=1}else{context.fillStyle=color.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 drawLine(posA,posB,thickness=.1,color,useWebGL,screenSpace,context){const halfDelta=vec2((posB.x-posA.x)/2,(posB.y-posA.y)/2);const size=vec2(thickness,halfDelta.length()*2);drawRect(posA.add(halfDelta),size,color,halfDelta.angle(),useWebGL,screenSpace,context)}function drawPoly(points,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){ASSERT(isColor(color)&&isColor(lineColor));context.fillStyle=color.toString();context.beginPath();for(const point of screenSpace?points:points.map(worldToScreen))context.lineTo(point.x,point.y);context.closePath();context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=screenSpace?lineWidth:lineWidth*cameraScale;context.stroke()}}function drawEllipse(pos,width=1,height=1,angle=0,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){ASSERT(isColor(color)&&isColor(lineColor));if(!screenSpace){pos=worldToScreen(pos);width*=cameraScale;height*=cameraScale;lineWidth*=cameraScale}context.fillStyle=color.toString();context.beginPath();context.ellipse(pos.x,pos.y,width,height,angle,0,9);context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}}function drawCircle(pos,radius=1,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),screenSpace,context=mainContext){drawEllipse(pos,radius,radius,0,color,lineWidth,lineColor,screenSpace,context)}function drawCanvas2D(pos,size,angle,mirror,drawFunction,screenSpace,context=mainContext){if(!screenSpace){pos=worldToScreen(pos);size=size.scale(cameraScale)}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,lineWidth=0,lineColor,textAlign,font,maxWidth,context=mainContext){drawTextScreen(text,worldToScreen(pos),size*cameraScale,color,lineWidth*cameraScale,lineColor,textAlign,font,maxWidth,context)}function drawTextOverlay(text,pos,size=1,color,lineWidth=0,lineColor,textAlign,font,maxWidth){drawText(text,pos,size,color,lineWidth,lineColor,textAlign,font,maxWidth,overlayContext)}function drawTextScreen(text,pos,size=1,color=new Color,lineWidth=0,lineColor=new Color(0,0,0),textAlign="center",font=fontDefault,maxWidth=undefined,context=overlayContext){context.fillStyle=color.toString();context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.textAlign=textAlign;context.font=size+"px "+font;context.textBaseline="middle";context.lineJoin="round";const lines=(text+"").split("\n");pos=pos.copy();pos.y-=(lines.length-1)*size/2;lines.forEach(line=>{lineWidth&&context.strokeText(line,pos.x,pos.y,maxWidth);context.fillText(line,pos.x,pos.y,maxWidth);pos.y+=size})}function setBlendMode(additive,useWebGL=glEnable,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL)glAdditive=additive;else{if(!context)context=mainContext;context.globalCompositeOperation=additive?"lighter":"source-over"}}function combineCanvases(){glCopyToContext(mainContext,true);mainContext.drawImage(overlayCanvas,0,0);glClearCanvas();overlayCanvas.width|=0}let engineFontImage;class FontImage{constructor(image,tileSize=vec2(8),paddingSize=vec2(0,1),context=overlayContext){if(!engineFontImage){engineFontImage=new Image;engineFontImage.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC"}this.image=image||engineFontImage;this.tileSize=tileSize;this.paddingSize=paddingSize;this.context=context}drawText(text,pos,scale=1,center){this.drawTextScreen(text,worldToScreen(pos).floor(),scale*cameraScale|0,center)}drawTextScreen(text,pos,scale=4,center){const context=this.context;context.save();const size=this.tileSize;const drawSize=size.add(this.paddingSize).scale(scale);const cols=this.image.width/this.tileSize.x|0;(text+"").split("\n").forEach((line,i)=>{const centerOffset=center?line.length*size.x*scale/2|0:0;for(let j=line.length;j--;){let charCode=line[j].charCodeAt(0);if(charCode<32||charCode>127)charCode=127;const tile=charCode-32;const x=tile%cols;const y=tile/cols|0;const drawPos=pos.add(vec2(j,i).multiply(drawSize));context.drawImage(this.image,x*size.x,y*size.y,size.x,size.y,drawPos.x-centerOffset,drawPos.y,size.x*scale,size.y*scale)}});context.restore()}}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}function keyIsDown(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&1)}function keyWasPressed(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&2)}function keyWasReleased(key,device=0){ASSERT(device>0||typeof key!=="number"||key<3,"use code string for keyboard");return inputData[device]&&!!(inputData[device][key]&4)}function keyDirection(up="ArrowUp",down="ArrowDown",left="ArrowLeft",right="ArrowRight"){const k=key=>keyIsDown(key)?1:0;return vec2(k(right)-k(left),k(up)-k(down))}function clearInput(){inputData=[[]];touchGamepadButtons=[]}function mouseIsDown(button){return keyIsDown(button)}function mouseWasPressed(button){return keyWasPressed(button)}function mouseWasReleased(button){return keyWasReleased(button)}let mousePos=vec2();let mousePosScreen=vec2();let mouseWheel=0;let isUsingGamepad=false;let inputPreventDefault=true;function setInputPreventDefault(preventDefault){inputPreventDefault=preventDefault}function gamepadIsDown(button,gamepad=0){return keyIsDown(button,gamepad+1)}function gamepadWasPressed(button,gamepad=0){return keyWasPressed(button,gamepad+1)}function gamepadWasReleased(button,gamepad=0){return keyWasReleased(button,gamepad+1)}function gamepadStick(stick,gamepad=0){return gamepadStickData[gamepad]?gamepadStickData[gamepad][stick]||vec2():vec2()}let inputData=[[]];function inputUpdate(){if(headlessMode)return;if(!(touchInputEnable&&isTouchDevice)&&!document.hasFocus())clearInput();mousePos=screenToWorld(mousePosScreen);gamepadsUpdate()}function inputUpdatePost(){if(headlessMode)return;for(const deviceInputData of inputData)for(const i in deviceInputData)deviceInputData[i]&=1;mouseWheel=0}function inputInit(){if(headlessMode)return;onkeydown=e=>{if(!e.repeat){isUsingGamepad=false;inputData[0][e.code]=3;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=3}};onkeyup=e=>{inputData[0][e.code]=4;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=4};function remapKey(c){return inputWASDEmulateDirection?c=="KeyW"?"ArrowUp":c=="KeyS"?"ArrowDown":c=="KeyA"?"ArrowLeft":c=="KeyD"?"ArrowRight":c:c}onmousedown=e=>{if(soundEnable&&!headlessMode&&audioContext&&audioContext.state!="running")audioContext.resume();isUsingGamepad=false;inputData[0][e.button]=3;mousePosScreen=mouseEventToScreen(e);inputPreventDefault&&e.button&&e.preventDefault()};onmouseup=e=>inputData[0][e.button]=inputData[0][e.button]&2|4;onmousemove=e=>mousePosScreen=mouseEventToScreen(e);onwheel=e=>mouseWheel=e.ctrlKey?0:sign(e.deltaY);oncontextmenu=e=>false;onblur=e=>clearInput();if(isTouchDevice&&touchInputEnable)touchInputInit()}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)}const gamepadStickData=[];function gamepadsUpdate(){const applyDeadZones=v=>{const min=.3,max=.8;const deadZone=v=>v>min?percent(v,min,max):v<-min?-percent(-v,min,max):0;return vec2(deadZone(v.x),deadZone(-v.y)).clampLength()};if(touchGamepadEnable&&isTouchDevice){ASSERT(touchGamepadButtons,"set touchGamepadEnable before calling init!");if(touchGamepadTimer.isSet()){const sticks=gamepadStickData[0]||(gamepadStickData[0]=[]);sticks[0]=vec2();if(touchGamepadAnalog)sticks[0]=applyDeadZones(touchGamepadStick);else if(touchGamepadStick.lengthSquared()>.3){sticks[0].x=Math.round(touchGamepadStick.x);sticks[0].y=-Math.round(touchGamepadStick.y);sticks[0]=sticks[0].clampLength()}const data=inputData[1]||(inputData[1]=[]);for(let i=10;i--;){const j=i==3?2:i==2?3:i;const wasDown=gamepadIsDown(j,0);data[j]=touchGamepadButtons[i]?wasDown?1:3:wasDown?4:0}}}if(!gamepadsEnable||!navigator||!navigator.getGamepads)return;if(!debug&&!document.hasFocus())return;const gamepads=navigator.getGamepads();for(let i=gamepads.length;i--;){const gamepad=gamepads[i];const data=inputData[i+1]||(inputData[i+1]=[]);const sticks=gamepadStickData[i]||(gamepadStickData[i]=[]);if(gamepad){for(let j=0;j<gamepad.axes.length-1;j+=2)sticks[j>>1]=applyDeadZones(vec2(gamepad.axes[j],gamepad.axes[j+1]));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.value||button.value>.9)if(!i&&button.pressed)isUsingGamepad=true}if(gamepadDirectionEmulateStick){const dpad=vec2((gamepadIsDown(15,i)&&1)-(gamepadIsDown(14,i)&&1),(gamepadIsDown(12,i)&&1)-(gamepadIsDown(13,i)&&1));if(dpad.lengthSquared())sticks[0]=dpad.clampLength()}touchGamepadEnable&&isUsingGamepad&&touchGamepadTimer.unset()}}}function vibrate(pattern=100){vibrateEnable&&!headlessMode&&navigator&&navigator.vibrate&&navigator.vibrate(pattern)}function vibrateStop(){vibrate(0)}const isTouchDevice=!headlessMode&&window.ontouchstart!==undefined;let touchGamepadTimer=new Timer,touchGamepadButtons,touchGamepadStick;function touchInputInit(){let handleTouch=handleTouchDefault;if(touchGamepadEnable){handleTouch=handleTouchGamepad;touchGamepadButtons=[];touchGamepadStick=vec2()}document.addEventListener("touchstart",e=>handleTouch(e),{passive:false});document.addEventListener("touchmove",e=>handleTouch(e),{passive:false});document.addEventListener("touchend",e=>handleTouch(e),{passive:false});onmousedown=onmouseup=()=>0;let wasTouching;function handleTouchDefault(e){if(soundEnable&&!headlessMode&&audioContext&&audioContext.state!="running")audioContext.resume();const touching=e.touches.length;const button=0;if(touching){const p=vec2(e.touches[0].clientX,e.touches[0].clientY);mousePosScreen=mouseEventToScreen(p);wasTouching?isUsingGamepad=touchGamepadEnable:inputData[0][button]=3}else if(wasTouching)inputData[0][button]=inputData[0][button]&2|4;wasTouching=touching;if(inputPreventDefault&&document.hasFocus())e.preventDefault();return true}function handleTouchGamepad(e){touchGamepadStick=vec2();touchGamepadButtons=[];isUsingGamepad=true;const touching=e.touches.length;if(touching){touchGamepadTimer.set();if(paused&&!wasTouching){touchGamepadButtons[9]=1;handleTouchDefault(e);return}}const stickCenter=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);const buttonCenter=mainCanvasSize.subtract(vec2(touchGamepadSize,touchGamepadSize));const startCenter=mainCanvasSize.scale(.5);for(const touch of e.touches){const touchPos=mouseEventToScreen(vec2(touch.clientX,touch.clientY));if(touchPos.distance(stickCenter)<touchGamepadSize){touchGamepadStick=touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength()}else if(touchPos.distance(buttonCenter)<touchGamepadSize){const button=touchPos.subtract(buttonCenter).direction();touchGamepadButtons[button]=1}else if(touchPos.distance(startCenter)<touchGamepadSize&&!wasTouching){touchGamepadButtons[9]=1}}handleTouchDefault(e);return true}}function touchGamepadRender(){if(!touchInputEnable||!isTouchDevice||headlessMode)return;if(!touchGamepadEnable||!touchGamepadTimer.isSet())return;const alpha=percent(touchGamepadTimer.get(),4,3);if(!alpha||paused)return;const context=overlayContext;context.save();context.globalAlpha=alpha*touchGamepadAlpha;context.strokeStyle="#fff";context.lineWidth=3;context.fillStyle=touchGamepadStick.lengthSquared()>0?"#fff":"#000";context.beginPath();const leftCenter=vec2(touchGamepadSize,mainCanvasSize.y-touchGamepadSize);if(touchGamepadAnalog){context.arc(leftCenter.x,leftCenter.y,touchGamepadSize/2,0,9);context.fill();context.stroke()}else{for(let i=10;i--;){const angle=i*PI/4;context.arc(leftCenter.x,leftCenter.y,touchGamepadSize*.6,angle+PI/8,angle+PI/8);i%2&&context.arc(leftCenter.x,leftCenter.y,touchGamepadSize*.33,angle,angle);i==1&&context.fill()}context.stroke()}const rightCenter=vec2(mainCanvasSize.x-touchGamepadSize,mainCanvasSize.y-touchGamepadSize);for(let i=4;i--;){const pos=rightCenter.add(vec2().setDirection(i,touchGamepadSize/2));context.fillStyle=touchGamepadButtons[i]?"#fff":"#000";context.beginPath();context.arc(pos.x,pos.y,touchGamepadSize/4,0,9);context.fill();context.stroke()}context.restore()}let audioContext=new AudioContext;let audioMasterGain;function audioInit(){if(!soundEnable||headlessMode)return;audioMasterGain=audioContext.createGain();audioMasterGain.connect(audioContext.destination);audioMasterGain.gain.value=soundVolume}class Sound{constructor(zzfxSound,range=soundDefaultRange,taper=soundDefaultTaper){if(!soundEnable||headlessMode)return;this.range=range;this.taper=taper;this.randomness=0;if(zzfxSound){const defaultRandomness=.05;this.randomness=zzfxSound[1]!=undefined?zzfxSound[1]:defaultRandomness;zzfxSound[1]=0;this.sampleChannels=[zzfxG(...zzfxSound)];this.sampleRate=zzfxR}}play(pos,volume=1,pitch=1,randomnessScale=1,loop=false){if(!soundEnable||headlessMode)return;if(!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 playbackRate=pitch+pitch*this.randomness*randomnessScale*rand(-1,1);this.gainNode=audioContext.createGain();this.source=playSamples(this.sampleChannels,volume,playbackRate,pan,loop,this.sampleRate,this.gainNode);return this.source}setVolume(volume=1){if(this.gainNode)this.gainNode.gain.value=volume}stop(){if(this.source)this.source.stop();this.source=undefined}getSource(){return this.source}playNote(semitoneOffset,pos,volume){return this.play(pos,volume,2**(semitoneOffset/12),0)}getDuration(){return this.sampleChannels&&this.sampleChannels[0].length/this.sampleRate}isLoading(){return!this.sampleChannels}}class SoundWave extends Sound{constructor(filename,randomness=0,range,taper,onloadCallback){super(undefined,range,taper);if(!soundEnable||headlessMode)return;this.onloadCallback=onloadCallback;this.randomness=randomness;this.loadSound(filename)}async loadSound(filename){const response=await fetch(filename);const arrayBuffer=await response.arrayBuffer();const audioBuffer=await audioContext.decodeAudioData(arrayBuffer);this.sampleChannels=[];for(let i=audioBuffer.numberOfChannels;i--;)this.sampleChannels[i]=Array.from(audioBuffer.getChannelData(i));this.sampleRate=audioBuffer.sampleRate;if(this.onloadCallback)this.onloadCallback()}}function playAudioFile(filename,volume=1,loop=false){if(!soundEnable||headlessMode)return;return new SoundWave(filename,0,0,0,s=>s.play(undefined,volume,1,1,loop))}function speak(text,language="",volume=1,rate=1,pitch=1){if(!soundEnable||headlessMode)return;if(!speechSynthesis)return;const utterance=new SpeechSynthesisUtterance(text);utterance.lang=language;utterance.volume=2*volume*soundVolume;utterance.rate=rate;utterance.pitch=pitch;speechSynthesis.speak(utterance);return utterance}function speakStop(){speechSynthesis&&speechSynthesis.cancel()}function getNoteFrequency(semitoneOffset,rootFrequency=220){return rootFrequency*2**(semitoneOffset/12)}function playSamples(sampleChannels,volume=1,rate=1,pan=0,loop=false,sampleRate=zzfxR,gainNode){if(!soundEnable||headlessMode)return;const channelCount=sampleChannels.length;const sampleLength=sampleChannels[0].length;const buffer=audioContext.createBuffer(channelCount,sampleLength,sampleRate);const source=audioContext.createBufferSource();sampleChannels.forEach((c,i)=>buffer.getChannelData(i).set(c));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);if(audioContext.state!="running"){audioContext.resume().then(()=>source.start())}else source.start();return source}function zzfx(...zzfxSound){return playSamples([zzfxG(...zzfxSound)])}const zzfxR=44100;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=zzfxR,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,cos=Math.cos(w),alpha=Math.sin(w)/2/quality,a0=1+alpha,a1=-2*cos/a0,a2=(1-alpha)/a0,b0=(1+sign(filter)*cos)/2/a0,b1=-(sign(filter)+cos)/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:Math.sin(t**3):Math.max(Math.min(Math.tan(t),1),-1):1-(2*t/PI2%2+2)%2:1-4*abs(Math.round(t/PI2)-t/PI2):Math.sin(t);s=(repeatTime?1-tremolo+tremolo*Math.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)*Math.cos(modulation*modOffset++);t+=f+f*noise*Math.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}let tileCollisionLayers=[];function getTileCollisionData(pos){for(const layer of tileCollisionLayers)if(pos.arrayCheck(layer.size))return layer.getCollisionData(pos);return 0}function tileCollisionTest(pos,size=vec2(),object){for(const layer of tileCollisionLayers)if(layer.collisionTest(pos,size,object))return layer}function tileCollisionRaycast(posStart,posEnd,object){for(const layer of tileCollisionLayers){const hitPos=layer.collisionRaycast(posStart,posEnd,object);if(hitPos)return hitPos}}class TileLayerData{constructor(tile,direction=0,mirror=false,color=new Color){this.tile=tile;this.direction=direction;this.mirror=mirror;this.color=color}clear(){this.tile=this.direction=0;this.mirror=false;this.color=new Color}}class TileLayer extends EngineObject{constructor(position,size,tileInfo=tile(),scale=vec2(1),renderOrder=0){super(position,size,tileInfo,0,undefined,renderOrder);this.canvas=document.createElement("canvas");this.context=this.canvas.getContext("2d");this.scale=scale;this.isOverlay=false;this.friction=0;this.elasticity=0;this.data=[];for(let j=this.size.area();j--;)this.data.push(new TileLayerData);if(headlessMode){this.redraw=()=>{};this.render=()=>{};this.redrawStart=()=>{};this.redrawEnd=()=>{};this.drawTileData=()=>{};this.drawCanvas2D=()=>{}}}setData(layerPos,data,redraw=false){if(layerPos.arrayCheck(this.size)){this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]=data;redraw&&this.drawTileData(layerPos)}}getData(layerPos){return layerPos.arrayCheck(this.size)&&this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]}update(){}render(){ASSERT(mainContext!=this.context,"must call redrawEnd() after drawing tiles");!glOverlay&&!this.isOverlay&&glCopyToContext(mainContext);let pos=worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));pos=pos.floor();(this.isOverlay?overlayContext:mainContext).drawImage(this.canvas,pos.x,pos.y,cameraScale*this.size.x*this.scale.x,cameraScale*this.size.y*this.scale.y)}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.redrawEnd()}redrawStart(clear=false){this.savedRenderSettings=[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale];mainCanvas=this.canvas;mainContext=this.context;mainCanvasSize=this.size.multiply(this.tileInfo.size);cameraPos=this.size.scale(.5);cameraScale=this.tileInfo.size.x;if(clear){mainCanvas.width=mainCanvasSize.x;mainCanvas.height=mainCanvasSize.y}this.context.imageSmoothingEnabled=!tilesPixelated;glPreRender()}redrawEnd(){ASSERT(mainContext==this.context,"must call redrawStart() before drawing tiles");glCopyToContext(mainContext,true);[mainCanvas,mainContext,mainCanvasSize,cameraPos,cameraScale]=this.savedRenderSettings}drawTileData(layerPos,clear=true){const s=this.tileInfo.size;if(clear){const pos=layerPos.multiply(s);this.context.clearRect(pos.x,this.canvas.height-pos.y,s.x,-s.y)}const d=this.getData(layerPos);if(d.tile!=undefined){ASSERT(mainContext==this.context,"must call redrawStart() before drawing tiles");const pos=layerPos.add(vec2(.5));const tileInfo=tile(d.tile,s,this.tileInfo.textureIndex,this.tileInfo.padding);drawTile(pos,vec2(1),tileInfo,d.color,d.direction*PI/2,d.mirror)}}drawCanvas2D(pos,size,angle,mirror,drawFunction){const context=this.context;context.save();pos=pos.subtract(this.pos).multiply(this.tileInfo.size);size=size.multiply(this.tileInfo.size);context.translate(pos.x,this.canvas.height-pos.y);context.rotate(angle);context.scale(mirror?-size.x:size.x,size.y);drawFunction(context);context.restore()}drawTile(pos,size=vec2(1),tileInfo,color=new Color,angle,mirror){this.drawCanvas2D(pos,size,angle,mirror,context=>{const textureInfo=tileInfo&&tileInfo.getTextureInfo();if(textureInfo){context.globalAlpha=color.a;context.drawImage(textureInfo.image,tileInfo.pos.x,tileInfo.pos.y,tileInfo.size.x,tileInfo.size.y,-.5,-.5,1,1);context.globalAlpha=1}else{context.fillStyle=color;context.fillRect(-.5,-.5,1,1)}})}drawRect(pos,size,color,angle){this.drawTile(pos,size,undefined,color,angle)}}class TileCollisionLayer extends TileLayer{constructor(position,size,tileInfo=tile(),renderOrder=0){const scale=vec2(1);super(position,size.floor(),tileInfo,scale,renderOrder);this.collisionData=[];this.initCollision(this.size);tileCollisionLayers.push(this)}destroy(){if(this.destroyed)return;const index=tileCollisionLayers.indexOf(this);ASSERT(index>=0,"tile collision layer not found in array");tileCollisionLayers.splice(index,1);super.destroy()}initCollision(size){this.size=size.floor();this.collisionData=[];this.collisionData.length=size.area();this.collisionData.fill(0)}setCollisionData(pos,data=1){const i=(pos.y|0)*this.size.x+pos.x|0;pos.arrayCheck(this.size)&&(this.collisionData[i]=data)}getCollisionData(pos){const i=(pos.y|0)*this.size.x+pos.x|0;return pos.arrayCheck(this.size)?this.collisionData[i]:0}collisionTest(pos,size=vec2(),object){const minX=max(pos.x-size.x/2|0,0);const minY=max(pos.y-size.y/2|0,0);const maxX=min(pos.x+size.x/2,this.size.x);const maxY=min(pos.y+size.y/2,this.size.y);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&&(!object||object.collideWithTile(tileData,vec2(x,y))))return true}return false}collisionRaycast(posStart,posEnd,object){const delta=posEnd.subtract(posStart);const totalLength=delta.length();const normalizedDelta=delta.normalize();const unit=vec2(abs(1/normalizedDelta.x),abs(1/normalizedDelta.y));const flooredPosStart=posStart.floor();let pos=flooredPosStart;let xi=unit.x*(delta.x<0?posStart.x-pos.x:pos.x-posStart.x+1);let yi=unit.y*(delta.y<0?posStart.y-pos.y:pos.y-posStart.y+1);while(true){const tileData=this.getCollisionData(pos);if(tileData&&(!object||object.collideWithTile(tileData,pos))){debugRaycast&&debugLine(posStart,posEnd,"#f00",.02);debugRaycast&&debugPoint(pos.add(vec2(.5)),"#ff0");return pos.add(vec2(.5))}if(xi>totalLength&&yi>totalLength)break;if(xi>yi)pos.y+=sign(delta.y),yi+=unit.y;else pos.x+=sign(delta.x),xi+=unit.x}debugRaycast&&debugLine(posStart,posEnd,"#00f",.02)}}class ParticleEmitter extends EngineObject{constructor(position,angle,emitSize=0,emitTime=0,emitRate=100,emitConeAngle=PI,tileInfo,colorStartA=new Color,colorStartB=new Color,colorEndA=new Color(1,1,1,0),colorEndB=new Color(1,1,1,0),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(position,vec2(),tileInfo,angle,undefined,renderOrder);this.emitSize=emitSize;this.emitTime=emitTime;this.emitRate=emitRate;this.emitConeAngle=emitConeAngle;this.colorStartA=colorStartA;this.colorStartB=colorStartB;this.colorEndA=colorEndA;this.colorEndB=colorEndB;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.particleDestroyCallback=undefined;this.particleCreateCallback=undefined;this.emitTimeBuffer=0}update(){this.parent&&super.update();if(!this.emitTime||this.getAliveTime()<=this.emitTime){if(this.emitRate*particleEmitRateScale){const rate=1/this.emitRate/particleEmitRateScale;for(this.emitTimeBuffer+=timeDelta;this.emitTimeBuffer>0;this.emitTimeBuffer-=rate)this.emitParticle()}}else this.destroy();if(debugParticles){const emitSize=typeof this.emitSize==="number"?vec2(this.emitSize):this.emitSize;debugRect(this.pos,emitSize,"#0f0",0,this.angle)}}emitParticle(){let pos=typeof this.emitSize==="number"?randInCircle(this.emitSize/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=this.pos.add(pos);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 particle=new Particle(pos,this.tileInfo,angle,colorStart,colorEnd,particleTime,sizeStart,sizeEnd,this.fadeRate,this.additive,this.trailScale,this.localSpace&&this,this.particleDestroyCallback);particle.velocity=vec2().setAngle(velocityAngle,speed);particle.angleVelocity=angleSpeed;particle.fadeRate=this.fadeRate;particle.damping=this.damping;particle.angleDamping=this.angleDamping;particle.elasticity=this.elasticity;particle.friction=this.friction;particle.gravityScale=this.gravityScale;particle.collideTiles=this.collideTiles;particle.renderOrder=this.renderOrder;particle.mirror=!!randInt(2);this.particleCreateCallback&&this.particleCreateCallback(particle);return particle}render(){}}class Particle extends EngineObject{constructor(position,tileInfo,angle,colorStart,colorEnd,lifeTime,sizeStart,sizeEnd,fadeRate,additive,trailScale,localSpaceEmitter,destroyCallback){super(position,vec2(),tileInfo,angle);this.colorStart=colorStart;this.colorEndDelta=colorEnd.subtract(colorStart);this.lifeTime=lifeTime;this.sizeStart=sizeStart;this.sizeEndDelta=sizeEnd-sizeStart;this.fadeRate=fadeRate;this.additive=additive;this.trailScale=trailScale;this.localSpaceEmitter=localSpaceEmitter;this.destroyCallback=destroyCallback;this.clampSpeedLinear=false}render(){const p=this.lifeTime>0?min((time-this.spawnTime)/this.lifeTime,1):1;const radius=this.sizeStart+p*this.sizeEndDelta;const size=vec2(radius);const fadeRate=this.fadeRate/2;const color=new Color(this.colorStart.r+p*this.colorEndDelta.r,this.colorStart.g+p*this.colorEndDelta.g,this.colorStart.b+p*this.colorEndDelta.b,(this.colorStart.a+p*this.colorEndDelta.a)*(p<fadeRate?p/fadeRate:p>1-fadeRate?(1-p)/fadeRate:1));this.additive&&setBlendMode(true);let pos=this.pos,angle=this.angle;if(this.localSpaceEmitter){pos=this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));angle+=this.localSpaceEmitter.angle}if(this.trailScale){let velocity=this.velocity;if(this.localSpaceEmitter)velocity=velocity.rotate(-this.localSpaceEmitter.angle);const speed=velocity.length();if(speed){const direction=velocity.scale(1/speed);const trailLength=speed*this.trailScale;size.y=max(size.x,trailLength);angle=direction.angle();drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))),size,this.tileInfo,color,angle,this.mirror)}}else drawTile(pos,size,this.tileInfo,color,angle,this.mirror);this.additive&&setBlendMode();debugParticles&&debugRect(pos,size,"#f005",0,angle);if(p==1){this.color=color;this.size=size;this.destroyCallback&&this.destroyCallback(this);this.destroyed=1}}}const medals={};let medalsDisplayQueue=[],medalsSaveName,medalsDisplayTimeLast;function medalsInit(saveName){medalsSaveName=saveName;if(!debugMedals)medalsForEach(medal=>medal.unlocked=!!localStorage[medal.storageKey()]);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))}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;if(src)(this.image=new Image).src=src;medals[id]=this}unlock(){if(medalsPreventUnlock||this.unlocked)return;ASSERT(medalsSaveName,"save name must be set");localStorage[this.storageKey()]=this.unlocked=true;medalsDisplayQueue.push(this)}render(hidePercent=0){const context=overlayContext;const width=min(medalDisplaySize.x,mainCanvas.width);const height=medalDisplaySize.y;const x=overlayCanvas.width-width;const y=-height*hidePercent;context.save();context.beginPath();context.fillStyle=new Color(.9,.9,.9).toString();context.strokeStyle=new Color(0,0,0).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,new Color(0,0,0),0,undefined,"left",undefined,textWidth);pos.y=y+height-gap.y*2-descriptionSize/2;drawTextScreen(this.description,pos,descriptionSize,new Color(0,0,0),0,undefined,"left",undefined,textWidth);context.restore()}renderIcon(pos,size){if(this.image)overlayContext.drawImage(this.image,pos.x-size/2,pos.y-size/2,size,size);else drawTextScreen(this.icon,pos,size*.7,new Color(0,0,0))}storageKey(){return medalsSaveName+"_"+this.id}}let glCanvas;let glContext;let glAntialias=true;let glShader,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glInstanceCount,glAdditive,glBatchAdditive;const gl_MAX_INSTANCES=1e4;const gl_INDICES_PER_INSTANCE=11;const gl_INSTANCE_BYTE_STRIDE=gl_INDICES_PER_INSTANCE*4;const gl_INSTANCE_BUFFER_SIZE=gl_MAX_INSTANCES*gl_INSTANCE_BYTE_STRIDE;function glInit(){if(!glEnable||headlessMode)return;glCanvas=document.createElement("canvas");glContext=glCanvas.getContext("webgl2",{antialias:glAntialias});const rootElement=mainCanvas.parentElement;glOverlay&&rootElement.appendChild(glCanvas);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;"+"}");const glInstanceData=new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);glPositionData=new Float32Array(glInstanceData);glColorData=new Uint32Array(glInstanceData);glArrayBuffer=glContext.createBuffer();glGeometryBuffer=glContext.createBuffer();const geometry=new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,geometry,glContext.STATIC_DRAW)}function glPreRender(){if(!glEnable||headlessMode)return;glClearCanvas();glContext.useProgram(glShader);glContext.activeTexture(glContext.TEXTURE0);if(textureInfos[0])glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture=textureInfos[0].glTexture);let offset=glAdditive=glBatchAdditive=0;const initVertexAttribArray=(name,type,typeSize,size)=>{const location=glContext.getAttribLocation(glShader,name);const stride=typeSize&&gl_INSTANCE_BYTE_STRIDE;const divisor=typeSize&&1;const normalize=typeSize==1;glContext.enableVertexAttribArray(location);glContext.vertexAttribPointer(location,size,type,normalize,stride,offset);glContext.vertexAttribDivisor(location,divisor);offset+=size*typeSize};glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);initVertexAttribArray("g",glContext.FLOAT,0,2);glContext.bindBuffer(glContext.ARRAY_BUFFER,glArrayBuffer);glContext.bufferData(glContext.ARRAY_BUFFER,gl_INSTANCE_BUFFER_SIZE,glContext.DYNAMIC_DRAW);initVertexAttribArray("p",glContext.FLOAT,4,4);initVertexAttribArray("u",glContext.FLOAT,4,4);initVertexAttribArray("c",glContext.UNSIGNED_BYTE,1,4);initVertexAttribArray("a",glContext.UNSIGNED_BYTE,1,4);initVertexAttribArray("r",glContext.FLOAT,4,1);const s=vec2(2*cameraScale).divide(mainCanvasSize);const p=vec2(-1).subtract(cameraPos.multiply(s));glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),false,[s.x,0,0,0,0,s.y,0,0,1,1,1,1,p.x,p.y,0,0])}function glClearCanvas(){glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height);glContext.clear(glContext.COLOR_BUFFER_BIT)}function glSetTexture(texture){if(headlessMode||texture==glActiveTexture)return;glFlush();glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture=texture)}function glCompileShader(source,type){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){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){const texture=glContext.createTexture();glContext.bindTexture(glContext.TEXTURE_2D,texture);if(image&&image.width){glSetTextureData(texture,image);const isPowerOfTwo=value=>!(value&value-1);if(!tilesPixelated&&isPowerOfTwo(image.width)&&isPowerOfTwo(image.height)){glContext.generateMipmap(glContext.TEXTURE_2D);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,glContext.LINEAR_MIPMAP_LINEAR);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,glContext.LINEAR);return texture}}else{const whitePixel=new Uint8Array([255,255,255,255]);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,1,1,0,glContext.RGBA,glContext.UNSIGNED_BYTE,whitePixel)}const filter=tilesPixelated?glContext.NEAREST:glContext.LINEAR;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MIN_FILTER,filter);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_MAG_FILTER,filter);return texture}function glSetTextureData(texture,image){ASSERT(!!image&&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)}function glFlush(){if(!glInstanceCount)return;const destBlend=glBatchAdditive?glContext.ONE:glContext.ONE_MINUS_SRC_ALPHA;glContext.blendFuncSeparate(glContext.SRC_ALPHA,destBlend,glContext.ONE,destBlend);glContext.enable(glContext.BLEND);glContext.bufferSubData(glContext.ARRAY_BUFFER,0,glPositionData);glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP,0,4,glInstanceCount);if(showWatermark)drawCount+=glInstanceCount;glInstanceCount=0;glBatchAdditive=glAdditive}function glCopyToContext(context,forceDraw=false){if(!glEnable||!glInstanceCount&&!forceDraw)return;glFlush();if(!glOverlay||forceDraw)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,uv0X,uv0Y,uv1X,uv1Y,rgba=-1,rgbaAdditive=0){ASSERT(typeof rgba=="number"&&typeof rgbaAdditive=="number","invalid color");if(glInstanceCount>=gl_MAX_INSTANCES||glBatchAdditive!=glAdditive)glFlush();let offset=glInstanceCount++*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}const engineName="LittleJS";const engineVersion="1.12.6";const frameRate=60;const timeDelta=1/frameRate;let engineObjects=[];let engineObjectsCollide=[];let frame=0;let time=0;let timeReal=0;let paused=false;function setPaused(isPaused){paused=isPaused}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0;const pluginUpdateList=[],pluginRenderList=[];function engineAddPlugin(updateFunction,renderFunction){ASSERT(!pluginUpdateList.includes(updateFunction));ASSERT(!pluginRenderList.includes(renderFunction));updateFunction&&pluginUpdateList.push(updateFunction);renderFunction&&pluginRenderList.push(renderFunction)}async function engineInit(gameInit,gameUpdate,gameUpdatePost,gameRender,gameRenderPost,imageSources=[],rootElement=document.body){ASSERT(!mainContext,"engine already initialized");ASSERT(Array.isArray(imageSources),"pass in images as array");gameInit||=()=>{};gameUpdate||=()=>{};gameUpdatePost||=()=>{};gameRender||=()=>{};gameRenderPost||=()=>{};function enginePreRender(){mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height);overlayContext.imageSmoothingEnabled=mainContext.imageSmoothingEnabled=!tilesPixelated;glPreRender()}function engineUpdate(frameTimeMS=0){let frameTimeDeltaMS=frameTimeMS-frameTimeLastMS;frameTimeLastMS=frameTimeMS;if(debug||showWatermark)averageFPS=lerp(.05,averageFPS,1e3/(frameTimeDeltaMS||1));const debugSpeedUp=debug&&keyIsDown("Equal");const debugSpeedDown=debug&&keyIsDown("Minus");if(debug)frameTimeDeltaMS*=debugSpeedUp?10:debugSpeedDown?.1:1;timeReal+=frameTimeDeltaMS/1e3;frameTimeBufferMS+=paused?0:frameTimeDeltaMS;if(!debugSpeedUp)frameTimeBufferMS=min(frameTimeBufferMS,50);if(debug&&debugVideoCaptureIsActive())frameTimeBufferMS=0;updateCanvas();if(paused){for(const o of engineObjects)o.parent||o.updateTransforms();inputUpdate();pluginUpdateList.forEach(f=>f());debugUpdate();gameUpdatePost();inputUpdatePost()}else{let deltaSmooth=0;if(frameTimeBufferMS<0&&frameTimeBufferMS>-9){deltaSmooth=frameTimeBufferMS;frameTimeBufferMS=0}for(;frameTimeBufferMS>=0;frameTimeBufferMS-=1e3/frameRate){time=frame++/frameRate;inputUpdate();gameUpdate();pluginUpdateList.forEach(f=>f());engineObjectsUpdate();debugUpdate();gameUpdatePost();inputUpdatePost()}frameTimeBufferMS+=deltaSmooth}if(!headlessMode){enginePreRender();gameRender();engineObjects.sort((a,b)=>a.renderOrder-b.renderOrder);for(const o of engineObjects)o.destroyed||o.render();gameRenderPost();pluginRenderList.forEach(f=>f());touchGamepadRender();debugRender();glCopyToContext(mainContext);if(showWatermark){overlayContext.textAlign="right";overlayContext.textBaseline="top";overlayContext.font="1em monospace";overlayContext.fillStyle="#000";const text=engineName+" "+"v"+engineVersion+" / "+drawCount+" / "+engineObjects.length+" / "+averageFPS.toFixed(1)+(glEnable?" GL":" 2D");overlayContext.fillText(text,mainCanvas.width-3,3);overlayContext.fillStyle="#fff";overlayContext.fillText(text,mainCanvas.width-2,2);drawCount=0}}debugVideoCaptureUpdate();requestAnimationFrame(engineUpdate)}function updateCanvas(){if(headlessMode)return;if(canvasFixedSize.x){mainCanvas.width=canvasFixedSize.x;mainCanvas.height=canvasFixedSize.y;const aspect=innerWidth/innerHeight;const fixedAspect=mainCanvas.width/mainCanvas.height;(glCanvas||mainCanvas).style.width=mainCanvas.style.width=overlayCanvas.style.width=aspect<fixedAspect?"100%":"";(glCanvas||mainCanvas).style.height=mainCanvas.style.height=overlayCanvas.style.height=aspect<fixedAspect?"":"100%"}else{mainCanvas.width=min(innerWidth,canvasMaxSize.x);mainCanvas.height=min(innerHeight,canvasMaxSize.y)}overlayCanvas.width=mainCanvas.width;overlayCanvas.height=mainCanvas.height;mainCanvasSize=vec2(mainCanvas.width,mainCanvas.height)}async function startEngine(){await gameInit();engineUpdate()}if(headlessMode)return startEngine();const styleRoot="margin:0;"+"background:#000;"+(canvasPixelated?"image-rendering:pixelated;":"")+"user-select:none;"+"-webkit-user-select:none;"+(!touchInputEnable?"":"touch-action:none;"+"-webkit-touch-callout:none");rootElement.style.cssText=styleRoot;rootElement.appendChild(mainCanvas=document.createElement("canvas"));mainContext=mainCanvas.getContext("2d");inputInit();audioInit();debugInit();glInit();rootElement.appendChild(overlayCanvas=document.createElement("canvas"));overlayContext=overlayCanvas.getContext("2d");const styleCanvas="position:absolute;"+"top:50%;left:50%;transform:translate(-50%,-50%)";mainCanvas.style.cssText=overlayCanvas.style.cssText=styleCanvas;if(glCanvas)glCanvas.style.cssText=styleCanvas;updateCanvas();const promises=imageSources.map((src,textureIndex)=>new Promise(resolve=>{const image=new Image;image.onerror=image.onload=()=>{textureInfos[textureIndex]=new TextureInfo(image);resolve()};image.crossOrigin="anonymous";image.src=src}));if(!imageSources.length){promises.push(new Promise(resolve=>{textureInfos[0]=new TextureInfo(new Image);resolve()}))}if(showSplashScreen){promises.push(new Promise(resolve=>{let t=0;console.log(`${engineName} Engine v${engineVersion}`);updateSplash();function updateSplash(){clearInput();drawEngineSplashScreen(t+=.01);t>1?resolve():setTimeout(updateSplash,16)}}))}await Promise.all(promises);return startEngine()}function engineObjectsUpdate(){engineObjectsCollide=engineObjects.filter(o=>o.collideSolidObjects);function updateObject(o){if(!o.destroyed){o.update();for(const child of o.children)updateObject(child)}}for(const o of engineObjects){if(!o.parent){updateObject(o);o.updateTransforms()}}engineObjects=engineObjects.filter(o=>!o.destroyed)}function engineObjectsDestroy(){for(const o of engineObjects)o.parent||o.destroy();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)isOverlapping(pos,size,o.pos,o.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}function drawEngineSplashScreen(t){const x=overlayContext;const w=overlayCanvas.width=innerWidth;const h=overlayCanvas.height=innerHeight;{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,Math.hypot(w,h)*.7);g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());g.addColorStop(1,hsl(0,0,0,p3).toString());x.save();x.fillStyle=g;x.fillRect(0,0,w,h)}const rect=(X,Y,W,H,C)=>{x.beginPath();x.rect(X,Y,W,C?H*p:H);x.fillStyle=C;C?x.fill():x.stroke()};const line=(X,Y,Z,W)=>{x.beginPath();x.lineTo(X,Y);x.lineTo(Z,W);x.stroke()};const circle=(X,Y,R,A=0,B=2*PI,C,F)=>{const D=(A+B)/2,E=p*(B-A)/2;x.beginPath();F&&x.lineTo(X,Y);x.arc(X,Y,R,D-E,D+E);x.fillStyle=C;C?x.fill():x.stroke()};const color=(c=0,l=0)=>hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();const alpha=wave(1,1,t);const p=percent(alpha,.1,.5);x.translate(w/2,h/2);const size=min(6,min(w,h)/99);x.scale(size,size);x.translate(-40,-35);x.lineJoin=x.lineCap="round";x.lineWidth=.1+p*1.9;const p2=percent(alpha,.1,1);x.setLineDash([99*p2,99]);rect(7,16,18,-8,color(2,2));rect(7,8,18,4,color(2,3));rect(25,8,8,8,color(2,1));rect(25,8,-18,8);rect(25,8,8,8);rect(25,16,7,23,color());rect(11,39,14,-23,color(1,1));rect(11,16,14,18,color(1,2));rect(11,16,14,8,color(1,3));rect(25,16,-14,24);rect(15,29,6,-9,color(2,2));circle(15,21,5,0,PI/2,color(2,4),1);rect(21,21,-6,9);rect(37,14,9,6,color(3,2));rect(37,14,4.5,6,color(3,3));rect(37,14,9,6);rect(50,20,10,-8,color(0,1));rect(50,20,6.5,-8,color(0,2));rect(50,20,3.5,-8,color(0,3));rect(50,20,10,-8);circle(55,2,11.4,.5,PI-.5,color(3,3));circle(55,2,11.4,.5,PI/2,color(3,2),1);circle(55,2,11.4,.5,PI-.5);rect(45,7,20,-7,color(0,2));rect(45,-1,20,4,color(0,3));rect(45,-1,20,8);for(let i=5;i--;){circle(60-i*6,30,9.9,0,2*PI,color(i+2,3));circle(60-i*6,30,10,-.5,PI+.5,color(i+2,2));circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1))}circle(36,30,10,PI/2,PI*3/2);circle(48,30,10,PI/2,PI*3/2);circle(60,30,10);line(36,20,60,20);circle(60,30,4,PI,3*PI,color(3,2));circle(60,30,4,PI,2*PI,color(3,3));circle(60,30,4,PI,3*PI);for(let i=6;i--;){x.beginPath();x.lineTo(53,54);x.lineTo(53,40);x.lineTo(53+(1+i*2.9)*p,40);x.lineTo(53+(4+i*3.5)*p,54);x.fillStyle=color(0,i%2+2);x.fill();i%2&&x.stroke()}rect(6,40,5,5);rect(6,40,5,5,color());rect(15,54,38,-14,color());for(let i=3;i--;)for(let j=2;j--;){circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));x.stroke();circle(15*i+15,47,j?7:1,0,PI,color(i,2));x.stroke()}line(6,40,68,40);line(77,54,4,54);const s=engineName;x.font="900 16px arial";x.textAlign="center";x.textBaseline="top";x.lineWidth=.1+p*3.9;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=41-w2/2;i<s.length;++i){x.fillStyle=color(i,2);const w=x.measureText(s[i]).width;x[j?"strokeText":"fillText"](s[i],X+w/2,55.5,17*p);X+=w}x.restore()}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");this.medals=medalsResult?medalsResult.result.data["medals"]:[];debugMedals&&console.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?scoreboardResult.result.data.scoreboards:[];debugMedals&&console.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&&console.log("newgrounds call failed",e);return}debugMedals&&console.log(xmlHttp.responseText);return xmlHttp.responseText&&JSON.parse(xmlHttp.responseText)}}let postProcess;class PostProcessPlugin{constructor(shaderCode,includeOverlay=false){ASSERT(!postProcess,"Post process already initialized");postProcess=this;if(headlessMode)return;if(!shaderCode)shaderCode="void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}";this.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.;"+"}");this.texture=glCreateTexture();this.includeOverlay=includeOverlay;engineAddPlugin(undefined,postProcessRender);function postProcessRender(){if(headlessMode)return;if(glEnable){glFlush();mainContext.drawImage(glCanvas,0,0)}else{glContext.viewport(0,0,glCanvas.width=mainCanvas.width,glCanvas.height=mainCanvas.height)}if(postProcess.includeOverlay){mainContext.drawImage(overlayCanvas,0,0);overlayCanvas.width|=0}glContext.useProgram(postProcess.shader);glContext.bindBuffer(glContext.ARRAY_BUFFER,glGeometryBuffer);glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL,1);glContext.disable(glContext.BLEND);glContext.activeTexture(glContext.TEXTURE0);glContext.bindTexture(glContext.TEXTURE_2D,postProcess.texture);glContext.texImage2D(glContext.TEXTURE_2D,0,glContext.RGBA,glContext.RGBA,glContext.UNSIGNED_BYTE,mainCanvas);const vertexByteStride=8;const pLocation=glContext.getAttribLocation(postProcess.shader,"p");glContext.enableVertexAttribArray(pLocation);glContext.vertexAttribPointer(pLocation,2,glContext.FLOAT,false,vertexByteStride,0);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)}}}class ZzFXMusic extends Sound{constructor(zzfxMusic){super(undefined);if(!soundEnable||headlessMode)return;this.randomness=0;this.sampleChannels=zzfxM(...zzfxMusic);this.sampleRate=zzfxR}playMusic(volume,loop=false){return super.play(undefined,volume,1,1,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=zzfxR/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;class UISystemPlugin{constructor(context=overlayContext){ASSERT(!uiSystem,"UI system already initialized");uiSystem=this;this.defaultColor=WHITE;this.defaultLineColor=BLACK;this.defaultTextColor=BLACK;this.defaultButtonColor=hsl(0,0,.5);this.defaultHoverColor=hsl(0,0,.7);this.defaultLineWidth=4;this.defaultFont="arial";this.uiObjects=[];this.uiContext=context;engineAddPlugin(uiUpdate,uiRender);function uiUpdate(){function updateObject(o){if(!o.visible)return;if(o.parent)o.pos=o.localPos.add(o.parent.pos);o.update();for(const c of o.children)updateObject(c)}uiSystem.uiObjects.forEach(o=>o.parent||updateObject(o))}function uiRender(){function renderObject(o){if(!o.visible)return;if(o.parent)o.pos=o.localPos.add(o.parent.pos);o.render();for(const c of o.children)renderObject(c)}uiSystem.uiObjects.forEach(o=>o.parent||renderObject(o))}}drawRect(pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){uiSystem.uiContext.fillStyle=color.toString();uiSystem.uiContext.beginPath();uiSystem.uiContext.rect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y);uiSystem.uiContext.fill();if(lineWidth){uiSystem.uiContext.strokeStyle=lineColor.toString();uiSystem.uiContext.lineWidth=lineWidth;uiSystem.uiContext.stroke()}}drawLine(posA,posB,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){uiSystem.uiContext.strokeStyle=lineColor.toString();uiSystem.uiContext.lineWidth=lineWidth;uiSystem.uiContext.beginPath();uiSystem.uiContext.lineTo(posA.x,posA.y);uiSystem.uiContext.lineTo(posB.x,posB.y);uiSystem.uiContext.stroke()}drawTile(pos,size,tileInfo,color=uiSystem.defaultColor,angle=0,mirror=false){drawTile(pos,size,tileInfo,color,angle,mirror,BLACK,false,true,uiSystem.uiContext)}drawText(text,pos,size,color=uiSystem.defaultColor,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor,align="center",font=uiSystem.defaultFont){drawTextScreen(text,pos,size.y,color,lineWidth,lineColor,align,font,size.x,uiSystem.uiContext)}}class UIObject{constructor(pos=vec2(),size=vec2()){this.localPos=pos.copy();this.pos=pos.copy();this.size=size.copy();this.color=uiSystem.defaultColor;this.lineColor=uiSystem.defaultLineColor;this.textColor=uiSystem.defaultTextColor;this.hoverColor=uiSystem.defaultHoverColor;this.lineWidth=uiSystem.defaultLineWidth;this.font=uiSystem.defaultFont;this.textHeight=undefined;this.visible=true;this.children=[];this.parent=undefined;uiSystem.uiObjects.push(this)}addChild(child){ASSERT(!child.parent&&!this.children.includes(child));this.children.push(child);child.parent=this}removeChild(child){ASSERT(child.parent==this&&this.children.includes(child));this.children.splice(this.children.indexOf(child),1);child.parent=undefined}update(){const mouseWasOver=this.mouseIsOver;const mouseDown=mouseIsDown(0);if(!mouseDown||isTouchDevice){this.mouseIsOver=isOverlapping(this.pos,this.size,mousePosScreen);if(!mouseDown&&isTouchDevice)this.mouseIsOver=false;if(this.mouseIsOver&&!mouseWasOver)this.onEnter();if(!this.mouseIsOver&&mouseWasOver)this.onLeave()}if(mouseWasPressed(0)&&this.mouseIsOver){this.mouseIsHeld=true;this.onPress();if(isTouchDevice)this.mouseIsOver=false}else if(this.mouseIsHeld&&!mouseDown){this.mouseIsHeld=false;this.onRelease()}}render(){if(this.size.x&&this.size.y)uiSystem.drawRect(this.pos,this.size,this.color,this.lineWidth,this.lineColor)}onEnter(){}onLeave(){}onPress(){}onRelease(){}onChange(){}}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.lineWidth=0}render(){const textSize=vec2(this.size.x,this.textHeight||this.size.y);uiSystem.drawText(this.text,this.pos,textSize,this.textColor,this.lineWidth,this.lineColor,this.align,this.font)}}class UITile extends UIObject{constructor(pos,size,tileInfo,color=WHITE,angle=0,mirror=false){super(pos,size);this.tileInfo=tileInfo;this.angle=angle;this.mirror=mirror;this.color=color}render(){uiSystem.drawTile(this.pos,this.size,this.tileInfo,this.color,this.angle,this.mirror)}}class UIButton extends UIObject{constructor(pos,size,text="",color=uiSystem.defaultButtonColor){super(pos,size);this.text=text;this.color=color}render(){const lineColor=this.mouseIsHeld?this.color:this.lineColor;const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,lineColor);const textScale=.8;const textSize=vec2(this.size.x,this.textHeight||this.size.y*textScale);uiSystem.drawText(this.text,this.pos,textSize,this.textColor,0,undefined,this.align,this.font)}}class UICheckbox extends UIObject{constructor(pos,size,checked=false){super(pos,size);this.checked=checked}onPress(){this.checked=!this.checked;this.onChange()}render(){const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,this.lineColor);if(this.checked){uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,-.5))),this.pos.add(this.size.multiply(vec2(.5,.5))),this.lineWidth,this.lineColor);uiSystem.drawLine(this.pos.add(this.size.multiply(vec2(-.5,.5))),this.pos.add(this.size.multiply(vec2(.5,-.5))),this.lineWidth,this.lineColor)}}}class UIScrollbar extends UIObject{constructor(pos,size,value=.5,text="",color=uiSystem.defaultButtonColor,handleColor=WHITE){super(pos,size);this.value=value;this.text=text;this.color=color;this.handleColor=handleColor}update(){super.update();if(this.mouseIsHeld){const handleSize=vec2(this.size.y);const handleWidth=this.size.x-handleSize.x;const p1=this.pos.x-handleWidth/2;const p2=this.pos.x+handleWidth/2;const oldValue=this.value;this.value=percent(mousePosScreen.x,p1,p2);this.value==oldValue||this.onChange()}}render(){const lineColor=this.mouseIsHeld?this.color:this.lineColor;const color=this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,lineColor);const handleSize=vec2(this.size.y);const handleWidth=this.size.x-handleSize.x;const p1=this.pos.x-handleWidth/2;const p2=this.pos.x+handleWidth/2;const handlePos=vec2(lerp(this.value,p1,p2),this.pos.y);const barColor=this.mouseIsHeld?this.color:this.handleColor;uiSystem.drawRect(handlePos,handleSize,barColor,this.lineWidth,this.lineColor);const textScale=.8;const textSize=vec2(this.size.x,this.textHeight||this.size.y*textScale);uiSystem.drawText(this.text,this.pos,textSize,this.textColor,0,undefined,this.align,this.font)}}let box2d;let box2dDebug=false;function box2dSetDebug(enable){box2dDebug=enable}class Box2dObject extends EngineObject{constructor(pos=vec2(),size,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.body.object=this;this.outlineColor=BLACK}destroy(){this.body&&box2d.world.DestroyBody(this.body);this.body=0;super.destroy()}update(){this.pos=box2d.vec2From(this.body.GetPosition());this.angle=-this.body.GetAngle()}render(){if(this.tileInfo)super.render();else this.drawFixtures(this.color,this.outlineColor,this.lineWidth,mainContext)}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,outlineColor,lineWidth=.1,context){this.getFixtureList().forEach(fixture=>box2d.drawFixture(fixture,this.pos,this.angle,color,outlineColor,lineWidth,context))}beginContact(otherObject){}endContact(otherObject){}addShape(shape,density=1,friction=1,restitution=0,isSensor=false){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){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){function box2dCreatePolygonShape(points){function box2dCreatePointList(points){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}return box2d.instance.wrapPointer(buffer,box2d.instance.b2Vec2)}ASSERT(3<=points.length&&points.length<=8);const shape=new box2d.instance.b2PolygonShape;const box2dPoints=box2dCreatePointList(points);shape.Set(box2dPoints,points.length);return shape}const shape=box2dCreatePolygonShape(points);return this.addShape(shape,density,friction,restitution,isSensor)}addRegularPoly(diameter=1,sides=8,density,friction,restitution,isSensor){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){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){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){const shape=new box2d.instance.b2EdgeShape;shape.Set(box2d.vec2dTo(point1),box2d.vec2dTo(point2));return this.addShape(shape,density,friction,restitution,isSensor)}addEdgeLoop(points,density,friction,restitution,isSensor){const fixtures=[];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)}return fixtures}addEdgeList(points,density,friction,restitution,isSensor){const fixtures=[];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)}return fixtures}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()}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);localCenter&&data.set_center(box2d.vec2dTo(localCenter));mass&&data.set_mass(mass);momentOfInertia&&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)})}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();this.body.ApplyLinearImpulse(box2d.vec2dTo(acceleration),box2d.vec2dTo(pos))}applyTorque(torque){this.setAwake();this.body.ApplyTorque(torque)}applyAngularAcceleration(acceleration){this.setAwake();this.body.ApplyAngularImpulse(acceleration)}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 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.castObjectType(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(objectA.body.GetAngle()-objectB.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=objectB.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(objectA.body.GetAngle()-objectB.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=objectB.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(objectA.body.GetAngle()-objectB.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.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;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;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,outlineColor=BLACK,lineWidth=.1,context=mainContext){const shape=box2d.castObjectType(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)));box2d.drawPoly(pos,angle,points,color,outlineColor,lineWidth,context);break}case box2d.instance.b2Shape.e_circle:{const radius=shape.get_m_radius();box2d.drawCircle(pos,radius,color,outlineColor,lineWidth,context);break}case box2d.instance.b2Shape.e_edge:{const v1=box2d.vec2From(shape.get_m_vertex1());const v2=box2d.vec2From(shape.get_m_vertex2());box2d.drawLine(pos,angle,v1,v2,color,lineWidth,context);break}}}drawCircle(pos,radius,color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),0,0,context=>{context.beginPath();context.arc(0,0,radius,0,9);box2d.drawFillStroke(color,outlineColor,lineWidth,context)},0,context)}drawPoly(pos,angle,points,color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),angle,0,context=>{context.beginPath();points.forEach(p=>context.lineTo(p.x,p.y));context.closePath();box2d.drawFillStroke(color,outlineColor,lineWidth,context)},0,context)}drawLine(pos,angle,posA,posB,color=WHITE,lineWidth=.1,context=mainContext){drawCanvas2D(pos,vec2(1),angle,0,context=>{context.beginPath();context.lineTo(posA.x,posA.y);context.lineTo(posB.x,posB.y);box2d.drawFillStroke(0,color,lineWidth,context)},0,context)}drawFillStroke(color=WHITE,outlineColor=BLACK,lineWidth=.1,context=mainContext){if(color){context.fillStyle=color.toString();context.fill()}if(outlineColor&&lineWidth){context.lineWidth=lineWidth;context.lineJoin=context.lineCap="round";context.strokeStyle=outlineColor.toString();context.stroke()}}vec2From(v){ASSERT(v instanceof box2d.instance.b2Vec2);return new Vector2(v.get_x(),v.get_y())}vec2FromPointer(v){return box2d.vec2From(box2d.instance.wrapPointer(v,box2d.instance.b2Vec2))}vec2dTo(v){ASSERT(v instanceof Vector2);return new box2d.instance.b2Vec2(v.x,v.y)}isNull(o){return!box2d.instance.getPointer(o)}castObjectType(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);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)box2d.step()}function box2dRender(){if(box2dDebug||debugPhysics&&debugOverlay)box2d.world.DrawDebugData()}function setupDebugDraw(){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);box2d.drawLine(vec2(),0,point1,point2,color,undefined,overlayContext)};debugDraw.DrawPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);box2d.drawPoly(vec2(),0,points,undefined,color,undefined,overlayContext)};debugDraw.DrawSolidPolygon=function(vertices,vertexCount,color){color=getDebugColor(color);const points=getPointsList(vertices,vertexCount);box2d.drawPoly(vec2(),0,points,color,color,undefined,overlayContext)};debugDraw.DrawCircle=function(center,radius,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);box2d.drawCircle(center,radius,undefined,color,undefined,overlayContext)};debugDraw.DrawSolidCircle=function(center,radius,axis,color){color=getDebugColor(color);center=box2d.vec2FromPointer(center);axis=box2d.vec2FromPointer(axis).scale(radius);box2d.drawCircle(center,radius,color,color,undefined,overlayContext);box2d.drawLine(center,0,vec2(),axis,color,undefined,overlayContext)};debugDraw.DrawTransform=function(transform){transform=box2d.instance.wrapPointer(transform,box2d.instance.b2Transform);const pos=vec2(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);box2d.drawLine(pos,angle,vec2(),p1,c1,undefined,overlayContext);box2d.drawLine(pos,angle,vec2(),p2,c2,undefined,overlayContext)};debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);box2d.world.SetDebugDraw(debugDraw)}}export{engineName,engineVersion,frameRate,timeDelta,engineObjects,frame,time,timeReal,paused,setPaused,engineInit,engineObjectsUpdate,engineObjectsDestroy,engineObjectsCollect,engineObjectsCallback,engineObjectsRaycast,engineAddPlugin,debug,debugOverlay,showWatermark,ASSERT,debugRect,debugPoly,debugCircle,debugPoint,debugLine,debugOverlap,debugText,debugClear,debugScreenshot,debugSaveCanvas,debugSaveText,debugSaveDataURL,debugShowErrors,debugVideoCaptureIsActive,debugVideoCaptureStart,debugVideoCaptureStop,cameraPos,cameraScale,canvasMaxSize,canvasFixedSize,canvasPixelated,tilesPixelated,fontDefault,showSplashScreen,headlessMode,tileSizeDefault,tileFixBleedScale,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultElasticity,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,glEnable,glOverlay,gamepadsEnable,gamepadDirectionEmulateStick,inputWASDEmulateDirection,touchGamepadEnable,touchGamepadAnalog,touchGamepadSize,touchGamepadAlpha,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,setCameraPos,setCameraScale,setCanvasMaxSize,setCanvasFixedSize,setCanvasPixelated,setTilesPixelated,setFontDefault,setShowSplashScreen,setHeadlessMode,setGlEnable,setGlOverlay,setTileSizeDefault,setTileFixBleedScale,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultElasticity,setObjectDefaultFriction,setObjectMaxSpeed,setGravity,setParticleEmitRateScale,setTouchInputEnable,setGamepadsEnable,setGamepadDirectionEmulateStick,setInputWASDEmulateDirection,setTouchGamepadEnable,setTouchGamepadAnalog,setTouchGamepadSize,setTouchGamepadAlpha,setVibrateEnable,setSoundEnable,setSoundVolume,setSoundDefaultRange,setSoundDefaultTaper,setMedalDisplayTime,setMedalDisplaySlideTime,setMedalDisplaySize,setMedalsPreventUnlock,setShowWatermark,setDebugKey,PI,abs,min,max,sign,mod,clamp,percent,distanceWrap,lerpWrap,distanceAngle,lerpAngle,lerp,smoothStep,nearestPowerOfTwo,isOverlapping,isIntersecting,wave,formatTime,fetchJSON,rand,randInt,randSign,randInCircle,randVector,randColor,RandomGenerator,Vector2,Color,Timer,vec2,rgb,hsl,isColor,WHITE,BLACK,GRAY,RED,ORANGE,YELLOW,GREEN,CYAN,BLUE,PURPLE,MAGENTA,textureInfos,tile,TileInfo,TextureInfo,mainCanvas,mainContext,overlayCanvas,overlayContext,mainCanvasSize,screenToWorld,worldToScreen,drawTile,drawRect,drawLine,drawPoly,drawEllipse,drawCircle,drawCanvas2D,drawText,drawTextOverlay,drawTextScreen,setBlendMode,combineCanvases,engineFontImage,FontImage,isFullscreen,toggleFullscreen,setCursor,getCameraSize,glCanvas,glContext,glCompileShader,glCopyToContext,glCreateProgram,glCreateTexture,glSetTextureData,glDraw,glFlush,glSetTexture,glSetAntialias,glClearCanvas,glAntialias,glShader,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glInstanceCount,glAdditive,glBatchAdditive,keyIsDown,keyWasPressed,keyWasReleased,keyDirection,clearInput,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseWheel,isUsingGamepad,inputPreventDefault,setInputPreventDefault,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,gamepadsUpdate,vibrate,vibrateStop,isTouchDevice,Sound,SoundWave,playAudioFile,speak,speakStop,getNoteFrequency,playSamples,zzfx,zzfxG,zzfxR,audioContext,EngineObject,tileCollisionLayers,getTileCollisionData,tileCollisionTest,tileCollisionRaycast,TileLayerData,TileLayer,TileCollisionLayer,ParticleEmitter,Particle,medals,medalsPreventUnlock,medalsInit,Medal};export{newgrounds,NewgroundsPlugin,NewgroundsMedal,postProcess,PostProcessPlugin,ZzFXMusic,uiSystem,UISystemPlugin,UIObject,UIText,UITile,UIButton,UICheckbox,UIScrollbar,box2d,box2dDebug,box2dSetDebug,box2dInit,Box2dPlugin,Box2dObject,Box2dRaycastResult,Box2dJoint,Box2dTargetJoint,Box2dDistanceJoint,Box2dPinJoint,Box2dRopeJoint,Box2dRevoluteJoint,Box2dGearJoint,Box2dPrismaticJoint,Box2dWheelJoint,Box2dWeldJoint,Box2dFrictionJoint,Box2dPulleyJoint,Box2dMotorJoint};
|