Làm thế nào để đạt được tốc độ di chuyển đồng đều trên một đường cong bezier?


22

Tôi đang cố gắng di chuyển một hình ảnh dọc theo đường cong Bezier. Đây là cách tôi làm điều đó:

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

}

Vấn đề của tôi là hình ảnh di chuyển không đồng đều. Ban đầu, nó di chuyển chậm và sau đó nó tăng tốc dần dần và cuối cùng nó di chuyển rất nhanh. Tôi nên làm gì để thoát khỏi sự tăng tốc này?

Câu trả lời:


27

Có thể tính gần đúng một giải pháp cho vấn đề này đối với hầu hết các quỹ đạo tham số. Ý tưởng là như sau: nếu bạn phóng to đủ sâu trên một đường cong, bạn không thể nói chính đường cong đó từ tiếp tuyến của nó tại điểm đó.

Bằng cách đưa ra giả định này, không cần phải tính toán trước bất kỳ thứ gì ngoài hai vectơ (ba cho các đường cong Bezier hình khối, v.v. ).

Vì vậy, đối với đường cong chúng ta tính toán vectơ tiếp tuyến của nó tại điểm . Định mức của vectơ này là và do đó, quãng đường di chuyển trong một khoảng thời gian có thể xấp xỉ là . Theo sau đó, một quãng đường được di chuyển trong một khoảng thời gian .M(t)dMdttdMdTΔtdMdTΔtLL÷dMdT

Ứng dụng: đường cong Bezier bậc hai

Nếu các điểm kiểm soát của đường cong Bezier là , và , quỹ đạo có thể được biểu thị là:MộtBC

M(t)= =(1-t)2Một+2t(1-t)B+t2C= =t2(Một-2B+C)+t(-2Một+2B)+Một

Vậy đạo hàm là:

dMdt= =t(2Một-4B+2C)+(-2Một+2B)

Bạn chỉ cần lưu trữ vectơ và ở đâu đó. Sau đó, với một cho trước , nếu bạn muốn tăng chiều dài , bạn làm:v1= =2Một-4B+2Cv2= =-2Một+2BtL

t= =t+Ltôiength(tv1+v2)

Đường cong hình khối Bezier

Lý do tương tự áp dụng cho một đường cong có bốn điểm kiểm soát , , và :MộtBCD

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

Đạo hàm là:

dMdt= =t2(-3Một+9B-9C+3D)+t(6Một-12B+6C)+(-3Một+3B)

Chúng tôi tính toán trước ba vectơ:

v1= =-3Một+9B-9C+3Dv2= =6Một-12B+6Cv3= =-3Một+3B

và công thức cuối cùng là:

t= =t+Ltôiength(t2v1+tv2+v3)

Vấn đề chính xác

Nếu bạn đang chạy ở tốc độ khung hình hợp lý, (cần được tính theo thời lượng khung hình) sẽ đủ nhỏ để phép tính gần đúng hoạt động.L

Tuy nhiên, bạn có thể gặp sự không chính xác trong trường hợp cực đoan. Nếu quá lớn, bạn có thể thực hiện phép tính toán, ví dụ như sử dụng 10 phần:L

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

1
Chào. Tôi đang đọc câu trả lời của bạn, nhưng tôi không thể hiểu được L. Bạn có ý gì bởi "cái này nên được tính theo thời lượng khung hình"?
Michael IV

Là L = chiều dài đoạn đường cong?
Michael IV

L là chiều dài đường cong, tức là khoảng cách bạn muốn đi trong khung hình hiện tại.
sam hocevar

OK tôi nhìn thấy bây giờ. Và bạn nghĩ phép tính gần đúng này tốt như kỹ thuật tách đường cong từ câu trả lời dưới đây?
Michael IV

Khi Lđủ nhỏ, phép tính gần đúng này thực sự luôn chính xác hơn câu trả lời dưới đây, vâng. Nó cũng sử dụng ít bộ nhớ hơn (vì nó sử dụng đạo hàm thay vì lưu trữ tất cả các giá trị điểm). Khi Lbắt đầu phát triển, bạn có thể sử dụng kỹ thuật tôi đề nghị ở cuối.
sam hocevar

6

Bạn cần phải chỉnh sửa lại đường cong. Cách dễ nhất để làm điều này là tính độ dài cung của một số đoạn của đường cong và sử dụng chúng để tìm ra nơi bạn nên lấy mẫu từ đó. Ví dụ: có thể tại t = 0,5 (nửa chừng), bạn nên chuyển s = 0,7 đến đường cong để có được vị trí "nửa chừng". Bạn cần lưu trữ một danh sách các độ dài cung của các đoạn đường cong khác nhau để làm điều này.

Có thể có nhiều cách tốt hơn, nhưng đây là một số mã C # rất đơn giản mà tôi đã viết để làm điều này trong trò chơi của mình. Nên dễ dàng chuyển đến Mục tiêu 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);
    }
}

Chỉnh sửa: Điều đáng chú ý là điều này sẽ không cung cấp cho bạn độ dài cung chính xác, vì không thể có được độ dài cung của một đường cong hình khối. Tất cả điều này không phải là ước tính độ dài của các phân khúc khác nhau. Tùy thuộc vào độ dài của đường cong, bạn có thể cần tăng độ phân giải để ngăn nó thay đổi tốc độ khi đến một đoạn mới. Tôi thường sử dụng ~ 100, mà tôi chưa bao giờ có vấn đề với.


0

Một giải pháp rất nhẹ là xấp xỉ tốc độ chứ không phải xấp xỉ đường cong. Trên thực tế phương pháp này độc lập với hàm đường cong và cho phép bạn sử dụng bất kỳ đường cong chính xác nào thay vì sử dụng các đạo hàm hoặc xấp xỉ.

Đây là mã cho 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;
}

Mặc dù giải pháp không phụ thuộc vào chức năng đường cong, tôi muốn lưu ý nó ở đây vì tôi cũng đang tìm cách làm thế nào để đạt được tốc độ không đổi trên đường cong Bezier, và sau đó tôi nghĩ ra giải pháp này. Xem xét mức độ phổ biến của chức năng này có thể hữu ích ở đây.


-3

Tôi không biết gì về cocos2, nhưng đường cong bezier là một loại phương trình tham số, vì vậy bạn sẽ có thể nhận được giá trị x và y của mình theo thời gian.


4
Thêm một ví dụ + giải thích thêm và đây sẽ là một câu trả lời tốt.
MichaelHouse
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.