1474 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1474 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
class Bump {
 | 
						||
  constructor(renderingEngine = PIXI) {
 | 
						||
    if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using bump.js"); 
 | 
						||
 | 
						||
    //Find out which rendering engine is being used (the default is Pixi)
 | 
						||
    this.renderer = "";
 | 
						||
 | 
						||
    //If the `renderingEngine` is Pixi, set up Pixi object aliases
 | 
						||
    if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
 | 
						||
      this.renderer = "pixi";
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  //`addCollisionProperties` adds extra properties to sprites to help
 | 
						||
  //simplify the collision code. It won't add these properties if they
 | 
						||
  //already exist on the sprite. After these properties have been
 | 
						||
  //added, this methods adds a Boolean property to the sprite called `_bumpPropertiesAdded` 
 | 
						||
  //and sets it to `true` to flag that the sprite has these
 | 
						||
  //new properties
 | 
						||
  addCollisionProperties(sprite) {
 | 
						||
 | 
						||
    //Add properties to Pixi sprites
 | 
						||
    if (this.renderer === "pixi") {
 | 
						||
 | 
						||
      //gx
 | 
						||
      if (sprite.gx === undefined) {
 | 
						||
        Object.defineProperty(sprite, "gx", {
 | 
						||
          get(){return sprite.getGlobalPosition().x},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
 | 
						||
      //gy
 | 
						||
      if (sprite.gy === undefined) {
 | 
						||
        Object.defineProperty(sprite, "gy", {
 | 
						||
          get(){return sprite.getGlobalPosition().y},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
      
 | 
						||
      //centerX
 | 
						||
      if (sprite.centerX === undefined) {
 | 
						||
        Object.defineProperty(sprite, "centerX", {
 | 
						||
          get(){return sprite.x + sprite.width / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
 | 
						||
      //centerY
 | 
						||
      if (sprite.centerY === undefined) {
 | 
						||
        Object.defineProperty(sprite, "centerY", {
 | 
						||
          get(){return sprite.y + sprite.height / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
      
 | 
						||
      //halfWidth
 | 
						||
      if (sprite.halfWidth === undefined) {
 | 
						||
        Object.defineProperty(sprite, "halfWidth", {
 | 
						||
          get(){return sprite.width / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
      
 | 
						||
      //halfHeight
 | 
						||
      if (sprite.halfHeight === undefined) {
 | 
						||
        Object.defineProperty(sprite, "halfHeight", {
 | 
						||
          get(){return sprite.height / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
      
 | 
						||
      //xAnchorOffset
 | 
						||
      if (sprite.xAnchorOffset === undefined) {
 | 
						||
        Object.defineProperty(sprite, "xAnchorOffset", {
 | 
						||
          get(){
 | 
						||
            if (sprite.anchor !== undefined) {
 | 
						||
              return sprite.height * sprite.anchor.x;
 | 
						||
            } else {
 | 
						||
              return 0;
 | 
						||
            }
 | 
						||
          },
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
      
 | 
						||
      //yAnchorOffset
 | 
						||
      if (sprite.yAnchorOffset === undefined) {
 | 
						||
        Object.defineProperty(sprite, "yAnchorOffset", {
 | 
						||
          get(){
 | 
						||
            if (sprite.anchor !== undefined) {
 | 
						||
              return sprite.width * sprite.anchor.y;
 | 
						||
            } else {
 | 
						||
              return 0;
 | 
						||
            }
 | 
						||
          },
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
 | 
						||
      if (sprite.circular && sprite.radius === undefined) {
 | 
						||
        Object.defineProperty(sprite, "radius", {
 | 
						||
          get(){return sprite.width / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        });
 | 
						||
      }
 | 
						||
 | 
						||
      //Earlier code - not needed now.
 | 
						||
      /*
 | 
						||
      Object.defineProperties(sprite, {
 | 
						||
        "gx": {
 | 
						||
          get(){return sprite.getGlobalPosition().x},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "gy": {
 | 
						||
          get(){return sprite.getGlobalPosition().y},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "centerX": {
 | 
						||
          get(){return sprite.x + sprite.width / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "centerY": {
 | 
						||
          get(){return sprite.y + sprite.height / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "halfWidth": {
 | 
						||
          get(){return sprite.width / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "halfHeight": {
 | 
						||
          get(){return sprite.height / 2},
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "xAnchorOffset": {
 | 
						||
          get(){
 | 
						||
            if (sprite.anchor !== undefined) {
 | 
						||
              return sprite.height * sprite.anchor.x;
 | 
						||
            } else {
 | 
						||
              return 0;
 | 
						||
            }
 | 
						||
          },
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        },
 | 
						||
        "yAnchorOffset": {
 | 
						||
          get(){
 | 
						||
            if (sprite.anchor !== undefined) {
 | 
						||
              return sprite.width * sprite.anchor.y;
 | 
						||
            } else {
 | 
						||
              return 0;
 | 
						||
            }
 | 
						||
          },
 | 
						||
          enumerable: true, configurable: true
 | 
						||
        }
 | 
						||
      });
 | 
						||
      */
 | 
						||
    }
 | 
						||
 | 
						||
    //Add a Boolean `_bumpPropertiesAdded` property to the sprite to flag it
 | 
						||
    //as having these new properties
 | 
						||
    sprite._bumpPropertiesAdded = true;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  hitTestPoint
 | 
						||
  ------------
 | 
						||
 | 
						||
  Use it to find out if a point is touching a circlular or rectangular sprite.
 | 
						||
  Parameters: 
 | 
						||
  a. An object with `x` and `y` properties.
 | 
						||
  b. A sprite object with `x`, `y`, `centerX` and `centerY` properties.
 | 
						||
  If the sprite has a `radius` property, the function will interpret
 | 
						||
  the shape as a circle.
 | 
						||
  */
 | 
						||
 | 
						||
  hitTestPoint(point, sprite) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite); 
 | 
						||
 | 
						||
    let shape, left, right, top, bottom, vx, vy, magnitude, hit;
 | 
						||
 | 
						||
    //Find out if the sprite is rectangular or circular depending
 | 
						||
    //on whether it has a `radius` property
 | 
						||
    if (sprite.radius) {
 | 
						||
      shape = "circle";
 | 
						||
    } else {
 | 
						||
      shape = "rectangle";
 | 
						||
    }
 | 
						||
 | 
						||
    //Rectangle
 | 
						||
    if (shape === "rectangle") {
 | 
						||
 | 
						||
      //Get the position of the sprite's edges
 | 
						||
      left = sprite.x - sprite.xAnchorOffset;
 | 
						||
      right = sprite.x + sprite.width - sprite.xAnchorOffset;
 | 
						||
      top = sprite.y - sprite.yAnchorOffset;
 | 
						||
      bottom = sprite.y + sprite.height - sprite.yAnchorOffset;
 | 
						||
 | 
						||
      //Find out if the point is intersecting the rectangle
 | 
						||
      hit = point.x > left && point.x < right && point.y > top && point.y < bottom;
 | 
						||
    }
 | 
						||
 | 
						||
    //Circle
 | 
						||
    if (shape === "circle") {
 | 
						||
 | 
						||
      //Find the distance between the point and the
 | 
						||
      //center of the circle
 | 
						||
      let vx = point.x - sprite.x - (sprite.width / 2) + sprite.xAnchorOffset,
 | 
						||
          vy = point.y - sprite.y - (sprite.height / 2) + sprite.yAnchorOffset,
 | 
						||
          magnitude = Math.sqrt(vx * vx + vy * vy);
 | 
						||
 | 
						||
      //The point is intersecting the circle if the magnitude
 | 
						||
      //(distance) is less than the circle's radius
 | 
						||
      hit = magnitude < sprite.radius;
 | 
						||
    }
 | 
						||
 | 
						||
    //`hit` will be either `true` or `false`
 | 
						||
    return hit;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  hitTestCircle
 | 
						||
  -------------
 | 
						||
 | 
						||
  Use it to find out if two circular sprites are touching.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY` and `radius` properties.
 | 
						||
  b. A sprite object with `centerX`, `centerY` and `radius`.
 | 
						||
  */
 | 
						||
 | 
						||
  hitTestCircle(c1, c2, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
    if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2); 
 | 
						||
 | 
						||
    let vx, vy, magnitude, combinedRadii, hit;
 | 
						||
 | 
						||
    //Calculate the vector between the circles’ center points
 | 
						||
    if (global) {
 | 
						||
      //Use global coordinates
 | 
						||
      vx = (c2.gx + (c2.width / 2) - c2.xAnchorOffset) - (c1.gx + (c1.width / 2) - c1.xAnchorOffset);
 | 
						||
      vy = (c2.gy + (c2.width / 2) - c2.yAnchorOffset) - (c1.gy + (c1.width / 2) - c1.yAnchorOffset);
 | 
						||
    } else {
 | 
						||
      //Use local coordinates
 | 
						||
      vx = (c2.x + (c2.width / 2) - c2.xAnchorOffset) - (c1.x + (c1.width / 2) - c1.xAnchorOffset);
 | 
						||
      vy = (c2.y + (c2.width / 2) - c2.yAnchorOffset) - (c1.y + (c1.width / 2) - c1.yAnchorOffset);
 | 
						||
    }
 | 
						||
 | 
						||
    //Find the distance between the circles by calculating
 | 
						||
    //the vector's magnitude (how long the vector is)
 | 
						||
    magnitude = Math.sqrt(vx * vx + vy * vy);
 | 
						||
 | 
						||
    //Add together the circles' total radii
 | 
						||
    combinedRadii = c1.radius + c2.radius;
 | 
						||
 | 
						||
    //Set `hit` to `true` if the distance between the circles is
 | 
						||
    //less than their `combinedRadii`
 | 
						||
    hit = magnitude < combinedRadii;
 | 
						||
 | 
						||
    //`hit` will be either `true` or `false`
 | 
						||
    return hit;
 | 
						||
  } 
 | 
						||
 | 
						||
  /*
 | 
						||
  circleCollision
 | 
						||
  ---------------
 | 
						||
 | 
						||
  Use it to prevent a moving circular sprite from overlapping and optionally
 | 
						||
  bouncing off a non-moving circular sprite.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
 | 
						||
  b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
 | 
						||
  c. Optional: true or false to indicate whether or not the first sprite
 | 
						||
  should bounce off the second sprite.
 | 
						||
  The sprites can contain an optional mass property that should be greater than 1.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  circleCollision(c1, c2, bounce = false, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
    if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2); 
 | 
						||
 | 
						||
    let magnitude, combinedRadii, overlap,
 | 
						||
      vx, vy, dx, dy, s = {},
 | 
						||
      hit = false;
 | 
						||
 | 
						||
    //Calculate the vector between the circles’ center points
 | 
						||
 | 
						||
    if (global) {
 | 
						||
      //Use global coordinates
 | 
						||
      vx = (c2.gx + (c2.width / 2) - c2.xAnchorOffset) - (c1.gx + (c1.width / 2) - c1.xAnchorOffset);
 | 
						||
      vy = (c2.gy + (c2.width / 2) - c2.yAnchorOffset) - (c1.gy + (c1.width / 2) - c1.yAnchorOffset);
 | 
						||
    } else {
 | 
						||
      //Use local coordinates
 | 
						||
      vx = (c2.x + (c2.width / 2) - c2.xAnchorOffset) - (c1.x + (c1.width / 2) - c1.xAnchorOffset);
 | 
						||
      vy = (c2.y + (c2.width / 2) - c2.yAnchorOffset) - (c1.y + (c1.width / 2) - c1.yAnchorOffset);
 | 
						||
    }
 | 
						||
 | 
						||
    //Find the distance between the circles by calculating
 | 
						||
    //the vector's magnitude (how long the vector is)
 | 
						||
    magnitude = Math.sqrt(vx * vx + vy * vy);
 | 
						||
 | 
						||
    //Add together the circles' combined half-widths
 | 
						||
    combinedRadii = c1.radius + c2.radius;
 | 
						||
 | 
						||
    //Figure out if there's a collision
 | 
						||
    if (magnitude < combinedRadii) {
 | 
						||
 | 
						||
      //Yes, a collision is happening
 | 
						||
      hit = true;
 | 
						||
 | 
						||
      //Find the amount of overlap between the circles
 | 
						||
      overlap = combinedRadii - magnitude;
 | 
						||
 | 
						||
      //Add some "quantum padding". This adds a tiny amount of space
 | 
						||
      //between the circles to reduce their surface tension and make
 | 
						||
      //them more slippery. "0.3" is a good place to start but you might
 | 
						||
      //need to modify this slightly depending on the exact behaviour
 | 
						||
      //you want. Too little and the balls will feel sticky, too much
 | 
						||
      //and they could start to jitter if they're jammed together
 | 
						||
      let quantumPadding = 0.3;
 | 
						||
      overlap += quantumPadding;
 | 
						||
 | 
						||
      //Normalize the vector
 | 
						||
      //These numbers tell us the direction of the collision
 | 
						||
      dx = vx / magnitude;
 | 
						||
      dy = vy / magnitude;
 | 
						||
 | 
						||
      //Move circle 1 out of the collision by multiplying
 | 
						||
      //the overlap with the normalized vector and subtract it from
 | 
						||
      //circle 1's position
 | 
						||
      c1.x -= overlap * dx;
 | 
						||
      c1.y -= overlap * dy;
 | 
						||
 | 
						||
      //Bounce
 | 
						||
      if (bounce) {
 | 
						||
        //Create a collision vector object, `s` to represent the bounce "surface".
 | 
						||
        //Find the bounce surface's x and y properties
 | 
						||
        //(This represents the normal of the distance vector between the circles)
 | 
						||
        s.x = vy;
 | 
						||
        s.y = -vx;
 | 
						||
 | 
						||
        //Bounce c1 off the surface
 | 
						||
        this.bounceOffSurface(c1, s);
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return hit;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  movingCircleCollision
 | 
						||
  ---------------------
 | 
						||
 | 
						||
  Use it to make two moving circles bounce off each other.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
 | 
						||
  b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
 | 
						||
  The sprites can contain an optional mass property that should be greater than 1.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  movingCircleCollision(c1, c2, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
    if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2); 
 | 
						||
 | 
						||
    let combinedRadii, overlap, xSide, ySide,
 | 
						||
      //`s` refers to the distance vector between the circles
 | 
						||
      s = {},
 | 
						||
      p1A = {},
 | 
						||
      p1B = {},
 | 
						||
      p2A = {},
 | 
						||
      p2B = {},
 | 
						||
      hit = false;
 | 
						||
 | 
						||
    //Apply mass, if the circles have mass properties
 | 
						||
    c1.mass = c1.mass || 1;
 | 
						||
    c2.mass = c2.mass || 1;
 | 
						||
 | 
						||
    //Calculate the vector between the circles’ center points
 | 
						||
    if (global) {
 | 
						||
 | 
						||
      //Use global coordinates
 | 
						||
      s.vx = (c2.gx + c2.radius - c2.xAnchorOffset) - (c1.gx + c1.radius - c1.xAnchorOffset);
 | 
						||
      s.vy = (c2.gy + c2.radius - c2.yAnchorOffset) - (c1.gy + c1.radius - c1.yAnchorOffset);
 | 
						||
    } else {
 | 
						||
 | 
						||
      //Use local coordinates
 | 
						||
      s.vx = (c2.x + c2.radius - c2.xAnchorOffset) - (c1.x + c1.radius - c1.xAnchorOffset);
 | 
						||
      s.vy = (c2.y + c2.radius - c2.yAnchorOffset) - (c1.y + c1.radius - c1.yAnchorOffset);
 | 
						||
    }
 | 
						||
 | 
						||
    //Find the distance between the circles by calculating
 | 
						||
    //the vector's magnitude (how long the vector is)
 | 
						||
    s.magnitude = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
 | 
						||
 | 
						||
    //Add together the circles' combined half-widths
 | 
						||
    combinedRadii = c1.radius + c2.radius;
 | 
						||
 | 
						||
    //Figure out if there's a collision
 | 
						||
    if (s.magnitude < combinedRadii) {
 | 
						||
 | 
						||
      //Yes, a collision is happening
 | 
						||
      hit = true;
 | 
						||
 | 
						||
      //Find the amount of overlap between the circles
 | 
						||
      overlap = combinedRadii - s.magnitude;
 | 
						||
 | 
						||
      //Add some "quantum padding" to the overlap
 | 
						||
      overlap += 0.3;
 | 
						||
 | 
						||
      //Normalize the vector.
 | 
						||
      //These numbers tell us the direction of the collision
 | 
						||
      s.dx = s.vx / s.magnitude;
 | 
						||
      s.dy = s.vy / s.magnitude;
 | 
						||
 | 
						||
      //Find the collision vector.
 | 
						||
      //Divide it in half to share between the circles, and make it absolute
 | 
						||
      s.vxHalf = Math.abs(s.dx * overlap / 2);
 | 
						||
      s.vyHalf = Math.abs(s.dy * overlap / 2);
 | 
						||
 | 
						||
      //Find the side that the collision is occurring on
 | 
						||
      (c1.x > c2.x) ? xSide = 1 : xSide = -1;
 | 
						||
      (c1.y > c2.y) ? ySide = 1 : ySide = -1;
 | 
						||
 | 
						||
      //Move c1 out of the collision by multiplying
 | 
						||
      //the overlap with the normalized vector and adding it to
 | 
						||
      //the circles' positions
 | 
						||
      c1.x = c1.x + (s.vxHalf * xSide);
 | 
						||
      c1.y = c1.y + (s.vyHalf * ySide);
 | 
						||
 | 
						||
      //Move c2 out of the collision
 | 
						||
      c2.x = c2.x + (s.vxHalf * -xSide);
 | 
						||
      c2.y = c2.y + (s.vyHalf * -ySide);
 | 
						||
 | 
						||
      //1. Calculate the collision surface's properties
 | 
						||
 | 
						||
      //Find the surface vector's left normal
 | 
						||
      s.lx = s.vy;
 | 
						||
      s.ly = -s.vx;
 | 
						||
 | 
						||
      //2. Bounce c1 off the surface (s)
 | 
						||
 | 
						||
      //Find the dot product between c1 and the surface
 | 
						||
      let dp1 = c1.vx * s.dx + c1.vy * s.dy;
 | 
						||
 | 
						||
      //Project c1's velocity onto the collision surface
 | 
						||
      p1A.x = dp1 * s.dx;
 | 
						||
      p1A.y = dp1 * s.dy;
 | 
						||
 | 
						||
      //Find the dot product of c1 and the surface's left normal (s.lx and s.ly)
 | 
						||
      let dp2 = c1.vx * (s.lx / s.magnitude) + c1.vy * (s.ly / s.magnitude);
 | 
						||
 | 
						||
      //Project the c1's velocity onto the surface's left normal
 | 
						||
      p1B.x = dp2 * (s.lx / s.magnitude);
 | 
						||
      p1B.y = dp2 * (s.ly / s.magnitude);
 | 
						||
 | 
						||
      //3. Bounce c2 off the surface (s)
 | 
						||
 | 
						||
      //Find the dot product between c2 and the surface
 | 
						||
      let dp3 = c2.vx * s.dx + c2.vy * s.dy;
 | 
						||
 | 
						||
      //Project c2's velocity onto the collision surface
 | 
						||
      p2A.x = dp3 * s.dx;
 | 
						||
      p2A.y = dp3 * s.dy;
 | 
						||
 | 
						||
      //Find the dot product of c2 and the surface's left normal (s.lx and s.ly)
 | 
						||
      let dp4 = c2.vx * (s.lx / s.magnitude) + c2.vy * (s.ly / s.magnitude);
 | 
						||
 | 
						||
      //Project c2's velocity onto the surface's left normal
 | 
						||
      p2B.x = dp4 * (s.lx / s.magnitude);
 | 
						||
      p2B.y = dp4 * (s.ly / s.magnitude);
 | 
						||
 | 
						||
      //4. Calculate the bounce vectors
 | 
						||
 | 
						||
      //Bounce c1
 | 
						||
      //using p1B and p2A
 | 
						||
      c1.bounce = {};
 | 
						||
      c1.bounce.x = p1B.x + p2A.x;
 | 
						||
      c1.bounce.y = p1B.y + p2A.y;
 | 
						||
 | 
						||
      //Bounce c2
 | 
						||
      //using p1A and p2B
 | 
						||
      c2.bounce = {};
 | 
						||
      c2.bounce.x = p1A.x + p2B.x;
 | 
						||
      c2.bounce.y = p1A.y + p2B.y;
 | 
						||
 | 
						||
      //Add the bounce vector to the circles' velocity
 | 
						||
      //and add mass if the circle has a mass property
 | 
						||
      c1.vx = c1.bounce.x / c1.mass;
 | 
						||
      c1.vy = c1.bounce.y / c1.mass;
 | 
						||
      c2.vx = c2.bounce.x / c2.mass;
 | 
						||
      c2.vy = c2.bounce.y / c2.mass;
 | 
						||
    }
 | 
						||
    return hit;
 | 
						||
  }
 | 
						||
  /*
 | 
						||
  multipleCircleCollision
 | 
						||
  -----------------------
 | 
						||
 | 
						||
  Checks all the circles in an array for a collision against
 | 
						||
  all the other circles in an array, using `movingCircleCollision` (above)
 | 
						||
  */
 | 
						||
 | 
						||
  multipleCircleCollision(arrayOfCircles, global = false) {
 | 
						||
    for (let i = 0; i < arrayOfCircles.length; i++) {
 | 
						||
 | 
						||
      //The first circle to use in the collision check
 | 
						||
      var c1 = arrayOfCircles[i];
 | 
						||
      for (let j = i + 1; j < arrayOfCircles.length; j++) {
 | 
						||
 | 
						||
        //The second circle to use in the collision check
 | 
						||
        let c2 = arrayOfCircles[j];
 | 
						||
 | 
						||
        //Check for a collision and bounce the circles apart if
 | 
						||
        //they collide. Use an optional `mass` property on the sprite
 | 
						||
        //to affect the bounciness of each marble
 | 
						||
        this.movingCircleCollision(c1, c2, global);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  rectangleCollision
 | 
						||
  ------------------
 | 
						||
 | 
						||
  Use it to prevent two rectangular sprites from overlapping. 
 | 
						||
  Optionally, make the first rectangle bounce off the second rectangle.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
  b. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
  c. Optional: true or false to indicate whether or not the first sprite
 | 
						||
  should bounce off the second sprite.
 | 
						||
  */
 | 
						||
 
 | 
						||
  rectangleCollision(
 | 
						||
    r1, r2, bounce = false, global = true
 | 
						||
  ) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1); 
 | 
						||
    if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2); 
 | 
						||
 | 
						||
    let collision, combinedHalfWidths, combinedHalfHeights,
 | 
						||
      overlapX, overlapY, vx, vy;
 | 
						||
 | 
						||
    //Calculate the distance vector
 | 
						||
    if (global) {
 | 
						||
      vx = (r1.gx + r1.halfWidth - r1.xAnchorOffset) - (r2.gx + r2.halfWidth - r2.xAnchorOffset);
 | 
						||
      vy = (r1.gy + r1.halfHeight - r1.yAnchorOffset) - (r2.gy + r2.halfHeight - r2.yAnchorOffset);
 | 
						||
    } else {
 | 
						||
      //vx = r1.centerX - r2.centerX;
 | 
						||
      //vy = r1.centerY - r2.centerY;
 | 
						||
      vx = (r1.x + r1.halfWidth - r1.xAnchorOffset) - (r2.x + r2.halfWidth - r2.xAnchorOffset);
 | 
						||
      vy = (r1.y + r1.halfHeight - r1.yAnchorOffset) - (r2.y + r2.halfHeight - r2.yAnchorOffset);
 | 
						||
    }
 | 
						||
 | 
						||
    //Figure out the combined half-widths and half-heights
 | 
						||
    combinedHalfWidths = r1.halfWidth + r2.halfWidth;
 | 
						||
    combinedHalfHeights = r1.halfHeight + r2.halfHeight;
 | 
						||
 | 
						||
    //Check whether vx is less than the combined half widths
 | 
						||
    if (Math.abs(vx) < combinedHalfWidths) {
 | 
						||
 | 
						||
      //A collision might be occurring!
 | 
						||
      //Check whether vy is less than the combined half heights
 | 
						||
      if (Math.abs(vy) < combinedHalfHeights) {
 | 
						||
 | 
						||
        //A collision has occurred! This is good!
 | 
						||
        //Find out the size of the overlap on both the X and Y axes
 | 
						||
        overlapX = combinedHalfWidths - Math.abs(vx);
 | 
						||
        overlapY = combinedHalfHeights - Math.abs(vy);
 | 
						||
 | 
						||
        //The collision has occurred on the axis with the
 | 
						||
        //*smallest* amount of overlap. Let's figure out which
 | 
						||
        //axis that is
 | 
						||
 | 
						||
        if (overlapX >= overlapY) {
 | 
						||
          //The collision is happening on the X axis
 | 
						||
          //But on which side? vy can tell us
 | 
						||
 | 
						||
          if (vy > 0) {
 | 
						||
            collision = "top";
 | 
						||
            //Move the rectangle out of the collision
 | 
						||
            r1.y = r1.y + overlapY;
 | 
						||
          } else {
 | 
						||
            collision = "bottom";
 | 
						||
            //Move the rectangle out of the collision
 | 
						||
            r1.y = r1.y - overlapY;
 | 
						||
          }
 | 
						||
 | 
						||
          //Bounce
 | 
						||
          if (bounce) {
 | 
						||
            r1.vy *= -1;
 | 
						||
 | 
						||
            /*Alternative
 | 
						||
            //Find the bounce surface's vx and vy properties
 | 
						||
            var s = {};
 | 
						||
            s.vx = r2.x - r2.x + r2.width;
 | 
						||
            s.vy = 0;
 | 
						||
 | 
						||
            //Bounce r1 off the surface
 | 
						||
            //this.bounceOffSurface(r1, s);
 | 
						||
            */
 | 
						||
 | 
						||
          }
 | 
						||
        } else {
 | 
						||
          //The collision is happening on the Y axis
 | 
						||
          //But on which side? vx can tell us
 | 
						||
 | 
						||
          if (vx > 0) {
 | 
						||
            collision = "left";
 | 
						||
            //Move the rectangle out of the collision
 | 
						||
            r1.x = r1.x + overlapX;
 | 
						||
          } else {
 | 
						||
            collision = "right";
 | 
						||
            //Move the rectangle out of the collision
 | 
						||
            r1.x = r1.x - overlapX;
 | 
						||
          }
 | 
						||
 | 
						||
          //Bounce
 | 
						||
          if (bounce) {
 | 
						||
            r1.vx *= -1;
 | 
						||
 | 
						||
            /*Alternative
 | 
						||
            //Find the bounce surface's vx and vy properties
 | 
						||
            var s = {};
 | 
						||
            s.vx = 0;
 | 
						||
            s.vy = r2.y - r2.y + r2.height;
 | 
						||
 | 
						||
            //Bounce r1 off the surface
 | 
						||
            this.bounceOffSurface(r1, s);
 | 
						||
            */
 | 
						||
 | 
						||
          }
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        //No collision
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      //No collision
 | 
						||
    }
 | 
						||
 | 
						||
    //Return the collision string. it will be either "top", "right",
 | 
						||
    //"bottom", or "left" depending on which side of r1 is touching r2.
 | 
						||
    return collision;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  hitTestRectangle
 | 
						||
  ----------------
 | 
						||
 | 
						||
  Use it to find out if two rectangular sprites are touching.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
  b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  hitTestRectangle(r1, r2, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1); 
 | 
						||
    if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2); 
 | 
						||
 | 
						||
    let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
 | 
						||
 | 
						||
    //A variable to determine whether there's a collision
 | 
						||
    hit = false;
 | 
						||
 | 
						||
    //Calculate the distance vector
 | 
						||
    if (global) {
 | 
						||
      vx = (r1.gx + r1.halfWidth - r1.xAnchorOffset) - (r2.gx + r2.halfWidth - r2.xAnchorOffset);
 | 
						||
      vy = (r1.gy + r1.halfHeight - r1.yAnchorOffset) - (r2.gy + r2.halfHeight - r2.yAnchorOffset);
 | 
						||
    } else {
 | 
						||
      vx = (r1.x + r1.halfWidth - r1.xAnchorOffset) - (r2.x + r2.halfWidth - r2.xAnchorOffset);
 | 
						||
      vy = (r1.y + r1.halfHeight - r1.yAnchorOffset) - (r2.y + r2.halfHeight - r2.yAnchorOffset);
 | 
						||
    }
 | 
						||
 | 
						||
    //Figure out the combined half-widths and half-heights
 | 
						||
    combinedHalfWidths = r1.halfWidth + r2.halfWidth;
 | 
						||
    combinedHalfHeights = r1.halfHeight + r2.halfHeight;
 | 
						||
 | 
						||
    //Check for a collision on the x axis
 | 
						||
    if (Math.abs(vx) < combinedHalfWidths) {
 | 
						||
 | 
						||
      //A collision might be occuring. Check for a collision on the y axis
 | 
						||
      if (Math.abs(vy) < combinedHalfHeights) {
 | 
						||
 | 
						||
        //There's definitely a collision happening
 | 
						||
        hit = true;
 | 
						||
      } else {
 | 
						||
 | 
						||
        //There's no collision on the y axis
 | 
						||
        hit = false;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
 | 
						||
      //There's no collision on the x axis
 | 
						||
      hit = false;
 | 
						||
    }
 | 
						||
 | 
						||
    //`hit` will be either `true` or `false`
 | 
						||
    return hit;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  hitTestCircleRectangle
 | 
						||
  ----------------
 | 
						||
 | 
						||
  Use it to find out if a circular shape is touching a rectangular shape
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
  b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  hitTestCircleRectangle(c1, r1, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1); 
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
 | 
						||
    let region, collision, c1x, c1y, r1x, r1y;
 | 
						||
 | 
						||
    //Use either global or local coordinates
 | 
						||
    if (global) {
 | 
						||
      c1x = c1.gx;
 | 
						||
      c1y = c1.gy
 | 
						||
      r1x = r1.gx;
 | 
						||
      r1y = r1.gy;
 | 
						||
    } else {
 | 
						||
      c1x = c1.x;
 | 
						||
      c1y = c1.y;
 | 
						||
      r1x = r1.x;
 | 
						||
      r1y = r1.y;
 | 
						||
    }
 | 
						||
 | 
						||
    //Is the circle above the rectangle's top edge?
 | 
						||
    if (c1y - c1.yAnchorOffset < r1y - r1.halfHeight - r1.yAnchorOffset) {
 | 
						||
 | 
						||
      //If it is, we need to check whether it's in the 
 | 
						||
      //top left, top center or top right
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "topLeft";
 | 
						||
      } else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "topRight";
 | 
						||
      } else {
 | 
						||
        region = "topMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle isn't above the top edge, so it might be
 | 
						||
    //below the bottom edge
 | 
						||
    else if (c1y - c1.yAnchorOffset > r1y + r1.halfHeight - r1.yAnchorOffset) {
 | 
						||
 | 
						||
      //If it is, we need to check whether it's in the bottom left,
 | 
						||
      //bottom center, or bottom right
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "bottomLeft";
 | 
						||
      } else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "bottomRight";
 | 
						||
      } else {
 | 
						||
        region = "bottomMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle isn't above the top edge or below the bottom edge,
 | 
						||
    //so it must be on the left or right side
 | 
						||
    else {
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "leftMiddle";
 | 
						||
      } else {
 | 
						||
        region = "rightMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //Is this the circle touching the flat sides
 | 
						||
    //of the rectangle?
 | 
						||
    if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
 | 
						||
 | 
						||
      //Yes, it is, so do a standard rectangle vs. rectangle collision test
 | 
						||
      collision = this.hitTestRectangle(c1, r1, global);
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle is touching one of the corners, so do a
 | 
						||
    //circle vs. point collision test
 | 
						||
    else {
 | 
						||
      let point = {};
 | 
						||
 | 
						||
      switch (region) {
 | 
						||
        case "topLeft":
 | 
						||
          point.x = r1x - r1.xAnchorOffset;
 | 
						||
          point.y = r1y - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "topRight":
 | 
						||
          point.x = r1x + r1.width - r1.xAnchorOffset;
 | 
						||
          point.y = r1y - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "bottomLeft":
 | 
						||
          point.x = r1x - r1.xAnchorOffset;
 | 
						||
          point.y = r1y + r1.height - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "bottomRight":
 | 
						||
          point.x = r1x + r1.width - r1.xAnchorOffset;
 | 
						||
          point.y = r1y + r1.height - r1.yAnchorOffset;
 | 
						||
      }
 | 
						||
 | 
						||
      //Check for a collision between the circle and the point
 | 
						||
      collision = this.hitTestCirclePoint(c1, point, global);
 | 
						||
    }
 | 
						||
 | 
						||
    //Return the result of the collision.
 | 
						||
    //The return value will be `undefined` if there's no collision
 | 
						||
    if (collision) {
 | 
						||
      return region;
 | 
						||
    } else {
 | 
						||
      return collision;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  hitTestCirclePoint
 | 
						||
  ------------------
 | 
						||
 | 
						||
  Use it to find out if a circular shape is touching a point
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY`, and `radius` properties.
 | 
						||
  b. A point object with `x` and `y` properties.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  hitTestCirclePoint(c1, point, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
 | 
						||
    //A point is just a circle with a diameter of
 | 
						||
    //1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
 | 
						||
    //Collision test. Just supply the point with the properties
 | 
						||
    //it needs
 | 
						||
    point.diameter = 1;
 | 
						||
    point.width = point.diameter;
 | 
						||
    point.radius = 0.5;
 | 
						||
    point.centerX = point.x;
 | 
						||
    point.centerY = point.y;
 | 
						||
    point.gx = point.x;
 | 
						||
    point.gy = point.y;
 | 
						||
    point.xAnchorOffset = 0;
 | 
						||
    point.yAnchorOffset = 0;
 | 
						||
    point._bumpPropertiesAdded = true;
 | 
						||
    return this.hitTestCircle(c1, point, global);
 | 
						||
  }
 | 
						||
 
 | 
						||
  /*
 | 
						||
  circleRectangleCollision
 | 
						||
  ------------------------
 | 
						||
 | 
						||
  Use it to bounce a circular shape off a rectangular shape
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
  b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  circleRectangleCollision(
 | 
						||
    c1, r1, bounce = false, global = false
 | 
						||
  ) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1); 
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
 | 
						||
    let region, collision, c1x, c1y, r1x, r1y;
 | 
						||
 | 
						||
    //Use either the global or local coordinates
 | 
						||
    if (global) {
 | 
						||
      c1x = c1.gx;
 | 
						||
      c1y = c1.gy;
 | 
						||
      r1x = r1.gx;
 | 
						||
      r1y = r1.gy;
 | 
						||
    } else {
 | 
						||
      c1x = c1.x;
 | 
						||
      c1y = c1.y;
 | 
						||
      r1x = r1.x;
 | 
						||
      r1y = r1.y;
 | 
						||
    }
 | 
						||
 | 
						||
    //Is the circle above the rectangle's top edge?
 | 
						||
    if (c1y - c1.yAnchorOffset < r1y - r1.halfHeight - r1.yAnchorOffset) {
 | 
						||
 | 
						||
      //If it is, we need to check whether it's in the 
 | 
						||
      //top left, top center or top right
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "topLeft";
 | 
						||
      } else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "topRight";
 | 
						||
      } else {
 | 
						||
        region = "topMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle isn't above the top edge, so it might be
 | 
						||
    //below the bottom edge
 | 
						||
    else if (c1y - c1.yAnchorOffset > r1y + r1.halfHeight - r1.yAnchorOffset) {
 | 
						||
 | 
						||
      //If it is, we need to check whether it's in the bottom left,
 | 
						||
      //bottom center, or bottom right
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "bottomLeft";
 | 
						||
      } else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "bottomRight";
 | 
						||
      } else {
 | 
						||
        region = "bottomMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle isn't above the top edge or below the bottom edge,
 | 
						||
    //so it must be on the left or right side
 | 
						||
    else {
 | 
						||
      if (c1x - c1.xAnchorOffset < r1x - r1.halfWidth - r1.xAnchorOffset) {
 | 
						||
        region = "leftMiddle";
 | 
						||
      } else {
 | 
						||
        region = "rightMiddle";
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    //Is this the circle touching the flat sides
 | 
						||
    //of the rectangle?
 | 
						||
    if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
 | 
						||
 | 
						||
      //Yes, it is, so do a standard rectangle vs. rectangle collision test
 | 
						||
      collision = this.rectangleCollision(c1, r1, bounce, global);
 | 
						||
    }
 | 
						||
 | 
						||
    //The circle is touching one of the corners, so do a
 | 
						||
    //circle vs. point collision test
 | 
						||
    else {
 | 
						||
      let point = {};
 | 
						||
 | 
						||
      switch (region) {
 | 
						||
        case "topLeft":
 | 
						||
          point.x = r1x - r1.xAnchorOffset;
 | 
						||
          point.y = r1y - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "topRight":
 | 
						||
          point.x = r1x + r1.width - r1.xAnchorOffset;
 | 
						||
          point.y = r1y - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "bottomLeft":
 | 
						||
          point.x = r1x - r1.xAnchorOffset;
 | 
						||
          point.y = r1y + r1.height - r1.yAnchorOffset;
 | 
						||
          break;
 | 
						||
 | 
						||
        case "bottomRight":
 | 
						||
          point.x = r1x + r1.width - r1.xAnchorOffset;
 | 
						||
          point.y = r1y + r1.height - r1.yAnchorOffset;
 | 
						||
      }
 | 
						||
 | 
						||
      //Check for a collision between the circle and the point
 | 
						||
      collision = this.circlePointCollision(c1, point, bounce, global);
 | 
						||
    }
 | 
						||
 | 
						||
    if (collision) {
 | 
						||
      return region;
 | 
						||
    } else {
 | 
						||
      return collision;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  circlePointCollision
 | 
						||
  --------------------
 | 
						||
 | 
						||
  Use it to boucnce a circle off a point.
 | 
						||
  Parameters: 
 | 
						||
  a. A sprite object with `centerX`, `centerY`, and `radius` properties.
 | 
						||
  b. A point object with `x` and `y` properties.
 | 
						||
 | 
						||
  */
 | 
						||
 | 
						||
  circlePointCollision(c1, point, bounce = false, global = false) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1); 
 | 
						||
 | 
						||
    //A point is just a circle with a diameter of
 | 
						||
    //1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
 | 
						||
    //Collision test. Just supply the point with the properties
 | 
						||
    //it needs
 | 
						||
    point.diameter = 1;
 | 
						||
    point.width = point.diameter;
 | 
						||
    point.radius = 0.5;
 | 
						||
    point.centerX = point.x;
 | 
						||
    point.centerY = point.y;
 | 
						||
    point.gx = point.x;
 | 
						||
    point.gy = point.y;
 | 
						||
    point.xAnchorOffset = 0;
 | 
						||
    point.yAnchorOffset = 0;
 | 
						||
    point._bumpPropertiesAdded = true;
 | 
						||
    return this.circleCollision(c1, point, bounce, global);
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  bounceOffSurface
 | 
						||
  ----------------
 | 
						||
 | 
						||
  Use this to bounce an object off another object.
 | 
						||
  Parameters: 
 | 
						||
  a. An object with `v.x` and `v.y` properties. This represents the object that is colliding
 | 
						||
  with a surface.
 | 
						||
  b. An object with `x` and `y` properties. This represents the surface that the object
 | 
						||
  is colliding into.
 | 
						||
  The first object can optionally have a mass property that's greater than 1. The mass will
 | 
						||
  be used to dampen the bounce effect.
 | 
						||
  */
 | 
						||
 | 
						||
  bounceOffSurface(o, s) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!o._bumpPropertiesAdded) this.addCollisionProperties(o); 
 | 
						||
 | 
						||
    let dp1, dp2,
 | 
						||
      p1 = {},
 | 
						||
      p2 = {},
 | 
						||
      bounce = {},
 | 
						||
      mass = o.mass || 1;
 | 
						||
 | 
						||
    //1. Calculate the collision surface's properties
 | 
						||
    //Find the surface vector's left normal
 | 
						||
    s.lx = s.y;
 | 
						||
    s.ly = -s.x;
 | 
						||
 | 
						||
    //Find its magnitude
 | 
						||
    s.magnitude = Math.sqrt(s.x * s.x + s.y * s.y);
 | 
						||
 | 
						||
    //Find its normalized values
 | 
						||
    s.dx = s.x / s.magnitude;
 | 
						||
    s.dy = s.y / s.magnitude;
 | 
						||
 | 
						||
    //2. Bounce the object (o) off the surface (s)
 | 
						||
 | 
						||
    //Find the dot product between the object and the surface
 | 
						||
    dp1 = o.vx * s.dx + o.vy * s.dy;
 | 
						||
 | 
						||
    //Project the object's velocity onto the collision surface
 | 
						||
    p1.vx = dp1 * s.dx;
 | 
						||
    p1.vy = dp1 * s.dy;
 | 
						||
 | 
						||
    //Find the dot product of the object and the surface's left normal (s.lx and s.ly)
 | 
						||
    dp2 = o.vx * (s.lx / s.magnitude) + o.vy * (s.ly / s.magnitude);
 | 
						||
 | 
						||
    //Project the object's velocity onto the surface's left normal
 | 
						||
    p2.vx = dp2 * (s.lx / s.magnitude);
 | 
						||
    p2.vy = dp2 * (s.ly / s.magnitude);
 | 
						||
 | 
						||
    //Reverse the projection on the surface's left normal
 | 
						||
    p2.vx *= -1;
 | 
						||
    p2.vy *= -1;
 | 
						||
 | 
						||
    //Add up the projections to create a new bounce vector
 | 
						||
    bounce.x = p1.vx + p2.vx;
 | 
						||
    bounce.y = p1.vy + p2.vy;
 | 
						||
 | 
						||
    //Assign the bounce vector to the object's velocity
 | 
						||
    //with optional mass to dampen the effect
 | 
						||
    o.vx = bounce.x / mass;
 | 
						||
    o.vy = bounce.y / mass;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  contain
 | 
						||
  -------
 | 
						||
  `contain` can be used to contain a sprite with `x` and
 | 
						||
  `y` properties inside a rectangular area.
 | 
						||
 | 
						||
  The `contain` function takes four arguments: a sprite with `x` and `y`
 | 
						||
  properties, an object literal with `x`, `y`, `width` and `height` properties. The 
 | 
						||
  third argument is a Boolean (true/false) value that determines if the sprite
 | 
						||
  should bounce when it hits the edge of the container. The fourth argument
 | 
						||
  is an extra user-defined callback function that you can call when the
 | 
						||
  sprite hits the container
 | 
						||
  ```js
 | 
						||
  contain(anySprite, {x: 0, y: 0, width: 512, height: 512}, true, callbackFunction);
 | 
						||
  ```
 | 
						||
  The code above will contain the sprite's position inside the 512 by
 | 
						||
  512 pixel area defined by the object. If the sprite hits the edges of
 | 
						||
  the container, it will bounce. The `callBackFunction` will run if 
 | 
						||
  there's a collision.
 | 
						||
 | 
						||
  An additional feature of the `contain` method is that if the sprite
 | 
						||
  has a `mass` property, it will be used to dampen the sprite's bounce
 | 
						||
  in a natural looking way.
 | 
						||
 | 
						||
  If the sprite bumps into any of the containing object's boundaries,
 | 
						||
  the `contain` function will return a value that tells you which side
 | 
						||
  the sprite bumped into: “left”, “top”, “right” or “bottom”. Here's how
 | 
						||
  you could keep the sprite contained and also find out which boundary
 | 
						||
  it hit:
 | 
						||
  ```js
 | 
						||
  //Contain the sprite and find the collision value
 | 
						||
  let collision = contain(anySprite, {x: 0, y: 0, width: 512, height: 512});
 | 
						||
 | 
						||
  //If there's a collision, display the boundary that the collision happened on
 | 
						||
  if(collision) {
 | 
						||
    if collision.has("left") console.log("The sprite hit the left");  
 | 
						||
    if collision.has("top") console.log("The sprite hit the top");  
 | 
						||
    if collision.has("right") console.log("The sprite hit the right");  
 | 
						||
    if collision.has("bottom") console.log("The sprite hit the bottom");  
 | 
						||
  }
 | 
						||
  ```
 | 
						||
  If the sprite doesn't hit a boundary, the value of
 | 
						||
  `collision` will be `undefined`. 
 | 
						||
  */
 | 
						||
 | 
						||
 /*
 | 
						||
  contain(sprite, container, bounce = false, extra = undefined) {
 | 
						||
 | 
						||
    //Helper methods that compensate for any possible shift the the
 | 
						||
    //sprites' anchor points
 | 
						||
    let nudgeAnchor = (o, value, axis) => {
 | 
						||
      if (o.anchor !== undefined) {
 | 
						||
        if (o.anchor[axis] !== 0) {
 | 
						||
          return value * ((1 - o.anchor[axis]) - o.anchor[axis]);
 | 
						||
        } else {
 | 
						||
          return value;
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        return value; 
 | 
						||
      }
 | 
						||
    };
 | 
						||
 | 
						||
    let compensateForAnchor = (o, value, axis) => {
 | 
						||
      if (o.anchor !== undefined) {
 | 
						||
        if (o.anchor[axis] !== 0) {
 | 
						||
          return value * o.anchor[axis];
 | 
						||
        } else {
 | 
						||
          return 0;
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        return 0; 
 | 
						||
      }
 | 
						||
    };
 | 
						||
 | 
						||
    let compensateForAnchors = (a, b, property1, property2) => {
 | 
						||
       return compensateForAnchor(a, a[property1], property2) + compensateForAnchor(b, b[property1], property2)
 | 
						||
    };    
 | 
						||
    //Create a set called `collision` to keep track of the
 | 
						||
    //boundaries with which the sprite is colliding
 | 
						||
    let collision = new Set();
 | 
						||
 | 
						||
    //Left
 | 
						||
    if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") < container.x - sprite.parent.gx - compensateForAnchor(container, container.width, "x")) {
 | 
						||
      //Bounce the sprite if `bounce` is true
 | 
						||
      if (bounce) sprite.vx *= -1;
 | 
						||
 | 
						||
      //If the sprite has `mass`, let the mass
 | 
						||
      //affect the sprite's velocity
 | 
						||
      if(sprite.mass) sprite.vx /= sprite.mass;
 | 
						||
 | 
						||
      //Keep the sprite inside the container
 | 
						||
      sprite.x = container.x - sprite.parent.gx + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
 | 
						||
 | 
						||
      //Add "left" to the collision set
 | 
						||
      collision.add("left");
 | 
						||
    }
 | 
						||
 | 
						||
    //Top
 | 
						||
    if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") < container.y - sprite.parent.gy - compensateForAnchor(container, container.height, "y")) {
 | 
						||
      if (bounce) sprite.vy *= -1;
 | 
						||
      if(sprite.mass) sprite.vy /= sprite.mass;
 | 
						||
      sprite.y = container.x - sprite.parent.gy + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
 | 
						||
      collision.add("top");
 | 
						||
    }
 | 
						||
 | 
						||
    //Right
 | 
						||
    if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") + sprite.width > container.width - compensateForAnchor(container, container.width, "x")) {
 | 
						||
      if (bounce) sprite.vx *= -1;
 | 
						||
      if(sprite.mass) sprite.vx /= sprite.mass;
 | 
						||
      sprite.x = container.width - sprite.width + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
 | 
						||
      collision.add("right");
 | 
						||
    }
 | 
						||
 | 
						||
    //Bottom
 | 
						||
    if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") + sprite.height > container.height - compensateForAnchor(container, container.height, "y")) {
 | 
						||
      if (bounce) sprite.vy *= -1;
 | 
						||
      if(sprite.mass) sprite.vy /= sprite.mass;
 | 
						||
      sprite.y = container.height - sprite.height + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
 | 
						||
      collision.add("bottom");
 | 
						||
    }
 | 
						||
 | 
						||
    //If there were no collisions, set `collision` to `undefined`
 | 
						||
    if (collision.size === 0) collision = undefined;
 | 
						||
 | 
						||
    //The `extra` function runs if there was a collision
 | 
						||
    //and `extra` has been defined
 | 
						||
    if (collision && extra) extra(collision);
 | 
						||
 | 
						||
    //Return the `collision` value
 | 
						||
    return collision;
 | 
						||
  }
 | 
						||
  */
 | 
						||
  contain(sprite, container, bounce = false, extra = undefined) {
 | 
						||
 | 
						||
    //Add collision properties
 | 
						||
    if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite); 
 | 
						||
 | 
						||
    //Give the container x and y anchor offset values, if it doesn't
 | 
						||
    //have any
 | 
						||
    if (container.xAnchorOffset === undefined) container.xAnchorOffset = 0;
 | 
						||
    if (container.yAnchorOffset === undefined) container.yAnchorOffset = 0;
 | 
						||
    if (sprite.parent.gx === undefined) sprite.parent.gx = 0;
 | 
						||
    if (sprite.parent.gy === undefined) sprite.parent.gy = 0;
 | 
						||
 | 
						||
    //Create a Set called `collision` to keep track of the
 | 
						||
    //boundaries with which the sprite is colliding
 | 
						||
    let collision = new Set();
 | 
						||
 | 
						||
    //Left
 | 
						||
    if (sprite.x - sprite.xAnchorOffset < container.x - sprite.parent.gx - container.xAnchorOffset) {
 | 
						||
 | 
						||
      //Bounce the sprite if `bounce` is true
 | 
						||
      if (bounce) sprite.vx *= -1;
 | 
						||
 | 
						||
      //If the sprite has `mass`, let the mass
 | 
						||
      //affect the sprite's velocity
 | 
						||
      if (sprite.mass) sprite.vx /= sprite.mass;
 | 
						||
 | 
						||
      //Reposition the sprite inside the container
 | 
						||
      sprite.x = container.x - sprite.parent.gx - container.xAnchorOffset + sprite.xAnchorOffset;
 | 
						||
 | 
						||
      //Make a record of the side which the container hit
 | 
						||
      collision.add("left");
 | 
						||
    }
 | 
						||
 | 
						||
    //Top
 | 
						||
    if (sprite.y - sprite.yAnchorOffset < container.y - sprite.parent.gy - container.yAnchorOffset) {
 | 
						||
      if (bounce) sprite.vy *= -1;
 | 
						||
      if (sprite.mass) sprite.vy /= sprite.mass;
 | 
						||
      sprite.y = container.y - sprite.parent.gy - container.yAnchorOffset + sprite.yAnchorOffset;;
 | 
						||
      collision.add("top");
 | 
						||
    }
 | 
						||
 | 
						||
    //Right
 | 
						||
    if (sprite.x - sprite.xAnchorOffset + sprite.width > container.width - container.xAnchorOffset) {
 | 
						||
      if (bounce) sprite.vx *= -1;
 | 
						||
      if (sprite.mass) sprite.vx /= sprite.mass;
 | 
						||
      sprite.x = container.width - sprite.width - container.xAnchorOffset + sprite.xAnchorOffset;
 | 
						||
      collision.add("right");
 | 
						||
    }
 | 
						||
 | 
						||
    //Bottom
 | 
						||
    if (sprite.y - sprite.yAnchorOffset + sprite.height > container.height - container.yAnchorOffset) {
 | 
						||
      if (bounce) sprite.vy *= -1;
 | 
						||
      if (sprite.mass) sprite.vy /= sprite.mass;
 | 
						||
      sprite.y = container.height - sprite.height - container.yAnchorOffset + sprite.yAnchorOffset;
 | 
						||
      collision.add("bottom");
 | 
						||
    }
 | 
						||
 | 
						||
    //If there were no collisions, set `collision` to `undefined`
 | 
						||
    if (collision.size === 0) collision = undefined;
 | 
						||
 | 
						||
    //The `extra` function runs if there was a collision
 | 
						||
    //and `extra` has been defined
 | 
						||
    if (collision && extra) extra(collision);
 | 
						||
 | 
						||
    //Return the `collision` value
 | 
						||
    return collision;
 | 
						||
  } 
 | 
						||
 | 
						||
  //`outsideBounds` checks whether a sprite is outide the boundary of
 | 
						||
  //another object. It returns an object called `collision`. `collision` will be `undefined` if there's no
 | 
						||
  //collision. But if there is a collision, `collision` will be
 | 
						||
  //returned as a Set containg strings that tell you which boundary
 | 
						||
  //side was crossed: "left", "right", "top" or "bottom" 
 | 
						||
  outsideBounds(s, bounds, extra) {
 | 
						||
 | 
						||
    let x = bounds.x,
 | 
						||
        y = bounds.y,
 | 
						||
        width = bounds.width,
 | 
						||
        height = bounds.height;
 | 
						||
 | 
						||
    //The `collision` object is used to store which
 | 
						||
    //side of the containing rectangle the sprite hits
 | 
						||
    let collision = new Set();
 | 
						||
 | 
						||
    //Left
 | 
						||
    if (s.x < x - s.width) {
 | 
						||
      collision.add("left");
 | 
						||
    }
 | 
						||
    //Top
 | 
						||
    if (s.y < y - s.height) {
 | 
						||
      collision.add("top");
 | 
						||
    }
 | 
						||
    //Right
 | 
						||
    if (s.x > width + s.width) {
 | 
						||
      collision.add("right");
 | 
						||
    }
 | 
						||
    //Bottom
 | 
						||
    if (s.y > height + s.height) {
 | 
						||
      collision.add("bottom");
 | 
						||
    }
 | 
						||
 | 
						||
    //If there were no collisions, set `collision` to `undefined`
 | 
						||
    if (collision.size === 0) collision = undefined;
 | 
						||
 | 
						||
    //The `extra` function runs if there was a collision
 | 
						||
    //and `extra` has been defined
 | 
						||
    if (collision && extra) extra(collision);
 | 
						||
 | 
						||
    //Return the `collision` object
 | 
						||
    return collision;
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
  _getCenter
 | 
						||
  ----------
 | 
						||
 | 
						||
  A utility that finds the center point of the sprite. If it's anchor point is the
 | 
						||
  sprite's top left corner, then the center is calculated from that point.
 | 
						||
  If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center
 | 
						||
  */
 | 
						||
 | 
						||
  _getCenter(o, dimension, axis) {
 | 
						||
    if (o.anchor !== undefined) {
 | 
						||
      if (o.anchor[axis] !== 0) {
 | 
						||
        return 0;
 | 
						||
      } else {
 | 
						||
        //console.log(o.anchor[axis])
 | 
						||
        return dimension / 2;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      return dimension; 
 | 
						||
    }
 | 
						||
  }
 | 
						||
  
 | 
						||
 | 
						||
  /*
 | 
						||
  hit
 | 
						||
  ---
 | 
						||
  A convenient universal collision function to test for collisions
 | 
						||
  between rectangles, circles, and points.
 | 
						||
  */
 | 
						||
 | 
						||
  hit(a, b, react = false, bounce = false, global, extra = undefined) {
 | 
						||
 | 
						||
    //Local references to bump's collision methods
 | 
						||
    let hitTestPoint = this.hitTestPoint.bind(this),
 | 
						||
        hitTestRectangle = this.hitTestRectangle.bind(this),
 | 
						||
        hitTestCircle = this.hitTestCircle.bind(this),
 | 
						||
        movingCircleCollision = this.movingCircleCollision.bind(this),
 | 
						||
        circleCollision = this.circleCollision.bind(this),
 | 
						||
        hitTestCircleRectangle = this.hitTestCircleRectangle.bind(this),
 | 
						||
        rectangleCollision = this.rectangleCollision.bind(this),
 | 
						||
        circleRectangleCollision = this.circleRectangleCollision.bind(this);
 | 
						||
 | 
						||
    let collision,
 | 
						||
      aIsASprite = a.parent !== undefined,
 | 
						||
      bIsASprite = b.parent !== undefined;
 | 
						||
 | 
						||
    //Check to make sure one of the arguments isn't an array
 | 
						||
    if (aIsASprite && b instanceof Array || bIsASprite && a instanceof Array) {
 | 
						||
      //If it is, check for a collision between a sprite and an array
 | 
						||
      spriteVsArray();
 | 
						||
    } else {
 | 
						||
      //If one of the arguments isn't an array, find out what type of
 | 
						||
      //collision check to run
 | 
						||
      collision = findCollisionType(a, b);
 | 
						||
      if (collision && extra) extra(collision);
 | 
						||
    }
 | 
						||
 | 
						||
    //Return the result of the collision.
 | 
						||
    //It will be `undefined` if there's no collision and `true` if 
 | 
						||
    //there is a collision. `rectangleCollision` sets `collsision` to
 | 
						||
    //"top", "bottom", "left" or "right" depeneding on which side the
 | 
						||
    //collision is occuring on
 | 
						||
    return collision;
 | 
						||
 | 
						||
    function findCollisionType(a, b) {
 | 
						||
      //Are `a` and `b` both sprites?
 | 
						||
      //(We have to check again if this function was called from
 | 
						||
      //`spriteVsArray`)
 | 
						||
      let aIsASprite = a.parent !== undefined;
 | 
						||
      let bIsASprite = b.parent !== undefined;
 | 
						||
 | 
						||
      if (aIsASprite && bIsASprite) {
 | 
						||
        //Yes, but what kind of sprites?
 | 
						||
        if (a.diameter && b.diameter) {
 | 
						||
          //They're circles
 | 
						||
          return circleVsCircle(a, b);
 | 
						||
        } else if (a.diameter && !b.diameter) {
 | 
						||
          //The first one is a circle and the second is a rectangle
 | 
						||
          return circleVsRectangle(a, b);
 | 
						||
        } else {
 | 
						||
          //They're rectangles
 | 
						||
          return rectangleVsRectangle(a, b);
 | 
						||
        }
 | 
						||
      }
 | 
						||
      //They're not both sprites, so what are they?
 | 
						||
      //Is `a` not a sprite and does it have x and y properties?
 | 
						||
      else if (bIsASprite && !(a.x === undefined) && !(a.y === undefined)) {
 | 
						||
        //Yes, so this is a point vs. sprite collision test
 | 
						||
        return hitTestPoint(a, b);
 | 
						||
      } else {
 | 
						||
        //The user is trying to test some incompatible objects
 | 
						||
        throw new Error(`I'm sorry, ${a} and ${b} cannot be use together in a collision test.'`);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    function spriteVsArray() {
 | 
						||
      //If `a` happens to be the array, flip it around so that it becomes `b`
 | 
						||
      if (a instanceof Array) {
 | 
						||
        let [a, b] = [b, a];
 | 
						||
      }
 | 
						||
      //Loop through the array in reverse
 | 
						||
      for (let i = b.length - 1; i >= 0; i--) {
 | 
						||
        let sprite = b[i];
 | 
						||
        collision = findCollisionType(a, sprite);
 | 
						||
        if (collision && extra) extra(collision, sprite);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    function circleVsCircle(a, b) {
 | 
						||
      //If the circles shouldn't react to the collision,
 | 
						||
      //just test to see if they're touching
 | 
						||
      if (!react) {
 | 
						||
        return hitTestCircle(a, b);
 | 
						||
      }
 | 
						||
      //Yes, the circles should react to the collision
 | 
						||
      else {
 | 
						||
        //Are they both moving?
 | 
						||
        if (a.vx + a.vy !== 0 && b.vx + b.vy !== 0) {
 | 
						||
          //Yes, they are both moving
 | 
						||
          //(moving circle collisions always bounce apart so there's
 | 
						||
          //no need for the third, `bounce`, argument)
 | 
						||
          return movingCircleCollision(a, b, global);
 | 
						||
        } else {
 | 
						||
          //No, they're not both moving
 | 
						||
          return circleCollision(a, b, bounce, global);
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    function rectangleVsRectangle(a, b) {
 | 
						||
      //If the rectangles shouldn't react to the collision, just
 | 
						||
      //test to see if they're touching
 | 
						||
      if (!react) {
 | 
						||
        return hitTestRectangle(a, b, global);
 | 
						||
      } else {
 | 
						||
        return rectangleCollision(a, b, bounce, global);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    function circleVsRectangle(a, b) {
 | 
						||
      //If the rectangles shouldn't react to the collision, just
 | 
						||
      //test to see if they're touching
 | 
						||
      if (!react) {
 | 
						||
        return hitTestCircleRectangle(a, b, global);
 | 
						||
      } else {
 | 
						||
        return circleRectangleCollision(a, b, bounce, global);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
} |