Arduboyで狙い撃つぜ!(atan2テーブルの作り方) その5
今回もM-KAI氏のものを参考にした。
http://web.archive.org/web/20021220053234/http://www02.geocities.co.jp:80/SiliconValley-Bay/8572/algobib/lv029.htm
atanテーブルの検索ではなく、インデックスを求め、テーブルから一発で値を取得できる。
今までは、
x,yの比をテーブル化しておき、
1. x,yの比を求める
2. テーブルから求めた比に近い値を検索する
3. そのインデックスを返す
今回は、
インデックスをテーブル化しておき、
1. x,yの比を求める
2. 求めた比をインデックスにテーブルの値を返す
という流れになる。
atanテーブルを作成するのは、今まで通り。
import math for angle in range(16): r = (angle + 0.5) / 64 * 2 * math.pi x = math.cos(r) y = math.sin(r) print(str(y / x) + ',')
今までは、このatanテーブルをArduboyで使用していたが、
Arduboyで作成した関数と同等なものをPythonで作成する。
そして、xを64に固定して、(1周を64分割するため)
0〜64までyを変化させ、get_atan2からそれぞれの比のインデックスをテーブルとして出力する。
このインデックス値は0〜45度を表す、0〜8の値になる。
今までは、atanテーブルから近い値を探し、そのインデックスを返していたが、
予めインデックスのテーブルを作成しておき、x,yの比を添字にしてテーブルを参照するだけで、
sinテーブルを参照できるインデックスが手に入る、というものになる。
import math import sys atan_tbl = [ 0.049126849769467254, 0.14833598753834742, 0.25048696019130545, 0.3578057213145241, 0.4729647758913199, 0.5993769336819237, 0.7416505462720353, 0.9063471690191469, 1.1033299757334754, 1.3483439134867197, 1.668399205583507, 2.11432235754864, 2.7948127724904768, 3.9922237837700827, 6.7414524054149885, 20.35546762498714, ] def get_atan2(y, x): if x == 0: ratio = sys.float_info.max else: ratio = abs(y / x) idx = 0 for i in range(16): idx = i if ratio < atan_tbl[idx]: break if x < 0: idx = 32 - idx if y < 0: idx = 64 - idx return idx for y in range(64 + 1): print(str(get_atan2(y, 64)) + ', ', end='') if y % 16 == 15: print('')
x,yの絶対値で場合分けし、x,yの比が0〜1に収まるようにしておき、
それを64倍して今回作成したatanテーブルを参照する。
比が1になることがあるので、テーブルは65個必要ということ。
#include <Arduboy2.h> Arduboy2 arduboy; #define BOSS_SHOT 10 typedef struct CharaData { float x, y; float vx, vy; int8_t life; }; CharaData player; CharaData boss; CharaData bossShot[BOSS_SHOT]; uint16_t counter; const float sinTbl[] PROGMEM = { 0.0, 0.0980171403295606, 0.19509032201612825, 0.29028467725446233, 0.3826834323650898, 0.47139673682599764, 0.5555702330196022, 0.6343932841636455, 0.7071067811865475, 0.7730104533627369, 0.8314696123025451, 0.8819212643483549, 0.9238795325112867, 0.9569403357322089, 0.9807852804032304, 0.9951847266721968, }; float getSin(int16_t angle) { float result = 1.f; uint8_t idx = angle & 0x1f; if (idx < 16) { result = pgm_read_float(sinTbl + idx); } else if (idx > 16) { result = pgm_read_float(sinTbl + 32 - idx); } return (angle & 0x3f) > 32 ? -result : result; } float getCos(int16_t angle) { return getSin(16 + angle); } const uint8_t atanTbl[] PROGMEM = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, }; uint8_t getAtan2(float y, float x) { int8_t idx = 0; // 0度 if (x == 0.f && y == 0.f) return idx; // 同じ位置の時は0度とみなす float ax = abs(x); float ay = abs(y); if (ax > ay) { uint8_t ratio = ay / ax * 64; idx = pgm_read_byte(atanTbl + ratio); } else { uint8_t ratio = ax / ay * 64; idx = 16 - pgm_read_byte(atanTbl + ratio); } // idxは第1象限の値なので、実際の象限の値に調整する if (x < 0) idx = 32 - idx; if (y < 0) idx = 64 - idx; return idx; } void setup() { arduboy.begin(); player.x = 20; player.y = 64 / 2; boss.x = 128 / 2; boss.y = 64 / 2; } void loop() { if (!arduboy.nextFrame()) return; arduboy.clear(); if (arduboy.pressed(LEFT_BUTTON)) { player.x -= 1.f; } else if (arduboy.pressed(RIGHT_BUTTON)) { player.x += 1.f; } if (arduboy.pressed(UP_BUTTON)) { player.y -= 1.f; } else if (arduboy.pressed(DOWN_BUTTON)) { player.y += 1.f; } arduboy.fillRect(player.x - 8.f, player.y - 4.f, 16, 8); arduboy.drawTriangle(boss.x, boss.y - 8.f, boss.x - 8.f, boss.y + 8.f, boss.x + 8.f, boss.y + 8.f); if (++counter % 10 == 0) { for (int8_t i = 0; i < BOSS_SHOT; ++i) { CharaData *p = &bossShot[i]; if (p->life == 0) { p->life = 1; p->x = boss.x; p->y = boss.y + 2.f; // プレイヤの方向を求める uint8_t r = getAtan2(player.y - p->y, player.x - p->x); // 弾の移動方向を求める p->vx = getCos(r); p->vy = getSin(r); break; } } } for (int8_t i = 0; i < BOSS_SHOT; ++i) { CharaData *p = &bossShot[i]; if (p->life > 0) { p->x += p->vx; p->y += p->vy; if (p->x < 0.f || 128.f < p->x || p->y < 0.f || 64.f < p->y) { p->life = 0; } arduboy.fillCircle(p->x, p->y, 2); } } arduboy.display(); }
今回の方法で、テーブルから一発で値を取得できるようになり、
テーブルを検索していた頃に比べると、高速化ができた。
しかし、一つ気になる箇所がある。
if (ax > ay) { uint8_t ratio = ay / ax * 64; ... } else { uint8_t ratio = ax / ay * 64; ... }
x,yの比を求めるために割り算している点だ。
割り算は重いので、十分な高速化ができていないように思われる。