平面攻击检测
/// <summary>
/// 点在直线上
/// 使用斜率计算,先排除 斜率不存在和与另外两点重合
/// </summary>
/// <param name="p"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
public bool IsPointInLine(Vector3 p, Vector3 p1, Vector3 p2)
{
/// 先排除 p 与 p1,p2 两点相等
if (p1.x == p.x || p2.x == p.x)
{
/// p1.x==p2.x 一条竖线 x 相等在一条直线上
/// 和线上的点相等
if (p1.x == p2.x || p.x == p1.x && p.z == p1.z || p.x == p2.x && p.z == p2.z)
return true;
return false;
}
/// k=(y-y1)/(x-x1) 斜率相等并且有公共交点,一条直线
if ((p1.z - p.z) / (p1.x - p.x) == (p2.z - p.z) / (p2.x - p.x))
return true;
return false;
}
/// <summary>
/// 点在扇形内
/// </summary>
/// <param name="p">检测点</param>
/// <param name="center">扇形圆心</param>
/// <param name="dir">方向</param>
/// <param name="len">长度</param>
/// <param name="angle">角度</param>
/// <returns></returns>
public bool IsPointInSector(Vector3 p, Vector3 center, Vector3 dir, float len, float angle)
{
p.y = center.y = dir.y = 0;
/// 距离
if (Vector3.Distance(p, center) > len)
{
return false;
}
return IsPointAngleInSector(p, center, dir, angle);
}
/// <summary>
/// 点在圆内
/// </summary>
/// <param name="p"></param>
/// <param name="center"></param>
/// <param name="radius"></param>
/// <returns></returns>
public bool IsPointInCircle(Vector3 p, Vector3 center, float radius)
{
return Vector3.Distance(p, center) <= radius;
}
/// <summary>
/// 点在矩形内
/// </summary>
/// A_______________B
/// | dir |
/// |------ 0 ----->|
/// |_______________|
/// D C
/// <param name="p">点</param>
/// <param name="center">矩形中心</param>
/// <param name="dir">矩形方向,对应长</param>
/// <param name="len">长</param>
/// <param name="width">宽</param>
/// <returns></returns>
public bool IsPointInRect(Vector3 p, Vector3 center, Vector3 dir, float len, float width)
{
dir = dir.normalized;
Vector3 dirV = GetVertical(dir);
Vector3 dirStart = center + len / 2 * dir;
Vector3 dirEnd = center - len / 2 * dir;
Vector3 A = dirEnd - width / 2 * dirV;
Vector3 D = dirEnd + width / 2 * dirV;
Vector3 B = dirStart - width / 2 * dirV;
Vector3 C = dirStart + width / 2 * dirV;
return IsPointInRect(p, A, B, C, D);
}
/// <summary>
/// 点在矩形内
/// </summary>
/// <param name="p"></param>
/// <param name="A"></param>
/// <param name="B"></param>
/// <param name="C"></param>
/// <param name="D"></param>
/// <returns></returns>
public bool IsPointInRect(Vector3 p, Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
return GetCross(A, B, p) * GetCross(C, D, p) >= 0 && GetCross(B, C, p) * GetCross(D, A, p) >= 0;
}
/// <summary>
/// 线段ab与线段cd相交
/// c d 两点在 a b 两侧,a b 两点在 c d 两侧
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="d"></param>
/// <returns></returns>
public bool IsLineCrossLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, bool useMethod = true)
{
if (useMethod)
{
/// 根据范围 a b 线段 x 坐标和 c d 线段没有交集,同 y
if (Mathf.Max(a.x, b.x) < Mathf.Min(c.x, d.x) || Mathf.Max(c.x, d.x) < Mathf.Min(a.x, b.x)
|| Mathf.Max(a.y, b.y) < Mathf.Min(c.y, d.y) || Mathf.Max(c.y, d.y) < Mathf.Min(a.y, b.y))
{
return false;
}
/// c d 两点在 a b 两侧,a b 两点在 c d 两侧
if (GetCross(a, b, c) * GetCross(a, b, d) <= 0 && GetCross(c, d, a) * GetCross(c, d, b) <= 0)
return true;
return false;
}
else
{
int num = LineCrossPoint(a, b, c, d, out Vector3 cross);
/// 平行无交点
if (num == 0) return false;
/// 重合
/// 1.c d,有一个或都在 ab 中间 相交
/// 2.c d,在 ab 两侧 相交
/// 3.c d,在 ab 一侧 不相交
if (num == 2)
{
bool cp = GetDot(c, a, b) <= 0;
bool dp = GetDot(d, a, b) <= 0;
/// 有一个在中间
if (cp || dp)
return true;
/// 大于0 表示,a 不在 c d 中间
return GetDot(a, c, d) <= 0;
;
}
/// 只剩 1 了,有一个交点,交点在 ab 中间,就相交
return GetDot(cross, a, b) <= 0 && GetDot(cross, c, d) <= 0;
}
}
/// <summary>
/// 线段与圆相交
/// </summary>
/// <param name="center"></param>
/// <param name="radius"></param>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public bool IsLineCrossCircle(Vector3 center, float radius, Vector3 a, Vector3 b)
{
return PointLineDistance(center, a, b) <= radius;
}
/// <summary>
/// 线段与矩形相交
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="center"></param>
/// <param name="dir"></param>
/// <param name="len"></param>
/// <param name="width"></param>
/// <returns></returns>
public bool IsLineCrossRect(Vector3 p1, Vector3 p2, Vector3 center, Vector3 dir, float len, float width)
{
dir = dir.normalized;
Vector3 dirV = GetVertical(dir);
Vector3 dirStart = center + len / 2 * dir;
Vector3 dirEnd = center - len / 2 * dir;
Vector3 A = dirEnd - width / 2 * dirV;
Vector3 D = dirEnd + width / 2 * dirV;
Vector3 B = dirStart - width / 2 * dirV;
Vector3 C = dirStart + width / 2 * dirV;
return IsLineCrossRect(p1, p2, A, B, C, D);
}
/// <summary>
/// 线段与矩形相交
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <param name="A"></param>
/// <param name="B"></param>
/// <param name="C"></param>
/// <param name="D"></param>
/// <returns></returns>
public bool IsLineCrossRect(Vector3 p1, Vector3 p2, Vector3 A, Vector3 B, Vector3 C, Vector3 D)
{
/// 有一点在矩形内
if (IsPointInRect(p1, A, B, C, D) || IsPointInRect(p2, A, B, C, D))
{
return true;
}
/// 有一条线段相交
return IsLineCrossLine(p1, p2, A, B) || IsLineCrossLine(p1, p2, B, C) || IsLineCrossLine(p1, p2, C, D) || IsLineCrossLine(p1, p2, D, A);
}
/// <summary>
/// 圆与扇形相交
/// </summary>
/// <param name="p">圆心</param>
/// <param name="radius">圆半径</param>
/// <param name="center">扇形圆心</param>
/// <param name="dir">方向</param>
/// <param name="len">长度</param>
/// <param name="angle">角度</param>
/// <returns></returns>
/// <returns></returns>
public bool IsCircleCrossSector(Vector3 p, float radius, Vector3 center, Vector3 dir, float len, float angle)
{
p.y = center.y = dir.y = 0;
/// 距离
if (Vector3.Distance(p, center) > radius + len)
{
return false;
}
/// 角度在扇形里,前面距离也够,所有是有相交点的
if (IsPointAngleInSector(p, center, dir, angle))
{
return true;
}
Vector3 left = Quaternion.AngleAxis(-angle / 2, Vector3.up) * dir.normalized * len;
Vector3 right = Quaternion.AngleAxis(angle / 2, Vector3.up) * dir.normalized * len;
if (PointLineDistance(p, center, center + left) <= radius || PointLineDistance(p, center, center + right) <= radius)
return true;
return false;
}
/// <summary>
/// 圆与圆相交或者包含
/// </summary>
/// <param name="c1"></param>
/// <param name="r1"></param>
/// <param name="c2"></param>
/// <param name="r2"></param>
/// <returns></returns>
public bool IsCircleCrossCircle(Vector3 c1, float r1, Vector3 c2, float r2)
{
c1.y = c2.y = 0;
return Vector3.Distance(c1, c2) <= r1 + r2;
}
/// <summary>
/// 圆与矩形相交
/// 第一步 圆心是否在矩形内 在返回 true
/// 第二步 圆心到四条边的距离小于半径,有一条返回true
/// </summary>
/// A_______________B
/// | dir |
/// |------ 0 ----->|
/// |_______________|
/// D C
/// <param name="p">圆心</param>
/// <param name="radius"></param>
/// <param name="center"></param>
/// <param name="dir"></param>
/// <param name="len"></param>
/// <param name="width"></param>
/// <returns></returns>
public bool IsCircleCrossRect(Vector3 p, float radius, Vector3 center, Vector3 dir, float len, float width)
{
dir = dir.normalized;
Vector3 dirV = GetVertical(dir);
Vector3 dirStart = center + len / 2 * dir;
Vector3 dirEnd = center - len / 2 * dir;
Vector3 A = dirEnd - width / 2 * dirV;
Vector3 D = dirEnd + width / 2 * dirV;
Vector3 B = dirStart - width / 2 * dirV;
Vector3 C = dirStart + width / 2 * dirV;
/// 第一步 圆心是否在矩形内 在返回 true
// GetCross(A, B, p) * GetCross(C, D, p) >= 0 说明在AB CD 两条线中间
// GetCross(B, C, p) * GetCross(D, A, p) >= 0 说明在BC DA 两条线中间
if (GetCross(A, B, p) * GetCross(C, D, p) >= 0 && GetCross(B, C, p) * GetCross(D, A, p) >= 0)
return true;
/// 第二步 圆心到四条边的距离小于半径,有一条返回true
if (PointLineDistance(p, A, B) <= radius || PointLineDistance(p, B, C) <= radius || PointLineDistance(p, C, D) <= radius || PointLineDistance(p, D, A) <= radius)
return true;
return false;
}
/// <summary>
/// 矩形与矩形相交
/// </summary>
/// <param name="center1"></param>
/// <param name="dir1"></param>
/// <param name="len1"></param>
/// <param name="width1"></param>
/// <param name="center2"></param>
/// <param name="dir2"></param>
/// <param name="len2"></param>
/// <param name="width2"></param>
/// <returns></returns>
public bool IsRectCrossRect(Vector3 center1, Vector3 dir1, float len1, float width1, Vector3 center2, Vector3 dir2, float len2, float width2)
{
return true;
}
/// <summary>
/// 点是否在扇形角度内
/// </summary>
/// <param name="p"></param>
/// <param name="center"></param>
/// <param name="dir"></param>
/// <param name="angle"></param>
/// <returns></returns>
bool IsPointAngleInSector(Vector3 p, Vector3 center, Vector3 dir, float angle)
{
if (angle >= 360f)
return true;
Vector3 target = p - center;
float LineAngle = Vector3.Angle(target, dir);
bool inSectorAngle = LineAngle <= angle / 2f;
return inSectorAngle;
}
/// <summary>
/// 点到线段的距离
/// 第一种:
/// 求点在直线上的投影 c ,
/// 判断投影是否在直线上,在返回点到投影的距离,
/// 不在返回点到两端点距离的小值
/// 第二种:
/// 判断点是否在线段上,在返回最小值
/// 不在,判断角 pp1p2 或者 pp2p1 是否是钝角或者直角
/// 判断方法 a*a>=c*c+b*b a是斜边 另外一个角是钝角或直角 点 p 到另外一个点的距离最近
/// 根据三角形面积
/// S=Sqrt(p(p-a)(p-b)(p-c)) p=(a+b+c)/2
/// S=d*h/2 h为点到直线的距离
/// h=2S/d
/// </summary>
/// <returns></returns>
float PointLineDistance(Vector3 p, Vector3 p1, Vector3 p2, bool useMethod = false)
{
if (p1 == p2)
return Vector3.Distance(p1, p);
if (useMethod)
{
/// 第一种
Vector3 cross = PointVerticalLine(p, p1, p2);
/// 反向或者有个为 0
if (GetDot(cross - p1, cross - p2) <= 0)
return Vector3.Distance(p, cross);
float pp1 = Vector3.Distance(p, p1);
float pp2 = Vector3.Distance(p, p2);
return pp1 > pp2 ? pp2 : pp1;
}
else
{
/// 第二种
float pp1 = Vector3.Distance(p, p1);
float pp2 = Vector3.Distance(p, p2);
/// 点在直线上
if (IsPointInLine(p, p1, p2))
{
if (GetDot(p - p1, p - p2) <= 0)
return 0;
if (GetDot(p - p1, p - p2) > 0)
return pp1 > pp2 ? pp2 : pp1;
}
float p1p2 = Vector3.Distance(p1, p2);
/// 钝角或者直角 pp1是最长边
if (pp1 * pp1 >= pp2 * pp2 + p1p2 * p1p2)
return pp2;
/// 钝角或者直角 pp2是最长边
if (pp2 * pp2 >= pp1 * pp1 + p1p2 * p1p2)
return pp1;
/// 根据三角形面积
/// S=Sqrt(p(p-a)(p-b)(p-c)) p=(a+b+c)/2
/// S=d*h/2 h为点到直线的距离
/// h=2S/d
float l = (pp1 + pp2 + p1p2) / 2;
float S = Mathf.Sqrt(l * (l - pp1) * (l - pp2) * (l - p1p2));
return 2 * S / p1p2;
}
}
/// <summary>
/// 两直线交叉点
/// 0 两条线平行,2 两条线重合,1 有交点
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="c"></param>
/// <param name="d"></param>
/// <returns></returns>
int LineCrossPoint(Vector3 a, Vector3 b, Vector3 c, Vector3 d, out Vector3 cross)
{
cross = Vector3.zero;
float k1 = 0, k2 = 0;
int state = 0;
if (a.x != b.x)
{
k1 = (b.z - a.z) / (b.x - a.x);
state |= 1;
}
if (c.x != d.x)
{
k2 = (d.z - c.z) / (d.x - c.x);
state |= 2;
}
switch (state)
{
case 0:
/// 两条线都无斜率
return a.x == c.x ? 2 : 0;
case 1:
/// l1 有斜率 l2 无斜率
cross.x = c.x;
cross.z = k1 * (cross.x - a.x) + a.z;
return 1;
case 2:
/// l2 有斜率 l1 无斜率
cross.x = a.x;
cross.z = k2 * (cross.x - c.x) + c.z;
return 1;
case 3:
/// 两条线都有斜率
if (k1 == k2)
{
return GetCross(a, b, c) == 0 ? 2 : 0;
}
/// 斜率存在时
/// k=(y-y1)/(x-x1)=>y=k(x-x1)+y1
/// 交点:k1(x-x1)+y1=k2(x-x2)+y2=>x=(k1x1-k2x2-y1+y2)/(k1-k2)
cross.x = (k1 * a.x - k2 * c.x - a.z + c.z) / (k1 - k2);
cross.z = k1 * (cross.x - a.x) + a.z;
return 1;
}
return 0;
}
/// <summary>
/// 点在直线的投影 平面
/// </summary>
/// 第一种
/// 斜率公式: k=(y-y1)/(x-x1)
/// 投影点在 p1p2 上:y=k*(x-p1x)+p1y
/// 投影点在 p 和投影点上斜率为 -1/k:y=(-1/k)*(x-p0x)+p0y
/// x=(p0x/k+p0y+k*p1x-p1y)/(1/k+k)
/// 第二种
/// p1p 在 p1p2 单位向量上的投影长度 dot(p1p,p1p2) 符号为方向
/// 结果 * p1p2 p1p投
/// p 投坐标 =p1p投+p1
/// <param name="p"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
Vector3 PointVerticalLine(Vector3 p, Vector3 p1, Vector3 p2, bool useMethod = false)
{
if (useMethod)
{
if (p1.x == p2.x)
return new Vector3(p1.x, 0, p.z);
if (p1.z == p2.z)
return new Vector3(p.x, 0, p1.z);
float k = (p1.z - p2.z) / (p1.x - p2.x);
Vector3 cross = Vector3.zero;
cross.x = (p.x / k + p.z + k * p1.x - p1.y) / (k + 1 / k);
cross.z = k * (cross.x - p1.x) + p1.z;
return cross;
}
else
{
float len = GetDot(p - p1, (p2 - p1).normalized);
Vector3 cross = len * (p2 - p1).normalized;
return cross + p1;
}
}
/// <summary>
/// p1p2 x p1p
/// XZ平面叉乘公式 A={a1,b1} B={a2,b2} AxB=a1b2-a2b1
/// |p1p2|*|p1p|*sin 大于0在左,小于0在右
/// </summary>
/// <returns></returns>
float GetCross(Vector3 p1, Vector3 p2, Vector3 p)
{
return (p2.x - p1.x) * (p.z - p1.z) - (p.x - p1.x) * (p2.z - p1.z);
}
/// <summary>
/// a * b 平面点乘 a={x1,y1} b={x2,y2} a*b=x1x2+y1y2
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
float GetDot(Vector3 a, Vector3 b)
{
return a.x * b.x + a.z * b.z;
}
/// <summary>
/// 返回 pp1,pp2 点乘
/// </summary>
/// <param name="p"></param>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
float GetDot(Vector3 p, Vector3 p1, Vector3 p2)
{
return GetDot(p1 - p, p2 - p);
}
/// <summary>
/// 垂直向量 顺时针
/// V1=(a,b) V2=(b,-a)
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
Vector3 GetVertical(Vector3 v)
{
return new Vector3(v.z, 0, -v.x);
}
/// <summary>
/// 顺时针 向量旋转,旋转后的向量
/// </summary>
/// <param name="dir"></param>
/// <param name="angle"></param>
/// <param name="up">旋转轴 法向量</param>
/// <returns></returns>
public Vector3 GetRotateVector(Vector3 dir, float angle)
{
return Quaternion.AngleAxis(angle, Vector3.up) * dir;
}
/// <summary>
/// 顺时针 b 绕 a 旋转后的坐标
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="angle"></param>
/// <param name="up"></param>
/// <returns></returns>
public Vector3 GetRotateVector(Vector3 a, Vector3 b, float angle)
{
return Quaternion.AngleAxis(angle, Vector3.up) * (b - a) + a;
}
/// <summary>
/// 逆时针旋转公式:x=x0*cosB-y0*sinB y=x0*sinB+y0*cosB
/// 顺时针将角度变为 -B
/// </summary>
/// <param name="dir"></param>
/// <param name="angle"></param>
/// <param name="isShun"></param>
/// <returns></returns>
public Vector3 GetRotateVector(Vector3 dir, float angle, bool isShun)
{
float radians = angle * Mathf.Deg2Rad;
float sin = Mathf.Sin(radians);
float cos = Mathf.Cos(radians);
if (!isShun)
{
return new Vector3(dir.x * cos - dir.z * sin, dir.y, dir.x * sin + dir.z * cos);
}
else
{
return new Vector3(dir.x * cos + dir.z * sin, dir.y, -dir.x * sin + dir.z * cos);
}
}