Düşük güçlü donanımda dairesel hareket


10

Eski 2D oyunlarda çevrelerde hareket eden platformları ve düşmanları düşünüyordum ve bunun nasıl yapıldığını merak ediyordum. Parametrik denklemleri anlıyorum ve bunu yapmak için günah ve cos kullanmak önemsiz, ancak bir NES veya SNES gerçek zamanlı tetikleme çağrıları yapabilir mi? Ağır cehalet kabul ediyorum, ama bunların pahalı operasyonlar olduğunu düşündüm. Bu hareketi daha ucuza hesaplamanın akıllıca bir yolu var mı?

Sadece önceden hesaplanmış trig kullanan trig toplam kimliklerinden bir algoritma türetmek için çalışıyordum, ama bu kıvrık görünüyor.


1
Aslında bu soruyu birkaç yıl önce bir iş görüşmesi sırasında sordum.
Crashworks

Yanıtlar:


14

Tanımladığınız gibi donanımda, genel durum için ortak bir çözüm, birinin ilgilendiği trigonometri fonksiyonları için, bazen değerler için sabit nokta gösterimleri ile birlikte, bir arama tablosu oluşturmaktır.

Bu teknikle ilgili potansiyel sorun, bellek alanını tüketmesidir, ancak tablonuzdaki verilerin daha düşük bir çözünürlüğüne karar vererek veya daha az veri depolamak ve çalışma zamanında yansıtmak için bazı işlevlerin periyodik doğasından yararlanarak bunu önemsiz gösterebilirsiniz.

Bununla birlikte, spesifik olarak çapraz çevreler için - ya rasterleştirmek ya da bir şeyi bir yere taşımak için , Bresenham'ın çizgi algoritmasının bir varyasyonu kullanılabilir . Bresenham'ın gerçek algoritması , elbette, sekiz "birincil" yönde olmayan çizgileri oldukça ucuza çevirmek için de yararlıdır.


2
Gerçek hikaye. LUT ve bir daire 256 derece verim ucuz trig olarak tanımlanır, yansıtma sadece bellek sıkıysa ve birkaç bayt kazanmak için son çare olarak yapıldı. Bresenham referansı farklı hareketler için de geçerlidir.
Patrick Hughes

4
Modern donanımlarda bile, bir trig çağrısı hala bir arama tablosu. Bu sadece bir Taylor genişletme yoluyla biraz ayrıntı ile donanım bir arama tablosu. (Aslında bir büyük konsol üreticisinin SIMD sin () işlevini uygulaması basitçe kodlanmış bir Taylor serisidir.)
Crashworks

3
@Crashworks: Taylor serisi olmasının kesinlikle bir yolu yok, gerçekten aptalca olurdu. Muhtemelen bir minimax polinomudur. Aslında, şimdiye kadar gördüğüm tüm sin () uygulamaları minimax polinomlarına dayanmaktadır.
sam hocevar

@ SamHocevar Olabilir. Az önce balta + bx ^ 3 + cx ^ 5 + ... 'ın toplandığını gördüm ve "Taylor serisi" ni aldım.
Crashworks

9

Bir varyasyonu var Bresenham algoritması tarafından James Frith tamamen çarpma ortadan kaldırır çünkü daha hızlı olmalı. Yarıçap sabit kalırsa sonuçları bir tabloda saklayabilse de, bunu başarmak için herhangi bir arama tablosuna gerek yoktur. Hem Bresenham hem de Frith'in algoritması 8 kat simetri kullandığından, bu arama tablosu nispeten kısa olacaktır.

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

Tuhaf sonuçlar alıyorsanız, bunun nedeni tanımlanmamış (veya en azından belirtilmemiş) davranışları çağırmanızdır . C ++, "a () + b ()" değerlendirilirken ilk olarak hangi çağrının değerlendirileceğini belirtmez ve daha sonra integralleri değiştirmeyi çağırır. Bunu önlemek için, olduğu gibi okumak aynı ifadede bir değişken değiştirmeyin xoff++ + xoffve --yoff + yoff. Değişiklikçiniz bunu düzeltir, notta bir çakmak yerine yerine sabitlemeyi düşünün. (Örnekler için C ++ standardının 5. paragrafına ve bunu açıkça çağıran standardese bakınız)
MaulingMonkey

