SİNÜS KOSİNÜS
Retro bilgisayar-Sinüs Kosinüs Tablolarında Doğrusal Geçiş-
Demolarda en çok kullanılan fonksiyonlar sinüs ve kosinüs olmalı. Scroll textlerde, obje oluşturmada, kamera yollarında hatta sıkıştırmada bile sık sık kullanılıyorlar. Ama bir değerin sinüsünü almak, bellekte bir yerden değer okumaktan çok daha uzun sürüyor. Özellikle her framede yapılacak milyonlarca sinüs işlemi varsa, örneğin bir kübü eğip bükecekseniz sinüs ve kosinüsler kodu oldukça yavaşlatıyorlar.

Ama sıklıkla sinüs hesaplamaktan kaçınmak için bazı yöntemler var. Örneğin bir şeyi sadece 10 derecelik açılarla döndürecekseniz sadece sin(10) ve cos(10) değerlerini kullanmanız yeterli. Bu değeri de FPU'ya her seferinde baştan hesaplatmamak için bir yere not edip sürekli oradan kullanabiliriz. İşimizi biraz daha kolaylaştırmak için de 10, 20, 30,... 360 olmak üzere 36 açının sinüs ve kosinüslerini bir tabloda tutabiliriz. Sadece bu açıları kullandığımız sürece hiçbir sorunumuz yok. İstediğimiz açının sinüsünü tablodaki değere bakarak anında elde edebiliriz.

Fakat her zaman işimiz bu kadar kolay olmayabilir. Örneğin yukarıdaki şekildeki gibi belli açıların değil de, tüm açıların sinüsüne ihtiyacımız olduğu zaman ne yapacağız? Sanırım bunun en kolay cevabı aradaki açılar için tabloda tuttuğumuz değerleri kullanmak. Değerini bilmediğimiz her açı için, ondan küçük ve değerini bildiğimiz ilk açıyı kullanmak ilk bakışta mantıklı gözüküyor. Örneğin her 1 derece için birer adet (yani toplamda 360 tane) sinüs ve kosinüs değerini Sin ve Cos adlı arraylerde tuttuğumuz zaman, Cos(14.5f) dediğimizde derleyici bunu float-long çevirimi ile 14 yapıp Cos(14) değerini kullanacak. Dahası
"#define Cos(a) Cos[(unsigned int)(a)%360]"
makrosu ile sadece 0 ve 360 arası değil, bunların ilerisindeki açıların sinüs ve kosinüsünü tablomuzdan çıkarabiliriz.

Fakat yukarıdaki şekilde görüldüğü gibi bu bize kesikli bir fonksiyon sağlayacak. Özellikle yavaş hareketlerde bu tabloyu kullanmak, göze bir takılma hissi verecek. Örneğin cisim yumuşak bir şekilde dönmek yerine ani atlamalarla ilerleyecek. Eğer amacımız sadece hız kazanmaksa ve yumuşaklığı kaybetmek umurumuzda değilse kullanabileceğimiz en iyi yöntem bu. Ama hem hız hem de yumuşaklık istiyorsak tablonun kullanımına ufak bir optimizasyon yapabiliriz.

