Skip to main content

2D 向量计算

主要功能:

  • 两个2D向量相加
/*!
* @Author: CPS
* @email: 373704015@qq.com
* @Date:
* @Last Modified by: SSD_WIN10
* @Last Modified time: 2021-01-26 00:45:19
* @Projectname
* @file_path D:\CPS\MyProject\test\test.js
* @Filename test.js
* @Description: 计算向量图形是否相交
*/
"use strict";

class Vector2d {
/**
* Description - 创建一个基于平面的向量对象
*
* @param {int} x - 向量的x坐标
* @param {int} y - 向量的y坐标
*
* @returns {<type>} - {description}
*
*/
constructor({ x = 1, y = 1 }) {
this.x = x;
this.y = y;
}

// 大小: 获取向量长度
lengthSquared() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}

/* 两个向量相加 ,返回新的向量对象*/
static add(vec1, vec2) {
const x = vec1.x + vec2.x;
const y = vec1.y + vec2.y;
return new Vector2d({ x, y });
}

/* 两个向量相减 */
static sub(vec1, vec2) {
const x = vec1.x - vec2.x;
const y = vec1.y - vec2.y;
return new Vector2d({ x, y });
}

/*点积: 先对两个数字序列中的每组对应元素求积,再对所有积求和,结果即为点积 */
static dot(vec1, vec2) {
return vec1.x * vec2.x + vec1.y * vec2.y;
}

/*旋转: 旋转矩阵求解*/
static rotate(vec, angle) {
const cosVal = Math.cos(angle);
const sinVal = Math.sin(angle);
const x = vec.x * cosVal - vec.y * sinVal;
const y = vec.x * sinVal + vec.y * cosVal;
return new Vector2d({ x, y });
}
}

class Circle {
/**
* Description - 圆形
*
* @param {type} x - 圆心 位置x
* @param {type} y - 圆心 位置y
* @param {type} r - 圆半径
*
* @returns {<Circle>} - {description}
*
*/
constructor({ x, y, r }) {
this.x = x;
this.y = y;
this.r = r;
}

/* 当圆作为向量时,所处的中心位置*/
get P() {
return new Vector2d({ x: this.x, y: this.y });
}
}

/* 创建矩形 */
class Rect {
/**
* Description - 矩形
*
* @param {type} x - 中心坐标x
* @param {type} y - 中心坐标x
* @param {type} w - 宽
* @param {type} h - 高
* @param {int} rotation - 轴心旋转(deg)
* @returns {<type>} - {description}
*
*/
constructor({ x, y, w, h, rotation = 0 }) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.rotation = rotation;
}

/* 矩形中心向量 */
get C() {
return new Vector2d({ x: this.x, y: this.y });
}

/* [向量] 矩形四角点的向量[顺时针方向] */
get _leftTop() {
return new Vector2d({ x: this.x - this.w / 2, y: this.y - this.h / 2 });
}
get _rightTop() {
return new Vector2d({ x: this.x + this.w / 2, y: this.y - this.h / 2 });
}
get _rightBottom() {
return new Vector2d({ x: this.x + this.w / 2, y: this.y + this.h / 2 });
}
get _leftBottom() {
return new Vector2d({ x: this.x - this.w / 2, y: this.y + this.h / 2 });
}

/*[向量] 未旋转的X对称轴*/
get _axisX() {
return new Vector2d({ x: 1, y: 0 });
}
/*[向量] 未旋转的Y对称轴*/
get _axisY() {
return new Vector2d({ x: 0, y: 1 });
}

/*[向量] 四顶点距离中心的距离,用来修正偏移,方便旋转计算*/
get leftTopC() {
return Vector2d.sub({ x: this.leftTop, y: this.C });
}
get rightTopC() {
return Vector2d.sub({ x: this.rightTop, y: this.C });
}
get rightBottomC() {
return Vector2d.sub({ x: this.rightBottom, y: this.C });
}
get leftBottomC() {
return Vector2d.sub({ x: this.leftBottom, y: this.C });
}

/* [向量] 旋转后4角顶点 */
get leftTop() {
return this.rotation % 360 === 0 ? this._leftTop : Vector2d.add(this.C, Vector2d.rotate(this.leftTopC, this._rotation));
}
get rightTop() {
return this.rotation % 360 === 0 ? this._rightTop : Vector2d.add(this.C, Vector2d.rotate(this.rightTopC, this._rotation));
}
get rightBottom() {
return this.rotation % 360 === 0 ? this._rightBottom : Vector2d.add(this.C, Vector2d.rotate(this.rightBottomC, this._rotation));
}
get leftBottom() {
return this.rotation % 360 === 0 ? this._leftBottom : Vector2d.add(this.C, Vector2d.rotate(this.leftBottomC, this._rotation));
}
/*[向量] 旋转后的对称轴*/
get axisX() {
return this.rotation % 360 === 0 ? this._axisX : Vector2d.rotate(this._axisX, this._rotation);
}
get axisY() {
return this.rotation % 360 === 0 ? this._axisY : Vector2d.rotate(this._axisY, this._rotation);
}

