var Phaser; (function (Phaser) { /// /// /// /// /// /// /// /// /// /// /** * Phaser - Advanced Physics - Collision Handlers * * Based on the work Ju Hyung Lee started in JS PhyRus. */ (function (Physics) { var Collision = (function () { function Collision() { } Collision.prototype.collide = function (a, b, contacts) { // Circle (a is the circle) if(a.type == Physics.AdvancedPhysics.SHAPE_TYPE_CIRCLE) { if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_CIRCLE) { return this.circle2Circle(a, b, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_SEGMENT) { return this.circle2Segment(a, b, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_POLY) { return this.circle2Poly(a, b, contacts); } } // Segment (a is the segment) if(a.type == Physics.AdvancedPhysics.SHAPE_TYPE_SEGMENT) { if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_CIRCLE) { return this.circle2Segment(b, a, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_SEGMENT) { return this.segment2Segment(a, b, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_POLY) { return this.segment2Poly(a, b, contacts); } } // Poly (a is the poly) if(a.type == Physics.AdvancedPhysics.SHAPE_TYPE_POLY) { if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_CIRCLE) { return this.circle2Poly(b, a, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_SEGMENT) { return this.segment2Poly(b, a, contacts); } else if(b.type == Physics.AdvancedPhysics.SHAPE_TYPE_POLY) { return this.poly2Poly(a, b, contacts); } } }; Collision.prototype._circle2Circle = function (c1, r1, c2, r2, contactArr) { var rmax = r1 + r2; var t = new Phaser.Vec2(); //var t = vec2.sub(c2, c1); Phaser.Vec2Utils.subtract(c2, c1, t); var distsq = t.lengthSq(); if(distsq > rmax * rmax) { return 0; } var dist = Math.sqrt(distsq); var p = new Phaser.Vec2(); Phaser.Vec2Utils.multiplyAdd(c1, t, 0.5 + (r1 - r2) * 0.5 / dist, p); //var p = vec2.mad(c1, t, 0.5 + (r1 - r2) * 0.5 / dist); var n = new Phaser.Vec2(); //var n = (dist != 0) ? vec2.scale(t, 1 / dist) : vec2.zero; if(dist != 0) { Phaser.Vec2Utils.scale(t, 1 / dist, n); } var d = dist - rmax; contactArr.push(new Physics.Contact(p, n, d, 0)); return 1; }; Collision.prototype.circle2Circle = function (circ1, circ2, contactArr) { return this._circle2Circle(circ1.tc, circ1.radius, circ2.tc, circ2.radius, contactArr); }; Collision.prototype.circle2Segment = function (circ, seg, contactArr) { var rsum = circ.radius + seg.radius; // Normal distance from segment var dn = Phaser.Vec2Utils.dot(circ.tc, seg.tn) - Phaser.Vec2Utils.dot(seg.ta, seg.tn); var dist = (dn < 0 ? dn * -1 : dn) - rsum; if(dist > 0) { return 0; } // Tangential distance along segment var dt = Phaser.Vec2Utils.cross(circ.tc, seg.tn); var dtMin = Phaser.Vec2Utils.cross(seg.ta, seg.tn); var dtMax = Phaser.Vec2Utils.cross(seg.tb, seg.tn); if(dt < dtMin) { if(dt < dtMin - rsum) { return 0; } return this._circle2Circle(circ.tc, circ.radius, seg.ta, seg.radius, contactArr); } else if(dt > dtMax) { if(dt > dtMax + rsum) { return 0; } return this._circle2Circle(circ.tc, circ.radius, seg.tb, seg.radius, contactArr); } var n = new Phaser.Vec2(); if(dn > 0) { n.copyFrom(seg.tn); } else { Phaser.Vec2Utils.negative(seg.tn, n); } //var n = (dn > 0) ? seg.tn : vec2.neg(seg.tn); var c1 = new Phaser.Vec2(); Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + dist * 0.5), c1); var c2 = new Phaser.Vec2(); Phaser.Vec2Utils.negative(n, c2); contactArr.push(new Physics.Contact(c1, c2, dist, 0)); //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + dist * 0.5)), vec2.neg(n), dist, 0)); return 1; }; Collision.prototype.circle2Poly = function (circ, poly, contactArr) { var minDist = -999999; var minIdx = -1; for(var i = 0; i < poly.verts.length; i++) { var plane = poly.tplanes[i]; var dist = Phaser.Vec2Utils.dot(circ.tc, plane.normal) - plane.d - circ.radius; if(dist > 0) { return 0; } else if(dist > minDist) { minDist = dist; minIdx = i; } } var n = poly.tplanes[minIdx].normal; var a = poly.tverts[minIdx]; var b = poly.tverts[(minIdx + 1) % poly.verts.length]; var dta = Phaser.Vec2Utils.cross(a, n); var dtb = Phaser.Vec2Utils.cross(b, n); var dt = Phaser.Vec2Utils.cross(circ.tc, n); if(dt > dta) { return this._circle2Circle(circ.tc, circ.radius, a, 0, contactArr); } else if(dt < dtb) { return this._circle2Circle(circ.tc, circ.radius, b, 0, contactArr); } var c1 = new Phaser.Vec2(); Phaser.Vec2Utils.multiplyAdd(circ.tc, n, -(circ.radius + minDist * 0.5), c1); var c2 = new Phaser.Vec2(); Phaser.Vec2Utils.negative(n, c2); contactArr.push(new Physics.Contact(c1, c2, minDist, 0)); //contactArr.push(new Contact(vec2.mad(circ.tc, n, -(circ.r + minDist * 0.5)), vec2.neg(n), minDist, 0)); return 1; }; Collision.prototype.segmentPointDistanceSq = function (seg, p) { var w = new Phaser.Vec2(); var d = new Phaser.Vec2(); Phaser.Vec2Utils.subtract(p, seg.ta, w); Phaser.Vec2Utils.subtract(seg.tb, seg.ta, d); //var w = vec2.sub(p, seg.ta); //var d = vec2.sub(seg.tb, seg.ta); var proj = w.dot(d); if(proj <= 0) { return w.dot(w); } var vsq = d.dot(d); if(proj >= vsq) { return w.dot(w) - 2 * proj + vsq; } return w.dot(w) - proj * proj / vsq; }; Collision.prototype.segment2Segment = // FIXME and optimise me lots!!! function (seg1, seg2, contactArr) { var d = []; d[0] = this.segmentPointDistanceSq(seg1, seg2.ta); d[1] = this.segmentPointDistanceSq(seg1, seg2.tb); d[2] = this.segmentPointDistanceSq(seg2, seg1.ta); d[3] = this.segmentPointDistanceSq(seg2, seg1.tb); var idx1 = d[0] < d[1] ? 0 : 1; var idx2 = d[2] < d[3] ? 2 : 3; var idxm = d[idx1] < d[idx2] ? idx1 : idx2; var s, t; var u = Phaser.Vec2Utils.subtract(seg1.tb, seg1.ta); var v = Phaser.Vec2Utils.subtract(seg2.tb, seg2.ta); switch(idxm) { case 0: s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.ta, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); s = s < 0 ? 0 : (s > 1 ? 1 : s); t = 0; break; case 1: s = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg2.tb, seg1.ta), u) / Phaser.Vec2Utils.dot(u, u); s = s < 0 ? 0 : (s > 1 ? 1 : s); t = 1; break; case 2: s = 0; t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.ta, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); t = t < 0 ? 0 : (t > 1 ? 1 : t); break; case 3: s = 1; t = Phaser.Vec2Utils.dot(Phaser.Vec2Utils.subtract(seg1.tb, seg2.ta), v) / Phaser.Vec2Utils.dot(v, v); t = t < 0 ? 0 : (t > 1 ? 1 : t); break; } var minp1 = Phaser.Vec2Utils.multiplyAdd(seg1.ta, u, s); var minp2 = Phaser.Vec2Utils.multiplyAdd(seg2.ta, v, t); return this._circle2Circle(minp1, seg1.radius, minp2, seg2.radius, contactArr); }; Collision.prototype.findPointsBehindSeg = // Identify vertexes that have penetrated the segment. function (contactArr, seg, poly, dist, coef) { var dta = Phaser.Vec2Utils.cross(seg.tn, seg.ta); var dtb = Phaser.Vec2Utils.cross(seg.tn, seg.tb); var n = new Phaser.Vec2(); Phaser.Vec2Utils.scale(seg.tn, coef, n); //var n = vec2.scale(seg.tn, coef); for(var i = 0; i < poly.verts.length; i++) { var v = poly.tverts[i]; if(Phaser.Vec2Utils.dot(v, n) < Phaser.Vec2Utils.dot(seg.tn, seg.ta) * coef + seg.radius) { var dt = Phaser.Vec2Utils.cross(seg.tn, v); if(dta >= dt && dt >= dtb) { contactArr.push(new Physics.Contact(v, n, dist, (poly.id << 16) | i)); } } } }; Collision.prototype.segment2Poly = function (seg, poly, contactArr) { var seg_td = Phaser.Vec2Utils.dot(seg.tn, seg.ta); var seg_d1 = poly.distanceOnPlane(seg.tn, seg_td) - seg.radius; if(seg_d1 > 0) { return 0; } var n = new Phaser.Vec2(); Phaser.Vec2Utils.negative(seg.tn, n); var seg_d2 = poly.distanceOnPlane(n, -seg_td) - seg.radius; //var seg_d2 = poly.distanceOnPlane(vec2.neg(seg.tn), -seg_td) - seg.r; if(seg_d2 > 0) { return 0; } var poly_d = -999999; var poly_i = -1; for(var i = 0; i < poly.verts.length; i++) { var plane = poly.tplanes[i]; var dist = seg.distanceOnPlane(plane.normal, plane.d); if(dist > 0) { return 0; } if(dist > poly_d) { poly_d = dist; poly_i = i; } } var poly_n = new Phaser.Vec2(); Phaser.Vec2Utils.negative(poly.tplanes[poly_i].normal, poly_n); //var poly_n = vec2.neg(poly.tplanes[poly_i].n); var va = new Phaser.Vec2(); Phaser.Vec2Utils.multiplyAdd(seg.ta, poly_n, seg.radius, va); //var va = vec2.mad(seg.ta, poly_n, seg.r); var vb = new Phaser.Vec2(); Phaser.Vec2Utils.multiplyAdd(seg.tb, poly_n, seg.radius, vb); //var vb = vec2.mad(seg.tb, poly_n, seg.r); if(poly.containPoint(va)) { contactArr.push(new Physics.Contact(va, poly_n, poly_d, (seg.id << 16) | 0)); } if(poly.containPoint(vb)) { contactArr.push(new Physics.Contact(vb, poly_n, poly_d, (seg.id << 16) | 1)); } // Floating point precision problems here. // This will have to do for now. poly_d -= 0.1; if(seg_d1 >= poly_d || seg_d2 >= poly_d) { if(seg_d1 > seg_d2) { this.findPointsBehindSeg(contactArr, seg, poly, seg_d1, 1); } else { this.findPointsBehindSeg(contactArr, seg, poly, seg_d2, -1); } } // If no other collision points are found, try colliding endpoints. if(contactArr.length == 0) { var poly_a = poly.tverts[poly_i]; var poly_b = poly.tverts[(poly_i + 1) % poly.verts.length]; if(this._circle2Circle(seg.ta, seg.radius, poly_a, 0, contactArr)) { return 1; } if(this._circle2Circle(seg.tb, seg.radius, poly_a, 0, contactArr)) { return 1; } if(this._circle2Circle(seg.ta, seg.radius, poly_b, 0, contactArr)) { return 1; } if(this._circle2Circle(seg.tb, seg.radius, poly_b, 0, contactArr)) { return 1; } } return contactArr.length; }; Collision.prototype.findMSA = // Find the minimum separating axis for the given poly and plane list. function (poly, planes, num) { var min_dist = -999999; var min_index = -1; for(var i = 0; i < num; i++) { var dist = poly.distanceOnPlane(planes[i].normal, planes[i].d); if(dist > 0) { // no collision return { dist: 0, index: -1 }; } else if(dist > min_dist) { min_dist = dist; min_index = i; } } // new object - see what we can do here return { dist: min_dist, index: min_index }; }; Collision.prototype.findVertsFallback = function (contactArr, poly1, poly2, n, dist) { var num = 0; for(var i = 0; i < poly1.verts.length; i++) { var v = poly1.tverts[i]; if(poly2.containPointPartial(v, n)) { contactArr.push(new Physics.Contact(v, n, dist, (poly1.id << 16) | i)); num++; } } for(var i = 0; i < poly2.verts.length; i++) { var v = poly2.tverts[i]; if(poly1.containPointPartial(v, n)) { contactArr.push(new Physics.Contact(v, n, dist, (poly2.id << 16) | i)); num++; } } return num; }; Collision.prototype.findVerts = // Find the overlapped vertices. function (contactArr, poly1, poly2, n, dist) { var num = 0; for(var i = 0; i < poly1.verts.length; i++) { var v = poly1.tverts[i]; if(poly2.containPoint(v)) { contactArr.push(new Physics.Contact(v, n, dist, (poly1.id << 16) | i)); num++; } } for(var i = 0; i < poly2.verts.length; i++) { var v = poly2.tverts[i]; if(poly1.containPoint(v)) { contactArr.push(new Physics.Contact(v, n, dist, (poly2.id << 16) | i)); num++; } } return num > 0 ? num : this.findVertsFallback(contactArr, poly1, poly2, n, dist); }; Collision.prototype.poly2Poly = function (poly1, poly2, contactArr) { var msa1 = this.findMSA(poly2, poly1.tplanes, poly1.verts.length); if(msa1.index == -1) { console.log('poly2poly 0', msa1); return 0; } var msa2 = this.findMSA(poly1, poly2.tplanes, poly2.verts.length); if(msa2.index == -1) { console.log('poly2poly 1', msa2); return 0; } // Penetration normal direction should be from poly1 to poly2 if(msa1.dist > msa2.dist) { return this.findVerts(contactArr, poly1, poly2, poly1.tplanes[msa1.index].normal, msa1.dist); } return this.findVerts(contactArr, poly1, poly2, Phaser.Vec2Utils.negative(poly2.tplanes[msa2.index].normal), msa2.dist); }; return Collision; })(); Physics.Collision = Collision; })(Phaser.Physics || (Phaser.Physics = {})); var Physics = Phaser.Physics; })(Phaser || (Phaser = {}));