Asteroiden und Schiff in meinem MMBasic-Spiel sind Polygone, die jeden Frame um einen kleinen Winkel weitergedreht werden. Mein erster Ansatz war der naheliegende: pro Eckpunkt einmal COS und SIN ausrechnen und manuell rotieren. Das funktioniert, frisst aber Frame-Zeit, sobald viele Objekte auf dem Schirm sind. Mit der MATH-Familie von MMBasic lässt sich das deutlich kürzer und schneller fassen.
Der naive Weg
So sah meine erste Variante aus, eine FOR-Schleife pro Asteroid mit eigenen cos_rot und sin_rot:
SUB DrawAsteroid_Old(idx)
LOCAL INTEGER i
LOCAL FLOAT cos_rot, sin_rot
LOCAL INTEGER temp_x(15), temp_y(15)
cos_rot = COS(RAD(arot(idx)))
sin_rot = SIN(RAD(arot(idx)))
FOR i = 0 TO apoints(idx) - 1
temp_x(i) = ax(idx) + (avertices(idx, i, 0) * cos_rot - avertices(idx, i, 1) * sin_rot)
temp_y(i) = ay(idx) + (avertices(idx, i, 0) * sin_rot + avertices(idx, i, 1) * cos_rot)
NEXT i
END SUB
Korrekt, lesbar, und in jeder Iteration rechnet MMBasic vier Multiplikationen plus zwei Additionen pro Eckpunkt. Bei 40 Asteroiden mit 8–12 Ecken kommt da was zusammen.
Mit MATH V_ROTATE und MATH SLICE
In den MATH-Befehlen von MMBasic steckt deutlich mehr als die meisten Tutorials zeigen. Ich speichere die Vertices nicht in einem 3D-Array, sondern in zwei 2D-Arrays für x und y. Dann ziehe ich mit MATH SLICE die Zeile für einen Asteroiden raus, drehe sie mit MATH V_ROTATE und addiere die Position dazu.
DIM FLOAT vert_x(MAX_AST-1, 15), vert_y(MAX_AST-1, 15)
DIM FLOAT src_x(15), src_y(15), tmp_x(15), tmp_y(15)
SUB DrawAsteroid_New(idx)
MATH SLICE vert_x(), idx, , src_x()
MATH SLICE vert_y(), idx, , src_y()
MATH V_ROTATE 0, 0, RAD(arot(idx)), src_x(), src_y(), tmp_x(), tmp_y()
MATH ADD tmp_x(), ax(idx), tmp_x()
MATH ADD tmp_y(), ay(idx), tmp_y()
POLYGON apoints(idx), tmp_x(), tmp_y(), RGB(WHITE)
END SUB
Was gleich bleibt: der Datenfluss. Was sich ändert: die Rotation läuft nicht mehr Eckpunkt für Eckpunkt durch BASIC-Code, sondern als ein einziger MATH V_ROTATE-Aufruf intern. MATH ADD mit Skalar verschiebt das ganze Array auf einen Schlag. Der Aufwand pro Asteroid sind ein paar Zeilen, die Schleife verschwindet.
Ich habe beide Varianten gegeneinander laufen lassen, mit 40 Asteroiden zu je 10 Vertices über 1000 Iterationen. Die MATH-Variante lag bei mir deutlich vorn, das genaue Verhältnis hängt vom Gerät und der Vertex-Zahl ab. Voraussetzung war ein Refactor von einem 3D-Vertex-Array (idx, vertex, xy) auf zwei 2D-Arrays für x und y, sonst kommt MATH SLICE nicht an die Zeile. Seitdem schaue ich vor jeder Inner-Loop in der Geometrie zuerst, ob MATH V_ROTATE, MATH ADD, MATH SCALE, MATH SLICE oder MATH SET schon das passende Werkzeug mitbringen, und meistens tun sie das.