littlejsengine 1.12.4 → 1.13.1

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.
Files changed (88) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +437 -219
  4. package/dist/littlejs.esm.js +5950 -5307
  5. package/dist/littlejs.esm.min.js +4 -1
  6. package/dist/littlejs.js +5921 -5295
  7. package/dist/littlejs.min.js +4 -1
  8. package/dist/littlejs.release.js +5485 -4886
  9. package/examples/box2d/game.js +37 -42
  10. package/examples/box2d/gameObjects.js +41 -37
  11. package/examples/box2d/index.html +2 -2
  12. package/examples/box2d/scenes.js +24 -20
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +1 -1
  15. package/examples/breakout/gameObjects.js +2 -2
  16. package/examples/breakout/index.html +1 -1
  17. package/examples/breakoutTutorial/README.md +2 -2
  18. package/examples/breakoutTutorial/game.js +1 -1
  19. package/examples/breakoutTutorial/index.html +1 -1
  20. package/examples/empty/index.html +1 -1
  21. package/examples/htmlMenu/index.html +1 -1
  22. package/examples/index.html +410 -159
  23. package/examples/module/game.js +1 -1
  24. package/examples/module/index.html +1 -1
  25. package/examples/platformer/game.js +6 -15
  26. package/examples/platformer/gameCharacter.js +6 -6
  27. package/examples/platformer/gameEffects.js +74 -57
  28. package/examples/platformer/gameLevel.js +61 -70
  29. package/examples/platformer/gameObjects.js +4 -4
  30. package/examples/platformer/index.html +1 -1
  31. package/examples/puzzle/index.html +1 -1
  32. package/examples/shorts/base.html +24 -3
  33. package/examples/shorts/box2d.js +46 -0
  34. package/examples/shorts/box2dCar.js +45 -0
  35. package/examples/shorts/flappyGame.js +52 -0
  36. package/examples/shorts/helloWorld.js +6 -3
  37. package/examples/shorts/hillGlideGame.js +56 -0
  38. package/examples/shorts/landerGame.js +32 -0
  39. package/examples/shorts/maze.js +47 -0
  40. package/examples/shorts/medals.js +42 -0
  41. package/examples/shorts/nineSlice.js +21 -0
  42. package/examples/shorts/piano.js +52 -0
  43. package/examples/shorts/platformer.js +24 -20
  44. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  45. package/examples/shorts/postProcess.js +45 -0
  46. package/examples/shorts/raycasting.js +71 -0
  47. package/examples/shorts/slidingPuzzle.js +42 -0
  48. package/examples/shorts/spaceGame.js +56 -0
  49. package/examples/shorts/starfield.js +17 -0
  50. package/examples/shorts/tileLayer.js +3 -5
  51. package/examples/shorts/tiles.png +0 -0
  52. package/examples/shorts/tiltedView.js +8 -7
  53. package/examples/shorts/topDown.js +18 -26
  54. package/examples/shorts/uiSystem.js +36 -0
  55. package/examples/starter/build.bat +6 -1
  56. package/examples/starter/game.js +4 -2
  57. package/examples/starter/index.html +23 -13
  58. package/examples/stress/index.html +2 -3
  59. package/examples/style.css +136 -0
  60. package/examples/typescript/build.js +1 -2
  61. package/examples/typescript/game.js +2 -2
  62. package/examples/typescript/game.ts +1 -1
  63. package/examples/typescript/index.html +1 -1
  64. package/examples/uiSystem/game.js +9 -25
  65. package/examples/uiSystem/index.html +1 -1
  66. package/package.json +1 -1
  67. package/plugins/box2d.js +29 -35
  68. package/plugins/drawUtilities.js +126 -0
  69. package/plugins/newgrounds.js +1 -1
  70. package/plugins/pluginExport.js +8 -2
  71. package/plugins/postProcess.js +2 -2
  72. package/plugins/uiSystem.js +162 -68
  73. package/plugins/zzfxm.js +1 -1
  74. package/reference.md +10 -10
  75. package/src/engine.js +59 -39
  76. package/src/engineAudio.js +38 -20
  77. package/src/engineBuild.bat +6 -1
  78. package/src/engineBuild.js +32 -7
  79. package/src/engineDebug.js +53 -26
  80. package/src/engineDraw.js +108 -57
  81. package/src/engineExport.js +22 -9
  82. package/src/engineInput.js +112 -40
  83. package/src/engineObject.js +68 -67
  84. package/src/engineParticles.js +26 -9
  85. package/src/engineSettings.js +15 -16
  86. package/src/engineTileLayer.js +312 -153
  87. package/src/engineUtilities.js +198 -130
  88. package/src/engineWebGL.js +58 -35
