沿具有可自定义顶点高度和距离的两点之间的弧形路径查找 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);
        }

    }
}