Files
esengine/source/src/Math/Rectangle.ts

560 lines
20 KiB
TypeScript
Raw Normal View History

2020-07-23 11:00:46 +08:00
module es {
export class Rectangle implements IEquatable<Rectangle> {
static emptyRectangle: Rectangle = new Rectangle();
/**
* x坐标
*/
public x: number = 0;
/**
* y坐标
*/
public y: number = 0;
/**
*
*/
public width: number = 0;
/**
*
*/
public height: number = 0;
/**
* X=0, Y=0, Width=0, Height=0
*/
public static get empty(): Rectangle {
return this.emptyRectangle;
}
/**
* Number.Min/Max值的矩形
*/
public static get maxRect(): Rectangle {
return new Rectangle(Number.MIN_VALUE / 2, Number.MIN_VALUE / 2, Number.MAX_VALUE, Number.MAX_VALUE);
}
/**
* X坐标
*/
public get left(): number {
return this.x;
}
/**
* X坐标
*/
public get right(): number {
return this.x + this.width;
}
/**
* y坐标
*/
public get top(): number {
return this.y;
}
/**
* y坐标
*/
public get bottom(): number {
return this.y + this.height;
}
2020-07-28 16:25:20 +08:00
2020-07-23 11:00:46 +08:00
/**
*
*/
public get max() {
return new Vector2(this.right, this.bottom);
}
2020-07-07 12:18:51 +08:00
/**
* 000
*/
public isEmpty(): boolean {
return ((((this.width == 0) && (this.height == 0)) && (this.x == 0)) && (this.y == 0));
2020-07-23 11:00:46 +08:00
}
/** 这个矩形的左上角坐标 */
2020-07-23 11:00:46 +08:00
public get location() {
return new Vector2(this.x, this.y);
}
2020-07-28 16:25:20 +08:00
2020-07-23 11:00:46 +08:00
public set location(value: Vector2) {
this.x = value.x;
this.y = value.y;
}
/**
* -
*/
2020-07-23 11:00:46 +08:00
public get size() {
return new Vector2(this.width, this.height);
}
2020-06-19 09:16:49 +08:00
2020-07-23 11:00:46 +08:00
public set size(value: Vector2) {
this.width = value.x;
this.height = value.y;
}
2020-06-19 09:16:49 +08:00
/**
*
* "宽度 " "高度 "
*/
public get center() {
return new Vector2(this.x + (this.width / 2), this.y + (this.height / 2));
}
// temp 用于计算边界的矩阵
public _tempMat: Matrix2D;
public _transformMat: Matrix2D;
2020-07-28 16:25:20 +08:00
/**
* Rectanglestruct实例
* @param x X坐标
* @param y y坐标
* @param width
* @param height
*/
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
/**
* /
2020-07-28 16:25:20 +08:00
* @param minX
* @param minY
* @param maxX
* @param maxY
*/
public static fromMinMax(minX: number, minY: number, maxX: number, maxY: number) {
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
/**
*
* @param points
* @returns
2020-07-28 16:25:20 +08:00
*/
public static rectEncompassingPoints(points: Vector2[]) {
// 我们需要求出x/y的最小值/最大值
let minX = Number.POSITIVE_INFINITY;
let minY = Number.POSITIVE_INFINITY;
let maxX = Number.NEGATIVE_INFINITY;
let maxY = Number.NEGATIVE_INFINITY;
for (let i = 0; i < points.length; i++) {
let pt = points[i];
if (pt.x < minX) minX = pt.x;
if (pt.x > maxX) maxX = pt.x;
if (pt.y < minY) minY = pt.y;
if (pt.y > maxY) maxY = pt.y;
}
return this.fromMinMax(minX, minY, maxX, maxY);
}
2020-07-23 11:00:46 +08:00
/**
*
* @param edge
*/
public getSide(edge: Edge) {
switch (edge) {
case Edge.top:
return this.top;
case Edge.bottom:
return this.bottom;
case Edge.left:
return this.left;
case Edge.right:
return this.right;
default:
throw new Error("Argument Out Of Range");
}
}
/**
*
* @param x X坐标
* @param y Y坐标
2020-07-23 11:00:46 +08:00
*/
public contains(x: number, y: number): boolean {
return ((((this.x <= x) && (x < (this.x + this.width))) &&
(this.y <= y)) && (y < (this.y + this.height)));
}
/**
*
* @param horizontalAmount
* @param verticalAmount
*/
public inflate(horizontalAmount: number, verticalAmount: number) {
this.x -= horizontalAmount;
this.y -= verticalAmount;
this.width += horizontalAmount * 2;
this.height += verticalAmount * 2;
}
/**
*
* @param value
*/
public intersects(value: Rectangle) {
2020-07-23 11:00:46 +08:00
return value.left < this.right &&
this.left < value.right &&
value.top < this.bottom &&
this.top < value.bottom;
}
public rayIntersects(ray: Ray2D, distance: Ref<number>): boolean {
2020-08-25 14:21:37 +08:00
distance.value = 0;
2020-07-31 19:33:04 +08:00
let maxValue = Number.MAX_VALUE;
if (Math.abs(ray.direction.x) < 1E-06) {
2020-07-31 19:33:04 +08:00
if ((ray.start.x < this.x) || (ray.start.x > this.x + this.width))
2020-08-25 14:21:37 +08:00
return false;
} else {
2020-07-31 19:33:04 +08:00
let num11 = 1 / ray.direction.x;
let num8 = (this.x - ray.start.x) * num11;
let num7 = (this.x + this.width - ray.start.x) * num11;
if (num8 > num7) {
2020-07-31 19:33:04 +08:00
let num14 = num8;
num8 = num7;
num7 = num14;
}
2020-08-25 14:21:37 +08:00
distance.value = Math.max(num8, distance.value);
2020-07-31 19:33:04 +08:00
maxValue = Math.min(num7, maxValue);
2020-08-25 14:21:37 +08:00
if (distance.value > maxValue)
return false;
2020-07-31 19:33:04 +08:00
}
if (Math.abs(ray.direction.y) < 1E-06) {
2020-07-31 19:33:04 +08:00
if ((ray.start.y < this.y) || (ray.start.y > this.y + this.height))
2020-08-25 14:21:37 +08:00
return false;
} else {
2020-07-31 19:33:04 +08:00
let num10 = 1 / ray.direction.y;
let num6 = (this.y - ray.start.y) * num10;
let num5 = (this.y + this.height - ray.start.y) * num10;
if (num6 > num5) {
2020-07-31 19:33:04 +08:00
let num13 = num6;
num6 = num5;
num5 = num13;
}
2020-08-25 14:21:37 +08:00
distance.value = Math.max(num6, distance.value);
2020-07-31 19:33:04 +08:00
maxValue = Math.max(num5, maxValue);
2020-08-25 14:21:37 +08:00
if (distance.value > maxValue)
return false;
2020-07-31 19:33:04 +08:00
}
2020-08-25 14:21:37 +08:00
return true;
2020-07-31 19:33:04 +08:00
}
2020-07-23 11:00:46 +08:00
/**
*
* @param value
*/
public containsRect(value: Rectangle) {
return ((((this.x <= value.x) && (value.x < (this.x + this.width))) &&
(this.y <= value.y)) &&
(value.y < (this.y + this.height)));
}
2020-06-19 09:16:49 +08:00
2020-07-23 11:00:46 +08:00
public getHalfSize() {
return new Vector2(this.width * 0.5, this.height * 0.5);
}
2020-06-19 18:16:42 +08:00
public getClosestPointOnBoundsToOrigin() {
let max = this.max;
let minDist = Math.abs(this.location.x);
let boundsPoint = new Vector2(this.location.x, 0);
if (Math.abs(max.x) < minDist) {
minDist = Math.abs(max.x);
boundsPoint.x = max.x;
boundsPoint.y = 0;
}
if (Math.abs(max.y) < minDist) {
minDist = Math.abs(max.y);
boundsPoint.x = 0;
boundsPoint.y = max.y;
}
if (Math.abs(this.location.y) < minDist) {
minDist = Math.abs(this.location.y);
boundsPoint.x = 0;
boundsPoint.y = this.location.y;
}
return boundsPoint;
}
/**
*
* @param point
*/
public getClosestPointOnRectangleToPoint(point: Vector2) {
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
let res = new Vector2();
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
return res;
}
2020-07-23 11:00:46 +08:00
/**
*
* @param point
* @param edgeNormal
* @returns
2020-07-23 11:00:46 +08:00
*/
public getClosestPointOnRectangleBorderToPoint(point: Vector2, edgeNormal: Vector2): Vector2 {
edgeNormal = Vector2.zero;
2020-07-23 11:00:46 +08:00
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
2020-07-23 11:00:46 +08:00
let res = new Vector2();
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
// 如果点在矩形内我们需要将res推到边界上因为它将在矩形内
2020-07-23 11:00:46 +08:00
if (this.contains(res.x, res.y)) {
let dl = res.x - this.left;
let dr = this.right - res.x;
let dt = res.y - this.top;
let db = this.bottom - res.y;
let min = Math.min(dl, dr, dt, db);
if (min == dt) {
res.y = this.top;
edgeNormal.y = -1;
} else if (min == db) {
res.y = this.bottom;
edgeNormal.y = 1;
} else if (min == dl) {
res.x = this.left;
edgeNormal.x = -1;
} else {
res.x = this.right;
edgeNormal.x = 1;
}
2020-06-19 09:16:49 +08:00
} else {
2020-07-23 11:00:46 +08:00
if (res.x == this.left) edgeNormal.x = -1;
if (res.x == this.right) edgeNormal.x = 1;
if (res.y == this.top) edgeNormal.y = -1;
if (res.y == this.bottom) edgeNormal.y = 1;
}
2020-07-23 11:00:46 +08:00
return res;
}
2020-07-23 11:00:46 +08:00
/**
* RectangleFRectangleF包含两个其他矩形的重叠区域
* @param value1
* @param value2
* @returns
2020-07-23 11:00:46 +08:00
*/
public static intersect(value1: Rectangle, value2: Rectangle) {
if (value1.intersects(value2)) {
let right_side = Math.min(value1.x + value1.width, value2.x + value2.width);
let left_side = Math.max(value1.x, value2.x);
let top_side = Math.max(value1.y, value2.y);
let bottom_side = Math.min(value1.y + value1.height, value2.y + value2.height);
return new Rectangle(left_side, top_side, right_side - left_side, bottom_side - top_side);
} else {
return new Rectangle(0, 0, 0, 0);
2020-07-23 11:00:46 +08:00
}
}
2020-06-11 20:36:36 +08:00
/**
*
* @param offsetX X坐标
* @param offsetY y坐标
*/
public offset(offsetX: number, offsetY: number) {
this.x += offsetX;
this.y += offsetY;
}
2020-07-08 18:12:17 +08:00
/**
*
* @param value1
* @param value2
*/
public static union(value1: Rectangle, value2: Rectangle) {
let x = Math.min(value1.x, value2.x);
let y = Math.min(value1.y, value2.y);
return new Rectangle(x, y,
Math.max(value1.right, value2.right) - x,
Math.max(value1.bottom, value2.bottom) - y);
}
2020-07-23 11:00:46 +08:00
/**
*
* @param value1
* @param value2
*/
public static overlap(value1: Rectangle, value2: Rectangle): Rectangle {
let x = Math.max(Math.max(value1.x, value2.x), 0);
let y = Math.max(Math.max(value1.y, value2.y), 0);
return new Rectangle(x, y,
Math.max(Math.min(value1.right, value2.right) - x, 0),
Math.max(Math.min(value1.bottom, value2.bottom) - y, 0));
2020-07-07 12:18:51 +08:00
}
public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
rotation: number, width: number, height: number) {
2020-07-28 16:25:20 +08:00
if (rotation == 0) {
2020-07-28 16:11:58 +08:00
this.x = parentPosition.x + position.x - origin.x * scale.x;
this.y = parentPosition.y + position.y - origin.y * scale.y;
this.width = width * scale.x;
this.height = height * scale.y;
} else {
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
2020-07-28 16:11:58 +08:00
let worldPosX = parentPosition.x + position.x;
let worldPosY = parentPosition.y + position.y;
// 考虑到原点,将参考点设置为世界参考
this._transformMat = Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
this._tempMat = Matrix2D.createScale(scale.x, scale.y);
2020-07-28 16:11:58 +08:00
this._transformMat = this._transformMat.multiply(this._tempMat);
this._tempMat = Matrix2D.createRotation(rotation);
2020-07-28 16:11:58 +08:00
this._transformMat = this._transformMat.multiply(this._tempMat);
this._tempMat = Matrix2D.createTranslation(worldPosX, worldPosY);
2020-07-28 16:11:58 +08:00
this._transformMat = this._transformMat.multiply(this._tempMat);
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
2020-07-28 16:11:58 +08:00
let topLeft = new Vector2(worldPosX, worldPosY);
let topRight = new Vector2(worldPosX + width, worldPosY);
let bottomLeft = new Vector2(worldPosX, worldPosY + height);
let bottomRight = new Vector2(worldPosX + width, worldPosY + height);
Vector2Ext.transformR(topLeft, this._transformMat, topLeft);
Vector2Ext.transformR(topRight, this._transformMat, topRight);
Vector2Ext.transformR(bottomLeft, this._transformMat, bottomLeft);
Vector2Ext.transformR(bottomRight, this._transformMat, bottomRight);
2020-07-28 16:11:58 +08:00
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
2020-07-28 16:11:58 +08:00
let minX = Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let maxX = Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let minY = Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
let maxY = Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
this.location = new Vector2(minX, minY);
this.width = maxX - minX;
this.height = maxY - minY;
}
2020-07-23 11:00:46 +08:00
}
/**
*
* @param deltaX
* @param deltaY
*/
public getSweptBroadphaseBounds(deltaX: number, deltaY: number){
let broadphasebox = Rectangle.empty;
broadphasebox.x = deltaX > 0 ? this.x : this.x + deltaX;
broadphasebox.y = deltaY > 0 ? this.y : this.y + deltaY;
broadphasebox.width = deltaX > 0 ? deltaX + this.width : this.width - deltaX;
broadphasebox.height = deltaY > 0 ? deltaY + this.height : this.height - deltaY;
return broadphasebox;
}
/**
* true
* moveX和moveY将返回b1为避免碰撞而必须移动的移动量
* @param other
* @param moveX
* @param moveY
*/
public collisionCheck(other: Rectangle, moveX: Ref<number>, moveY: Ref<number>){
moveX.value = moveY.value = 0;
let l = other.x - (this.x + this.width);
let r = (other.x + other.width) - this.x;
let t = (other.y - (this.y + this.height));
let b = (other.y + other.height) - this.y;
// 检验是否有碰撞
if (l > 0 || r < 0 || t > 0 || b < 0)
return false;
// 求两边的偏移量
moveX.value = Math.abs(l) < r ? l : r;
moveY.value = Math.abs(t) < b ? t : b;
// 只使用最小的偏移量
if (Math.abs(moveX.value) < Math.abs(moveY.value))
moveY.value = 0;
else
moveX.value = 0;
return true;
}
/**
*
* @param rectA
* @param rectB
* @returns
* /
*
* Vector2.Zero
*/
public static getIntersectionDepth(rectA: Rectangle, rectB: Rectangle): Vector2 {
// 计算半尺寸
let halfWidthA = rectA.width / 2;
let halfHeightA = rectA.height / 2;
let halfWidthB = rectB.width / 2;
let halfHeightB = rectB.height / 2;
// 计算中心
let centerA = new Vector2(rectA.left + halfWidthA, rectA.top + halfHeightA);
let centerB = new Vector2(rectB.left + halfWidthB, rectB.top + halfHeightB);
// 计算当前中心间的距离和最小非相交距离
let distanceX = centerA.x - centerB.x;
let distanceY = centerA.y - centerB.y;
let minDistanceX = halfWidthA + halfWidthB;
let minDistanceY = halfHeightA + halfHeightB;
// 如果我们根本不相交,则返回(00)
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY)
return Vector2.zero;
// 计算并返回交叉点深度
let depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
let depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return new Vector2(depthX, depthY);
}
/**
*
* @param other
*/
public equals(other: Rectangle) {
return this === other;
}
/**
*
*/
public getHashCode(): number{
return (this.x ^ this.y ^ this.width ^ this.height);
}
}
2020-07-23 11:00:46 +08:00
}