Chuyển động tròn trên phần cứng công suất thấp


10

Tôi đã suy nghĩ về các nền tảng và kẻ thù di chuyển trong vòng tròn trong các trò chơi 2D cũ và tôi đã tự hỏi làm thế nào nó được thực hiện. Tôi hiểu các phương trình tham số, và việc sử dụng sin và cos để thực hiện nó là chuyện nhỏ, nhưng liệu NES hoặc SNES có thể thực hiện các cuộc gọi trig thời gian thực không? Tôi thừa nhận sự thiếu hiểu biết nặng nề, nhưng tôi nghĩ đó là những hoạt động đắt tiền. Có một số cách thông minh để tính toán chuyển động đó rẻ hơn?

Tôi đã làm việc để tạo ra một thuật toán từ các danh tính trig sum sẽ chỉ sử dụng trig tính toán trước, nhưng điều đó có vẻ khó hiểu.


1
Tôi thực sự đã được hỏi câu hỏi này trong một cuộc phỏng vấn việc làm vài năm trước.
Crashworks

Câu trả lời:


14

Trên phần cứng như bạn đang mô tả, một giải pháp phổ biến cho trường hợp chung là chỉ cần tạo một bảng tra cứu cho các hàm lượng giác mà người ta quan tâm, đôi khi kết hợp với biểu diễn điểm cố định cho các giá trị.

Vấn đề tiềm ẩn với kỹ thuật này là nó tiêu tốn dung lượng bộ nhớ, mặc dù bạn có thể hạ thấp điều này bằng cách giải quyết độ phân giải dữ liệu thấp hơn trong bảng của bạn hoặc bằng cách tận dụng tính chất định kỳ của một số chức năng để lưu trữ ít dữ liệu hơn và phản chiếu nó khi chạy.

Tuy nhiên, đối với các vòng tròn đi qua cụ thể - để raster chúng hoặc di chuyển thứ gì đó dọc theo một vòng, có thể sử dụng một biến thể của thuật toán dòng của Bresenham . Tất nhiên, thuật toán thực tế của Bresenham cũng hữu ích cho việc đi qua các đường không nằm trong tám hướng "chính" khá rẻ.


2
Câu chuyện có thật. LUT và một vòng tròn được định nghĩa là 256 độ mang lại giá trị rẻ, việc phản chiếu chỉ được thực hiện nếu bộ nhớ bị hạn chế và là phương sách cuối cùng để đạt được một vài byte. Tham chiếu Bresenham là tại chỗ cho phong trào khác nhau, quá.
Patrick Hughes

4
Ngay cả trên phần cứng hiện đại, một cuộc gọi trig vẫn là một bảng tra cứu. Nó chỉ là một bảng tra cứu trong phần cứng, với một số tinh chỉnh thông qua bản mở rộng Taylor. (Trên thực tế, một nhà sản xuất bảng điều khiển chính thực hiện chức năng SIMD sin () chỉ đơn giản là một chuỗi Taylor được mã hóa cứng.)
Crashworks

3
@Crashworks: hoàn toàn không có cách nào đó là một loạt Taylor, nó sẽ thực sự ngu ngốc đối với họ. Đây có lẽ là một đa thức minimax. Trên thực tế, tất cả các triển khai hiện đại của sin () tôi từng thấy đều dựa trên đa thức minimax.
sam hocevar

@SamHocevar Có thể. Tôi chỉ thấy tổng kết của ax + bx ^ 3 + cx ^ 5 + ... và giả sử "Taylor series".
Crashworks

9

Có một biến thể thuật toán của Bresenham bởi James Frith , nó thậm chí còn nhanh hơn vì nó loại bỏ hoàn toàn phép nhân. Nó không cần bất kỳ bảng tra cứu nào để đạt được điều này, mặc dù người ta có thể lưu trữ kết quả trong một bảng nếu bán kính không đổi. Do thuật toán của cả Bresenham và Frith đều sử dụng đối xứng 8 lần, nên bảng tra cứu này sẽ tương đối ngắn.

// FCircle.c - Draws a circle using Frith's algorithm.
// Copyright (c) 1996  James E. Frith - All Rights Reserved.
// Email:  jfrith@compumedia.com

typedef unsigned char   uchar;
typedef unsigned int    uint;

extern void SetPixel(uint x, uint y, uchar color);

// FCircle --------------------------------------------
// Draws a circle using Frith's Algorithm.

void FCircle(int x, int y, int radius, uchar color)
{
  int balance, xoff, yoff;

  xoff = 0;
  yoff = radius;
  balance = -radius;

  do {
    SetPixel(x+xoff, y+yoff, color);
    SetPixel(x-xoff, y+yoff, color);
    SetPixel(x-xoff, y-yoff, color);
    SetPixel(x+xoff, y-yoff, color);
    SetPixel(x+yoff, y+xoff, color);
    SetPixel(x-yoff, y+xoff, color);
    SetPixel(x-yoff, y-xoff, color);
    SetPixel(x+yoff, y-xoff, color);

    balance += xoff++;
    if ((balance += xoff) >= 0)
        balance -= --yoff * 2;

  } while (xoff <= yoff);
} // FCircle //