İstediğimiz açının bir yanındaki tablo elemanını kullanmak yerine, her iki yanındaki bilgiyi de kullanabiliriz. Örneğin Cos(14.5) için Cos(14)+Cos(15)/2.0f demek, gerçeğine çok daha yakın sonuçlar verecekti. Yapmamız gereken tabloda iki değerin arasında sinüs ve kosinüs fonksiyonlarının doğrusal değiştiğini varsaymak. Bir açının sinüsü istendiğinde, bu açının tabloda bulunduğu aralıklardaki sınıra ne kadar yakın olduğuna bakıp ona göre bir değer hesaplamak.
Örneğin tablomuzda Sin(54) = 0.80902f ve Sin(55) = 0.81915f ve bulmak istediğimiz değer de Sin(54.7f) olsun. İlk yöntemimize göre şöyle bir sonuç alacaktık:
Sin(54.7f) = Sin(54) = 0.80902f
Eğer daha akıllı bir sistem geliştirip bir önceki değil de en yakındaki tablo değerine bakarsak:
Sin(54.7f) = Sin(55) = 0.81915f
Yeni yönteme göre ise bu iki değerden herhangi birini değil, bunların belli oranlarının toplamını kullanmamız gerekiyor. 54.7f derece, 54 dereceye 0.7f yakın, 55 dereceye ise 0.3f. Sınırların değerlerini uzaklıklarla ters orantılı olarak kullanırsak:
Sin(54.7f) = Sin(54) * 0.3f + Sin(55) * 0.7f
Sin(54.7f) = 0.80902f * 0.3f + 0.81915f * 0.7f
Sin(54.7f) = 0.81611f sonucunu elde ederiz. Sin(54.7f)'in gerçekteki değeri olan 0.81614f'e önceki ikisinden çok daha fazla yaklaştık.
Buradaki püf noktası, sinüs fonksiyonunun aslında kübik bir şekli olmasına rağmen, küçük aralıklarda baktığımızda fonksiyonda düze yakın çizgiler elde etmemiz. Evet şekil biraz bozulacak ama yumuşaklık kaybolmayacak ve hareket olarak sinüs uyguladığımızda akıcı görüntüler elde edeceğiz.
Şimdi de bu tekniği koda nasıl dökeceğimizi anlatalım. Önceki basit tablo yönteminde ufak bir makro yeterliyken, artık yeni bir fonksiyon tanımlıyorum:
float sine (float fAngle) { unsigned int iCeiling = (unsigned int)fAngle ; float fFraction = fAngle - iCeiling ;
return Sin(iCeiling % 360) * (1.0f - fFraction) + Sin(iCeiling + 1) % 360) * fFraction ; } iCeiling değeri, verilen açının hemen altındaki tamsayı. fFraction ise bu sayıyla arasındaki uzaklık, yani bir sonraki tamsayının katsayısı. Burada üstüne basmamız gereken şey negatif açılarda işlerin sarpa sardığı. Negatif % 360 işleminin sonucu negatif olacağı için tablo dışına taşacağız. Burada unsigned int kullanarak taşmayı engelledim ama negatif açılar için yanlış sonuçlar geliyor. (Aslında fonksiyonun negatifi geliyor.) Negatif açıları da kullanmak için gereken değişiklikleri size bırakıyorum.
Şimdi bir başka soru bu doğrusal tekniğinin daha da geliştirilip geliştirilemeyeceği olabilir. Aslında birinci dereceden (doğrusal) yerine ikinci dereceden (kuadratik) bir teknik mevcut. Sinüse çok daha yakın sonuçlar veriyor ama tabii ki daha yavaş. Bunu üçüncü, dördüncü diye götürmek mümkün. Hatta fonksiyon periyodik olduğu için (yani aslında 360 değil de sonsuz tane açının sinüsünü bildiğimiz için) tüm sinüsü verecek olan sonsuz dereceli bir polinom yazabiliriz. Ama o kadar yaklaşık değeri FPU bile vermiyor, o yüzden kasmanın anlamı yok. :)
Bu yöntem üzerinde birkaç iyileştirme de yapılabilir. Örneğin 360 derece yerine, 180 ya da 90 derecenin içindeki açıları tutabiliriz. Ya da hem kosinüs hem sinüs tablosu yerine, sadece birini tutup diğerini kaydırmayla elde edebiliriz. 360 tane açı yerine 256 ya da 1024 gibi yuvarlak bir sayı kullanıp modülasyon işlemini hızlandırabiliriz (kendimiz AND kullanarak tabii).
Aslında tablo tutmadan da sinüse yakın sonuçları hızlıca elde edebilirsiniz. Ama bu doğrusal birleştirme yönteminin güzelliği sadece trigonometrik ya da sadece periyodik fonksiyonlar değil de her şeye uygulanabilmesi. Örneğin bir tabloya attığınız splinelarla elde edilmiş kamera ve obje konumlarını, hatta kısa animasyonların framelerini bile yumuşak geçişlerle gösterebilirsiniz. Ya da bir objeyi diğerine dönüştürmek için kullanabilirsiniz. Aklınıza gelebilecek her türlü geçiş efekti ya da statik data kullanımı için işe yarayabilir.
Bu yazı burada bitiyor. Bir başka yazıya kadar bol prodlu günler dilerim.