低功耗硬件上的圆周运动


10

我当时在考虑平台和敌人在旧的2D游戏中盘旋运动,我想知道这样做是如何进行的。我了解参数方程式,使用sin和cos做到这一点很简单,但是NES或SNES可以进行实时触发调用吗?我承认严重的无知,但我认为那是昂贵的手术。有什么聪明的方法可以更便宜地计算出该运动吗?

我一直在研究从trig sum identities派生一种算法,该算法仅使用预先计算的trig,但这似乎很复杂。


1
实际上,几年前在一次面试中有人问我这个问题。
Crashworks 2012年

Answers:


14

在您所描述的硬件上,通常情况下的常见解决方案是简单地为一个感兴趣的三角函数生成一个查找表,有时将其与值的定点表示形式结合使用。

这种技术的潜在问题是,它会占用内存空间,尽管您可以通过降低表中数据的分辨率或使用某些函数的周期性特性来存储较少的数据并在运行时对其进行镜像来对此进行低估。

但是,对于特定的遍历圆-对其进行栅格化或沿某个圆移动,可以使用Bresenham线算法的一种变体。当然,布雷森纳姆(Bresenham)的实际算法对于遍历非廉价的八个“主要”方向的线也很有用。


2
真实的故事。LUT和一个圆被定义为256度产生便宜的触发,只有在内存紧张的情况下才进行镜像,并且作为最后的手段来获得一些字节。对于不同的运动,布雷森纳姆的参考文献也很丰富。
Patrick Hughes 2012年

4
即使在现代硬件上,触发调用仍然是查找表。它只是硬件中的查找表,通过泰勒扩展进行了一些改进。(实际上,一个主要的控制台制造商对SIMD sin()函数的实现只是一个硬编码的泰勒系列。)
Crashworks 2012年

3
@Crashworks:绝对不可能是泰勒系列,这对他们真的很愚蠢。它很可能是极小极大多项式。实际上,我见过的sin()的所有现代实现都是基于minimax多项式。
sam hocevar 2012年

@SamHocevar可能是。我只是看到ax + bx ^ 3 + cx ^ 5 + ...的总和,并假设为“泰勒级数”。
Crashworks 2012年

9

还有一个变化布氏算法詹姆斯·弗里思这应该是更快,因为它完全消除了乘法。它不需要任何查找表来实现这一目的,尽管如果半径保持恒定,则可以将结果存储在一个表中。由于Bresenham和Frith的算法都使用8倍对称性,因此该查找表将相对较短。

// 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 //

如果您得到奇怪的结果,那是因为您正在调用未定义(或至少未指定)的行为。C ++在评估“ a()+ b()”时未指定首先评估哪个调用,而是进一步调用了修改积分。为避免这种情况,请不要在xoff++ + xoff与和中读取相同的表达式中修改变量--yoff + yoff。您的变更列表将解决此问题,请考虑将其修复就位,而不是作为便笺。(有关示例和明确
标明

@MaulingMonkey:你是约的问题的评价顺序的权利balance += xoff++ + xoffbalance -= --yoff + yoff。我保持不变,因为这是Frith算法的最初编写方式,后来他自己添加了修复程序(请参见此处)。立即修复。
ProphetV 2012年

2

您还可以使用Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series使用trig函数的近似版本

例如,您可以使用泰勒级数的前四个项来合理近似正弦

正弦


通常这是正确的,但是有很多警告,我什至会说,除非您对自己的工作非常熟悉,否则几乎不应该编写自己的sin()代码。特别是,(列出的)多项式要比列出的多项式更好,甚至有理数更好,因此,您需要了解在哪里应用公式以及如何使用sin和cos的周期性将论点缩小到系列适​​用。这是那种古老的格言“一点知识就是危险的事情”的说法之一。
Steven Stadnicki 2012年

您可以提供一些参考,以便我可以学习此多项式或其他更好的近似值吗?我真的很想学习。这一系列的东西是我微积分课程中最令人困扰的部分。

最经典的起点是《数值食谱》一书,其中提供了有关计算核心数值函数及其近似值的数学知识的大量信息。对于一种有些过时但仍然值得了解的方法,您可能会看到的另一个地方是查找所谓的CORDIC算法。
Steven Stadnicki 2012年

@Vandell:如果您想创建极小极大多项式,我很高兴听到您对LolRemez的看法
sam hocevar 2012年

泰勒级数近似于单个点而不是间隔上的函数行为。多项式非常适合评估x = 0附近的sin(0)或它的七阶导数,但是在x = pi / 2处的误差(此后您可以简单地镜像并重复)非常大。您可以通过评估围绕x = pi / 4的泰勒级数来做大约50倍的运算,但是您真正想要的是一个多项式,该函数可以最小化区间上的最大误差,但会以接近单个点的精度为代价。
马克·托马斯(Thomas Thomas)

2

Goertzel算法是一种很棒的均匀绕行的算法。每步只需要2乘法和2加法,没有查找表,并且状态非常小(4个数字)。

首先根据所需步长(在本例中为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;

该算法使用4个数字作为其状态,初始化如下:

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

最后是主循环:

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);
}

然后它可以永远消失。这是前50分:

Goertzel算法

该算法当然可以在定点硬件上工作。与布雷森汉姆(Bresenham)的明显胜利是不断超越对手。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.