Nếu bạn nhận được kết quả kỳ lạ, đó là vì bạn đang gọi hành vi không xác định (hoặc ít nhất là không xác định) . C ++ không chỉ định cuộc gọi nào được đánh giá đầu tiên khi đánh giá "a () + b ()" và gọi thêm các sửa đổi tích phân. Để tránh điều này, đừng sửa đổi một biến trong cùng một biểu thức bạn đọc nó như trong xoff++ + xoff--yoff + yoff. Người thay đổi của bạn sẽ khắc phục điều này, xem xét sửa nó tại chỗ thay vì như một cách giải quyết. (Xem phần 5 đoạn 4 của tiêu chuẩn C ++ để biết ví dụ và tiêu chuẩn gọi rõ ràng điều này)
MaulingMonkey

@MaendingMonkey: Bạn nói đúng về thứ tự đánh giá có vấn đề của balance += xoff++ + xoffbalance -= --yoff + yoff. Tôi đã không thay đổi điều này, vì đây là cách mà thuật toán của Frith ban đầu được viết, với bản sửa lỗi sau đó được thêm vào (xem tại đây ). Đã sửa bây giờ.
Tiên tri

2

Bạn cũng có thể sử dụng phiên bản xấp xỉ của các hàm lượng giác bằng cách sử dụng Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Ví dụ: bạn có thể có một xấp xỉ hợp lý của sin bằng cách sử dụng bốn thuật ngữ chuỗi taylor đầu tiên

sin


Điều này nói chung là đúng, nhưng đi kèm với rất nhiều cảnh báo mà tôi muốn nói rằng bạn hầu như không bao giờ nên viết mã sin () của riêng mình trừ khi bạn rất quen thuộc với những gì bạn đang làm. Cụ thể, có các đa thức tốt hơn một chút so với đa thức được liệt kê, thậm chí gần đúng hơn và bạn cần hiểu nơi áp dụng công thức và cách sử dụng tính chu kỳ của sin và cos để thu hẹp đối số của bạn trong phạm vi loạt áp dụng. Đây là một trong những trường hợp mà câu cách ngôn cũ 'một chút kiến ​​thức là một điều nguy hiểm' đúng.
Steven Stadnicki

Bạn có thể đưa ra một số tài liệu tham khảo để tôi có thể tìm hiểu đa thức này hoặc các xấp xỉ tốt hơn khác không? Tôi thực sự muốn học điều đó. Loạt bài này là phần thổi tâm trí nhất trong khóa học tính toán của tôi.

Nơi cổ điển để bắt đầu là cuốn sách Bí quyết số, cung cấp một chút thông tin về tính toán các hàm số cốt lõi và toán học đằng sau các xấp xỉ của chúng. Một nơi khác mà bạn có thể tìm kiếm, cho một cách tiếp cận hơi lỗi thời nhưng vẫn đáng để biết, là tìm kiếm cái gọi là thuật toán CORDIC .
Steven Stadnicki

@Vandell: nếu bạn muốn tạo đa thức minimax, tôi rất vui khi nghe suy nghĩ của bạn về LolRemez .
sam hocevar

Sê-ri Taylor xấp xỉ hành vi của một hàm xung quanh một điểm, không phải trên một khoảng. Đa thức là tuyệt vời để đánh giá sin (0) hoặc đạo hàm thứ bảy của nó quanh x = 0, nhưng sai số tại x = pi / 2, sau đó bạn có thể chỉ cần phản chiếu và lặp lại, là khá lớn. Thay vào đó, bạn có thể làm tốt hơn khoảng năm mươi lần bằng cách đánh giá chuỗi Taylor xung quanh x = pi / 4, nhưng điều bạn thực sự muốn là một đa thức giúp giảm thiểu sai số tối đa trên khoảng, với chi phí chính xác gần một điểm.
Marcks Thomas

2

Một thuật toán tuyệt vời để di chuyển đồng đều trên một vòng tròn là thuật toán Goertzel . Nó chỉ yêu cầu 2 phép nhân và 2 phép cộng mỗi bước, không có bảng tra cứu và trạng thái rất tối thiểu (4 số).

Trước tiên, xác định một số hằng số, có thể được mã hóa cứng, dựa trên kích thước bước cần thiết (trong trường hợp này là 2π / 64):

float const step = 2.f * M_PI / 64;
float const s = sin(step);
float const c = cos(step);
float const m = 2.f * c;

Thuật toán sử dụng 4 số làm trạng thái của nó, được khởi tạo như thế này:

float t[4] = { s, c, 2.f * s * c, 1.f - 2.f * s * s };

Và cuối cùng là vòng lặp chính:

for (int i = 0; ; i++)
{
    float x = m * t[2] - t[0];
    float y = m * t[3] - t[1];
    t[0] = t[2]; t[1] = t[3]; t[2] = x; t[3] = y;
    printf("%f %f\n", x, y);
}

Nó có thể đi mãi mãi. Dưới đây là 50 điểm đầu tiên:

Thuật toán Goertzel

Thuật toán tất nhiên có thể làm việc trên phần cứng điểm cố định. Chiến thắng rõ ràng trước Bresenham là tốc độ không đổi trên vòng tròn.

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.