记录一下空间圆的生成功能。
已知空间两端点f和t,求f所在平面F,半径为r的圆,如下:
求动点p(x,y,z)则可以得到圆,那么开始构建相应的方程组,首先计算平面F,如下:
接下来通过向量fp模长等于r,构建方程:
再次通过三角形tfp为直角三角形,所以向量tp模长的平方=r的平方+向量ft模长的平方,再次构建方程:
但是我们利用上面三个表达式无法解出解,因为后两个二次方程相减得到的依旧是三元方程,和第一个方程类似,所以最多只能将x、y、z中任意一个作为参数求得其余两个,如下:
当然哪怕我们不去求解方程组,也知道p(x,y,z)求不出来,因为p就是圆环上的动点,只存在无数多的解,但是我们只需要一个定点p,所以还需要一个明确的条件确定一个明确的p点。
我们可以参考圆规,如下:
假设我们三维空间中有一个圆规,圆规轴点为t,定轴为tf,动轴为tp1,所以动轴tp1与平面F相交就能确定定点p1,在通过fp1单位法向量*半径r得到定点p(x,y,z),那么用数学语言来描述:
以端点t为起点,不与射线tf重合(且不与平面F平行)的任意射线tp1,与平面F相交于p1点,则能通过射线与平面相交计算得到定点p1,在通过法向量乘模长得到定点p。
构建几何示意图:
那么求空间非直线tf上任意点p2,即可得到求得点p。
而任意点p2 = t + 非单位向量tf * 任意长度数值。
任意点p2可以用f绕x(y或z)轴旋转θ>0°且θ<90°(如θ=30°)得到,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpacePointAndCircle : MonoBehaviour
{
public Transform startTrans; //from
public Transform endTrans; //to
[Range(0, 90f)]
public float rotateAngle = 30f;
void Start()
{
}
void Update()
{
#if UNITY_EDITOR
Vector3 start = startTrans.position;
Vector3 end = endTrans.position;
Debug.DrawLine(start, end, Color.black);
Vector3 p2 = RotateAroundXAxis(start, end, rotateAngle * Mathf.Deg2Rad);
Debug.DrawLine(end, p2, Color.white);
#endif
}
/// <summary>
/// 空间任意起终点
/// 终点绕x轴旋转rad弧度
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="rad"></param>
/// <returns></returns>
private Vector3 RotateAroundXAxis(Vector3 start, Vector3 end, float rad)
{
Matrix3x3 mat = new Matrix3x3();
float cos = Mathf.Cos(rad);
float sin = Mathf.Sin(rad);
mat.m00 = 1;
mat.m01 = 0;
mat.m02 = 0;
mat.m10 = 0;
mat.m11 = cos;
mat.m12 = -sin;
mat.m20 = 0;
mat.m21 = sin;
mat.m22 = cos;
Vector3 ret = mat * (start - end) + end;
return ret;
}
}
效果如下:
接下来我们计算射线tp2与平面F相交点p1,如下:
射线与平面相交以前聊过,节省篇幅,我们直接运算就行了(不理解原理的可以返回几何向量栏目查看),如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpacePointAndCircle : MonoBehaviour
{
public Transform startTrans; //from
public Transform endTrans; //to
[Range(0, 90f)]
public float rotateAngle = 30f;
void Start()
{
}
void Update()
{
#if UNITY_EDITOR
Vector3 start = startTrans.position;
Vector3 end = endTrans.position;
Debug.DrawLine(start, end, Color.black);
Vector3 p2 = RotateAroundXAxis(start, end, rotateAngle * Mathf.Deg2Rad);
Debug.DrawLine(end, p2, Color.white);
Vector3 p1 = RayLineCrossPanel(start, end, p2);
Debug.DrawLine(p2, p1, Color.red);
#endif
}
/// <summary>
/// 通过start end计算start所处平面F方程
/// 通过end p2计算射线与平面F交点p1
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="p2"></param>
/// <returns></returns>
private Vector3 RayLineCrossPanel(Vector3 start, Vector3 end, Vector3 p2)
{
//start = from
//end = to
//构建平面F方程参数
Vector3 ft = end - start;
float u = ft.x, v = ft.y, w = ft.z;
float a = start.x, b = start.y, c = start.z;
//构建射线tp2参数
float sx = end.x;
float sy = end.y;
float sz = end.z;
Vector3 ntp2 = (p2 - end).normalized;
float dx = ntp2.x;
float dy = ntp2.y;
float dz = ntp2.z;
//计算p1
float n = ((u * a + v * b + w * c) - (u * sx + v * sy + w * sz)) / (u * dx + v * dy + w * dz);
Vector3 p1 = end + n * ntp2;
return p1;
}
/// <summary>
/// 空间任意起终点
/// 终点绕x轴旋转rad弧度
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="rad"></param>
/// <returns></returns>
private Vector3 RotateAroundXAxis(Vector3 start, Vector3 end, float rad)
{
Matrix3x3 mat = new Matrix3x3();
float cos = Mathf.Cos(rad);
float sin = Mathf.Sin(rad);
mat.m00 = 1;
mat.m01 = 0;
mat.m02 = 0;
mat.m10 = 0;
mat.m11 = cos;
mat.m12 = -sin;
mat.m20 = 0;
mat.m21 = sin;
mat.m22 = cos;
Vector3 ret = mat * (start - end) + end;
return ret;
}
}
效果如下:
这样我们就得到了start(from)和p1,也就是平面F上两个坐标点。方便计算以start(from)为起点,半径r的圆了,如下:
通过f + nfp1 * r就能得到p(x,y,z)了,如下:
Vector3 p = start + (p1 - start).normalized * circleRadius;
Debug.DrawLine(start, p, Color.blue);
效果如下:
既然我们得到了p(x,y,z),那么接下来就把圆gizmos出来,方法就是p(x,y,z)绕轴ft离散的旋转θ度可得,如下:
这种圆绘制的方法我们以前聊过很多了,无非就是间隔的逆时针旋转计算离散的坐标数组,再相连绘制,如下:
private void DrawCircleGizmos(Vector3 start, Vector3 end, Vector3 p)
{
Vector3[] posarr = CalculateCirclePoints(start, end, p, circleSegement);
for (int i = 0; i < posarr.Length; i++)
{
Vector3 fp = posarr[i];
Vector3 tp = posarr[(i + 1) % circleSegement];
Debug.DrawLine(fp, tp, Color.yellow);
}
}
/// <summary>
/// 算出圆环上所有离散坐标点
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="p"></param>
/// <param name="sege"></param>
/// <returns></returns>
private Vector3[] CalculateCirclePoints(Vector3 start, Vector3 end, Vector3 p, int sege)
{
Vector3[] posarr = new Vector3[sege];
posarr[0] = p;
Vector3 naxis = (end - start).normalized;
float segerad = 2f * Mathf.PI / (float)sege;
for (int i = 1; i < sege; i++)
{
float rad = segerad * i;
Vector3 segepos = RotateAroundAnyAxis(start, p, naxis, rad);
posarr[i] = segepos;
}
return posarr;
}
/// <summary>
/// p(x,y,z)点绕start为起点的任意坐标轴旋转后的坐标
/// </summary>
/// <param name="start"></param>
/// <param name="naxis"></param>
/// <param name="rad"></param>
/// <returns></returns>
private Vector3 RotateAroundAnyAxis(Vector3 start, Vector3 p, Vector3 naxis, float rad)
{
float n1 = naxis.x;
float n2 = naxis.y;
float n3 = naxis.z;
//获取p相对start的本地坐标
p -= start;
float sin = Mathf.Sin(rad);
float cos = Mathf.Cos(rad);
Matrix3x3 mat = new Matrix3x3();
mat.m00 = n1 * n1 * (1 - cos) + cos;
mat.m01 = n1 * n2 * (1 - cos) - n3 * sin;
mat.m02 = n1 * n3 * (1 - cos) + n2 * sin;
mat.m10 = n1 * n2 * (1 - cos) + n3 * sin;
mat.m11 = n2 * n2 * (1 - cos) + cos;
mat.m12 = n2 * n3 * (1 - cos) - n1 * sin;
mat.m20 = n1 * n3 * (1 - cos) - n2 * sin;
mat.m21 = n2 * n3 * (1 - cos) + n1 * sin;
mat.m22 = n3 * n3 * (1 - cos) + cos;
//绕轴旋转后,处理成世界坐标
Vector3 px = mat * p + start;
return px;
}
最终效果如下:
好,今天就写到这里。