{"action":"created","comment":{"url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/comments/86642565","pull_request_review_id":7294177,"id":86642565,"diff_hunk":"@@ -4338,4 +3909,1314 @@ p5.prototype._warn = function(message) {\n   }\n };\n \n+  /**\n+   * Collision Shape Base Class\n+   *\n+   * We have a set of collision shapes available that all conform to\n+   * a simple interface so that they can be checked against one another\n+   * using the Separating Axis Theorem.\n+   *\n+   * This base class implements all the required methods for a collision\n+   * shape and can be used as a collision point with no changes.\n+   * Other shapes should inherit from this and override most methods.\n+   *\n+   * @class p5.CollisionShape\n+   * @constructor\n+   * @param {p5.Vector} [center] (zero if omitted)\n+   * @param {number} [rotation] (zero if omitted)\n+   */\n+  p5.CollisionShape = function(center, rotation) {\n+    /**\n+     * Transform of this shape relative to its parent.  If there is no parent,\n+     * this is pretty much the world-space transform.\n+     * This should stay consistent with _offset, _rotation and _scale properties.\n+     * @property _localTransform\n+     * @type {p5.Transform2D}\n+     * @protected\n+     */\n+    this._localTransform = new p5.Transform2D();\n+    if (rotation) {\n+      this._localTransform.rotate(rotation);\n+    }\n+    if (center) {\n+      this._localTransform.translate(center);\n+    }\n+\n+    /**\n+     * Transform of whatever parent object (probably a sprite) this shape is\n+     * associated with.  If this is a free-floating shape, the parent transform\n+     * will remain an identity matrix.\n+     * @property _parentTransform\n+     * @type {p5.Transform2D}\n+     * @protected\n+     */\n+    this._parentTransform = new p5.Transform2D();\n+\n+    /**\n+     * The center of the collision shape in world-space.\n+     * @property _center\n+     * @private\n+     * @type {p5.Vector}\n+     */\n+    this._center = new p5.Vector();\n+\n+    /**\n+     * The center of the collision shape in local-space; also, the offset of the\n+     * collision shape's center from its parent sprite's center.\n+     * @property _offset\n+     * @type {p5.Vector}\n+     * @private\n+     */\n+    this._offset = new p5.Vector();\n+\n+    /**\n+     * Rotation in radians in local space (relative to parent).\n+     * Note that this will only be meaningful for shapes that can rotate,\n+     * i.e. Oriented Bounding Boxes\n+     * @property _rotation\n+     * @private\n+     * @type {number}\n+     */\n+    this._rotation = 0;\n+\n+    /**\n+     * Scale X and Y in local space.  Note that this will only be meaningful\n+     * for shapes that have dimensions (e.g. not for point colliders)\n+     * @property _scale\n+     * @type {p5.Vector}\n+     * @private\n+     */\n+    this._scale = new p5.Vector(1, 1);\n+\n+    /**\n+     * If true, when calling `updateFromSprite` this collider will adopt the\n+     * base dimensions of the sprite in addition to adopting its transform.\n+     * If false, only the transform (position/rotation/scale) will be adopted.\n+     * @property getsDimensionsFromSprite\n+     * @type {boolean}\n+     */\n+    this.getsDimensionsFromSprite = false;\n+\n+    // Public getters/setters\n+    Object.defineProperties(this, {\n+\n+      /**\n+       * The center of the collision shape in world-space.\n+       * Note: You can set this property with a value in world-space, but it will\n+       * actually modify the collision shape's local transform.\n+       * @property center\n+       * @type {p5.Vector}\n+       */\n+      'center': {\n+        enumerable: true,\n+        get: function() {\n+          return this._center.copy();\n+        }.bind(this),\n+        set: function(c) {\n+          this._localTransform\n+            .translate(p5.Vector.mult(this._center, -1))\n+            .translate(c);\n+          this._onTransformChanged();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * The center of the collision shape in local-space - if this collider is\n+       * owned by a sprite, the offset of the collider center from the sprite center.\n+       * @property offset\n+       * @type {p5.Vector}\n+       */\n+      'offset': {\n+        enumerable: true,\n+        get: function() {\n+          return this._offset.copy();\n+        }.bind(this),\n+        set: function(o) {\n+          this._localTransform\n+            .translate(p5.Vector.mult(this._offset, -1))\n+            .translate(o);\n+          this._onTransformChanged();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * The local-space rotation of the collider, in radians.\n+       * @property rotation\n+       * @type {number}\n+       */\n+      'rotation': {\n+        enumerable: true,\n+        get: function() {\n+          return this._rotation;\n+        }.bind(this),\n+        set: function(r) {\n+          this._localTransform\n+            .clear()\n+            .scale(this._scale)\n+            .rotate(r)\n+            .translate(this._offset);\n+          this._onTransformChanged();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * The local-space scale of the collider\n+       * @property scale\n+       * @type {p5.Vector}\n+       */\n+      'scale': {\n+        enumerable: true,\n+        get: function() {\n+          return this._scale.copy();\n+        }.bind(this),\n+        set: function(s) {\n+          this._localTransform\n+            .clear()\n+            .scale(s)\n+            .rotate(this._rotation)\n+            .translate(this._offset);\n+          this._onTransformChanged();\n+        }.bind(this)\n+      }\n+    });\n+\n+    this._onTransformChanged();\n+  };\n+\n+  /**\n+   * Update this collider based on the properties of a parent Sprite.\n+   * Descendant classes should override this method to adopt the dimensions\n+   * of the sprite if `getsDimensionsFromSprite` is true.\n+   * @method updateFromSprite\n+   * @param {Sprite} sprite\n+   * @see p5.CollisionShape.prototype.getsDimensionsFromSprite\n+   */\n+  p5.CollisionShape.prototype.updateFromSprite = function(sprite) {\n+    this.setParentTransform(sprite);\n+  };\n+\n+  /**\n+   * Update this collider's parent transform, which will in turn adjust its\n+   * position, rotation and scale in world-space and recompute cached values\n+   * if necessary.\n+   * If a Sprite is passed as the 'parent' then a new transform will be computed\n+   * from the sprite's position/rotation/scale and used.\n+   * @method setParentTransform\n+   * @param {p5.Transform2D|Sprite} parent\n+   */\n+  p5.CollisionShape.prototype.setParentTransform = function(parent) {\n+    if (parent instanceof Sprite) {\n+      this._parentTransform\n+        .clear()\n+        .scale(parent.scale) // TODO: ScaleX and ScaleY?\n+        .rotate(radians(parent.rotation))\n+        .translate(parent.position);\n+    } else if (parent instanceof p5.Transform2D) {\n+      this._parentTransform = parent.copy();\n+    } else {\n+      throw new TypeError('Bad argument to setParentTransform: ' + parent);\n+    }\n+    this._onTransformChanged();\n+  };\n+\n+  /**\n+   * Recalculate cached properties, relevant vectors, etc. when at least one\n+   * of the shape's transforms changes.  The base CollisionShape (and CollisionPoint)\n+   * only need to recompute the shape's center, but other shapes may need to\n+   * override this method and do additional recomputation.\n+   * @method _onTransformChanged\n+   * @protected\n+   */\n+  p5.CollisionShape.prototype._onTransformChanged = function() {\n+    // Recompute internal properties from transforms\n+\n+    // Rotation in local space\n+    this._rotation = this._localTransform.getRotation();\n+\n+    // Scale in local space\n+    this._scale = this._localTransform.getScale();\n+\n+    // Offset in local-space\n+    this._offset\n+      .set(0, 0)\n+      .transform(this._localTransform);\n+\n+    // Center in world-space\n+    this._center\n+      .set(this._offset.x, this._offset.y)\n+      .transform(this._parentTransform);\n+  };\n+\n+  /**\n+   * Compute the smallest movement needed to move this collision shape out of\n+   * another collision shape.  If the shapes are not overlapping, returns a\n+   * zero vector to indicate that no displacement is necessary.\n+   * @method collide\n+   * @param {p5.CollisionShape} other\n+   * @return {p5.Vector}\n+   */\n+  p5.CollisionShape.prototype.collide = function(other) {\n+    var displacee = this, displacer = other;\n+\n+    // Compute a displacement vector using the Separating Axis Theorem\n+    // (Valid only for convex shapes)\n+    //\n+    // If a line (axis) exists on which the two shapes' orthogonal projections\n+    // do not overlap, then the shapes do not overlap.  If the shapes'\n+    // projections do overlap on all candidate axes, the axis that had the\n+    // smallest overlap gives us the smallest possible displacement.\n+    //\n+    // @see http://www.dyn4j.org/2010/01/sat/\n+    var smallestOverlap = Infinity;\n+    var smallestOverlapAxis = null;\n+\n+    // We speed things up with an additional assumption that all collision\n+    // shapes are centrosymmetric: Circles, ellipses, and rectangles\n+    // are OK.  This lets us only compare the shapes' radii to the\n+    // distance between their centers, even for non-circular shapes.\n+    // Other convex shapes, (triangles, pentagons) will require more\n+    // complex use of their projections' positions on the axis.\n+    var deltaOfCenters = p5.Vector.sub(displacer.center, displacee.center);\n+\n+    // It turns out we only need to check a few axes, defined by the shapes\n+    // being checked.  For a polygon, the normal of each face is a possible\n+    // separating axis.\n+    var candidateAxes = deduplicateParallelVectors(displacee._getCandidateAxes(displacer).concat(displacer._getCandidateAxes(displacee)));\n+    var axis, deltaOfCentersOnAxis, distanceOfCentersOnAxis;\n+    for (var i = 0; i < candidateAxes.length; i++) {\n+      axis = candidateAxes[i];\n+\n+      // If distance between the shape's centers as projected onto the\n+      // separating axis is larger than the combined radii of the shapes\n+      // projected onto the axis, the shapes do not overlap on this axis.\n+      deltaOfCentersOnAxis = p5.Vector.project(deltaOfCenters, axis);\n+      distanceOfCentersOnAxis = deltaOfCentersOnAxis.mag();\n+      var r1 = displacee._getRadiusOnAxis(axis);\n+      var r2 = displacer._getRadiusOnAxis(axis);\n+      var overlap = r1 + r2 - distanceOfCentersOnAxis;\n+      if (overlap <= 0) {\n+        // These shapes are separated along this axis.\n+        // Early-out, returning a zero-vector displacement.\n+        return new p5.Vector();\n+      } else if (overlap < smallestOverlap) {\n+        // This is the smallest overlap we've found so far - store some\n+        // information about it, which we can use to give the smallest\n+        // displacement when we're done.\n+        smallestOverlap = overlap;\n+        // Normally use the delta of centers, which gives us direction along\n+        // with an axis.  In the rare case that the centers exactly overlap,\n+        // just use the original axis\n+        if (deltaOfCentersOnAxis.x === 0 && deltaOfCentersOnAxis.y === 0) {\n+          smallestOverlapAxis = axis;\n+        } else {\n+          smallestOverlapAxis = deltaOfCentersOnAxis;\n+        }\n+      }\n+    }\n+\n+    // If we make it here, we overlap on all possible axes and we\n+    // can compute the smallest vector that will displace this out of other.\n+    return smallestOverlapAxis.setMag(-smallestOverlap);\n+  };\n+\n+  /**\n+   * Check whether this shape overlaps another.\n+   * @method overlap\n+   * @param {p5.CollisionShape} other\n+   * @return {boolean}\n+   */\n+  p5.CollisionShape.prototype.overlap = function(other) {\n+    var displacement = this.collide(other);\n+    return displacement.x !== 0 || displacement.y !== 0;\n+  };\n+\n+  /*\n+   * Remove duplicate elements from an array without changing its order.\n+   * @param {Array.<p5.Vector>} array\n+   * @return {Array}\n+   */\n+  function deduplicateParallelVectors(array) {\n+    return array.filter(function(item, itemPos) {\n+      return !array.some(function(other, otherPos) {\n+        return itemPos < otherPos && item.isParallel(other);\n+      });\n+    });\n+  }\n+\n+  /**\n+   * Compute candidate separating axes relative to another object.\n+   * Override this method in subclasses to implement collision behavior.\n+   * @method _getCandidateAxes\n+   * @protected\n+   * @return {Array.<p5.Vector>}\n+   */\n+  p5.CollisionShape.prototype._getCandidateAxes = function() {\n+    return [];\n+  };\n+\n+  /**\n+   * Get this shape's radius (half-width of its projection) along the given axis.\n+   * Override this method in subclasses to implement collision behavior.\n+   * @method _getRadiusOnAxis\n+   * @protected\n+   * @param {p5.Vector} axis\n+   * @return {number}\n+   */\n+  p5.CollisionShape.prototype._getRadiusOnAxis = function() {\n+    return 0;\n+  };\n+\n+  /**\n+   * @property WORLD_AXES\n+   * @type {Array.<p5.Vector>}\n+   * @static\n+   * @final\n+   */\n+  p5.CollisionShape.WORLD_AXES = [\n+    new p5.Vector(1, 0),\n+    new p5.Vector(0, 1)\n+  ];\n+\n+  /**\n+   * Get world-space axis-aligned bounds information for this collision shape.\n+   * Used primarily for the quadtree.\n+   * @method getBoundingBox\n+   * @return {{top: number, bottom: number, left: number, right: number, width: number, height: number}}\n+   */\n+  p5.CollisionShape.prototype.getBoundingBox = function() {\n+    var radiusOnX = this._getRadiusOnAxis(p5.CollisionShape.WORLD_AXES[0]);\n+    var radiusOnY = this._getRadiusOnAxis(p5.CollisionShape.WORLD_AXES[1]);\n+    return {\n+      top: this.center - radiusOnY,\n+      bottom: this.center + radiusOnY,\n+      left: this.center - radiusOnX,\n+      right: this.center + radiusOnX,\n+      width: radiusOnX * 2,\n+      height: radiusOnY * 2\n+    };\n+  };\n+\n+  /**\n+   * A point collision shape, used to detect overlap and displacement vectors\n+   * vs other collision shapes.\n+   * @class p5.CollisionPoint\n+   * @constructor\n+   * @extends p5.CollisionShape\n+   * @param {p5.Vector} center\n+   */\n+  p5.CollisionPoint = function(center) {\n+    p5.CollisionShape.call(this, center);\n+  };\n+  p5.CollisionPoint.prototype = Object.create(p5.CollisionShape.prototype);\n+\n+  /**\n+   * Construct a new CollisionPoint with given offset for the given sprite.\n+   * @method createFromSprite\n+   * @static\n+   * @param {Sprite} sprite\n+   * @param {p5.Vector} [offset] from the sprite's center\n+   * @return {p5.CollisionPoint}\n+   */\n+  p5.CollisionPoint.createFromSprite = function(sprite, offset) {\n+    // Create the collision shape at the transformed offset\n+    var shape = new p5.CollisionPoint(offset);\n+    shape.setParentTransform(sprite);\n+    return shape;\n+  };\n+\n+  /**\n+   * Debug-draw this point collider\n+   * @method draw\n+   * @param {p5} sketch instance to use for drawing\n+   */\n+  p5.CollisionPoint.prototype.draw = function(sketch) {\n+    sketch.push();\n+    sketch.rectMode(sketch.CENTER);\n+    sketch.translate(this.center.x, this.center.y);\n+    sketch.noStroke();\n+    sketch.fill(0, 255, 0);\n+    sketch.ellipse(0, 0, 2, 2);\n+    sketch.pop();\n+  };\n+\n+  /**\n+   * A Circle collision shape, used to detect overlap and displacement vectors\n+   * with other collision shapes.\n+   * @class p5.CollisionCircle\n+   * @constructor\n+   * @extends p5.CollisionShape\n+   * @param {p5.Vector} center\n+   * @param {number} radius\n+   */\n+  p5.CollisionCircle = function(center, radius) {\n+    p5.CollisionShape.call(this, center);\n+\n+    /**\n+     * The unscaled radius of the circle collider.\n+     * @property radius\n+     * @type {number}\n+     */\n+    this.radius = radius;\n+\n+    /**\n+     * Final radius of this circle after being scaled by parent and local transforms,\n+     * cached so we don't recalculate it all the time.\n+     * @property _scaledRadius\n+     * @type {number}\n+     * @private\n+     */\n+    this._scaledRadius = 0;\n+\n+    this._computeScaledRadius();\n+  };\n+  p5.CollisionCircle.prototype = Object.create(p5.CollisionShape.prototype);\n+\n+  /**\n+   * Construct a new CollisionCircle with given offset for the given sprite.\n+   * @method createFromSprite\n+   * @static\n+   * @param {Sprite} sprite\n+   * @param {p5.Vector} [offset] from the sprite's center\n+   * @param {number} [radius]\n+   * @return {p5.CollisionCircle}\n+   */\n+  p5.CollisionCircle.createFromSprite = function(sprite, offset, radius) {\n+    var customSize = typeof radius === 'number';\n+    var shape = new p5.CollisionCircle(\n+      offset,\n+      customSize ? radius : 1\n+    );\n+    shape.getsDimensionsFromSprite = !customSize;\n+    shape.updateFromSprite(sprite);\n+    return shape;\n+  };\n+\n+  /**\n+   * Update this collider based on the properties of a parent Sprite.\n+   * @method updateFromSprite\n+   * @param {Sprite} sprite\n+   * @see p5.CollisionShape.prototype.getsDimensionsFromSprite\n+   */\n+  p5.CollisionCircle.prototype.updateFromSprite = function(sprite) {\n+    if (this.getsDimensionsFromSprite) {\n+      this.radius = Math.max(sprite.width, sprite.height)/2;\n+    }\n+    this.setParentTransform(sprite);\n+  };\n+\n+  /**\n+   * Recalculate cached properties, relevant vectors, etc. when at least one\n+   * of the shape's transforms changes.  The base CollisionShape (and CollisionPoint)\n+   * only need to recompute the shape's center, but other shapes may need to\n+   * override this method and do additional recomputation.\n+   * @method _onTransformChanged\n+   * @protected\n+   */\n+  p5.CollisionCircle.prototype._onTransformChanged = function() {\n+    p5.CollisionShape.prototype._onTransformChanged.call(this);\n+    this._computeScaledRadius();\n+  };\n+\n+  /**\n+   * Call to update the cached scaled radius value.\n+   * @method _computeScaledRadius\n+   * @private\n+   */\n+  p5.CollisionCircle.prototype._computeScaledRadius = function() {\n+    this._scaledRadius = new p5.Vector(this.radius, 0)\n+      .transform(this._localTransform)\n+      .transform(this._parentTransform)\n+      .sub(this.center)\n+      .mag();\n+  };\n+\n+  /**\n+   * Debug-draw this collision shape.\n+   * @method draw\n+   * @param {p5} sketch instance to use for drawing\n+   */\n+  p5.CollisionCircle.prototype.draw = function(sketch) {\n+    sketch.push();\n+    sketch.noFill();\n+    sketch.stroke(0, 255, 0);\n+    sketch.rectMode(sketch.CENTER);\n+    sketch.ellipse(this.center.x, this.center.y, this._scaledRadius*2, this._scaledRadius*2);\n+    sketch.pop();\n+  };\n+\n+  /**\n+   * Compute candidate separating axes relative to another object.\n+   * @method _getCandidateAxes\n+   * @protected\n+   * @param {p5.CollisionShape} other\n+   * @return {Array.<p5.Vector>}\n+   */\n+  p5.CollisionCircle.prototype._getCandidateAxes = function(other) {\n+    // A circle has infinite potential candidate axes, so the ones we pick\n+    // depend on what we're colliding against.\n+\n+    // TODO: Could generalize these two cases to other.vertices.map(v => sub(v, this.center))\n+    if (other instanceof p5.OrientedBoundingBox || other instanceof p5.AxisAlignedBoundingBox) {\n+      // There are four possible separating axes with a box - one for each\n+      // of its vertices, through the center of the circle.\n+      // TODO: Can we optimize and only check the nearest vertex?\n+      var halfDiagonals = other.halfDiagonals;\n+      return [\n+        p5.Vector.sub(p5.Vector.add(other.center, halfDiagonals[0]), this.center),\n+        p5.Vector.sub(p5.Vector.add(other.center, halfDiagonals[1]), this.center),\n+        p5.Vector.sub(p5.Vector.sub(other.center, halfDiagonals[0]), this.center),\n+        p5.Vector.sub(p5.Vector.sub(other.center, halfDiagonals[1]), this.center)\n+      ];\n+    }\n+\n+    // When checking against another circle or a point we only need to check the\n+    // axis through both shapes' centers.\n+    return [\n+      p5.Vector.sub(other.center, this.center)\n+    ];\n+  };\n+\n+  /**\n+   * Get this shape's radius (half-width of its projection) along the given axis.\n+   * @method _getRadiusOnAxis\n+   * @protected\n+   * @return {number}\n+   */\n+  p5.CollisionCircle.prototype._getRadiusOnAxis = function() {\n+    return this._scaledRadius;\n+  };\n+\n+  /**\n+   * An Axis-Aligned Bounding Box (AABB) collision shape, used to detect overlap\n+   * and compute minimum displacement vectors with other collision shapes.\n+   *\n+   * Cannot be rotated - hence the name.  You might use this in place of an\n+   * OBB because it simplifies some of the math and may improve performance.\n+   *\n+   * @class p5.AxisAlignedBoundingBox\n+   * @constructor\n+   * @extends p5.CollisionShape\n+   * @param {p5.Vector} center\n+   * @param {number} width\n+   * @param {number} height\n+   */\n+  p5.AxisAlignedBoundingBox = function(center, width, height) {\n+    p5.CollisionShape.call(this, center);\n+\n+    /**\n+     * Unscaled box width.\n+     * @property _width\n+     * @private\n+     * @type {number}\n+     */\n+    this._width = width;\n+\n+    /**\n+     * Unscaled box height.\n+     * @property _width\n+     * @private\n+     * @type {number}\n+     */\n+    this._height = height;\n+\n+    /**\n+     * Cached half-diagonals, used for computing a projected radius.\n+     * Already transformed into world-space.\n+     * @property _halfDiagonals\n+     * @private\n+     * @type {Array.<p5.Vector>}\n+     */\n+    this._halfDiagonals = [];\n+\n+    Object.defineProperties(this, {\n+\n+      /**\n+       * The untransformed width of the box collider.\n+       * Recomputes diagonals when set.\n+       * @property width\n+       * @type {number}\n+       */\n+      'width': {\n+        enumerable: true,\n+        get: function() {\n+          return this._width;\n+        }.bind(this),\n+        set: function(w) {\n+          this._width = w;\n+          this._halfDiagonals = this._computeHalfDiagonals();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * The unrotated height of the box collider.\n+       * Recomputes diagonals when set.\n+       * @property height\n+       * @type {number}\n+       */\n+      'height': {\n+        enumerable: true,\n+        get: function() {\n+          return this._height;\n+        }.bind(this),\n+        set: function(h) {\n+          this._height = h;\n+          this._halfDiagonals = this._computeHalfDiagonals();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * Two vectors representing adjacent half-diagonals of the box at its\n+       * current dimensions and orientation.\n+       * @property halfDiagonals\n+       * @readOnly\n+       * @type {Array.<p5.Vector>}\n+       */\n+      'halfDiagonals': {\n+        enumerable: true,\n+        get: function() {\n+          return this._halfDiagonals;\n+        }.bind(this)\n+      }\n+    });\n+\n+    this._computeHalfDiagonals();\n+  };\n+  p5.AxisAlignedBoundingBox.prototype = Object.create(p5.CollisionShape.prototype);\n+\n+  /**\n+   * Construct a new AxisAlignedBoundingBox with given offset for the given sprite.\n+   * @method createFromSprite\n+   * @static\n+   * @param {Sprite} sprite\n+   * @param {p5.Vector} [offset] from the sprite's center\n+   * @return {p5.CollisionCircle}\n+   */\n+  p5.AxisAlignedBoundingBox.createFromSprite = function(sprite, offset, width, height) {\n+    var customSize = typeof width === 'number' && typeof height === 'number';\n+    var box = new p5.AxisAlignedBoundingBox(\n+      offset,\n+      customSize ? width : 1,\n+      customSize ? height : 1\n+    );\n+    box.getsDimensionsFromSprite = !customSize;\n+    box.updateFromSprite(sprite);\n+    return box;\n+  };\n+\n+  /**\n+   * Update this collider based on the properties of a parent Sprite.\n+   * @method updateFromSprite\n+   * @param {Sprite} sprite\n+   * @see p5.CollisionShape.prototype.getsDimensionsFromSprite\n+   */\n+  p5.AxisAlignedBoundingBox.prototype.updateFromSprite = function(sprite) {\n+    if (this.getsDimensionsFromSprite) {\n+      this._width = sprite.width;\n+      this._height = sprite.height;\n+    }\n+    this.setParentTransform(sprite);\n+  };\n+\n+  /**\n+   * Recalculate cached properties, relevant vectors, etc. when at least one\n+   * of the shape's transforms changes.  The base CollisionShape (and CollisionPoint)\n+   * only need to recompute the shape's center, but other shapes may need to\n+   * override this method and do additional recomputation.\n+   * @method _onTransformChanged\n+   * @protected\n+   */\n+  p5.AxisAlignedBoundingBox.prototype._onTransformChanged = function() {\n+    p5.CollisionShape.prototype._onTransformChanged.call(this);\n+    this._computeHalfDiagonals();\n+  };\n+\n+  /**\n+   * Recompute this bounding box's half-diagonal vectors.\n+   * @method _computeHalfDiagonals\n+   * @private\n+   * @return {Array.<p5.Vector>}\n+   */\n+  p5.AxisAlignedBoundingBox.prototype._computeHalfDiagonals = function() {\n+    // We transform the rectangle (which may scale and rotate it) then compute\n+    // an axis-aligned bounding box _around_ it.\n+    var composedTransform = p5.Transform2D.mult(this._parentTransform, this._localTransform);\n+    var transformedDiagonals = [\n+      new p5.Vector(this._width / 2, -this._height / 2),\n+      new p5.Vector(this._width / 2, this._height / 2),\n+      new p5.Vector(-this._width / 2, this._height / 2)\n+    ].map(function(vertex) {\n+      return vertex.transform(composedTransform).sub(this.center);\n+    }.bind(this));\n+\n+    var halfWidth = Math.max(\n+      Math.abs(transformedDiagonals[0].x),\n+      Math.abs(transformedDiagonals[1].x)\n+    );\n+    var halfHeight = Math.max(\n+      Math.abs(transformedDiagonals[1].y),\n+      Math.abs(transformedDiagonals[2].y)\n+    );\n+\n+    this._halfDiagonals = [\n+      new p5.Vector(halfWidth, -halfHeight),\n+      new p5.Vector(halfWidth, halfHeight)\n+    ];\n+  };\n+\n+  /**\n+   * Debug-draw this collider.\n+   * @method draw\n+   * @param {p5} sketch - p5 instance to use for drawing\n+   */\n+  p5.AxisAlignedBoundingBox.prototype.draw = function(sketch) {\n+    sketch.push();\n+    sketch.rectMode(sketch.CENTER);\n+    sketch.translate(this.center.x, this.center.y);\n+    sketch.noFill();\n+    sketch.stroke(0, 255, 0);\n+    sketch.strokeWeight(1);\n+    sketch.rect(0, 0, Math.abs(this._halfDiagonals[0].x) * 2, Math.abs(this._halfDiagonals[0].y) * 2);\n+    sketch.pop();\n+  };\n+\n+  /**\n+   * Compute candidate separating axes relative to another object.\n+   * @method _getCandidateAxes\n+   * @protected\n+   * @return {Array.<p5.Vector>}\n+   */\n+  p5.AxisAlignedBoundingBox.prototype._getCandidateAxes = function() {\n+    return p5.CollisionShape.WORLD_AXES;\n+  };\n+\n+  /**\n+   * Get this shape's radius (half-width of its projection) along the given axis.\n+   * @method _getRadiusOnAxis\n+   * @protected\n+   * @param {p5.Vector} axis\n+   * @return {number}\n+   */\n+  p5.AxisAlignedBoundingBox.prototype._getRadiusOnAxis = function(axis) {\n+    // How to project a rect onto an axis:\n+    // Project the center-corner vectors for two adjacent corners (cached here)\n+    // onto the axis.  The larger magnitude of the two is your projection's radius.\n+    return Math.max(\n+      p5.Vector.project(this._halfDiagonals[0], axis).mag(),\n+      p5.Vector.project(this._halfDiagonals[1], axis).mag());\n+  };\n+\n+  /**\n+   * An Oriented Bounding Box (OBB) collision shape, used to detect overlap and\n+   * compute minimum displacement vectors with other collision shapes.\n+   * @class p5.OrientedBoundingBox\n+   * @constructor\n+   * @extends p5.CollisionShape\n+   * @param {p5.Vector} center of the rectangle in world-space\n+   * @param {number} width of the rectangle (when not rotated)\n+   * @param {number} height of the rectangle (when not rotated)\n+   * @param {number} rotation about center, in radians\n+   */\n+  p5.OrientedBoundingBox = function(center, width, height, rotation) {\n+    p5.CollisionShape.call(this, center, rotation);\n+\n+    /**\n+     * Unscaled box width.\n+     * @property _width\n+     * @private\n+     * @type {number}\n+     */\n+    this._width = width;\n+\n+    /**\n+     * Unscaled box height.\n+     * @property _width\n+     * @private\n+     * @type {number}\n+     */\n+    this._height = height;\n+\n+    /**\n+     * Cached separating axes this shape contributes to a collision.\n+     * @property _potentialAxes\n+     * @private\n+     * @type {Array.<p5.Vector>}\n+     */\n+    this._potentialAxes = [];\n+\n+    /**\n+     * Cached half-diagonals, used for computing a projected radius.\n+     * Already transformed into world-space.\n+     * @property _halfDiagonals\n+     * @private\n+     * @type {Array.<p5.Vector>}\n+     */\n+    this._halfDiagonals = [];\n+\n+    Object.defineProperties(this, {\n+\n+      /**\n+       * The unrotated width of the box collider.\n+       * Recomputes diagonals when set.\n+       * @property width\n+       * @type {number}\n+       */\n+      'width': {\n+        enumerable: true,\n+        get: function() {\n+          return this._width;\n+        }.bind(this),\n+        set: function(w) {\n+          this._width = w;\n+          this._onTransformChanged();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * The unrotated height of the box collider.\n+       * Recomputes diagonals when set.\n+       * @property height\n+       * @type {number}\n+       */\n+      'height': {\n+        enumerable: true,\n+        get: function() {\n+          return this._height;\n+        }.bind(this),\n+        set: function(h) {\n+          this._height = h;\n+          this._onTransformChanged();\n+        }.bind(this)\n+      },\n+\n+      /**\n+       * Two vectors representing adjacent half-diagonals of the box at its\n+       * current dimensions and orientation.\n+       * @property halfDiagonals\n+       * @readOnly\n+       * @type {Array.<p5.Vector>}\n+       */\n+      'halfDiagonals': {\n+        enumerable: true,\n+        get: function() {\n+          return this._halfDiagonals;\n+        }.bind(this)\n+      }\n+    });\n+\n+    this._onTransformChanged();\n+  };\n+  p5.OrientedBoundingBox.prototype = Object.create(p5.CollisionShape.prototype);\n+\n+  /**\n+   * Construct a new AxisAlignedBoundingBox with given offset for the given sprite.\n+   * @method createFromSprite\n+   * @static\n+   * @param {Sprite} sprite\n+   * @param {p5.Vector} [offset] from the sprite's center\n+   * @param {number} [width]\n+   * @param {number} [height]\n+   * @param {number} [rotation] in radians\n+   * @return {p5.CollisionCircle}\n+   */\n+  p5.OrientedBoundingBox.createFromSprite = function(sprite, offset, width, height, rotation) {\n+    var customSize = typeof width === 'number' && typeof height === 'number';\n+    var box = new p5.OrientedBoundingBox(\n+      offset,\n+      customSize ? width : 1,\n+      customSize ? height : 1,\n+      rotation\n+    );\n+    box.getsDimensionsFromSprite = !customSize;\n+    box.updateFromSprite(sprite);\n+    return box;\n+  };\n+\n+  /**\n+   * Update this collider based on the properties of a parent Sprite.\n+   * @method updateFromSprite\n+   * @param {Sprite} sprite\n+   * @see p5.CollisionShape.prototype.getsDimensionsFromSprite\n+   */\n+  p5.OrientedBoundingBox.prototype.updateFromSprite = function(sprite) {\n+    if (this.getsDimensionsFromSprite) {\n+      this._width = sprite.width;\n+      this._height = sprite.height;\n+    }\n+    this.setParentTransform(sprite);\n+  };\n+\n+  /**\n+   * Recalculate cached properties, relevant vectors, etc. when at least one\n+   * of the shape's transforms changes.  The base CollisionShape (and CollisionPoint)\n+   * only need to recompute the shape's center, but other shapes may need to\n+   * override this method and do additional recomputation.\n+   * @method _onTransformChanged\n+   * @protected\n+   */\n+  p5.OrientedBoundingBox.prototype._onTransformChanged = function() {\n+    p5.CollisionShape.prototype._onTransformChanged.call(this);\n+\n+    // Transform each vertex by the local and global matrices\n+    // then use their differences to determine width, height, and halfDiagonals\n+    var composedTransform = p5.Transform2D.mult(this._parentTransform, this._localTransform);\n+    var transformedVertices = [\n+      new p5.Vector(this._width / 2, -this._height / 2),\n+      new p5.Vector(this._width / 2, this._height / 2),\n+      new p5.Vector(-this._width / 2, this._height / 2)\n+    ].map(function(vertex) {\n+      return vertex.transform(composedTransform);\n+    });\n+\n+    this._halfDiagonals = [\n+      p5.Vector.sub(transformedVertices[0], this.center),\n+      p5.Vector.sub(transformedVertices[1], this.center)\n+    ];\n+\n+    this._potentialAxes = [\n+      p5.Vector.sub(transformedVertices[1], transformedVertices[2]),\n+      p5.Vector.sub(transformedVertices[1], transformedVertices[0])\n+    ];\n+  };\n+\n+  /**\n+   * Debug-draw this collider.\n+   * @method draw\n+   * @param {p5} sketch - p5 instance to use for drawing\n+   */\n+  p5.OrientedBoundingBox.prototype.draw = function(sketch) {\n+    var composedTransform = p5.Transform2D.mult(this._localTransform, this._parentTransform);\n+    var scale = composedTransform.getScale();\n+    var rotation = composedTransform.getRotation();\n+    sketch.push();\n+    sketch.translate(this.center.x, this.center.y);\n+    sketch.scale(scale.x, scale.y);\n+    if (sketch._angleMode === sketch.RADIANS) {\n+      sketch.rotate(rotation);\n+    } else {\n+      sketch.rotate(degrees(rotation));\n+    }\n+\n+    sketch.noFill();\n+    sketch.stroke(0, 255, 0);\n+    sketch.strokeWeight(1);\n+    sketch.rectMode(sketch.CENTER);\n+    sketch.rect(0, 0, this._width, this._height);\n+    sketch.pop();\n+  };\n+\n+  /**\n+   * Compute candidate separating axes relative to another object.\n+   * @method _getCandidateAxes\n+   * @protected\n+   * @return {Array.<p5.Vector>}\n+   */\n+  p5.OrientedBoundingBox.prototype._getCandidateAxes = function() {\n+    // An oriented bounding box always provides two of its face normals,\n+    // which we've precomputed.\n+    return this._potentialAxes;\n+  };\n+\n+  /**\n+   * Get this shape's radius (half-width of its projection) along the given axis.\n+   * @method _getRadiusOnAxis\n+   * @protected\n+   * @param {p5.Vector} axis\n+   * @return {number}\n+   */\n+  p5.OrientedBoundingBox.prototype._getRadiusOnAxis = function(axis) {\n+    // How to project a rect onto an axis:\n+    // Project the center-corner vectors for two adjacent corners (cached here)\n+    // onto the axis.  The larger magnitude of the two is your projection's radius.\n+    return Math.max(\n+      p5.Vector.project(this._halfDiagonals[0], axis).mag(),\n+      p5.Vector.project(this._halfDiagonals[1], axis).mag());\n+  };\n+\n+  /**\n+   * A 2D affine transformation (translation, rotation, scale) stored as a\n+   * 3x3 matrix that uses homogeneous coordinates.  Used to quickly transform\n+   * points or vectors between reference frames.\n+   * @class p5.Transform2D\n+   * @constructor\n+   * @extends Array\n+   * @param {p5.Transform2D|Array.<number>} [source]\n+   */\n+  p5.Transform2D = function(source) {\n+    // We only store the first six values.\n+    // the last row in a 2D transform matrix is always \"0 0 1\" so we can\n+    // save space and speed up certain calculations with this assumption.\n+    source = source || [1, 0, 0, 0, 1, 0];\n+    if (source.length !== 6) {\n+      throw new TypeError('Transform2D must have six components');\n+    }\n+    this.length = 6;\n+    this[0] = source[0];\n+    this[1] = source[1];\n+    this[2] = source[2];\n+    this[3] = source[3];\n+    this[4] = source[4];\n+    this[5] = source[5];\n+  };\n+  p5.Transform2D.prototype = Object.create(Array.prototype);\n+\n+  /**\n+   * Reset this transform to an identity transform, in-place.\n+   * @method clear\n+   * @return {p5.Transform2D} this transform\n+   */\n+  p5.Transform2D.prototype.clear = function() {\n+    this[0] = 1;\n+    this[1] = 0;\n+    this[2] = 0;\n+    this[3] = 0;\n+    this[4] = 1;\n+    this[5] = 0;\n+    return this;\n+  };\n+\n+  /**\n+   * Make a copy of this transform.\n+   * @method copy\n+   * @return {p5.Transform2D}\n+   */\n+  p5.Transform2D.prototype.copy = function() {\n+    return new p5.Transform2D(this);\n+  };\n+\n+  /**\n+   * Check whether two transforms are the same.\n+   * @method equals\n+   * @param {p5.Transform2D|Array.<number>} other\n+   * @return {boolean}\n+   */\n+  p5.Transform2D.prototype.equals = function(other) {\n+    if (!(other instanceof p5.Transform2D || Array.isArray(other))) {\n+      return false; // Never equal to other types.\n+    }\n+\n+    for (var i = 0; i < 6; i++) {\n+      if (this[i] !== other[i]) {\n+        return false;\n+      }\n+    }\n+    return true;\n+  };\n+\n+  /**\n+   * Multiply two transforms together, combining them.\n+   * Does not modify original transforms.  Assigns result into dest argument if\n+   * provided and returns it.  Otherwise returns a new transform.\n+   * @method mult\n+   * @static\n+   * @param {p5.Transform2D|Array.<number>} t1\n+   * @param {p5.Transform2D|Array.<number>} t2\n+   * @param {p5.Transform2D} [dest]\n+   * @return {p5.Transform2D}\n+   */\n+  p5.Transform2D.mult = function(t1, t2, dest) {\n+    dest = dest || new p5.Transform2D();\n+\n+    // Capture values of original matrices in local variables, in case one of\n+    // them is the one we're mutating.\n+    var t1_0, t1_1, t1_2, t1_3, t1_4, t1_5;\n+    t1_0 = t1[0];\n+    t1_1 = t1[1];\n+    t1_2 = t1[2];\n+    t1_3 = t1[3];\n+    t1_4 = t1[4];\n+    t1_5 = t1[5];\n+\n+    var t2_0, t2_1, t2_2, t2_3, t2_4, t2_5;\n+    t2_0 = t2[0];\n+    t2_1 = t2[1];\n+    t2_2 = t2[2];\n+    t2_3 = t2[3];\n+    t2_4 = t2[4];\n+    t2_5 = t2[5];\n+\n+    dest[0] = t1_0*t2_0 + t1_1*t2_3;\n+    dest[1] = t1_0*t2_1 + t1_1*t2_4;\n+    dest[2] = t1_0*t2_2 + t1_1*t2_5 + t1_2;\n+\n+    dest[3] = t1_3*t2_0 + t1_4*t2_3;\n+    dest[4] = t1_3*t2_1 + t1_4*t2_4;\n+    dest[5] = t1_3*t2_2 + t1_4*t2_5 + t1_5;\n+\n+    return dest;\n+  };\n+\n+  /**\n+   * Multiply this transform by another, combining them.\n+   * Modifies this transform and returns it.\n+   * @method mult\n+   * @param {p5.Transform2D|Float32Array|Array.<number>} other\n+   * @return {p5.Transform2D}\n+   */\n+  p5.Transform2D.prototype.mult = function(other) {\n+    return p5.Transform2D.mult(this, other, this);\n+  };\n+\n+  /**\n+   * Modify this transform, translating it by a certain amount.\n+   * Returns this transform.\n+   * @method translate\n+   * @return {p5.Transform2D}\n+   * @example\n+   *     // Two different ways to call this method.\n+   *     var t = new p5.Transform();\n+   *     // 1. Two numbers\n+   *     t.translate(x, y);\n+   *     // 2. One vector\n+   *     t.translate(new p5.Vector(x, y));\n+   */\n+  p5.Transform2D.prototype.translate = function(arg0, arg1) {\n+    var x, y;\n+    if (arg0 instanceof p5.Vector) {\n+      x = arg0.x;\n+      y = arg0.y;\n+    } else if (typeof arg0 === 'number' && typeof arg1 === 'number') {\n+      x = arg0;\n+      y = arg1;\n+    } else {\n+      var args = '';\n+      for (var i = 0; i < arguments.length; i++) {\n+        args += arguments[i] + ', ';\n+      }\n+      throw new TypeError('Invalid arguments to Transform2D.translate: ' + args);\n+    }\n+    return p5.Transform2D.mult([\n+      1, 0, x,\n+      0, 1, y\n+    ], this, this);\n+  };\n+\n+  /**\n+   * Retrieve the resolved translation of this transform.\n+   * @method getTranslation\n+   * @return {p5.Vector}\n+   */\n+  p5.Transform2D.prototype.getTranslation = function() {\n+    return new p5.Vector(this[2], this[5]);\n+  };\n+\n+  /**\n+   * Modify this transform, scaling it by a certain amount.\n+   * Returns this transform.\n+   * @method scale\n+   * @return {p5.Transform2D}\n+   * @example\n+   *     // Three different ways to call this method.\n+   *     var t = new p5.Transform();\n+   *     // 1. One scalar value\n+   *     t.scale(uniformScale);\n+   *     // 1. Two scalar values\n+   *     t.scale(scaleX, scaleY);\n+   *     // 2. One vector\n+   *     t.translate(new p5.Vector(scaleX, scaleY));\n+   */\n+  p5.Transform2D.prototype.scale = function(arg0, arg1) {\n+    var sx, sy;\n+    if (arg0 instanceof p5.Vector) {\n+      sx = arg0.x;\n+      sy = arg0.y;\n+    } else if (typeof arg0 === 'number' && typeof arg1 === 'number') {\n+      sx = arg0;\n+      sy = arg1;\n+    } else if (typeof arg0 === 'number') {\n+      sx = arg0;\n+      sy = arg0;\n+    } else {\n+      throw new TypeError('Invalid arguments to Transform2D.scale: ' + arguments);\n+    }\n+    return p5.Transform2D.mult([\n+      sx, 0, 0,\n+      0, sy, 0\n+    ], this, this);\n+  };\n+\n+  /**\n+   * Retrieve the scale vector of this transform.\n+   * @method getScale\n+   * @return {p5.Vector}\n+   */\n+  p5.Transform2D.prototype.getScale = function() {\n+    var a = this[0], b = this[1],\n+        c = this[3], d = this[4];\n+    return new p5.Vector(\n+      sign(a) * Math.sqrt(a*a + b*b),\n+      sign(d) * Math.sqrt(c*c + d*d)\n+    );\n+  };\n+\n+  /*\n+   * Return -1, 0, or 1 depending on whether a number is negative, zero, or positive.\n+   */\n+  function sign(x) {","path":"lib/p5.play.js","position":2755,"original_position":2755,"commit_id":"2a5e17003b9821c7d74d23f50c55ddcc72c8ae64","original_commit_id":"2a5e17003b9821c7d74d23f50c55ddcc72c8ae64","user":{"login":"islemaster","id":1615761,"avatar_url":"https://avatars.githubusercontent.com/u/1615761?v=3","gravatar_id":"","url":"https://api.github.com/users/islemaster","html_url":"https://github.com/islemaster","followers_url":"https://api.github.com/users/islemaster/followers","following_url":"https://api.github.com/users/islemaster/following{/other_user}","gists_url":"https://api.github.com/users/islemaster/gists{/gist_id}","starred_url":"https://api.github.com/users/islemaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/islemaster/subscriptions","organizations_url":"https://api.github.com/users/islemaster/orgs","repos_url":"https://api.github.com/users/islemaster/repos","events_url":"https://api.github.com/users/islemaster/events{/privacy}","received_events_url":"https://api.github.com/users/islemaster/received_events","type":"User","site_admin":false},"body":"Polyfilled - [`Math.sign` is a thing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) but only in Chrome+Firefox for now.","created_at":"2016-11-04T22:20:08Z","updated_at":"2016-11-04T22:20:08Z","html_url":"https://github.com/code-dot-org/p5.play/pull/12#discussion_r86642565","pull_request_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12","_links":{"self":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/comments/86642565"},"html":{"href":"https://github.com/code-dot-org/p5.play/pull/12#discussion_r86642565"},"pull_request":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12"}}},"pull_request":{"url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12","id":91112844,"html_url":"https://github.com/code-dot-org/p5.play/pull/12","diff_url":"https://github.com/code-dot-org/p5.play/pull/12.diff","patch_url":"https://github.com/code-dot-org/p5.play/pull/12.patch","issue_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/12","number":12,"state":"open","locked":false,"title":"Support rotated rectangle collisions","user":{"login":"islemaster","id":1615761,"avatar_url":"https://avatars.githubusercontent.com/u/1615761?v=3","gravatar_id":"","url":"https://api.github.com/users/islemaster","html_url":"https://github.com/islemaster","followers_url":"https://api.github.com/users/islemaster/followers","following_url":"https://api.github.com/users/islemaster/following{/other_user}","gists_url":"https://api.github.com/users/islemaster/gists{/gist_id}","starred_url":"https://api.github.com/users/islemaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/islemaster/subscriptions","organizations_url":"https://api.github.com/users/islemaster/orgs","repos_url":"https://api.github.com/users/islemaster/repos","events_url":"https://api.github.com/users/islemaster/events{/privacy}","received_events_url":"https://api.github.com/users/islemaster/received_events","type":"User","site_admin":false},"body":"# Description\r\n\r\nCould also be titled \"Overhaul the collision system.\"\r\n\r\nIntroduces a new collider type, `OrientedBoundingBox` that can be rotated and can compute minimum displacement vectors with other rotated rectangles and with circles.\r\n\r\nAlso tears out and replaces all the other collider types.  And refactors the collision resolution process.  And introduces a 2D transform class.\r\n\r\n# New Example - Advanced Custom Colliders\r\n![collisions6](https://cloud.githubusercontent.com/assets/1615761/19982376/1dc5589a-a1c3-11e6-86ae-38243b82f124.gif)\r\n\r\nDemonstrates all four collider types, applied with custom sizes and offsets, attached to sprites that move/scale/rotate.","created_at":"2016-10-27T00:17:17Z","updated_at":"2016-11-04T22:20:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"561ee763d264b5b3ad1157fa061bc64068ac0653","assignee":{"login":"ewjordan","id":70630,"avatar_url":"https://avatars.githubusercontent.com/u/70630?v=3","gravatar_id":"","url":"https://api.github.com/users/ewjordan","html_url":"https://github.com/ewjordan","followers_url":"https://api.github.com/users/ewjordan/followers","following_url":"https://api.github.com/users/ewjordan/following{/other_user}","gists_url":"https://api.github.com/users/ewjordan/gists{/gist_id}","starred_url":"https://api.github.com/users/ewjordan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ewjordan/subscriptions","organizations_url":"https://api.github.com/users/ewjordan/orgs","repos_url":"https://api.github.com/users/ewjordan/repos","events_url":"https://api.github.com/users/ewjordan/events{/privacy}","received_events_url":"https://api.github.com/users/ewjordan/received_events","type":"User","site_admin":false},"assignees":[{"login":"ewjordan","id":70630,"avatar_url":"https://avatars.githubusercontent.com/u/70630?v=3","gravatar_id":"","url":"https://api.github.com/users/ewjordan","html_url":"https://github.com/ewjordan","followers_url":"https://api.github.com/users/ewjordan/followers","following_url":"https://api.github.com/users/ewjordan/following{/other_user}","gists_url":"https://api.github.com/users/ewjordan/gists{/gist_id}","starred_url":"https://api.github.com/users/ewjordan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ewjordan/subscriptions","organizations_url":"https://api.github.com/users/ewjordan/orgs","repos_url":"https://api.github.com/users/ewjordan/repos","events_url":"https://api.github.com/users/ewjordan/events{/privacy}","received_events_url":"https://api.github.com/users/ewjordan/received_events","type":"User","site_admin":false},{"login":"bcjordan","id":206973,"avatar_url":"https://avatars.githubusercontent.com/u/206973?v=3","gravatar_id":"","url":"https://api.github.com/users/bcjordan","html_url":"https://github.com/bcjordan","followers_url":"https://api.github.com/users/bcjordan/followers","following_url":"https://api.github.com/users/bcjordan/following{/other_user}","gists_url":"https://api.github.com/users/bcjordan/gists{/gist_id}","starred_url":"https://api.github.com/users/bcjordan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bcjordan/subscriptions","organizations_url":"https://api.github.com/users/bcjordan/orgs","repos_url":"https://api.github.com/users/bcjordan/repos","events_url":"https://api.github.com/users/bcjordan/events{/privacy}","received_events_url":"https://api.github.com/users/bcjordan/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12/commits","review_comments_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12/comments","review_comment_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls/comments{/number}","comments_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/12/comments","statuses_url":"https://api.github.com/repos/code-dot-org/p5.play/statuses/2a5e17003b9821c7d74d23f50c55ddcc72c8ae64","head":{"label":"code-dot-org:rotating-rect-collider","ref":"rotating-rect-collider","sha":"2a5e17003b9821c7d74d23f50c55ddcc72c8ae64","user":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"repo":{"id":71513175,"name":"p5.play","full_name":"code-dot-org/p5.play","owner":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/code-dot-org/p5.play","description":"A p5.js library for games and playthings","fork":true,"url":"https://api.github.com/repos/code-dot-org/p5.play","forks_url":"https://api.github.com/repos/code-dot-org/p5.play/forks","keys_url":"https://api.github.com/repos/code-dot-org/p5.play/keys{/key_id}","collaborators_url":"https://api.github.com/repos/code-dot-org/p5.play/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/code-dot-org/p5.play/teams","hooks_url":"https://api.github.com/repos/code-dot-org/p5.play/hooks","issue_events_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/events{/number}","events_url":"https://api.github.com/repos/code-dot-org/p5.play/events","assignees_url":"https://api.github.com/repos/code-dot-org/p5.play/assignees{/user}","branches_url":"https://api.github.com/repos/code-dot-org/p5.play/branches{/branch}","tags_url":"https://api.github.com/repos/code-dot-org/p5.play/tags","blobs_url":"https://api.github.com/repos/code-dot-org/p5.play/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/code-dot-org/p5.play/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/code-dot-org/p5.play/git/refs{/sha}","trees_url":"https://api.github.com/repos/code-dot-org/p5.play/git/trees{/sha}","statuses_url":"https://api.github.com/repos/code-dot-org/p5.play/statuses/{sha}","languages_url":"https://api.github.com/repos/code-dot-org/p5.play/languages","stargazers_url":"https://api.github.com/repos/code-dot-org/p5.play/stargazers","contributors_url":"https://api.github.com/repos/code-dot-org/p5.play/contributors","subscribers_url":"https://api.github.com/repos/code-dot-org/p5.play/subscribers","subscription_url":"https://api.github.com/repos/code-dot-org/p5.play/subscription","commits_url":"https://api.github.com/repos/code-dot-org/p5.play/commits{/sha}","git_commits_url":"https://api.github.com/repos/code-dot-org/p5.play/git/commits{/sha}","comments_url":"https://api.github.com/repos/code-dot-org/p5.play/comments{/number}","issue_comment_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/comments{/number}","contents_url":"https://api.github.com/repos/code-dot-org/p5.play/contents/{+path}","compare_url":"https://api.github.com/repos/code-dot-org/p5.play/compare/{base}...{head}","merges_url":"https://api.github.com/repos/code-dot-org/p5.play/merges","archive_url":"https://api.github.com/repos/code-dot-org/p5.play/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/code-dot-org/p5.play/downloads","issues_url":"https://api.github.com/repos/code-dot-org/p5.play/issues{/number}","pulls_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls{/number}","milestones_url":"https://api.github.com/repos/code-dot-org/p5.play/milestones{/number}","notifications_url":"https://api.github.com/repos/code-dot-org/p5.play/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/code-dot-org/p5.play/labels{/name}","releases_url":"https://api.github.com/repos/code-dot-org/p5.play/releases{/id}","deployments_url":"https://api.github.com/repos/code-dot-org/p5.play/deployments","created_at":"2016-10-20T23:44:01Z","updated_at":"2016-10-24T22:38:44Z","pushed_at":"2016-11-04T21:41:36Z","git_url":"git://github.com/code-dot-org/p5.play.git","ssh_url":"git@github.com:code-dot-org/p5.play.git","clone_url":"https://github.com/code-dot-org/p5.play.git","svn_url":"https://github.com/code-dot-org/p5.play","homepage":null,"size":5337,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"code-dot-org:master","ref":"master","sha":"fff60319ff5737cf2c4289f2a99cc3173922b88e","user":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"repo":{"id":71513175,"name":"p5.play","full_name":"code-dot-org/p5.play","owner":{"login":"code-dot-org","id":5124627,"avatar_url":"https://avatars.githubusercontent.com/u/5124627?v=3","gravatar_id":"","url":"https://api.github.com/users/code-dot-org","html_url":"https://github.com/code-dot-org","followers_url":"https://api.github.com/users/code-dot-org/followers","following_url":"https://api.github.com/users/code-dot-org/following{/other_user}","gists_url":"https://api.github.com/users/code-dot-org/gists{/gist_id}","starred_url":"https://api.github.com/users/code-dot-org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/code-dot-org/subscriptions","organizations_url":"https://api.github.com/users/code-dot-org/orgs","repos_url":"https://api.github.com/users/code-dot-org/repos","events_url":"https://api.github.com/users/code-dot-org/events{/privacy}","received_events_url":"https://api.github.com/users/code-dot-org/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/code-dot-org/p5.play","description":"A p5.js library for games and playthings","fork":true,"url":"https://api.github.com/repos/code-dot-org/p5.play","forks_url":"https://api.github.com/repos/code-dot-org/p5.play/forks","keys_url":"https://api.github.com/repos/code-dot-org/p5.play/keys{/key_id}","collaborators_url":"https://api.github.com/repos/code-dot-org/p5.play/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/code-dot-org/p5.play/teams","hooks_url":"https://api.github.com/repos/code-dot-org/p5.play/hooks","issue_events_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/events{/number}","events_url":"https://api.github.com/repos/code-dot-org/p5.play/events","assignees_url":"https://api.github.com/repos/code-dot-org/p5.play/assignees{/user}","branches_url":"https://api.github.com/repos/code-dot-org/p5.play/branches{/branch}","tags_url":"https://api.github.com/repos/code-dot-org/p5.play/tags","blobs_url":"https://api.github.com/repos/code-dot-org/p5.play/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/code-dot-org/p5.play/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/code-dot-org/p5.play/git/refs{/sha}","trees_url":"https://api.github.com/repos/code-dot-org/p5.play/git/trees{/sha}","statuses_url":"https://api.github.com/repos/code-dot-org/p5.play/statuses/{sha}","languages_url":"https://api.github.com/repos/code-dot-org/p5.play/languages","stargazers_url":"https://api.github.com/repos/code-dot-org/p5.play/stargazers","contributors_url":"https://api.github.com/repos/code-dot-org/p5.play/contributors","subscribers_url":"https://api.github.com/repos/code-dot-org/p5.play/subscribers","subscription_url":"https://api.github.com/repos/code-dot-org/p5.play/subscription","commits_url":"https://api.github.com/repos/code-dot-org/p5.play/commits{/sha}","git_commits_url":"https://api.github.com/repos/code-dot-org/p5.play/git/commits{/sha}","comments_url":"https://api.github.com/repos/code-dot-org/p5.play/comments{/number}","issue_comment_url":"https://api.github.com/repos/code-dot-org/p5.play/issues/comments{/number}","contents_url":"https://api.github.com/repos/code-dot-org/p5.play/contents/{+path}","compare_url":"https://api.github.com/repos/code-dot-org/p5.play/compare/{base}...{head}","merges_url":"https://api.github.com/repos/code-dot-org/p5.play/merges","archive_url":"https://api.github.com/repos/code-dot-org/p5.play/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/code-dot-org/p5.play/downloads","issues_url":"https://api.github.com/repos/code-dot-org/p5.play/issues{/number}","pulls_url":"https://api.github.com/repos/code-dot-org/p5.play/pulls{/number}","milestones_url":"https://api.github.com/repos/code-dot-org/p5.play/milestones{/number}","notifications_url":"https://api.github.com/repos/code-dot-org/p5.play/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/code-dot-org/p5.play/labels{/name}","releases_url":"https://api.github.com/repos/code-dot-org/p5.play/releases{/id}","deployments_url":"https://api.github.com/repos/code-dot-org/p5.play/deployments","created_at":"2016-10-20T23:44:01Z","updated_at":"2016-10-24T22:38:44Z","pushed_at":"2016-11-04T21:41:36Z","git_url":"git://github.com/code-dot-org/p5.play.git","ssh_url":"git@github.com:code-dot-org/p5.play.git","clone_url":"https://github.com/code-dot-org/p5.play.git","svn_url":"https://github.com/code-dot-org/p5.play","homepage":null,"size":5337,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12"},"html":{"href":"https://github.com/code-dot-org/p5.play/pull/12"},"issue":{"href":"https://api.github.com/repos/code-dot-org/p5.play/issues/12"},"comments":{"href":"https://api.github.com/repos/code-dot-org/p5.play/issues/12/comments"},"review_comments":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12/comments"},"review_comment":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/code-dot-org/p5.play/pulls/12/commits"},"statuses":{"href":"https://api.github.com/repos/code-dot-org/p5.play/statuses/2a5e17003b9821c7d74d23f50c55ddcc72c8ae64"}}}}
{"actor":{"display_login":"islemaster"}}