需求:由两个点A,B组成一个向量,再加一个点C,构成关系,当这两个点转换到另外两个任意位置A'和B'的时候,需要计算C在新的A',B'位置体系中的相对位置
实现原理:
1.将向量AB和C都移动到原点,即起点A到原点
2.将向量A’B’也移动到原点
3.将AB向量旋转到A'B'向量,旋转过程应用到C
4.缩放(模长比值)
5.平移回到A'
原先在第3步旋转过程中,使用Quaternion.FromToRotation函数,发现旋转后的结果对不上,思考原因应该是个两个原点对齐的向量要重叠旋转方式不确定导致,我改用罗德里格斯公式原理,通过分步旋转解决了这个问题,下面具体代码
using UnityEngine;
public class RotationCalculator : MonoBehaviour
{
#region 原坐标关系
public Vector3 originStartPos = new Vector3(0.5f, 0.1f, 0.8f);
public Vector3 originEndPos = new Vector3(3f, 2f, 1f);
public Vector3 transitionPos = new Vector3(2.5f, 1.5f, 0.8f);
#endregion
#region 现坐标关系
public Vector3 targetStartPos = new Vector3(6f, 2f, 5f);
public Vector3 targetEndPos = new Vector3(2f, 4f, 5f);
public Vector3 afterTransitionPos;
#endregion
public static Vector3 Transition(Vector3 originStartPos, Vector3 originEndPos, Vector3 transitionPos, Vector3 targetStartPos, Vector3 targetEndPos)
{
//1.将要计算的点移动到原点
var relativePos = transitionPos - originStartPos;
//2.旋转到目标方向
var originDir = originEndPos - originStartPos;
var targetDir = targetEndPos - targetStartPos;
relativePos = CalcPositionFromDirectionRotate(relativePos, originDir, targetDir);
//3.缩放
var scale = Vector3.Magnitude(targetDir) / Vector3.Magnitude(originDir);
relativePos *= scale;
//4.平移到目标起点
var tagetPos = relativePos + targetStartPos;
return tagetPos;
}
/// <summary>
/// 计算一个相对于方向A的点,当旋转重合到方向B时的位置
/// </summary>
/// <param name="pos">要计算的坐标</param>
/// <param name="fromDir">原方向</param>
/// <param name="toDir">目标方向</param>
/// <returns></returns>
static Vector3 CalcPositionFromDirectionRotate(Vector3 pos, Vector3 originDir, Vector3 targetDir)
{
originDir = originDir.normalized;
targetDir = targetDir.normalized;
var tempPos = pos;
#region 1.对齐坐标系(将参考轴重叠到Z轴)
if (originDir != Vector3.forward)
{
var projectXZ_originDir = originDir;
if (originDir.y != 0)
{
//计算originDir在XZ平面上的投影向量
projectXZ_originDir = Vector3.ProjectOnPlane(originDir, Vector3.up);
}
//计算originDir的投影向量与Forward方向在XZ平面的夹角
var angle_axleY = Vector3.SignedAngle(projectXZ_originDir, Vector3.forward, Vector3.up);
Debug.LogFormat("绕Y轴旋转:{0}度", angle_axleY);
//创建绕X轴旋转angle度的四元数
Quaternion rotationY = Quaternion.AngleAxis(angle_axleY, Vector3.up);
tempPos = rotationY * tempPos;
//计算originDir与自己的投影向量在ZY平面的夹角
var angle_axleX = Vector3.SignedAngle(originDir, projectXZ_originDir, Vector3.right);
Debug.LogFormat("绕X轴旋转:{0}度", angle_axleX);
Quaternion rotationX = Quaternion.AngleAxis(angle_axleX, Vector3.right);
tempPos = rotationX * tempPos;
Debug.Log("参考轴重叠到Z轴后的坐标位置:" + tempPos);
}
#endregion
#region 2.移动到目标轴(与目标轴对齐)
if (targetDir != Vector3.forward)
{
//计算targetDir在XZ平面上的投影
var projectXZ_targetDir = targetDir;
if (targetDir.y != 0) //如果本来就在XZ平向上就不处理
{
//在XZ平向的投影
projectXZ_targetDir = Vector3.ProjectOnPlane(targetDir, Vector3.up);
}
//计算Vector3.forward与projectXZ_targetDir在XZ平面的夹角(度)
float angle_axleY = Vector3.SignedAngle(Vector3.forward, projectXZ_targetDir, Vector3.up);
Debug.LogFormat("绕Y轴旋转:{0}度", angle_axleY);
//创建绕Y轴旋转angle度的四元数
Quaternion rotationY = Quaternion.AngleAxis(angle_axleY, Vector3.up);
tempPos = rotationY * tempPos;
if (projectXZ_targetDir != targetDir)
{
//计算投影向量与目标向量在zy平向上的夹角
var angle_axleX = Vector3.SignedAngle(projectXZ_targetDir, targetDir, Vector3.right);
Debug.LogFormat("绕X轴旋转:{0}度", angle_axleX);
//创建绕X轴旋转angle度的四元数
Quaternion rotationX = Quaternion.AngleAxis(angle_axleX, Vector3.right);
tempPos = rotationX * tempPos;
}
}
#endregion
return tempPos;
}
// 可视化调试
void OnDrawGizmos()
{
Gizmos.color = Color.white;
Gizmos.DrawSphere(originStartPos, 0.1f);
Gizmos.DrawSphere(originEndPos, 0.1f);
Gizmos.color = Color.green;
Gizmos.DrawLine(originStartPos, originEndPos);
Gizmos.color = Color.red;
Gizmos.DrawSphere(transitionPos, 0.1f);
Gizmos.color = Color.white;
Gizmos.DrawSphere(targetStartPos, 0.1f);
Gizmos.DrawSphere(targetEndPos, 0.1f);
Gizmos.color = Color.green;
Gizmos.DrawLine(targetStartPos, targetEndPos);
Gizmos.color = Color.red;
afterTransitionPos = Transition(originStartPos, originEndPos, transitionPos, targetStartPos, targetEndPos);
Gizmos.DrawSphere(afterTransitionPos, 0.1f);
}
}
效果图:
技术应用
在技能编辑器中编辑一个3D的贝塞尔曲线,使用者本质只是编辑出了曲线的走势,所以我在(0,0,0)到(0,0,1)方向让使用者添加控制点,最终会在实际的人物与目标之前换算出这个曲线轨迹


文章评论