@@ -1 +1,4 @@
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&&notFirstBeat;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
+ // LittleJS Engine - MIT License - Copyright 2021 Frank Force
2
+ // https://github.com/KilledByAPixel/LittleJS
3
+
4
+ "use strict";const engineName="LittleJS";const engineVersion="1.13.1";const frameRate=60;const timeDelta=1/frameRate;let engineObjects=[];let engineObjectsCollide=[];let frame=0;let time=0;let timeReal=0;let paused=false;function getPaused(){return paused}function setPaused(isPaused=true){paused=isPaused}let frameTimeLastMS=0,frameTimeBufferMS=0,averageFPS=0;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(averageFPS,1e3/(frameTimeDeltaMS||1),.05);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(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();glFlush();debugVideoCaptureUpdate();if(showWatermark&&!debugVideoCaptureIsActive()){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)}if(debug||showWatermark)drawCount=0}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;"+"overflow:hidden;"+"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;drawCanvas=mainCanvas=document.createElement("canvas");rootElement.appendChild(mainCanvas);drawContext=mainContext=mainCanvas.getContext("2d");inputInit();audioInit();debugInit();glInit();overlayCanvas=document.createElement("canvas");rootElement.appendChild(overlayCanvas);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=()=>{const textureInfo=new TextureInfo(image);textureInfo.createWebGLTexture();textureInfos[textureIndex]=textureInfo;resolve()};image.crossOrigin="anonymous";image.src=src}));if(!imageSources.length){promises.push(new Promise(resolve=>{const textureInfo=new TextureInfo(new Image);textureInfos[0]=textureInfo;textureInfo.createWebGLTexture();resolve()}))}if(showSplashScreen){promises.push(new Promise(resolve=>{let t=0;console.log(`${engineName} Engine v${engineVersion}`);updateSplash();function updateSplash(){inputClear();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(0,p3/2,p4),p3).toString());g.addColorStop(1,hsl(0,0,0,p3).toString());x.save();x.fillStyle=g;x.fillRect(0,0,w,h)}const 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 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(...values){return Math.min(...values)}function max(...values){return Math.max(...values)}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(valueA,valueB,percent){if(valueA>=0&&valueA<=1&&((valueB<0||valueB>1)&&(percent<0||percent>1)))console.warn("lerp() parameter order changed! use lerp(start, end, p)");return valueA+clamp(percent)*(valueB-valueA)}function distanceWrap(valueA,valueB,wrapSize=1){const d=(valueA-valueB)%wrapSize;return d*2%wrapSize-d}function lerpWrap(valueA,valueB,percent,wrapSize=1){if(valueA>=0&&valueA<=1&&((valueB<0||valueB>1)&&(percent<0||percent>1)))console.warn("lerpWrap() parameter order changed! use lerpWrap(start, end, p)");return valueA+clamp(percent)*distanceWrap(valueB,valueA,wrapSize)}function distanceAngle(angleA,angleB){return distanceWrap(angleA,angleB,2*PI)}function lerpAngle(angleA,angleB,percent){return lerpWrap(angleA,angleB,percent,2*PI)}function smoothStep(percent){return percent*percent*(3-2*percent)}function isPowerOfTwo(value){return!(value&value-1)}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 isNumber(n){return typeof n=="number"&&!isNaN(n)}function rand(valueA=1,valueB=0){return valueB+Math.random()*(valueA-valueB)}function randInt(valueA,valueB=0){return Math.floor(rand(valueA,valueB))}function randBool(chance=.5){return rand()<chance}function randSign(){return randInt(2)*2-1}function randVec2(length=1){return(new Vector2).setAngle(rand(2*PI),length)}function randInCircle(radius=1,minRadius=0){return radius>0?randVec2(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=123456789){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))}bool(chance=.5){return this.float()<chance}sign(){return this.float()>.5?1:-1}floatSign(valueA=1,valueB=0){return this.float(valueA,valueB)*this.sign()}angle(){return this.float(-PI,PI)}vec2(valueA=1,valueB=0){return vec2(this.float(valueA,valueB),this.float(valueA,valueB))}}function vec2(x=0,y){return new Vector2(x,y===undefined?x:y)}function isVector2(v){return v instanceof Vector2}function ASSERT_VECTOR2_VALID(v){ASSERT(isVector2(v)&&v.isValid(),"Vector2 is invalid.",v)}function ASSERT_NUMBER_VALID(n){ASSERT(isNumber(n),"Number is invalid.",n)}function ASSERT_VECTOR2_NORMAL(v){ASSERT_VECTOR2_VALID(v);ASSERT(abs(v.lengthSquared()-1)<.01,"Vector2 is not normal.",v)}class Vector2{constructor(x=0,y=0){this.x=x;this.y=y;ASSERT(this.isValid(),"Constructed Vector2 is invalid.",this)}set(x=0,y=0){this.x=x;this.y=y;return this}copy(){return new Vector2(this.x,this.y)}add(v){return new Vector2(this.x+v.x,this.y+v.y)}subtract(v){return new Vector2(this.x-v.x,this.y-v.y)}multiply(v){return new Vector2(this.x*v.x,this.y*v.y)}divide(v){return new Vector2(this.x/v.x,this.y/v.y)}scale(s){return new Vector2(this.x*s,this.y*s)}length(){return this.lengthSquared()**.5}lengthSquared(){return this.x**2+this.y**2}distance(v){return this.distanceSquared(v)**.5}distanceSquared(v){return(this.x-v.x)**2+(this.y-v.y)**2}normalize(length=1){const l=this.length();return l?this.scale(length/l):new Vector2(0,length)}clampLength(length=1){const l=this.length();return l>length?this.scale(length/l):this}dot(v){return this.x*v.x+this.y*v.y}cross(v){return this.x*v.y-this.y*v.x}reflect(normal,restitution=1){return this.subtract(normal.scale((1+restitution)*this.dot(normal)))}angle(){return Math.atan2(this.x,this.y)}setAngle(angle=0,length=1){ASSERT_NUMBER_VALID(angle);ASSERT_NUMBER_VALID(length);this.x=length*Math.sin(angle);this.y=length*Math.cos(angle);return this}rotate(angle){ASSERT_NUMBER_VALID(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){ASSERT_NUMBER_VALID(direction);ASSERT_NUMBER_VALID(length);direction=mod(direction,4);ASSERT(direction==0||direction==1||direction==2||direction==3,"Vector2.setDirection() direction must be an integer between 0 and 3.");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)}abs(){return new Vector2(abs(this.x),abs(this.y))}floor(){return new Vector2(Math.floor(this.x),Math.floor(this.y))}mod(divisor=1){return new Vector2(mod(this.x,divisor),mod(this.y,divisor))}area(){return abs(this.x*this.y)}lerp(v,percent){ASSERT_VECTOR2_VALID(v);ASSERT_NUMBER_VALID(percent);const p=clamp(percent);return new Vector2(v.x*p+this.x*(1-p),v.y*p+this.y*(1-p))}arrayCheck(arraySize){return this.x>=0&&this.y>=0&&this.x<arraySize.x&&this.y<arraySize.y}toString(digits=3){ASSERT_NUMBER_VALID(digits);if(debug){if(this.isValid())return`(${(this.x<0?"":" ")+this.x.toFixed(digits)},${(this.y<0?"":" ")+this.y.toFixed(digits)} )`;else return`(${this.x}, ${this.y})`}}isValid(){return isNumber(this.x)&&isNumber(this.y)}}function rgb(r,g,b,a){return new Color(r,g,b,a)}function hsl(h,s,l,a){return(new Color).setHSLA(h,s,l,a)}function isColor(c){return c instanceof Color}function ASSERT_COLOR_VALID(c){ASSERT(isColor(c)&&c.isValid(),"Color is invalid.",c)}class Color{constructor(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;ASSERT(this.isValid(),"Constructed Color is invalid.",this)}set(r=1,g=1,b=1,a=1){this.r=r;this.g=g;this.b=b;this.a=a;return this}copy(){return new Color(this.r,this.g,this.b,this.a)}add(c){return new Color(this.r+c.r,this.g+c.g,this.b+c.b,this.a+c.a)}subtract(c){return new Color(this.r-c.r,this.g-c.g,this.b-c.b,this.a-c.a)}multiply(c){return new Color(this.r*c.r,this.g*c.g,this.b*c.b,this.a*c.a)}divide(c){return new Color(this.r/c.r,this.g/c.g,this.b/c.b,this.a/c.a)}scale(scale,alphaScale=scale){return new Color(this.r*scale,this.g*scale,this.b*scale,this.a*alphaScale)}clamp(){return new Color(clamp(this.r),clamp(this.g),clamp(this.b),clamp(this.a))}lerp(c,percent){ASSERT_COLOR_VALID(c);ASSERT_NUMBER_VALID(percent);const p=clamp(percent);return new Color(c.r*p+this.r*(1-p),c.g*p+this.g*(1-p),c.b*p+this.b*(1-p),c.a*p+this.a*(1-p))}setHSLA(h=0,s=0,l=1,a=1){h=mod(h,1);s=clamp(s);l=clamp(l);const q=l<.5?l*(1+s):l+s-l*s,p=2*l-q,f=(p,q,t)=>(t=mod(t,1))*6<1?p+(q-p)*6*t:t*2<1?q:t*3<2?p+(q-p)*(4-t*6):p;this.r=f(p,q,h+1/3);this.g=f(p,q,h);this.b=f(p,q,h-1/3);this.a=a;ASSERT_COLOR_VALID(this);return this}HSLA(){const r=clamp(this.r);const g=clamp(this.g);const b=clamp(this.b);const a=clamp(this.a);const maxC=max(r,g,b);const minC=min(r,g,b);const l=(maxC+minC)/2;let h=0,s=0;if(maxC!=minC){let d=maxC-minC;s=l>.5?d/(2-maxC-minC):d/(maxC+minC);if(r==maxC)h=(g-b)/d+(g<b?6:0);else if(g==maxC)h=(b-r)/d+2;else if(b==maxC)h=(r-g)/d+4}return[h/6,s,l,a]}mutate(amount=.05,alphaAmount=0){ASSERT_NUMBER_VALID(amount);ASSERT_NUMBER_VALID(alphaAmount);return new Color(this.r+rand(amount,-amount),this.g+rand(amount,-amount),this.b+rand(amount,-amount),this.a+rand(alphaAmount,-alphaAmount)).clamp()}toString(useAlpha=true){ASSERT(typeof useAlpha=="boolean","Use alpha boolean is invalid.",useAlpha);if(debug&&!this.isValid())return`#000`;const toHex=c=>((c=clamp(c)*255|0)<16?"0":"")+c.toString(16);return"#"+toHex(this.r)+toHex(this.g)+toHex(this.b)+(useAlpha?toHex(this.a):"")}setHex(hex){ASSERT(typeof hex=="string"&&hex[0]=="#","Color hex code must be a string starting with #");ASSERT([4,5,7,9].includes(hex.length),"Invalid hex");if(hex.length<6){const fromHex=c=>clamp(parseInt(hex[c],16)/15);this.r=fromHex(1);this.g=fromHex(2),this.b=fromHex(3);this.a=hex.length==5?fromHex(4):1}else{const fromHex=c=>clamp(parseInt(hex.slice(c,c+2),16)/255);this.r=fromHex(1);this.g=fromHex(3),this.b=fromHex(5);this.a=hex.length==9?fromHex(7):1}ASSERT_COLOR_VALID(this);return this}rgbaInt(){const r=clamp(this.r)*255|0;const g=clamp(this.g)*255<<8;const b=clamp(this.b)*255<<16;const a=clamp(this.a)*255<<24;return r+g+b+a}isValid(){return isNumber(this.r)&&isNumber(this.g)&&isNumber(this.b)&&isNumber(this.a)}}const WHITE=rgb();const CLEAR_WHITE=rgb(1,1,1,0);const BLACK=rgb(0,0,0);const CLEAR_BLACK=rgb(0,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){ASSERT(timeLeft===undefined||isNumber(timeLeft),"Constructed Timer is invalid.",timeLeft);this.time=timeLeft===undefined?undefined:time+timeLeft;this.setTime=timeLeft}set(timeLeft=0){ASSERT(isNumber(timeLeft),"Timer is invalid.",timeLeft);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 cameraAngle=0;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 tileSizeDefault=vec2(16);let tileFixBleedScale=0;let enablePhysicsSolver=true;let objectDefaultMass=1;let objectDefaultDamping=1;let objectDefaultAngleDamping=1;let objectDefaultRestitution=0;let objectDefaultFriction=.8;let objectMaxSpeed=1;let gravity=vec2();let particleEmitRateScale=1;let gamepadsEnable=true;let gamepadDirectionEmulateStick=true;let 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 setCameraAngle(angle){cameraAngle=angle}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 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 setObjectDefaultRestitution(restitution){objectDefaultRestitution=restitution}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)&&pos.isValid(),"object pos should be a vec2");ASSERT(isVector2(size)&&size.isValid(),"object size should be a vec2");ASSERT(!tileInfo||tileInfo instanceof TileInfo,"object tileInfo should be a TileInfo or undefined");ASSERT(typeof angle=="number"&&isFinite(angle),"object angle should be a number");ASSERT(isColor(color)&&color.isValid(),"object color should be a valid rgba color");ASSERT(typeof renderOrder=="number","object renderOrder should be a number");this.pos=pos.copy();this.size=size;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.restitution=objectDefaultRestitution;this.friction=objectDefaultFriction;this.gravityScale=1;this.renderOrder=renderOrder;this.velocity=vec2();this.angleVelocity=0;this.spawnTime=time;this.children=[];this.clampSpeed=true;this.groundObject=undefined;this.parent=undefined;this.localPos=vec2();this.localAngle=0;this.collideTiles=false;this.collideSolidObjects=false;this.isSolid=false;this.collideRaycast=false;engineObjects.push(this)}updateTransforms(){const parent=this.parent;if(parent){const mirror=parent.getMirrorSign();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.clampSpeed){this.velocity.x=clamp(this.velocity.x,-objectMaxSpeed,objectMaxSpeed);this.velocity.y=clamp(this.velocity.y,-objectMaxSpeed,objectMaxSpeed)}const oldPos=this.pos.copy();this.velocity.x*=this.damping;this.velocity.y*=this.damping;if(this.mass){this.velocity.x+=gravity.x*this.gravityScale;this.velocity.y+=gravity.y*this.gravityScale}this.pos.x+=this.velocity.x;this.pos.y+=this.velocity.y;this.angle+=this.angleVelocity*=this.angleDamping;ASSERT(this.angleDamping>=0&&this.angleDamping<=1);ASSERT(this.damping>=0&&this.damping<=1);if(!enablePhysicsSolver||!this.mass)return;const 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?randVec2(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 restitution=max(this.restitution,o.restitution);if(smallStepUp||isBlockedY||!isBlockedX){this.pos.y=o.pos.y+(sizeBoth.y/2+epsilon)*sign(oldPos.y-o.pos.y);if(o.groundObject&&wasMovingDown||!o.mass){if(wasMovingDown)this.groundObject=o;this.velocity.y*=-restitution}else if(o.mass){const inelastic=(this.mass*this.velocity.y+o.mass*o.velocity.y)/(this.mass+o.mass);const elastic0=this.velocity.y*(this.mass-o.mass)/(this.mass+o.mass)+o.velocity.y*2*o.mass/(this.mass+o.mass);const elastic1=o.velocity.y*(o.mass-this.mass)/(this.mass+o.mass)+this.velocity.y*2*this.mass/(this.mass+o.mass);this.velocity.y=lerp(inelastic,elastic0,restitution);o.velocity.y=lerp(inelastic,elastic1,restitution)}}if(!smallStepUp&&isBlockedX){this.pos.x=o.pos.x+(sizeBoth.x/2+epsilon)*sign(oldPos.x-o.pos.x);if(o.mass){const inelastic=(this.mass*this.velocity.x+o.mass*o.velocity.x)/(this.mass+o.mass);const elastic0=this.velocity.x*(this.mass-o.mass)/(this.mass+o.mass)+o.velocity.x*2*o.mass/(this.mass+o.mass);const elastic1=o.velocity.x*(o.mass-this.mass)/(this.mass+o.mass)+this.velocity.x*2*this.mass/(this.mass+o.mass);this.velocity.x=lerp(inelastic,elastic0,restitution);o.velocity.x=lerp(inelastic,elastic1,restitution)}else this.velocity.x*=-restitution}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 restitution=max(this.restitution,hitLayer.restitution);this.velocity.y*=-restitution;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.restitution}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.parent=0;child.destroy()}}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)}applyAngularAcceleration(acceleration){if(this.mass)this.angleVelocity+=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)return;const size=vec2(max(this.size.x,.2),max(this.size.y,.2));const color=rgb(this.collideTiles?1:0,this.collideSolidObjects?1:0,this.isSolid?1:0,.5);drawRect(this.pos,size,color,this.angle);if(this.parent)drawRect(this.pos,size.scale(.8),rgb(1,1,1,.5),this.angle);this.parent&&drawLine(this.pos,this.parent.pos,.1,rgb(1,1,1,.5))}}let mainCanvas;let mainContext;let overlayCanvas;let overlayContext;let drawCanvas;let drawContext;let mainCanvasSize=vec2();let textureInfos=[];let drawCount;function tile(pos=new Vector2,size=tileSizeDefault,textureIndex=0,padding=0){if(headlessMode)return new TileInfo;if(typeof size=="number"){ASSERT(size>0);size=new Vector2(size,size)}const tileInfo=new TileInfo(new Vector2,size,textureIndex,padding);const textureInfo=textureInfos[textureIndex];ASSERT(!!textureInfo,"Texture not loaded");const sizePaddedX=size.x+padding*2;const sizePaddedY=size.y+padding*2;if(typeof pos=="number"){const cols=textureInfo.size.x/sizePaddedX|0;ASSERT(cols>0,"Tile size is too big for texture");const posX=pos%cols,posY=pos/cols|0;tileInfo.pos.set(posX*sizePaddedX+padding,posY*sizePaddedY+padding)}else tileInfo.pos.set(pos.x*sizePaddedX+padding,pos.y*sizePaddedY+padding);return tileInfo}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;this.textureInfo=textureInfos[this.textureIndex]}offset(offset){return new TileInfo(this.pos.add(offset),this.size,this.textureIndex)}frame(frame){ASSERT(typeof frame=="number");return this.offset(new Vector2(frame*(this.size.x+this.padding*2),0))}setFullImage(image,glTexture){this.pos=new Vector2;this.size=new Vector2(image.width,image.height);this.textureInfo=new TextureInfo(image,glTexture);return this}}class TextureInfo{constructor(image,glTexture){this.image=image;this.size=vec2(image.width,image.height);this.sizeInverse=vec2(1/image.width,1/image.height);this.glTexture=glTexture}createWebGLTexture(){ASSERT(!this.glTexture);if(glEnable)this.glTexture=glCreateTexture(this.image)}}function screenToWorld(screenPos){let cameraPosRelativeX=(screenPos.x-mainCanvasSize.x/2+.5)/cameraScale;let cameraPosRelativeY=(screenPos.y-mainCanvasSize.y/2+.5)/-cameraScale;if(cameraAngle){const cos=Math.cos(-cameraAngle),sin=Math.sin(-cameraAngle);const rotatedX=cameraPosRelativeX*cos-cameraPosRelativeY*sin;const rotatedY=cameraPosRelativeX*sin+cameraPosRelativeY*cos;cameraPosRelativeX=rotatedX;cameraPosRelativeY=rotatedY}return new Vector2(cameraPosRelativeX+cameraPos.x,cameraPosRelativeY+cameraPos.y)}function worldToScreen(worldPos){let cameraPosRelativeX=worldPos.x-cameraPos.x;let cameraPosRelativeY=worldPos.y-cameraPos.y;if(cameraAngle){const cos=Math.cos(cameraAngle),sin=Math.sin(cameraAngle);const rotatedX=cameraPosRelativeX*cos-cameraPosRelativeY*sin;const rotatedY=cameraPosRelativeX*sin+cameraPosRelativeY*cos;cameraPosRelativeX=rotatedX;cameraPosRelativeY=rotatedY}return new Vector2(cameraPosRelativeX*cameraScale+mainCanvasSize.x/2-.5,cameraPosRelativeY*-cameraScale+mainCanvasSize.y/2-.5)}function getCameraSize(){return mainCanvasSize.scale(1/cameraScale)}function drawTile(pos,size=new Vector2(1),tileInfo,color=new Color,angle=0,mirror,additiveColor,useWebGL=glEnable,screenSpace,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");ASSERT(isVector2(pos)&&pos.isValid(),"drawTile pos should be a vec2");ASSERT(isVector2(size)&&size.isValid(),"drawTile size should be a vec2");ASSERT(isColor(color)&&(!additiveColor||isColor(additiveColor)),"drawTile color is invalid");ASSERT(isNumber(angle),"drawTile angle should be a number");const textureInfo=tileInfo&&tileInfo.textureInfo;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=new Vector2(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=drawContext){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=drawContext){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=drawContext){drawEllipse(pos,radius,radius,0,color,lineWidth,lineColor,screenSpace,context)}function drawCanvas2D(pos,size,angle=0,mirror=false,drawFunction,screenSpace=false,context=drawContext){if(!screenSpace){pos=worldToScreen(pos);size=size.scale(cameraScale)}context.save();context.translate(pos.x+.5,pos.y+.5);context.rotate(angle-cameraAngle);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=drawContext){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");let posY=pos.y;posY-=(lines.length-1)*size/2;lines.forEach(line=>{lineWidth&&context.strokeText(line,pos.x,posY,maxWidth);context.fillText(line,pos.x,posY,maxWidth);posY+=size})}function setBlendMode(additive=false,useWebGL=glEnable,context){ASSERT(!context||!useWebGL,"context only supported in canvas 2D mode");if(useWebGL)glAdditive=additive;else{if(!context)context=drawContext;context.globalCompositeOperation=additive?"lighter":"source-over"}}function combineCanvases(){glCopyToContext(mainContext);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(key!==undefined,"key is undefined");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(key!==undefined,"key is undefined");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(key!==undefined,"key is undefined");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 inputClear(){inputData=[[]];touchGamepadButtons=[]}function inputClearKey(key,device=0,clearDown=true,clearPressed=true,clearReleased=true){if(!inputData[device])return;inputData[device][key]&=~((clearDown?1:0)|(clearPressed?2:0)|(clearReleased?4:0))}function mouseIsDown(button){return keyIsDown(button)}function mouseWasPressed(button){return keyWasPressed(button)}function mouseWasReleased(button){return keyWasReleased(button)}let mousePos=vec2();let mousePosScreen=vec2();let mouseDelta=vec2();let mouseDeltaScreen=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())inputClear();mousePos=screenToWorld(mousePosScreen);mouseDelta=mouseDeltaScreen.multiply(vec2(1,-1)).rotate(-cameraAngle);gamepadsUpdate()}function inputUpdatePost(){if(headlessMode)return;for(const deviceInputData of inputData)for(const i in deviceInputData)deviceInputData[i]&=1;mouseWheel=0;mouseDelta=vec2();mouseDeltaScreen=vec2()}function inputInit(){if(headlessMode)return;document.addEventListener("keydown",onKeyDown);document.addEventListener("keyup",onKeyUp);document.addEventListener("mousedown",onMouseDown);document.addEventListener("mouseup",onMouseUp);document.addEventListener("mousemove",onMouseMove);document.addEventListener("wheel",onMouseWheel);document.addEventListener("contextmenu",onContextMenu);document.addEventListener("blur",onBlur);if(isTouchDevice&&touchInputEnable)touchInputInit();function onKeyDown(e){if(!e.repeat){isUsingGamepad=false;inputData[0][e.code]=3;if(inputWASDEmulateDirection)inputData[0][remapKey(e.code)]=3}}function onKeyUp(e){inputData[0][e.code]=inputData[0][e.code]&2|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}function onMouseDown(e){if(isTouchDevice&&touchInputEnable)return;if(soundEnable&&!headlessMode&&audioContext&&audioContext.state!="running")audioContext.resume();isUsingGamepad=false;inputData[0][e.button]=3;mousePosScreen=mouseEventToScreen(vec2(e.x,e.y));inputPreventDefault&&e.button&&e.preventDefault()}function onMouseUp(e){if(isTouchDevice&&touchInputEnable)return;inputData[0][e.button]=inputData[0][e.button]&2|4}function onMouseMove(e){mousePosScreen=mouseEventToScreen(vec2(e.x,e.y));mouseDeltaScreen=mouseDeltaScreen.add(vec2(e.movementX,e.movementY))}function onMouseWheel(e){mouseWheel=e.ctrlKey?0:sign(e.deltaY)}function onContextMenu(e){e.preventDefault()}function onBlur(){inputClear()}}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});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 pos=vec2(e.touches[0].clientX,e.touches[0].clientY);const lastMousePosScreen=mousePosScreen;mousePosScreen=mouseEventToScreen(pos);if(wasTouching){mouseDeltaScreen=mouseDeltaScreen.add(mousePosScreen.subtract(lastMousePosScreen));isUsingGamepad=touchGamepadEnable}else 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()}function pointerLockRequest(){if(!isTouchDevice)mainCanvas.requestPointerLock&&mainCanvas.requestPointerLock()}function pointerLockExit(){document.exitPointerLock&&document.exitPointerLock()}function pointerLockIsActive(){return document.pointerLockElement==mainCanvas}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(fadeTime=0){if(!this.source)return;const startFade=audioContext.currentTime;const endFade=startFade+fadeTime;this.gainNode.gain.linearRampToValueAtTime(1,startFade);this.gainNode.gain.linearRampToValueAtTime(0,endFade);this.source.stop(endFade);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 tileCollisionGetData(pos){for(const layer of tileCollisionLayers)if(pos.arrayCheck(layer.size))return layer.getCollisionData(pos);return 0}function tileCollisionTest(pos,size=vec2(),object,solidOnly=true){for(const layer of tileCollisionLayers){if(!solidOnly||layer.isSolid)if(layer.collisionTest(pos,size,object))return layer}}function tileCollisionRaycast(posStart,posEnd,object,solidOnly=true){for(const layer of tileCollisionLayers){if(!solidOnly||layer.isSolid){const hitPos=layer.collisionRaycast(posStart,posEnd,object);if(hitPos)return hitPos}}}function tileCollisionLoad(tileMapData,tileInfo=tile(),renderOrder=0,collisionLayer,draw=true){if(!tileMapData){const s=50;tileMapData={};tileMapData.height=tileMapData.width=s;tileMapData.layers=[{}];tileMapData.layers[0].data=new Array(s*s).fill(0)}ASSERT(tileMapData.width&&tileMapData.height);ASSERT(tileMapData.layers&&tileMapData.layers.length);const tileLayers=[];const levelSize=vec2(tileMapData.width,tileMapData.height);const layerCount=tileMapData.layers.length;for(let layerIndex=layerCount;layerIndex--;){const dataLayer=tileMapData.layers[layerIndex];ASSERT(dataLayer.data&&dataLayer.data.length);ASSERT(levelSize.area()==dataLayer.data.length);const layerRenderOrder=renderOrder-(layerCount-1-layerIndex);const tileLayer=new TileCollisionLayer(vec2(),levelSize,tileInfo,layerRenderOrder);tileLayers[layerIndex]=tileLayer;for(let x=levelSize.x;x--;)for(let y=levelSize.y;y--;){const pos=vec2(x,levelSize.y-1-y);const data=dataLayer.data[x+y*levelSize.x];if(data){const layerData=new TileLayerData(data-1);tileLayer.setData(pos,layerData);if(layerIndex===collisionLayer)tileLayer.setCollisionData(pos,1)}}if(draw)tileLayer.redraw()}return tileLayers}class TileLayerData{constructor(tile,direction=0,mirror=false,color=new Color){this.tile=tile;this.direction=direction;this.mirror=mirror;this.color=color}clear(){this.tile=this.direction=0;this.mirror=false;this.color=new Color}}class CanvasLayer extends EngineObject{constructor(position,size,angle=0,renderOrder=0,canvasSize=vec2(512)){super(position,size,undefined,angle,WHITE,renderOrder);this.canvas=headlessMode?undefined:new OffscreenCanvas(canvasSize.x,canvasSize.y);this.context=headlessMode?undefined:this.canvas.getContext("2d");this.glTexture=undefined;this.gravityScale=0}destroy(){if(this.destroyed)return;if(this.glTexture)glDeleteTexture(this.glTexture);super.destroy()}render(){this.draw(this.pos,this.size,this.angle,this.color,this.mirror,this.additiveColor)}draw(pos,size,angle=0,color=WHITE,mirror=false,additiveColor,screenSpace=false,context){const useWebgl=glEnable&&this.glTexture!=undefined;const tileInfo=(new TileInfo).setFullImage(this.canvas,this.glTexture);drawTile(pos,size,tileInfo,color,angle,mirror,additiveColor,useWebgl,screenSpace,context)}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.textureInfo;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)}useWebGL(enable=true){if(glEnable&&enable){if(this.glTexture)glSetTextureData(this.glTexture,this.canvas);else this.glTexture=glCreateTexture(this.canvas)}else this.glTexture=undefined}}class TileLayer extends CanvasLayer{constructor(position,size,tileInfo=tile(),scale=vec2(1),renderOrder=0,useWebGL=glEnable){super(position,size,0,renderOrder,size);this.tileInfo=tileInfo;const canvasSize=size.multiply(tileInfo.size);this.canvas=new OffscreenCanvas(canvasSize.x,canvasSize.y);this.context=this.canvas.getContext("2d");this.glTexture=useWebGL?glCreateTexture(this.canvas):undefined;this.friction=0;this.restitution=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=()=>{};this.useWebGL=()=>{}}}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]}render(){ASSERT(drawContext!=this.context,"must call redrawEnd() after drawing tiles!");const tileInfo=(new TileInfo).setFullImage(this.canvas,this.glTexture);const pos=this.pos.add(this.size.scale(.5));const useWebgl=glEnable&&this.glTexture!=undefined;drawTile(pos,this.size,tileInfo,WHITE,0,false,CLEAR_BLACK,useWebgl)}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();if(this.glTexture)this.useWebGL()}redrawStart(clear=false){this.savedRenderSettings=[drawCanvas,drawContext,mainCanvasSize,cameraPos,cameraScale];drawCanvas=this.canvas;drawContext=this.context;cameraPos=this.size.scale(.5);cameraScale=this.tileInfo.size.x;mainCanvasSize=this.size.multiply(this.tileInfo.size);if(clear){drawCanvas.width=mainCanvasSize.x;drawCanvas.height=mainCanvasSize.y}this.context.imageSmoothingEnabled=!tilesPixelated;glPreRender()}redrawEnd(){ASSERT(drawContext==this.context,"must call redrawStart() before drawing tiles");glCopyToContext(drawContext);[drawCanvas,drawContext,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(drawContext==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)}}}class TileCollisionLayer extends TileLayer{constructor(position,size,tileInfo=tile(),renderOrder=0,useWebGL=glEnable){const scale=vec2(1);super(position,size.floor(),tileInfo,scale,renderOrder,useWebGL);this.collisionData=[];this.initCollision(this.size);tileCollisionLayers.push(this);this.isSolid=true}destroy(){if(this.destroyed)return;const index=tileCollisionLayers.indexOf(this);ASSERT(index>=0,"tile collision layer not found in array");tileCollisionLayers.splice(index,1);super.destroy()}initCollision(size){this.size=size.floor();this.collisionData=[];this.collisionData.length=size.area();this.collisionData.fill(0)}setCollisionData(gridPos,data=1){const i=(gridPos.y|0)*this.size.x+gridPos.x|0;gridPos.arrayCheck(this.size)&&(this.collisionData[i]=data)}getCollisionData(gridPos){const i=(gridPos.y|0)*this.size.x+gridPos.x|0;return gridPos.arrayCheck(this.size)?this.collisionData[i]:0}collisionTest(pos,size=new Vector2,object){const posX=pos.x-this.pos.x;const posY=pos.y-this.pos.y;const minX=max(posX-size.x/2|0,0);const minY=max(posY-size.y/2|0,0);const maxX=min(posX+size.x/2,this.size.x);const maxY=min(posY+size.y/2,this.size.y);const hitPos=new Vector2;for(let y=minY;y<maxY;++y)for(let x=minX;x<maxX;++x){const tileData=this.collisionData[y*this.size.x+x];if(tileData)if(!object||object.collideWithTile(tileData,hitPos.set(x,y)))return true}return false}collisionRaycast(posStart,posEnd,object){const posStartX=posStart.x-this.pos.x;const posStartY=posStart.y-this.pos.y;const posEndX=posEnd.x-this.pos.x;const posEndY=posEnd.y-this.pos.y;const deltaX=posEndX-posStartX;const deltaY=posEndY-posStartY;const totalLength=(deltaX**2+deltaY**2)**.5;const unitX=abs(totalLength/deltaX);const unitY=abs(totalLength/deltaY);const pos=posStart.floor(),signDeltaX=sign(deltaX),signDeltaY=sign(deltaY);let xi=unitX*(deltaX<0?posStart.x-pos.x:pos.x-posStart.x+1)||0;let yi=unitY*(deltaY<0?posStart.y-pos.y:pos.y-posStart.y+1)||0;while(true){const tileData=this.getCollisionData(pos);if(tileData&&(!object||object.collideWithTile(tileData,pos))){pos.x+=.5;pos.y+=.5;debugRaycast&&debugLine(posStart,posEnd,"#f00",.02);debugRaycast&&debugPoint(pos,"#ff0");return pos}if(xi>=totalLength&&yi>=totalLength)break;if(xi>yi)pos.y+=signDeltaY,yi+=unitY;else pos.x+=signDeltaX,xi+=unitX}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.restitution=this.restitution;particle.friction=this.friction;particle.gravityScale=this.gravityScale;particle.collideTiles=this.collideTiles;particle.renderOrder=this.renderOrder;particle.mirror=randBool();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.clampSpeed=false}update(){super.update();if(this.collideTiles||this.collideSolidObjects){const length2=this.velocity.lengthSquared();if(length2>objectMaxSpeed*objectMaxSpeed){const s=objectMaxSpeed/length2**.5;this.velocity.x*=s;this.velocity.y*=s}}}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;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 rotatedCam=cameraPos.rotate(-cameraAngle);const p=vec2(-1).subtract(rotatedCam.multiply(s));const ca=Math.cos(cameraAngle);const sa=Math.sin(cameraAngle);glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader,"m"),false,[s.x*ca,s.y*sa,0,0,-s.x*sa,s.y*ca,0,0,1,1,1,0,p.x,p.y,0,1])}function glClearCanvas(){glContext.viewport(0,0,glCanvas.width=drawCanvas.width,glCanvas.height=drawCanvas.height);glContext.clear(glContext.COLOR_BUFFER_BIT)}function glSetTexture(texture,wrap=false){if(headlessMode||texture==glActiveTexture)return;glFlush();glContext.bindTexture(glContext.TEXTURE_2D,glActiveTexture=texture);const wrapMode=wrap?glContext.REPEAT:glContext.CLAMP_TO_EDGE;glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_S,wrapMode);glContext.texParameteri(glContext.TEXTURE_2D,glContext.TEXTURE_WRAP_T,wrapMode)}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);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 glDeleteTexture(texture){glContext.deleteTexture(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(!glEnable||!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(debug||showWatermark)drawCount+=glInstanceCount;glInstanceCount=0;glBatchAdditive=glAdditive}function glCopyToContext(context){if(!glEnable)return;glFlush();context.drawImage(glCanvas,0,0)}function glSetAntialias(antialias=true){ASSERT(!glCanvas,"must be called before engineInit");glAntialias=antialias}function glDraw(x,y,sizeX,sizeY,angle=0,uv0X=0,uv0Y=0,uv1X=1,uv1Y=1,rgba=-1,rgbaAdditive=0){if(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}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=drawCanvas.width,glCanvas.height=drawCanvas.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&&notFirstBeat;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.defaultDisabledColor=hsl(0,0,.2);this.defaultLineWidth=4;this.defaultCornerRadius=0;this.defaultFont="arial";this.defaultSoundPress=undefined;this.defaultSoundRelease=undefined;this.defaultSoundClick=undefined;this.uiObjects=[];this.uiContext=context;engineAddPlugin(uiUpdate,uiRender);function updateInvisible(o){for(const c of o.children)updateInvisible(c);o.updateInvisible()}function uiUpdate(){function updateObject(o){if(o.visible){if(o.parent)o.pos=o.localPos.add(o.parent.pos);for(let i=o.children.length;i--;)updateObject(o.children[i]);o.update()}else updateInvisible(o)}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,cornerRadius=uiSystem.defaultCornerRadius){const context=uiSystem.uiContext;context.fillStyle=color.toString();context.beginPath();if(cornerRadius&&context["roundRect"])context["roundRect"](pos.x-size.x/2,pos.y-size.y/2,size.x,size.y,cornerRadius);else context.rect(pos.x-size.x/2,pos.y-size.y/2,size.x,size.y);context.fill();if(lineWidth){context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.stroke()}}drawLine(posA,posB,lineWidth=uiSystem.defaultLineWidth,lineColor=uiSystem.defaultLineColor){const context=uiSystem.uiContext;context.strokeStyle=lineColor.toString();context.lineWidth=lineWidth;context.beginPath();context.lineTo(posA.x,posA.y);context.lineTo(posB.x,posB.y);context.stroke()}drawTile(pos,size,tileInfo,color=uiSystem.defaultColor,angle=0,mirror=false){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.textColor=uiSystem.defaultTextColor;this.hoverColor=uiSystem.defaultHoverColor;this.lineColor=uiSystem.defaultLineColor;this.lineWidth=uiSystem.defaultLineWidth;this.font=uiSystem.defaultFont;this.textHeight=undefined;this.visible=true;this.children=[];this.parent=undefined;this.extraTouchSize=0;this.soundPress=uiSystem.defaultSoundPress;this.soundRelease=uiSystem.defaultSoundRelease;this.soundClick=uiSystem.defaultSoundClick;this.mouseIsOver=false;this.interactive=false;this.mouseIsHeld=false;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 mousePress=mouseWasPressed(0);const mouseDown=mouseIsDown(0);if(mousePress||!mouseDown||this.mouseIsHeld){const size=this.size.add(vec2(isTouchDevice&&this.extraTouchSize||0));this.mouseIsOver=isOverlapping(this.pos,size,mousePosScreen)}if(this.mouseIsOver){if(mousePress)inputClearKey(0,0,0,1,0);if(!this.disabled){if(mousePress){if(this.interactive){this.onPress();if(this.soundPress)this.soundPress.play()}this.mouseIsHeld=true}if(!mouseDown&&this.mouseIsHeld&&this.interactive){this.onClick();if(this.soundClick)this.soundClick.play()}}}if(!mouseDown){if(this.mouseIsHeld&&this.interactive){this.onRelease();if(this.soundRelease)this.soundRelease.play()}if(isTouchDevice)this.mouseIsOver=false;this.mouseIsHeld=false}if(this.mouseIsOver!=mouseWasOver)this.mouseIsOver?this.onEnter():this.onLeave()}render(){if(this.size.x&&this.size.y)uiSystem.drawRect(this.pos,this.size,this.color,this.lineWidth,this.lineColor,this.cornerRadius)}updateInvisible(){this.mouseIsOver=this.mouseIsHeld=false}onEnter(){}onLeave(){}onPress(){}onRelease(){}onClick(){}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.disabledColor=uiSystem.defaultDisabledColor;this.disabled=false;this.interactive=true;this.color=color}render(){const lineColor=this.mouseIsHeld&&!this.disabled?this.color:this.lineColor;const color=this.disabled?this.disabledColor:this.mouseIsOver?this.hoverColor:this.color;uiSystem.drawRect(this.pos,this.size,color,this.lineWidth,lineColor,this.cornerRadius);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;this.interactive=true}onClick(){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,this.cornerRadius);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.handleColor=handleColor;this.color=color;this.interactive=true}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,this.cornerRadius);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(p1,p2,this.value),this.pos.y);const barColor=this.mouseIsHeld?this.color:this.handleColor;uiSystem.drawRect(handlePos,handleSize,barColor,this.lineWidth,this.lineColor,this.cornerRadius);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=.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=drawContext){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=drawContext){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=drawContext){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=drawContext){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=drawContext){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)}}function drawNineSliceScreen(pos,size,startTile,borderSize=32,extraSpace=1){drawNineSlice(pos,size,startTile,WHITE,borderSize,BLACK,extraSpace,false,true,overlayContext)}function drawNineSlice(pos,size,startTile,color,borderSize=1,additiveColor,extraSpace=.01,useWebGL=glEnable,screenSpace,context){const centerTile=startTile.offset(startTile.size);const centerSize=size.add(vec2(extraSpace-borderSize*2));const cornerSize=vec2(borderSize);const cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5));const flip=screenSpace?-1:1;drawTile(pos,centerSize,centerTile,color,0,false,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){const horizontal=i%2;const sidePos=cornerOffset.multiply(vec2(horizontal?i==1?1:-1:0,horizontal?0:i?-1:1));const sideSize=vec2(horizontal?borderSize:centerSize.x,horizontal?centerSize.y:borderSize);const sideTile=centerTile.offset(startTile.size.multiply(vec2(i==1?1:i==3?-1:0,i==0?-flip:i==2?flip:0)));drawTile(pos.add(sidePos),sideSize,sideTile,color,0,false,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){const flipX=i>1;const flipY=i&&i<3;const cornerPos=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-1:1));const cornerTile=centerTile.offset(startTile.size.multiply(vec2(flipX?-1:1,flipY?flip:-flip)));drawTile(pos.add(cornerPos),cornerSize,cornerTile,color,0,false,additiveColor,useWebGL,screenSpace,context)}}function drawThreeSliceScreen(pos,size,startTile,borderSize=32,extraSpace=1){drawThreeSlice(pos,size,startTile,WHITE,borderSize,BLACK,extraSpace,false,true,overlayContext)}function drawThreeSlice(pos,size,startTile,color,borderSize=1,additiveColor,extraSpace=.01,useWebGL=glEnable,screenSpace,context){const cornerTile=startTile.frame(0);const sideTile=startTile.frame(1);const centerTile=startTile.frame(2);const centerSize=size.add(vec2(extraSpace-borderSize*2));const cornerSize=vec2(borderSize);const cornerOffset=size.scale(.5).subtract(cornerSize.scale(.5));const flip=screenSpace?-1:1;drawTile(pos,centerSize,centerTile,color,0,false,additiveColor,useWebGL,screenSpace,context);for(let i=4;i--;){const angle=i*PI/2;const horizontal=i%2;const sidePos=cornerOffset.multiply(vec2(horizontal?i==1?1:-1:0,horizontal?0:i?-flip:flip));const sideSize=vec2(horizontal?centerSize.y:centerSize.x,borderSize);drawTile(pos.add(sidePos),sideSize,sideTile,color,angle,false,additiveColor,useWebGL,screenSpace,context)}for(let i=4;i--;){const angle=i*PI/2;const flipX=!i||i>2;const flipY=i>1;const cornerPos=cornerOffset.multiply(vec2(flipX?-1:1,flipY?-flip:flip));drawTile(pos.add(cornerPos),cornerSize,cornerTile,color,angle,false,additiveColor,useWebGL,screenSpace,context)}}export{engineName,engineVersion,frameRate,timeDelta,engineObjects,frame,time,timeReal,paused,getPaused,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,cameraAngle,cameraScale,canvasMaxSize,canvasFixedSize,canvasPixelated,tilesPixelated,fontDefault,showSplashScreen,headlessMode,tileSizeDefault,tileFixBleedScale,enablePhysicsSolver,objectDefaultMass,objectDefaultDamping,objectDefaultAngleDamping,objectDefaultRestitution,objectDefaultFriction,objectMaxSpeed,gravity,particleEmitRateScale,glEnable,gamepadsEnable,gamepadDirectionEmulateStick,inputWASDEmulateDirection,touchGamepadEnable,touchGamepadAnalog,touchGamepadSize,touchGamepadAlpha,vibrateEnable,soundEnable,soundVolume,soundDefaultRange,soundDefaultTaper,medalDisplayTime,medalDisplaySlideTime,medalDisplaySize,setCameraPos,setCameraAngle,setCameraScale,setCanvasMaxSize,setCanvasFixedSize,setCanvasPixelated,setTilesPixelated,setFontDefault,setShowSplashScreen,setHeadlessMode,setGLEnable,setTileSizeDefault,setTileFixBleedScale,setEnablePhysicsSolver,setObjectDefaultMass,setObjectDefaultDamping,setObjectDefaultAngleDamping,setObjectDefaultRestitution,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,randVec2,randColor,RandomGenerator,Vector2,Color,Timer,vec2,rgb,hsl,isColor,WHITE,CLEAR_WHITE,BLACK,CLEAR_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,glDeleteTexture,glSetTextureData,glDraw,glFlush,glSetTexture,glSetAntialias,glClearCanvas,glAntialias,glShader,glActiveTexture,glArrayBuffer,glGeometryBuffer,glPositionData,glColorData,glInstanceCount,glAdditive,glBatchAdditive,keyIsDown,keyWasPressed,keyWasReleased,keyDirection,inputClear,inputClearKey,mouseIsDown,mouseWasPressed,mouseWasReleased,mousePos,mousePosScreen,mouseDelta,mouseDeltaScreen,mouseWheel,isUsingGamepad,inputPreventDefault,setInputPreventDefault,gamepadIsDown,gamepadWasPressed,gamepadWasReleased,gamepadStick,gamepadsUpdate,vibrate,vibrateStop,isTouchDevice,pointerLockRequest,pointerLockExit,pointerLockIsActive,Sound,SoundWave,playAudioFile,speak,speakStop,getNoteFrequency,playSamples,zzfx,zzfxG,zzfxR,audioContext,EngineObject,tileCollisionLayers,tileCollisionGetData,tileCollisionTest,tileCollisionRaycast,tileCollisionLoad,TileLayerData,CanvasLayer,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,drawNineSlice,drawNineSliceScreen,drawThreeSlice,drawThreeSliceScreen};