วิธีการบรรลุความเร็วสม่ำเสมอของการเคลื่อนไหวบนเส้นโค้งเบซิเยร์?


22

ฉันพยายามที่จะย้ายภาพไปตามเส้นโค้ง Bezier นี่คือวิธีที่ฉันทำ:

- (void)startFly
{    
 [self runAction:[CCSequence actions:
             [CCBezierBy actionWithDuration:timeFlying bezier:[self getPathWithDirection:currentDirection]],
             [CCCallFuncN actionWithTarget:self selector:@selector(endFly)],
             nil]];

}

ปัญหาของฉันคือภาพเคลื่อนไหวไม่สม่ำเสมอ ในตอนแรกมันเคลื่อนที่ช้าแล้วเร่งความเร็วทีละน้อยและในตอนท้ายมันเคลื่อนที่เร็วมาก ฉันควรทำอย่างไรเพื่อกำจัดความเร่งนี้?

คำตอบ:


27

เป็นไปได้ที่จะประมาณวิธีแก้ปัญหานี้สำหรับวิถีกระสุนส่วนใหญ่ แนวคิดนี้มีดังต่อไปนี้: หากคุณซูมเข้าไปในโค้งที่ลึกพอคุณจะไม่สามารถบอกได้ว่าเส้นโค้งนั้นเกิดจากการสัมผัสกัน ณ จุดนั้น

ด้วยการทำสมมุติฐานนี้ไม่จำเป็นต้องคำนวณอะไรล่วงหน้ามากกว่าสองเวกเตอร์ (สามเส้นโค้งสำหรับเบซิเยร์ลูกบาศก์ฯลฯ )

ดังนั้นสำหรับเส้นโค้งM(t)เราคำนวณเวกเตอร์แทนเจนต์ของมันdMdtที่จุดtdMdTΔtdMdTΔtLL÷dMdT

แอพลิเคชัน: Bezier โค้งกำลังสอง

หากจุดควบคุมของเส้นโค้ง Bezier คือ ,และวิถีการเคลื่อนที่สามารถแสดงเป็น:ABC

M(t)=(1t)2A+2t(1t)B+t2C=t2(A2B+C)+t(2A+2B)+A

ดังนั้นอนุพันธ์คือ:

dMdt=t(2A4B+2C)+(2A+2B)

คุณแค่ต้องเก็บเวกเตอร์ v1=2A4B+2Cv 2=-2A+2BtLและที่ไหนสักแห่ง จากนั้นสำหรับกำหนดถ้าคุณต้องการเลื่อนความยาวคุณต้องทำ:v2=2A+2BtL

t=t+Llength(tv1+v2)

Cubic Bezier curves

การใช้เหตุผลแบบเดียวกันนั้นใช้กับเส้นโค้งที่มีจุดควบคุมสี่จุดA , ,และ :BCD

M(t)=(1t)3A+3t(1t)2B+3t2(1t)C+t3D=t3(A+3B3C+D)+t2(3A6B+3C)+t(3A+3B)+A

อนุพันธ์คือ:

dMdt=t2(3A+9B9C+3D)+t(6A12B+6C)+(3A+3B)

เราคำนวณค่าเวกเตอร์สามตัวล่วงหน้า:

v1=3A+9B9C+3Dv2=6A12B+6Cv3=3A+3B

และสูตรสุดท้ายคือ:

t=t+Llength(t2v1+tv2+v3)

ปัญหาความแม่นยำ

หากคุณทำงานที่อัตราเฟรมที่สมเหตุสมผล L (ซึ่งควรคำนวณตามระยะเวลาของเฟรม) จะมีขนาดเล็กพอสำหรับการประมาณการทำงาน

อย่างไรก็ตามคุณอาจพบความไม่ถูกต้องในบางกรณี ถ้าใหญ่เกินไปคุณสามารถทำการคำนวณทีละตัวอย่างเช่นใช้ 10 ส่วน:L

for (int i = 0; i < 10; i++)
    t = t + (L / 10) / length(t * v1 + v2);

1
สวัสดี ฉันกำลังอ่านคำตอบของคุณ แต่ฉันไม่เข้าใจว่าแอลคืออะไรคุณหมายถึงอะไร "ซึ่งควรคำนวณตามระยะเวลาของเฟรม"?
Michael IV

L = ความยาวส่วนโค้งหรือไม่
Michael IV

L คือความยาวโค้งนั่นคือระยะทางที่คุณต้องการเดินทางในระหว่างเฟรมปัจจุบัน
sam hocevar

ตกลงฉันเห็นแล้ว และคุณคิดว่าการประมาณนี้ดีเท่ากับเทคนิคการแยกส่วนโค้งจากคำตอบด้านล่าง
Michael IV

เมื่อLมีขนาดเล็กเพียงพอการประมาณนี้จะแม่นยำกว่าคำตอบด้านล่างเสมอใช่ นอกจากนี้ยังใช้หน่วยความจำน้อยลง (เพราะใช้อนุพันธ์แทนการจัดเก็บค่าจุดทั้งหมด) เมื่อLเริ่มเติบโตคุณสามารถใช้เทคนิคที่ฉันแนะนำตอนท้าย
sam hocevar

6

คุณต้องแก้ไขเส้นโค้งใหม่ วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการคำนวณความยาวส่วนโค้งของส่วนต่าง ๆ ของส่วนโค้งและใช้สิ่งเหล่านี้เพื่อหาตำแหน่งที่คุณควรเก็บตัวอย่าง ตัวอย่างเช่นบางทีที่ t = 0.5 (ผ่านไปครึ่งทาง) คุณควรผ่าน s = 0.7 ไปยังเส้นโค้งเพื่อให้ได้ตำแหน่ง "ครึ่งทาง" คุณต้องจัดเก็บรายการความยาวส่วนโค้งของส่วนโค้งต่างๆเพื่อทำสิ่งนี้