@MaulingMonkey: Sorunlu değerlendirme sırasına hakkında haklısın balance += xoff++ + xoffve balance -= --yoff + yoff. Bunu değiştirmeden bıraktım, çünkü Frith'in algoritması başlangıçta bu şekilde yazılmıştır, düzeltmenin kendisi tarafından eklenmiştir ( buraya bakınız ). Şimdi düzeltildi.
ProphetV

2

Taylor Expansions'ı kullanarak trig işlevlerinin yaklaşık bir sürümünü de kullanabilirsiniz http://en.wikipedia.org/wiki/Taylor_series

Örneğin, ilk dört taylor serisi terimini kullanarak sinüsün makul bir yaklaşımına sahip olabilirsiniz.

sinüs


Bu genellikle doğrudur, ancak olmadıkça neredeyse kendi sin () kod yazmak asla söylemek için şimdiye kadar gider o kadar çok uyarılar ile geliyor çok ne yaptığını aşina. Özellikle, listelenenlerden (daha az) daha iyi polinomlar, hatta daha iyi rasyonel yaklaşımlar vardır ve formülü nereye uygulayacağınızı ve günah ve cos'un periyodikliğini, argümanınızı serisi geçerlidir. Bu, eski aforizmanın 'biraz bilginin tehlikeli bir şey olduğu' durumlardan biri olduğu durumlardan biridir.
Steven Stadnicki

Bu polinomları veya daha iyi yaklaşımları öğrenebilmem için bazı referanslar verebilir misiniz? Bunu gerçekten öğrenmek istiyorum. Bu seri şey, matematik dersimin en akıl almaz parçasıydı.

Başlamak için klasik yer, temel sayısal fonksiyonları ve tahminlerinin arkasındaki matematiği hesaplamak hakkında oldukça fazla bilgi veren Sayısal Tarifler kitabıdır. Biraz modası geçmiş, ancak hala bilinmeye değer bir yaklaşım için bakabileceğiniz başka bir yer, CORDIC algoritmasını aramaktır .
Steven Stadnicki

@Vandell: minimax polinomları oluşturmak istiyorsanız, LolRemez hakkındaki düşüncelerinizi duymaktan mutluluk duyarım .
sam hocevar

Taylor serisi, bir işlevin davranışını bir aralıkta değil, tek bir nokta etrafında yaklaşık olarak tahmin eder. Polinom, sin (0) veya yedinci türevini x = 0 civarında değerlendirmek için harikadır, ancak x = pi / 2'deki hata, daha sonra basitçe yansıtabileceğiniz ve tekrarlayabileceğiniz oldukça büyüktür. Taylor serisini x = pi / 4 etrafında değerlendirerek yaklaşık elli kat daha iyi yapabilirsiniz, ancak gerçekten istediğiniz şey, aralıktaki maksimum hatayı tek bir noktaya yakın bir maliyetle en aza indiren bir polinomdur.
Marcks Thomas

2

Bir daire üzerinde eşit olarak dolaşmak için harika bir algoritma Goertzel algoritmasıdır . Adım başına sadece 2 çarpma ve 2 ekleme, arama tablosu ve çok düşük bir durum (4 sayı) gerektirir.

İlk olarak, gerekli adım boyutuna göre (muhtemelen bu durumda 2π / 64) sabit kodlanmış bazı sabitleri tanımlayın:

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

Algoritmanın durumu olarak 4 sayı kullanılır ve şu şekilde başlatılır:

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

Ve son olarak ana döngü:

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

O zaman sonsuza dek gidebilir. İşte ilk 50 puan:

Goertzel Algoritması

Algoritma elbette sabit nokta donanımı üzerinde çalışabilir. Bresenham'a karşı açık galibiyet daire üzerindeki sabit hızdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.