# Edit in JSFiddle

```// 分离轴检测
(function(global, doc) {
var inherit, extend, Vector, Projection,
Shape, Polygon, Point, Circle, Line,
getPolygonPointClosestToCircle, polygonCollidesWithCircle,
polygonCollidesWithPolygon, circleCollidesWithCircle,
shapeCollideswithLine;

// 对象扩展................................
extend = function extend(target, obj) {
var prop, val;
if (!Array.isArray(obj)) {
for (prop in obj) {
val = obj[prop];
if (typeof val === 'object') {
target[prop] = Array.isArray(val) ? [] : {};
extend(target[prop], val);
} else {
target[prop] = val;
}
}
} else {
for (prop = 0; prop < obj.length; prop++) {
val = obj[prop];
if (typeof val === 'object') {
target[prop] = Array.isArray(val) ? [] : {};
extend(target[prop], val);
} else {
target[prop] = val;
}
}
}
};

// 原型继承.................................
inherit = function(Child, Parent) {
var Temp = function() {
this.constructor = Child;
};
Temp.prototype = Parent.prototype;
Child.prototype = new Temp();
};

// 获取多边形上最靠近圆形的点...................
getPolygonPointClosestToCircle = function(polygon, circle) {
var min = Infinity,
closestPoint, length;

polygon.points.forEach(function(point) {
length = Math.pow(circle.x - point.x, 2) + Math.pow(circle.y - point.y, 2);
if (length < min) {
min = length;
closestPoint = point;
}
});

return closestPoint;
};

// 圆形碰撞圆形...........................................
circleCollidesWithCircle = function(c1, c2) {
var distace = Math.sqrt(Math.pow(c1.x - c2.x, 2) +
Math.pow(c1.y - c2.y, 2));
return {
axis: (new Vector(c1.x - c2.x, c1.y - c2.y)).normalize(),
}
};

// 多边形碰撞多边形.........................................
polygonCollidesWithPolygon = function(p1, p2) {
var axes = p1.getAxes();
axes = axes.concat(p2.getAxes());
return p1.minimumTranslationVector(axes, p2);
};

// 多边形和圆形之间的碰撞................................
polygonCollidesWithCircle = function(polygon, circle) {
var axes = polygon.getAxes(),
closestPoint = getPolygonPointClosestToCircle(polygon, circle),
v1, v2;

v1 = new Vector(closestPoint.x, closestPoint.y);
v2 = new Vector(circle.x, circle.y);

axes.push(v1.edge(v2).normalize());

return polygon.minimumTranslationVector(axes, circle);
};

// 图形和直线之间的碰撞...................................
shapeCollideswithLine = function(shape, line) {
var axes = line.getAxes();
return shape.minimumTranslationVector(axes, line);
};

// 矢量.....................................
Vector = function(point) {
if (arguments.length === 1) {
this.x = point.x;
this.y = point.y;
} else {
this.x = arguments[0];
this.y = arguments[1];
}
};

extend(Vector.prototype, {
getMagnitude: function() {
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
},
return new Vector(this.x + vector.x, this.y + vector.y);
},
subtract: function(vector) {
return new Vector(this.x - vector.x, this.y - vector.y);
},
dotProduct: function(vector) {
return this.x * vector.x + this.y * vector.y;
},
edge: function(vector) {
return this.subtract(vector);
},
prependicular: function() {
return new Vector(this.y, -this.x);
},

// 获取单位向量
normalize: function() {
var v = new Vector(0, 0),
m = this.getMagnitude();
if (m !== 0) {
v.x = this.x / m;
v.y = this.y / m;
}
return v;
},

// 获取法向量
normal: function() {
return this.prependicular().normalize();
}
});

// 投影.....................................
Projection = function(min, max) {
this.min = min;
this.max = max;
};

Projection.prototype.overlap = function(projection) {
return Math.min(this.max, projection.max) - Math.max(this.min, projection.min);
};

// 形状.....................................
Shape = function() {};

extend(Shape.prototype, {
collidesWith: function(anotherShape) {
var axes = this.getAxes().concat(anotherShape.getAxes()),
mtv;

return mtv.overlap > 0 ? true : false;
},

// 检查每个轴上投影的间隔，存在返回最小偏移量
minimumTranslationVector: function(axes, anotherShape) {
var minOverlap = Infinity,
i, axis, projection1, projection2,
overlap, axisWithSmallOverlap;

for (i = 0; i < axes.length; i++) {
axis = axes[i];
projection1 = this.project(axis);
projection2 = anotherShape.project(axis);
overlap = projection1.overlap(projection2);
if (overlap < minOverlap) {
axisWithSmallOverlap = axis;
minOverlap = overlap;
}
if (overlap <= 0) break;
}

return {
axis: axisWithSmallOverlap,
overlap: minOverlap
};
},

isPointInPath: function(context, x, y) {
this.createPath(context);
return context.isPointInPath(x, y);
},

run: function(dTime) {
this.velocity.x = this.lastVelocity.x;
this.velocity.y = this.lastVelocity.y;
this.move(this.velocity.x * dTime / 1000, this.velocity.y * dTime  / 1000);
},

// 获取轴线
getAxes: function() {
throw 'getAxes() not implemented';
},

// 获取在某一轴线上的投影
project: function(axis) {
throw 'project(axis) not implemented';
},

move: function() {
throw 'move(x, y) not implemented';
}
});

// 点..........................................
Point = function(point) {
if (arguments.length === 1) {
this.x = point.x;
this.y = point.y;
} else {
this.x = arguments[0];
this.y = arguments[1];
}
};

// 多边形......................................
Polygon = function(points, velocity) {
this.points = points || [];
this.lastVelocity = velocity || {
x: 10,
y: 10
};
this.velocity = {};
this.velocity.x = this.lastVelocity.x;
this.velocity.y = this.lastVelocity.y;
};

inherit(Polygon, Shape);

extend(Polygon.prototype, {
collidesWith: function(anotherShape) {
if (anotherShape instanceof Circle) {
return polygonCollidesWithCircle(this, anotherShape);
} else if (anotherShape instanceof Polygon) {
return polygonCollidesWithPolygon(this, anotherShape);
} else if (anotherShape instanceof Line) {
return shapeCollideswithLine(this, anotherShape);
}
},
project: function(axis) {
var scalars = [],
v = new Vector();
this.points.forEach(function(point) {
v.x = point.x;
v.y = point.y;
scalars.push(v.dotProduct(axis));
});
return new Projection(Math.min.apply(Math, scalars),
Math.max.apply(Math, scalars));
},
getAxes: function() {
var v1 = new Vector(),
v2 = new Vector(),
axes = [];

this.points.forEach(function(point, i, points) {
v1.x = point.x;
v1.y = point.y;

if (i + 1 < points.length) {
v2.x = points[i + 1].x;
v2.y = points[i + 1].y;
} else {
v2.x = points[0].x;
v2.y = points[0].y;
}

axes.push(v1.edge(v2).normal());
});

return axes;
},
centroid: function() {
var result = {
x: 0,
y: 0
},
n = this.points.length;
this.points.forEach(function(point) {
result.x += point.x;
result.y += point.y;
});
result.x /= n;
result.y /= n;
return result;
},
this.points.push(new Point(x, y));
},
move: function(dX, dY) {
this.points.forEach(function(point) {
point.x += dX;
point.y += dY;
});
},
createPath: function(context) {
context.beginPath();
this.points.forEach(function(point) {
context.lineTo(point.x, point.y);
});
context.closePath;
},
draw: function(context) {
this.createPath(context);
context.fill();
}
});

// 圆形.......................................
Circle = function(x, y, radius, velocity) {
this.x = x;
this.y = y;
this.lastVelocity = velocity || {
x: 10,
y: 10
};
this.velocity = {};
this.velocity.x = this.lastVelocity.x;
this.velocity.y = this.lastVelocity.y;
};

inherit(Circle, Shape);

extend(Circle.prototype, {
collidesWith: function(anotherShape) {
if (anotherShape instanceof Circle) {
return circleCollidesWithCircle(this, anotherShape);
} else if (anotherShape instanceof Polygon) {
return polygonCollidesWithCircle(anotherShape, this);
} else if (anotherShape instanceof Line) {
return shapeCollideswithLine(this, anotherShape);
}
},
getAxes: function() { return; },
project: function(axis) {
var dotProduct = new Vector(this.x, this.y).dotProduct(axis);
},
centroid: function() {
return {
x: this.x,
y: this.y
}
},
createPath: function(context) {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
context.closePath();
},
draw: function(context) {
this.createPath(context);
context.fill();
},
move: function(dX, dY) {
this.x += dX;
this.y += dY;
}
});

// 直线
Line = function(point, vector) {
if (arguments.length === 2) {
this.x = point.x;
this.y = point.y;
this.vector = vector;
} else {
this.x = arguments[0];
this.y = arguments[1];
this.vector = arguments[2];
}
};

inherit(Line, Shape);

extend(Line.prototype, {
getAxes: function() {
return [this.vector.normal()];
},
project: function(axis) {
var n = axis.dotProduct(this);
return new Projection(n, n + 1);
},
centroid: function() {
return {
x: this.x,
y: this.y
}
}
});

global.Circle = Circle;
global.Polygon = Polygon;
global.Point = Point;
global.Shape = Shape;
global.Vector = Vector;
global.Line = Line;
})(this, this.document);

(function(global, doc) {
var canvas = document.getElementById('mycanvas'),
cxt = canvas.getContext('2d'),
Rect = global.Rect,
w = 600,
h = 300,
rectArr = [],
tree, i, len, time;

// 设置canvas尺寸
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);

cxt.fillStyle = '#000';

var windowToCanvas = function(canvas, x, y) {
var bounds = canvas.getBoundingClientRect();

return {
x: (x - bounds.left) * (canvas.width / bounds.width),
y: (y - bounds.top) * (canvas.height / bounds.height)
};
};

var polygonPoints = [
[new Point(250, 130), new Point(250, 250),
new Point(350, 250), new Point(400, 150)],
[new Point(400, 130), new Point(400, 200),
new Point(490, 100), new Point(450, 10)]
],
c1 = new Circle(50, 50, 40, {x: 100, y: 100}),
c2 = new Circle(100, 200, 20),
p1 = new Polygon(polygonPoints[0], {x: -50, y: 80}),
p2 = new Polygon(polygonPoints[1], {x: 30, y: -60}),
lastLoc = {},
shapes = [],
shapeBeingDrag;

shapes.push(c1, p1, p2, c2);

shapes.forEach(function(shape) {
shape.draw(cxt);
});

var loc = windowToCanvas(this, e.clientX, e.clientY);
shapes.every(function(shape) {
if (shape.isPointInPath(cxt, loc.x, loc.y)) {
shapeBeingDrag = shape;
lastLoc.x = loc.x;
lastLoc.y = loc.y;
return false;
}
return true;
});
});

shapeBeingDrag = null;
});

var separate = function(shape, mtv, velocity) {
dX = mtv.axis.x * mtv.overlap;
dY = mtv.axis.y * mtv.overlap;
if (dX * velocity.x > 0) dX = -dX;
if (dY * velocity.y > 0) dY = -dY;
shape.move(dX, dY);
};

var loc, velocity;
if (shapeBeingDrag) {
cxt.clearRect(0, 0, w, h);

loc = windowToCanvas(this, e.clientX, e.clientY);
velocity = {
x: loc.x - lastLoc.x,
y: loc.y - lastLoc.y
};
shapeBeingDrag.move(loc.x - lastLoc.x, loc.y - lastLoc.y);
shapes.forEach(function(shape, i) {
var isCollided = false,
j, mtv;
for (j = 0; j < shapes.length; j++) {
if (j === i) continue;
mtv = shape.collidesWith(shapes[j]);
isCollided = mtv.overlap > 0 ? true : false;
if (isCollided) break;
}
if (isCollided) {
// separate(shapeBeingDrag, mtv, velocity);
cxt.save();
cxt.fillStyle = 'red';
shape.draw(cxt);
cxt.restore();
} else {
shape.draw(cxt);
}
});

lastLoc.x = loc.x;
lastLoc.y = loc.y;
}
});

var bounce = function(mtv, collider, collidee) {
var centroid1 = new Vector(collider.centroid()),
centroid2 = new Vector(collidee.centroid()),
centroidVector = centroid1.subtract(centroid2),
dotProductRatio, lastVelocity;

if (mtv.axis.dotProduct(centroidVector) < 0) {
mtv.axis.x = -mtv.axis.x;
mtv.axis.y = -mtv.axis.y;
}

if (mtv.axis.dotProduct(collider.velocity) > 0) {
return;
}

dotProductRatio = Math.abs(2 * mtv.axis.dotProduct(collider.velocity) /
mtv.axis.dotProduct(mtv.axis));

mtv.axis.x *= dotProductRatio;
mtv.axis.y *= dotProductRatio;

collider.lastVelocity.x = lastVelocity.x;
collider.lastVelocity.y = lastVelocity.y;
};

var bounds = [
new Line(0, 0, new Vector(1, 0)),
new Line(0, 0, new Vector(0, 1)),
new Line(0, h, new Vector(1, 0)),
new Line(w, 0, new Vector(0, 1))
];

draw = function() {
cxt.clearRect(0, 0, w, h);
shapes.forEach(function(shape, i) {
var mtv, j;
shape.run(16);

for (j = 0; j < shapes.length; j++) {
if (j === i) continue;
mtv = shape.collidesWith(shapes[j]);
if (mtv.overlap > 0) {
separate(shape, mtv, shape.velocity);
bounce(mtv, shape, shapes[j]);
}
}

for (j = 0; j < bounds.length; j++) {
mtv = shape.collidesWith(bounds[j]);
if (mtv.overlap > 0) {
separate(shape, mtv, shape.velocity);
bounce(mtv, shape, bounds[j]);
}
}

shape.draw(cxt);
});
requestAnimationFrame(draw);
};

})(this, this.document);```