อาจมีวิธีที่ดีกว่า แต่นี่คือรหัส C # ที่เรียบง่ายที่ฉันเขียนให้ทำในเกมของฉัน ควรง่ายต่อการย้ายไปยัง Objective C:

public sealed class CurveMap<TCurve> where TCurve : struct, ICurve
{
    private readonly float[] _arcLengths;
    private readonly float _ratio;
    public float length { get; private set; }
    public TCurve curve { get; private set; }
    public bool isSet { get { return !length.isNaN(); } }
    public int resolution { get { return _arcLengths.Length; } }

    public CurveMap(int resolution)
    {
        _arcLengths = new float[resolution];
        _ratio = 1f / resolution;
        length = float.NaN;
    }

    public void set(TCurve c)
    {
        curve = c;
        Vector2 o = c.sample(0);
        float ox = o.X;
        float oy = o.Y;
        float clen = 0;
        int nSamples = _arcLengths.Length;
        for(int i = 0; i < nSamples; i++)
        {
            float t = (i + 1) * _ratio;
            Vector2 p = c.sample(t);
            float dx = ox - p.X;
            float dy = oy - p.Y;
            clen += (dx * dx + dy * dy).sqrt();
            _arcLengths[i] = clen;
            ox = p.X;
            oy = p.Y;
        }
        length = clen;
    }

    public Vector2 sample(float u)
    {
        if(u <= 0) return curve.sample(0);
        if(u >= 1) return curve.sample(1);

        int index = 0;
        int low = 0;
        int high = resolution - 1;
        float target = u * length;
        float found = float.NaN;

        // Binary search to find largest value <= target
        while(low < high)
        {
            index = (low + high) / 2;
            found = _arcLengths[index];
            if (found < target)
                low = index + 1;
            else
                high = index;
        }

        // If the value we found is greater than the target value, retreat
        if (found > target)
            index--;

        if(index < 0) return curve.sample(0);
        if(index >= resolution - 1) return curve.sample(1);

        // Linear interpolation for index
        float min = _arcLengths[index];
        float max = _arcLengths[index + 1];
        Debug.Assert(min <= target && max >= target);
        float interp = (target - min) / (max - min);
        Debug.Assert(interp >= 0 && interp <= 1);
        return curve.sample((index + interp + 1) * _ratio);
    }
}

แก้ไข:มันน่าสังเกตว่าสิ่งนี้จะไม่ให้ความยาวส่วนโค้งที่แน่นอนแก่คุณเนื่องจากเป็นไปไม่ได้ที่จะได้ความยาวส่วนโค้งของลูกบาศก์โค้ง ทั้งหมดนี้จะเป็นการประมาณความยาวของส่วนต่าง ๆ คุณอาจต้องเพิ่มความละเอียดเพื่อป้องกันไม่ให้เส้นโค้งเปลี่ยนความเร็วเมื่อถึงเส้นโค้งใหม่ ฉันมักจะใช้ ~ 100 ซึ่งฉันไม่เคยมีปัญหา


0

วิธีแก้ปัญหาที่มีน้ำหนักเบามากคือประมาณค่าความเร็วมากกว่าการประมาณส่วนโค้ง ที่จริงวิธีการนี้เป็นอิสระจากฟังก์ชั่นเส้นโค้งและช่วยให้คุณใช้เส้นโค้งที่แน่นอนใด ๆ แทนการใช้อนุพันธ์หรือการประมาณ

นี่คือรหัสสำหรับ C # Unity 3D:

public float speed; // target linear speed

// determine an initial value by checking where speedFactor converges
float speedFactor = speed / 10; 

float targetStepSize = speed / 60f; // divide by fixedUpdate frame rate
float lastStepSize;

void Update ()
{   
    // Take a note of your previous position.
    Vector3 previousPosition = transform.position;

    // Advance on the curve to the next t;
    transform.position = BezierOrOtherCurveFunction(p0, p1, ..., t);

    // Measure your movement length
    lastStepSize = Vector3.Magnitude(transform.position - previousPosition);

    // Accelerate or decelerate according to your latest step size.
    if (lastStepSize < targetStepSize) 
    {
        speedFactor *= 1.1f;
    }
    else
    {
        speedFactor *= 0.9f;
    }

    t += speedFactor * Time.deltaTime;
}

แม้ว่าวิธีนี้จะเป็นอิสระจากฟังก์ชั่นของเส้นโค้ง แต่ฉันต้องการที่จะสังเกตมันที่นี่เพราะฉันยังมองหาวิธีที่จะทำให้ได้ความเร็วคงที่บน Bezier curve จากนั้นฉันก็หาวิธีแก้ปัญหานี้ พิจารณาความนิยมของฟังก์ชั่นนี้อาจเป็นประโยชน์ที่นี่


-3

ฉันไม่รู้อะไรเกี่ยวกับ cocos2 แต่เส้นโค้งเบซิเยร์เป็นสมการแบบพารามิเตอร์ดังนั้นคุณควรจะได้ค่า x และ y ของคุณในแง่ของเวลา


4
เพิ่มตัวอย่าง + คำอธิบายเพิ่มเติมและนี่จะเป็นคำตอบที่ดี
MichaelHouse
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.