저전력 하드웨어에서의 원 운동


10

나는 오래된 2D 게임에서 원으로 움직이는 플랫폼과 적에 대해 생각하고 있었고 그것이 어떻게 이루어 졌는지 궁금했습니다. 나는 파라 메트릭 방정식을 이해하는데, 그것을하기 위해 죄와 cos를 사용하는 것이 사소한 일이지만, NES 나 SNES가 실시간 trig 호출을 할 수 있습니까? 나는 무지가 크다는 것을 인정하지만 이것들은 값 비싼 작업이라고 생각했다. 그 동작을 더 싸게 계산하는 영리한 방법이 있습니까?

사전 계산 된 삼각법 만 사용할 삼각법 합계 ID에서 알고리즘을 도출하려고 노력했지만 복잡해 보입니다.


1
실제로 몇 년 전 면접 중에이 질문을 받았습니다.
Crashworks

답변:


14

설명하는 하드웨어에서 일반적인 경우에 대한 일반적인 해결책은 관심있는 삼각 함수에 대한 조회 테이블을 생성하는 것입니다. 때로는 값에 대한 고정 소수점 표현과 함께 사용하기도합니다.

이 기술의 잠재적 인 문제는 메모리 공간을 소비한다는 것입니다.하지만 테이블에서 데이터의 해상도를 낮추거나 일부 함수의주기적인 특성을 활용하여 적은 데이터를 저장하고 런타임에 미러링함으로써 메모리 공간을 소비 할 수 있습니다.

그러나 래스터 화하거나 하나를 따라 무언가를 이동시키기 위해 구체적으로 순회 하는 경우에는 Bresenham의 라인 알고리즘을 변형하여 사용할 수 있습니다 . 물론 Bresenham의 실제 알고리즘 은 8 개 "1 차"방향이 아닌 라인을 매우 저렴하게 통과하는 데 유용합니다.


2
실화. LUT와 원은 256 도의 값싼 삼각법으로 정의되며, 메모리가 빡빡하고 몇 바이트를 얻는 최후의 수단으로 미러링이 수행되었습니다. Bresenham 레퍼런스도 다른 움직임을 위해 자리 잡았습니다.
Patrick Hughes

4
최신 하드웨어에서도 삼각 호출은 여전히 ​​룩업 테이블입니다. 그것은 테일러 확장을 통해 약간 개선 된 하드웨어의 룩업 테이블 일뿐입니다. (사실 주요 콘솔 제조업체의 SIMD sin () 함수 구현은 단순히 하드 코딩 된 Taylor 시리즈입니다.)
Crashworks

3
@Crashworks : 테일러 시리즈는 절대 불가능합니다. 정말 바보 일 것입니다. 아마도 미니맥 다항식 일 것입니다. 실제로, 내가 본 모든 sin () 구현은 최소 다항식을 기반으로합니다.
sam hocevar

@ SamHocevar가 될 수 있습니다. 방금 ax + bx ^ 3 + cx ^ 5 + ...의 요약을보고 "Taylor series"라고 가정했습니다.
Crashworks

9

의 변형있다 Bresenham 알고리즘 에 의해 제임스 프리스 완전히 곱셈을 제거하기 때문에 더 빨리해야한다. 반경을 일정하게 유지하면 결과를 테이블에 저장할 수 있지만이 작업을 수행하기 위해 조회 테이블이 필요하지 않습니다. 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. 변경 사항 목록에서이 문제를 해결합니다. 참고로 고정하는 대신 수정하십시오. (참조 섹션 5 C의 제 4 항 ++ 표준 예제 및 명시 적으로 밖을 호출하는 standardese)
MaulingMonkey

@MaulingMonkey : balance += xoff++ + xoff및 의 문제가있는 평가 순서에 대해 맞습니다 balance -= --yoff + yoff. Frith의 알고리즘이 원래 작성된 방식이므로 나중에 수정 사항을 추가하여 변경하지 않았습니다 . ( 여기 참조 ). 지금 수정했습니다.
ProphetV 2016 년

2

Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series를 사용하여 대략적인 버전의 삼각 함수를 사용할 수도 있습니다 .

예를 들어, 처음 네 개의 테일러 계열 항을 사용하여 사인을 합리적으로 추정 할 수 있습니다.

사인


이것은 일반적으로 사실이지만, 너무 많은주의 사항이 있으므로 수행중인 작업에 매우 익숙 하지 않으면 실제로 자신의 sin () 코드를 작성해서는 안됩니다 . 특히, 나열된 것보다 (다소 적으로) 다항식이 더 좋고, 합리적인 근사치가 있으며, 공식을 적용 할 위치와 죄와 cos의 주기성을 사용하여 인수 범위를 좁히는 범위를 좁히는 방법을 이해해야합니다. 시리즈가 적용됩니다. 이것은 오래된 격언 '작은 지식은 위험한 것'인 경우 중 하나입니다.
Steven Stadnicki 2016 년

이 다항식이나 다른 근사값을 배울 수 있도록 몇 가지 참고 자료를 줄 수 있습니까? 정말 배우고 싶습니다. 이 시리즈는 미적분학 과정에서 가장 마음이 부는 부분이었습니다.

가장 먼저 시작해야 할 곳은 Numerical Recipes라는 책으로 핵심 수치 함수와 근사값을 계산하는 수학에 대한 정보를 제공합니다. 약간 구식이지만 여전히 알아볼 가치가있는 접근 방식을 찾는 또 다른 곳은 소위 CORDIC 알고리즘 을 찾는 것 입니다.
Steven Stadnicki 2016 년

@Vandell : minimax 다항식을 만들고 싶다면 LolRemez 에 대한 당신의 생각을 기꺼이 들겠습니다 .
sam hocevar 2016 년

Taylor 시리즈는 간격이 아닌 단일 지점 주위의 함수 동작과 비슷합니다. 다항식은 x = 0 주위의 sin (0) 또는 7 차 도함수를 평가하는 데 유용하지만 x = pi / 2의 오류는 단순히 미러링하고 반복 할 수 있으므로 상당히 큽니다. 대신 x = pi / 4 주위에서 Taylor 계열을 평가하여 약 50 배 더 잘 수행 할 수 있지만 실제로 원하는 것은 단일 지점 근처의 정밀도를 희생하면서 구간의 최대 오류를 최소화하는 다항식입니다.
Marcks 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 점은 다음과 같습니다.

고 르트 젤 알고리즘

이 알고리즘은 물론 고정 소수점 하드웨어에서 작동 할 수 있습니다. Bresenham과의 확실한 승리는 원을 넘어 일정한 속도입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.