/* 弧度转角度 单位换算 */
get _rotation() {
return (this.rotation / 180) * Math.PI;
}

/* 四角顶点的数组 */
get _vertexs() {
return [this._leftTop, this._rightTop, this._rightBottom, this._leftBottom];
}

get vertexs() {
return [this.leftTop, this.rightTop, this.rightBottom, this.leftBottom];
}
}

/* 圆和圆 相交判断*/
function circleWithCircle(circle1, circle2) {
const P1 = circle1.P;
const P2 = circle2.P;
const r1 = circle1.r;
const r2 = circle2.r;

/* 获取相交的圆心距*/
const len = Vector2d.sub(P1, P2);

/* 判断当前距离是否相交 */
return len.length() <= r1 + r2;
}

/* 旋转非轴对称矩形为对称矩形*/
function getP(rect, circle) {
const rotation = rect.rotation;
const C = rect.C;

// P 矩形中心到圆心的向量
let P;
if (rotation % 360 == 0) {
// 轴对称矩形
P = circle.P;
} else {
// 非对称轴矩形P
P = Vector2d.add(C, Vector2d.rotate(Vector2d.sub(circle.P, C), rect._rotation * -1));
}

return P;
}

// 点可以看成是半径为0的圆
/* 矩形[轴对称的矩形]和圆 相交判断*/
function rectWithCircle(rect, circle) {
const ratation = rect.rotation;
const C = rect.C;
const A3 = rect.A3;

const r = circle.r;
const P = getP(rect, circle);

const h = Vector2d.sub(A3, C);

// v
const v = new Vector2d({ x: Math.abs(P.x - C.x), y: Math.abs(P.y - C.y) });

/* u 矩形中心到A3矩形的中心距离 */
const u = new Vector2d({ x: Math.max(v.x - h.x, 0), y: Math.max(v.y - h.y, 0) });

/* */
return u.lengthSquared() <= r * r;
}

// 点可以看成是半径为0的圆
/* 矩形和矩形[轴对称] AABB */
function rectWithRectAABB(rect1, rect2) {
const P = rect2.C;
const w2 = rect2.w;
const h2 = rect2.h;

const { w, h, x, y } = rect1;
const C = rect1.C;

/* 创建一个包含rect2的矩形向量 */
const A3 = new Vector2d({ x: x + w / 2 + w2 / 2, y: y + h / 2 + h2 / 2 });
const H = Vector2d.sub(A3, C);

const v = new Vector2d({ x: Math.abs(P.x - C.x), y: Math.abs(P.y - C.y) });
const u = new Vector2d({ x: Math.max(v.x - H.x, 0), y: Math.max(v.y - H.y, 0) });

return u.lengthSquared() === 0;
}

function isCross(rect1, rect2, axis) {
// 矩形1的4个顶点投影并排序
const vertexs1ScalarProjection = rect1.vertexs.map(vex => {
Vector2d.dot(vex, axis).sort((a, b) => a - b);
});

// 矩形2的4个顶点投影并排序
const vertexs2ScalarProjection = rect2.vertexs.map(vex => {
Vector2d.dot(vex, axis).sort((a, b) => a - b);
});

// 矩形1最小长度
const rect1Min = vertexs1ScalarProjection[0];
// 矩形1最大长度
const rect1Max = vertexs1ScalarProjection[vertexs1ScalarProjection.length - 1];

// 矩形2最小长度
const rect2Min = vertexs2ScalarProjection[0];
// 矩形2最大长度
const rect2Max = vertexs2ScalarProjection[vertexs2ScalarProjection.length - 1];

/* 判断是否相交 */
return rect1Max >= rect2Min && rect2Max >= rect1Min;
}

/* 原理 将两个矩形进行四面投影*/
// 将两个矩形的四个顶点投影到对称轴上,然后判断期物理距离是否有重合,就能得出是否相交
function rectWithRectOBB(rect1, rect2) {
const rect1AxisX = rect1.axisX;
const rect1AxisY = rect1.axisY;
const rect2AxisX = rect2.axisX;
const rect2AxisY = rect2.axisY;

/* 一旦有不相交的轴就可以return false */
if (!isCross(rec1, rect2, rect1AxisX)) return false;
if (!isCross(rec1, rect2, rect1AxisY)) return false;
if (!isCross(rec1, rect2, rect2AxisX)) return false;
if (!isCross(rec1, rect2, rect2AxisY)) return false;

return true;
}

module.exports = {
circleWithCircle,
rectWithCircle,
rectWithRectAABB,
rectWithRectOBB,
Rect,
Circle,
};