Files
esengine/source/src/Physics/Shapes/ShapeCollisions/ShapeCollisions.ts

450 lines
19 KiB
TypeScript
Raw Normal View History

2020-07-23 11:00:46 +08:00
module es {
2020-08-03 14:06:47 +08:00
/**
*
* (shape1)
* pos应该设置为shape1pos - shape2.pos)
*/
2020-07-23 11:00:46 +08:00
export class ShapeCollisions {
/**
*
* @param first
* @param second
* @param result
2020-07-23 11:00:46 +08:00
*/
public static polygonToPolygon(first: Polygon, second: Polygon, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
let isIntersecting = true;
let firstEdges = first.edgeNormals;
let secondEdges = second.edgeNormals;
let minIntervalDistance = Number.POSITIVE_INFINITY;
let translationAxis = new Vector2();
let polygonOffset = Vector2.subtract(first.position, second.position);
let axis: Vector2;
// 循环穿过两个多边形的所有边
for (let edgeIndex = 0; edgeIndex < firstEdges.length + secondEdges.length; edgeIndex++) {
// 1. 找出当前多边形是否相交
// 多边形的归一化轴垂直于缓存给我们的当前边
if (edgeIndex < firstEdges.length) {
axis = firstEdges[edgeIndex];
} else {
axis = secondEdges[edgeIndex - firstEdges.length];
}
// 求多边形在当前轴上的投影
let minA = 0;
let minB = 0;
let maxA = 0;
let maxB = 0;
let intervalDist = 0;
let ta = this.getInterval(axis, first, minA, maxA);
minA = ta.min;
minB = ta.max;
let tb = this.getInterval(axis, second, minB, maxB);
minB = tb.min;
maxB = tb.max;
// 将区间设为第二个多边形的空间。由轴上投影的位置差偏移。
let relativeIntervalOffset = Vector2.dot(polygonOffset, axis);
minA += relativeIntervalOffset;
maxA += relativeIntervalOffset;
// 检查多边形投影是否正在相交
intervalDist = this.intervalDistance(minA, maxA, minB, maxB);
if (intervalDist > 0)
isIntersecting = false;
// 对于多对多数据类型转换添加一个Vector2?参数称为deltaMovement。为了提高速度我们这里不使用它
// TODO: 现在找出多边形是否会相交。只要检查速度就行了
// 如果多边形不相交,也不会相交,退出循环
if (!isIntersecting)
return false;
2020-07-23 11:00:46 +08:00
// 检查当前间隔距离是否为最小值。如果是,则存储间隔距离和当前距离。这将用于计算最小平移向量
intervalDist = Math.abs(intervalDist);
if (intervalDist < minIntervalDistance) {
minIntervalDistance = intervalDist;
translationAxis = axis;
if (Vector2.dot(translationAxis, polygonOffset) < 0)
translationAxis = new Vector2(-translationAxis);
}
}
2020-07-23 11:00:46 +08:00
// 利用最小平移向量对多边形进行推入。
result.normal = translationAxis;
result.minimumTranslationVector = Vector2.multiply(new Vector2(-translationAxis.x, -translationAxis.y), new Vector2(minIntervalDistance));
2020-06-16 00:04:28 +08:00
return true;
}
2020-06-16 00:04:28 +08:00
2020-07-23 11:00:46 +08:00
/**
* [minA, maxA][minB, maxB]
* @param minA
* @param maxA
* @param minB
* @param maxB
*/
public static intervalDistance(minA: number, maxA: number, minB: number, maxB) {
if (minA < minB)
return minB - maxA;
return minA - minB;
}
2020-06-16 00:04:28 +08:00
2020-07-23 11:00:46 +08:00
/**
* [minmax]
* @param axis
* @param polygon
* @param min
* @param max
*/
public static getInterval(axis: Vector2, polygon: Polygon, min: number, max: number) {
let dot = Vector2.dot(polygon.points[0], axis);
min = max = dot;
for (let i = 1; i < polygon.points.length; i++) {
dot = Vector2.dot(polygon.points[i], axis);
if (dot < min) {
min = dot;
} else if (dot > max) {
max = dot;
}
}
2020-06-16 00:04:28 +08:00
2020-07-28 16:25:20 +08:00
return {min: min, max: max};
2020-07-23 11:00:46 +08:00
}
2020-06-15 08:46:38 +08:00
2020-07-23 11:00:46 +08:00
/**
*
* @param circle
* @param polygon
* @param result
2020-07-23 11:00:46 +08:00
*/
public static circleToPolygon(circle: Circle, polygon: Polygon, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
let poly2Circle = Vector2.subtract(circle.position, polygon.position);
2020-06-15 08:46:38 +08:00
2020-08-25 14:21:37 +08:00
let distanceSquared = new Ref(0);
let closestPoint = Polygon.getClosestPointOnPolygonToPoint(polygon.points, poly2Circle, distanceSquared, result.normal);
2020-06-15 08:46:38 +08:00
2020-07-23 11:00:46 +08:00
let circleCenterInsidePoly = polygon.containsPoint(circle.position);
2020-08-25 14:21:37 +08:00
if (distanceSquared.value > circle.radius * circle.radius && !circleCenterInsidePoly)
return false;
2020-06-15 08:46:38 +08:00
2020-07-23 11:00:46 +08:00
let mtv: Vector2;
if (circleCenterInsidePoly) {
2020-08-25 14:21:37 +08:00
mtv = Vector2.multiply(result.normal, new Vector2(Math.sqrt(distanceSquared.value) - circle.radius));
2020-06-16 00:04:28 +08:00
} else {
2020-08-25 14:21:37 +08:00
if (distanceSquared.value == 0) {
2020-07-23 11:00:46 +08:00
mtv = Vector2.multiply(result.normal, new Vector2(circle.radius));
} else {
2020-08-25 14:21:37 +08:00
let distance = Math.sqrt(distanceSquared.value);
mtv = Vector2.multiply(new Vector2(-Vector2.subtract(poly2Circle, closestPoint)), new Vector2((circle.radius - distanceSquared.value) / distance));
2020-07-23 11:00:46 +08:00
}
2020-06-15 08:46:38 +08:00
}
2020-07-23 11:00:46 +08:00
result.minimumTranslationVector = mtv;
result.point = Vector2.add(closestPoint, polygon.position);
2020-06-16 00:04:28 +08:00
return true;
}
2020-07-23 11:00:46 +08:00
/**
*
* @param circle
* @param box
* @param result
2020-07-23 11:00:46 +08:00
*/
public static circleToBox(circle: Circle, box: Box, result: CollisionResult): boolean {
let closestPointOnBounds = box.bounds.getClosestPointOnRectangleBorderToPoint(circle.position, result.normal);
2020-06-16 00:04:28 +08:00
2020-07-27 18:16:19 +08:00
// 处理那些中心在盒子里的圆,因为比较好操作,
2020-07-23 11:00:46 +08:00
if (box.containsPoint(circle.position)) {
result.point = closestPointOnBounds;
2020-06-16 00:04:28 +08:00
2020-07-27 18:16:19 +08:00
// 计算mtv。找到安全的没有碰撞的位置然后从那里得到mtv
let safePlace = Vector2.add(closestPointOnBounds, Vector2.multiply(result.normal, new Vector2(circle.radius)));
2020-07-23 11:00:46 +08:00
result.minimumTranslationVector = Vector2.subtract(circle.position, safePlace);
return true;
2020-07-23 11:00:46 +08:00
}
2020-07-23 11:00:46 +08:00
let sqrDistance = Vector2.distanceSquared(closestPointOnBounds, circle.position);
2020-07-27 18:16:19 +08:00
// 看盒子上的点与圆的距离是否小于半径
2020-07-23 11:00:46 +08:00
if (sqrDistance == 0) {
result.minimumTranslationVector = Vector2.multiply(result.normal, new Vector2(circle.radius));
} else if (sqrDistance <= circle.radius * circle.radius) {
result.normal = Vector2.subtract(circle.position, closestPointOnBounds);
let depth = result.normal.length() - circle.radius;
result.point = closestPointOnBounds;
Vector2Ext.normalize(result.normal);
2020-07-23 11:00:46 +08:00
result.minimumTranslationVector = Vector2.multiply(new Vector2(depth), result.normal);
return true;
2020-07-23 11:00:46 +08:00
}
return false;
2020-07-23 11:00:46 +08:00
}
2020-07-23 11:00:46 +08:00
/**
*
* @param point
* @param circle
* @param result
2020-07-23 11:00:46 +08:00
*/
public static pointToCircle(point: Vector2, circle: Circle, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
let distanceSquared = Vector2.distanceSquared(point, circle.position);
let sumOfRadii = 1 + circle.radius;
let collided = distanceSquared < sumOfRadii * sumOfRadii;
if (collided) {
result.normal = Vector2.normalize(Vector2.subtract(point, circle.position));
let depth = sumOfRadii - Math.sqrt(distanceSquared);
result.minimumTranslationVector = Vector2.multiply(new Vector2(-depth, -depth), result.normal);
result.point = Vector2.add(circle.position, Vector2.multiply(result.normal, new Vector2(circle.radius, circle.radius)));
return true;
2020-07-23 11:00:46 +08:00
}
return false;
2020-07-23 11:00:46 +08:00
}
2020-07-31 17:17:44 +08:00
public static pointToBox(point: Vector2, box: Box, result: CollisionResult){
if (box.containsPoint(point)){
// 在方框的空间里找到点
result.point = box.bounds.getClosestPointOnRectangleBorderToPoint(point, result.normal);
result.minimumTranslationVector = Vector2.subtract(point, result.point);
return true;
}
return false;
}
2020-07-23 11:00:46 +08:00
/**
*
* @param lineA
* @param lineB
* @param closestTo
*/
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2): Vector2 {
2020-07-23 11:00:46 +08:00
let v = Vector2.subtract(lineB, lineA);
let w = Vector2.subtract(closestTo, lineA);
let t = Vector2.dot(w, v) / Vector2.dot(v, v);
t = MathHelper.clamp(t, 0, 1);
return Vector2.add(lineA, Vector2.multiply(v, new Vector2(t, t)));
}
2020-07-23 11:00:46 +08:00
/**
*
* @param point
* @param poly
* @param result
2020-07-23 11:00:46 +08:00
*/
public static pointToPoly(point: Vector2, poly: Polygon, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
if (poly.containsPoint(point)) {
2020-08-25 14:21:37 +08:00
let distanceSquared = new Ref(0);
let closestPoint = Polygon.getClosestPointOnPolygonToPoint(poly.points, Vector2.subtract(point, poly.position), distanceSquared, result.normal);
2020-07-23 11:00:46 +08:00
2020-08-25 14:21:37 +08:00
result.minimumTranslationVector = Vector2.multiply(result.normal, new Vector2(Math.sqrt(distanceSquared.value), Math.sqrt(distanceSquared.value)));
2020-07-23 11:00:46 +08:00
result.point = Vector2.add(closestPoint, poly.position);
return true;
2020-07-23 11:00:46 +08:00
}
2020-06-16 00:04:28 +08:00
return false;
2020-07-23 11:00:46 +08:00
}
2020-06-16 00:04:28 +08:00
2020-07-23 11:00:46 +08:00
/**
*
* @param first
* @param second
* @param result
2020-07-23 11:00:46 +08:00
*/
2020-07-28 16:25:20 +08:00
public static circleToCircle(first: Circle, second: Circle, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
let distanceSquared = Vector2.distanceSquared(first.position, second.position);
let sumOfRadii = first.radius + second.radius;
let collided = distanceSquared < sumOfRadii * sumOfRadii;
2020-07-28 16:25:20 +08:00
if (collided) {
2020-07-23 11:00:46 +08:00
result.normal = Vector2.normalize(Vector2.subtract(first.position, second.position));
let depth = sumOfRadii - Math.sqrt(distanceSquared);
result.minimumTranslationVector = Vector2.multiply(new Vector2(-depth), result.normal);
result.point = Vector2.add(second.position, Vector2.multiply(result.normal, new Vector2(second.radius)));
return true;
2020-07-23 11:00:46 +08:00
}
return false;
2020-06-16 00:04:28 +08:00
}
2020-07-23 11:00:46 +08:00
/**
*
* @param first
* @param second
* @param result
2020-07-23 11:00:46 +08:00
*/
2020-07-28 16:25:20 +08:00
public static boxToBox(first: Box, second: Box, result: CollisionResult): boolean {
2020-07-23 11:00:46 +08:00
let minkowskiDiff = this.minkowskiDifference(first, second);
2020-07-28 16:25:20 +08:00
if (minkowskiDiff.contains(0, 0)) {
2020-07-23 11:00:46 +08:00
// 计算MTV。如果它是零我们就可以称它为非碰撞
result.minimumTranslationVector = minkowskiDiff.getClosestPointOnBoundsToOrigin();
2020-07-07 12:18:51 +08:00
if (result.minimumTranslationVector.equals(Vector2.zero))
return false;
2020-07-07 12:18:51 +08:00
2020-07-23 11:00:46 +08:00
result.normal = new Vector2(-result.minimumTranslationVector.x, -result.minimumTranslationVector.y);
2020-08-25 14:21:37 +08:00
result.normal.normalize();
2020-07-07 18:54:19 +08:00
return true;
2020-07-23 11:00:46 +08:00
}
2020-07-07 12:18:51 +08:00
return false;
2020-07-23 11:00:46 +08:00
}
2020-07-07 12:18:51 +08:00
2020-07-31 17:17:44 +08:00
private static minkowskiDifference(first: Box, second: Box): Rectangle {
2020-07-23 11:00:46 +08:00
// 我们需要第一个框的左上角
// 碰撞器只会修改运动的位置所以我们需要用位置来计算出运动是什么。
let positionOffset = Vector2.subtract(first.position, Vector2.add(first.bounds.location, Vector2.divide(first.bounds.size, new Vector2(2))));
let topLeft = Vector2.subtract(Vector2.add(first.bounds.location, positionOffset), second.bounds.max);
let fullSize = Vector2.add(first.bounds.size, second.bounds.size);
2020-07-07 12:18:51 +08:00
2020-07-23 11:00:46 +08:00
return new Rectangle(topLeft.x, topLeft.y, fullSize.x, fullSize.y)
}
2020-08-03 14:06:47 +08:00
2020-08-03 14:45:57 +08:00
public static lineToPoly(start: Vector2, end: Vector2, polygon: Polygon, hit: RaycastHit): boolean {
let normal = Vector2.zero;
let intersectionPoint = Vector2.zero;
let fraction = Number.MAX_VALUE;
let hasIntersection = false;
for (let j = polygon.points.length - 1, i = 0; i < polygon.points.length; j = i, i ++){
let edge1 = Vector2.add(polygon.position, polygon.points[j]);
let edge2 = Vector2.add(polygon.position, polygon.points[i]);
let intersection: Vector2 = Vector2.zero;
if (this.lineToLine(edge1, edge2, start, end, intersection)){
hasIntersection = true;
// TODO: 这是得到分数的正确和最有效的方法吗?
// 先检查x分数。如果是NaN就用y代替
let distanceFraction = (intersection.x - start.x) / (end.x - start.x);
if (Number.isNaN(distanceFraction) || Number.isFinite(distanceFraction))
distanceFraction = (intersection.y - start.y) / (end.y - start.y);
if (distanceFraction < fraction){
let edge = Vector2.subtract(edge2, edge1);
normal = new Vector2(edge.y, -edge.x);
fraction = distanceFraction;
intersectionPoint = intersection;
}
}
}
if (hasIntersection){
2020-08-25 14:21:37 +08:00
normal.normalize();
2020-08-03 14:45:57 +08:00
let distance = Vector2.distance(start, intersectionPoint);
hit.setValuesNonCollider(fraction, distance, intersectionPoint, normal);
return true;
}
return false;
}
public static lineToLine(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2){
let b = Vector2.subtract(a2, a1);
let d = Vector2.subtract(b2, b1);
let bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0表示这两条直线平行因此有无穷个交点
if (bDotDPerp == 0)
return false;
let c = Vector2.subtract(b1, a1);
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1)
return false;
let u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1)
return false;
intersection = Vector2.add(a1, Vector2.multiply(new Vector2(t), b));
2020-08-03 14:45:57 +08:00
return true;
}
public static lineToCircle(start: Vector2, end: Vector2, s: Circle, hit: RaycastHit): boolean{
// 计算这里的长度并分别对d进行标准化因为如果我们命中了我们需要它来得到分数
let lineLength = Vector2.distance(start, end);
let d = Vector2.divide(Vector2.subtract(end, start), new Vector2(lineLength));
let m = Vector2.subtract(start, s.position);
let b = Vector2.dot(m, d);
let c = Vector2.dot(m, m) - s.radius * s.radius;
// 如果r的原点在s之外(c>0)和r指向s (b>0) 则返回
if (c > 0 && b > 0)
return false;
let discr = b * b - c;
// 线不在圆圈上
if (discr < 0)
return false;
// 射线相交圆
hit.fraction = -b - Math.sqrt(discr);
// 如果分数为负数,射线从圈内开始,
if (hit.fraction < 0)
hit.fraction = 0;
hit.point = Vector2.add(start, Vector2.multiply(new Vector2(hit.fraction), d));
hit.distance = Vector2.distance(start, hit.point);
hit.normal = Vector2.normalize(Vector2.subtract(hit.point, s.position));
hit.fraction = hit.distance / lineLength;
return true;
}
2020-08-03 14:06:47 +08:00
/**
* second检查被deltaMovement移动的框的结果
* @param first
* @param second
* @param movement
* @param hit
*/
public static boxToBoxCast(first: Box, second: Box, movement: Vector2, hit: RaycastHit): boolean{
// 首先,我们检查是否有重叠。如果有重叠,我们就不做扫描测试
let minkowskiDiff = this.minkowskiDifference(first, second);
if (minkowskiDiff.contains(0, 0)){
// 计算MTV。如果它是零我们就可以称它为非碰撞
let mtv = minkowskiDiff.getClosestPointOnBoundsToOrigin();
if (mtv.equals(Vector2.zero))
return false;
hit.normal = new Vector2(-mtv.x);
2020-08-25 14:21:37 +08:00
hit.normal.normalize();
2020-08-03 14:06:47 +08:00
hit.distance = 0;
hit.fraction = 0;
return true;
}else{
// 射线投射移动矢量
let ray = new Ray2D(Vector2.zero, new Vector2(-movement.x));
2020-08-25 14:21:37 +08:00
let fraction = new Ref(0);
if (minkowskiDiff.rayIntersects(ray, fraction) && fraction.value <= 1){
hit.fraction = fraction.value;
hit.distance = movement.length() * fraction.value;
hit.normal = new Vector2(-movement.x, -movement.y);
hit.normal.normalize();
hit.centroid = Vector2.add(first.bounds.center, Vector2.multiply(movement, new Vector2(fraction.value)));
2020-08-03 14:06:47 +08:00
return true;
}
}
return false;
}
2020-07-07 12:18:51 +08:00
}
2020-07-23 11:00:46 +08:00
}