沿具有可自定义顶点高度和距离的两点之间的弧形路径查找 waypoints
Find waypoints along arcing path between two points with customizable apex height & distance
我有一个功能脚本,它为 2 个游戏对象(vector3)之间的电影机轨道创建 waypoints 的弧线,我想添加一个变量,以便我可以控制弧线的高度在这张照片中看到:
目前我的方法是根据两点之间的距离半径创建圆弧,我希望能够控制圆弧的高度,以便它计算出类似于在图片中画线。并根据 posA 和 posB 之间的距离控制最大高度出现在弧中的哪个位置。
这是我目前用于创建圆弧的代码,它计算半径为两点之间距离一半的圆弧:
static void CalcWaypoints(CinemachineSmoothPath path, Vector3 posA, Vector3 posB)
{
int greenRingPointsNum = 8;
float metersPerWaypoint = 10;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(Vector3.Distance(posA, posB) / metersPerWaypoint);
path.m_Waypoints = new CinemachineSmoothPath.Waypoint[segmentsToCreate + greenRingPointsNum];
Debug.Log("Creating " + segmentsToCreate + " waypoints");
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction of the target gameobject
var centerDirection = Quaternion.LookRotation((posA - posB).normalized);
for (var i = 0; i < segmentsToCreate; i++)
{
var angle = Mathf.PI * (i) / (segmentsToCreate + 1f);
var y = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(0, y, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = centerPos + pos;
}
//create a circle of points around the target gameobject at a give radius
float greenRadius = 20f;
int waypointNum = segmentsToCreate;
for (int i = 0; i < greenRingPointsNum; i++)
{
float angle = i * Mathf.PI * 2f / greenRingPointsNum;
Vector3 newPos = posB + new Vector3(Mathf.Cos(angle) * greenRadius, posB.y, Mathf.Sin(angle) * greenRadius);
path.m_Waypoints[waypointNum] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[waypointNum].position = newPos;
waypointNum++;
}
}
希望你们中的一位大神能帮忙:)
像下图这样的圆弧,我可以控制高度和两点之间的距离:
最简单的方法可能是动态制作 Bézier curves 然后跟随它们。
首先,我们定义顶点的方向,找到顶点:
float apexHeightFromA = 5f; // apex is 5 world units above A
float apexDistanceFactor = 0.5f; // apex is halfway from A to B
Vector3 upDirection = Vector3.up; // direction apex is in relative to points
Vector3 aToB = posB - posA;
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
现在,可以定义贝塞尔曲线了。如果我们使用两条三次贝塞尔曲线,我们将需要两个控制点,一个在顶点的两侧。也就是说,一个往A点,一个往B点。
Vector3 controlPointApexA;
Vector3 controlPointApexB;
如何定义这些是任意的。一个好的起点可能是水平移动到每个控制点指向的终点的一半。
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
float controlPointDistanceFactor = 0.5f;
controlPointA = posApex
+ controlPointDistanceFactor * Vector3.ProjectOnPlane(apexToA, upDirection);
controlPointB = posApex
+ controlPointDistanceFactor * Vector3.ProjectOnPlane(apexToB, upDirection);
无论我们如何定义控制点,我们都可以沿着曲线进行迭代。
首先,我们需要确定有多少 waypoints 应该出现在顶点之前和之后。一个合理的假设是根据它在点之间的位置到达它。
float apexTravelFactor = apexDistanceFactor;
然后,我们可以使用二次贝塞尔曲线的公式...
...从 A 到顶点或从顶点到 B 取决于我们在曲线中的位置。:
// cache for efficiency
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / segmentsToCreate;
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = currentPos;
}
总而言之,将常量等移动到顶部:
static void CalcWaypoints(CinemachineSmoothPath path, Vector3 posA, Vector3 posB)
{
//////////////////////////////////////////////
// CONSTANTS & FACTORS
// good candidates for [Serializable] fields
// in a singleton and/or if this were not static
//////////////////////////////////////////////
int greenRingPointsNum = 8;
float metersPerWaypoint = 10;
float apexHeightFromA = 5f; // apex is 5 world units above A
float apexDistanceFactor = 0.5f; // apex is halfway from A to B
Vector3 upDirection = Vector3.up; // direction apex is in relative to points
// can fiddle with to change "thickness" of curve
float controlPointDistanceFactor = 0.5f;
// can fiddle with to change how many waypoints come before vs after the apex
float apexTravelFactor = apexDistanceFactor;
////////
// LOGIC
////////
Vector3 aToB = posB - posA;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(aToB.magnitude / metersPerWaypoint);
path.m_Waypoints = new CinemachineSmoothPath.Waypoint[segmentsToCreate
+ greenRingPointsNum];
Debug.Log("Creating " + segmentsToCreate + " waypoints");
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
Vector3 controlPointA = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToA, upDirection);
Vector3 controlPointB = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToB, upDirection);
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / segmentsToCreate;
// if you want to have waypoint at posB:
// float overallT = (float)i / (segmentsToCreate-1);
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = currentPos;
}
//create a circle of points around the target gameobject at a give radius
float greenRadius = 20f;
int waypointNum = segmentsToCreate;
for (int i = 0; i < greenRingPointsNum; i++)
{
float angle = i * Mathf.PI * 2f / greenRingPointsNum;
Vector3 newPos = posB + new Vector3(Mathf.Cos(angle) * greenRadius, posB.y,
Mathf.Sin(angle) * greenRadius);
path.m_Waypoints[waypointNum] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[waypointNum].position = newPos;
waypointNum++;
}
}
为了好玩,尝试为 apexHeightFromA
设置一个负值,and/or 一个将“顶点”置于 posA
和 [=19= 的“高度”之间的值].你会看到它看起来应该还不错,虽然它肯定不再是“顶点”了;)
如果你想预览 waypoints,请看下面:
public class TestScript : MonoBehaviour
{
[SerializeField]
float apexHeightFromA = 5f; // apex is 5 world units above A
[SerializeField] float apexDistanceFactor = 0.5f; // apex is halfway from A to B
[SerializeField] Vector3 upDirection = Vector3.up; // direction apex is in relative to points
[SerializeField] Vector3 posA;
[SerializeField] Vector3 posB;
private void OnDrawGizmos()
{
CalcWaypoints();
}
void CalcWaypoints()
{
//////////////////////////////////////////////
// CONSTANTS & FACTORS
// good candidates for [Serializable] fields
// in a singleton and/or if this were not static
//////////////////////////////////////////////
// animate apex distance factor
apexDistanceFactor = Mathf.PingPong(Time.time * 0.3f, .8f) + 0.1f;
float metersPerWaypoint = 10;
// can fiddle with to change "thickness" of curve
float controlPointDistanceFactor = 0.5f;
// can fiddle with to change how many waypoints come before vs after the apex
float apexTravelFactor = apexDistanceFactor;
////////
// LOGIC
////////
Vector3 aToB = posB - posA;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(aToB.magnitude / metersPerWaypoint);
Debug.Log("Creating " + segmentsToCreate + " waypoints");
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
Vector3 controlPointA = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToA, upDirection);
Vector3 controlPointB = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToB, upDirection);
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / (segmentsToCreate + 1);
// if you want to have waypoint at posB:
// float overallT = (float)i / (segmentsToCreate-1);
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
Gizmos.DrawSphere(currentPos, 1f);
}
}
}
我有一个功能脚本,它为 2 个游戏对象(vector3)之间的电影机轨道创建 waypoints 的弧线,我想添加一个变量,以便我可以控制弧线的高度在这张照片中看到:
目前我的方法是根据两点之间的距离半径创建圆弧,我希望能够控制圆弧的高度,以便它计算出类似于在图片中画线。并根据 posA 和 posB 之间的距离控制最大高度出现在弧中的哪个位置。
这是我目前用于创建圆弧的代码,它计算半径为两点之间距离一半的圆弧:
static void CalcWaypoints(CinemachineSmoothPath path, Vector3 posA, Vector3 posB)
{
int greenRingPointsNum = 8;
float metersPerWaypoint = 10;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(Vector3.Distance(posA, posB) / metersPerWaypoint);
path.m_Waypoints = new CinemachineSmoothPath.Waypoint[segmentsToCreate + greenRingPointsNum];
Debug.Log("Creating " + segmentsToCreate + " waypoints");
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction of the target gameobject
var centerDirection = Quaternion.LookRotation((posA - posB).normalized);
for (var i = 0; i < segmentsToCreate; i++)
{
var angle = Mathf.PI * (i) / (segmentsToCreate + 1f);
var y = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(0, y, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = centerPos + pos;
}
//create a circle of points around the target gameobject at a give radius
float greenRadius = 20f;
int waypointNum = segmentsToCreate;
for (int i = 0; i < greenRingPointsNum; i++)
{
float angle = i * Mathf.PI * 2f / greenRingPointsNum;
Vector3 newPos = posB + new Vector3(Mathf.Cos(angle) * greenRadius, posB.y, Mathf.Sin(angle) * greenRadius);
path.m_Waypoints[waypointNum] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[waypointNum].position = newPos;
waypointNum++;
}
}
希望你们中的一位大神能帮忙:)
像下图这样的圆弧,我可以控制高度和两点之间的距离:
最简单的方法可能是动态制作 Bézier curves 然后跟随它们。
首先,我们定义顶点的方向,找到顶点:
float apexHeightFromA = 5f; // apex is 5 world units above A
float apexDistanceFactor = 0.5f; // apex is halfway from A to B
Vector3 upDirection = Vector3.up; // direction apex is in relative to points
Vector3 aToB = posB - posA;
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
现在,可以定义贝塞尔曲线了。如果我们使用两条三次贝塞尔曲线,我们将需要两个控制点,一个在顶点的两侧。也就是说,一个往A点,一个往B点。
Vector3 controlPointApexA;
Vector3 controlPointApexB;
如何定义这些是任意的。一个好的起点可能是水平移动到每个控制点指向的终点的一半。
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
float controlPointDistanceFactor = 0.5f;
controlPointA = posApex
+ controlPointDistanceFactor * Vector3.ProjectOnPlane(apexToA, upDirection);
controlPointB = posApex
+ controlPointDistanceFactor * Vector3.ProjectOnPlane(apexToB, upDirection);
无论我们如何定义控制点,我们都可以沿着曲线进行迭代。
首先,我们需要确定有多少 waypoints 应该出现在顶点之前和之后。一个合理的假设是根据它在点之间的位置到达它。
float apexTravelFactor = apexDistanceFactor;
然后,我们可以使用二次贝塞尔曲线的公式...
...从 A 到顶点或从顶点到 B 取决于我们在曲线中的位置。:
// cache for efficiency
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / segmentsToCreate;
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = currentPos;
}
总而言之,将常量等移动到顶部:
static void CalcWaypoints(CinemachineSmoothPath path, Vector3 posA, Vector3 posB)
{
//////////////////////////////////////////////
// CONSTANTS & FACTORS
// good candidates for [Serializable] fields
// in a singleton and/or if this were not static
//////////////////////////////////////////////
int greenRingPointsNum = 8;
float metersPerWaypoint = 10;
float apexHeightFromA = 5f; // apex is 5 world units above A
float apexDistanceFactor = 0.5f; // apex is halfway from A to B
Vector3 upDirection = Vector3.up; // direction apex is in relative to points
// can fiddle with to change "thickness" of curve
float controlPointDistanceFactor = 0.5f;
// can fiddle with to change how many waypoints come before vs after the apex
float apexTravelFactor = apexDistanceFactor;
////////
// LOGIC
////////
Vector3 aToB = posB - posA;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(aToB.magnitude / metersPerWaypoint);
path.m_Waypoints = new CinemachineSmoothPath.Waypoint[segmentsToCreate
+ greenRingPointsNum];
Debug.Log("Creating " + segmentsToCreate + " waypoints");
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
Vector3 controlPointA = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToA, upDirection);
Vector3 controlPointB = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToB, upDirection);
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / segmentsToCreate;
// if you want to have waypoint at posB:
// float overallT = (float)i / (segmentsToCreate-1);
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
path.m_Waypoints[i] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[i].position = currentPos;
}
//create a circle of points around the target gameobject at a give radius
float greenRadius = 20f;
int waypointNum = segmentsToCreate;
for (int i = 0; i < greenRingPointsNum; i++)
{
float angle = i * Mathf.PI * 2f / greenRingPointsNum;
Vector3 newPos = posB + new Vector3(Mathf.Cos(angle) * greenRadius, posB.y,
Mathf.Sin(angle) * greenRadius);
path.m_Waypoints[waypointNum] = new CinemachineSmoothPath.Waypoint();
path.m_Waypoints[waypointNum].position = newPos;
waypointNum++;
}
}
为了好玩,尝试为 apexHeightFromA
设置一个负值,and/or 一个将“顶点”置于 posA
和 [=19= 的“高度”之间的值].你会看到它看起来应该还不错,虽然它肯定不再是“顶点”了;)
如果你想预览 waypoints,请看下面:
public class TestScript : MonoBehaviour
{
[SerializeField]
float apexHeightFromA = 5f; // apex is 5 world units above A
[SerializeField] float apexDistanceFactor = 0.5f; // apex is halfway from A to B
[SerializeField] Vector3 upDirection = Vector3.up; // direction apex is in relative to points
[SerializeField] Vector3 posA;
[SerializeField] Vector3 posB;
private void OnDrawGizmos()
{
CalcWaypoints();
}
void CalcWaypoints()
{
//////////////////////////////////////////////
// CONSTANTS & FACTORS
// good candidates for [Serializable] fields
// in a singleton and/or if this were not static
//////////////////////////////////////////////
// animate apex distance factor
apexDistanceFactor = Mathf.PingPong(Time.time * 0.3f, .8f) + 0.1f;
float metersPerWaypoint = 10;
// can fiddle with to change "thickness" of curve
float controlPointDistanceFactor = 0.5f;
// can fiddle with to change how many waypoints come before vs after the apex
float apexTravelFactor = apexDistanceFactor;
////////
// LOGIC
////////
Vector3 aToB = posB - posA;
//Here we calculate how many segments will fit between the two points
int segmentsToCreate = Mathf.RoundToInt(aToB.magnitude / metersPerWaypoint);
Debug.Log("Creating " + segmentsToCreate + " waypoints");
Vector3 flatAToB = Vector3.ProjectOnPlane(aToB, upDirection);
Vector3 posApex = posA
+ flatAToB * apexDistanceFactor
+ apexHeightFromA * upDirection;
Vector3 apexToA = posA - posApex;
Vector3 apexToB = posB - posApex;
Vector3 controlPointA = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToA, upDirection);
Vector3 controlPointB = posApex
+ controlPointDistanceFactor
* Vector3.ProjectOnPlane(apexToB, upDirection);
Vector3 controlAToA = posA - controlPointA;
Vector3 controlBToB = posB - controlPointB;
Vector3 controlAToApex = posApex - controlPointA;
Vector3 controlBToApex = posApex - controlPointB;
for (var i = 0; i < segmentsToCreate; i++)
{
float overallT = (float)i / (segmentsToCreate + 1);
// if you want to have waypoint at posB:
// float overallT = (float)i / (segmentsToCreate-1);
Vector3 control, controlToOrigin, controlToDest;
float t;
// are we going from a to apex or apex to b?
if (overallT < apexTravelFactor)
{
// going from a to apex
control = controlPointA;
controlToOrigin = controlAToA;
controlToDest = controlAToApex;
t = overallT / apexTravelFactor;
}
else
{
// going from apex to b
control = controlPointB;
controlToOrigin = controlBToApex;
controlToDest = controlBToB;
t = (overallT - apexTravelFactor) / (1f - apexTravelFactor);
}
Vector3 currentPos = control
+ Mathf.Pow(1f - t, 2f) * controlToOrigin
+ Mathf.Pow(t, 2f) * controlToDest;
Gizmos.DrawSphere(currentPos, 1f);
}
}
}