Grafikus alkalmazásoknál alapvető funkció egy pont adott szöggel való elforgatása, az origó vagy egy megadott forgáspont körül. A forgatás megvalósítását egy olyan függvénnyel oldhatjuk meg, amelynek argumentumai a forgatandó pont, a forgáspont és az elforgatás szöge, visszatérési értéke pedig az elforgatott pont.
A kérdés, hogy a függvény törzsében milyen képletet megvalósító kód kerül. Ennek megválaszolásához némi matematikai ismeret szükséges. Általában a vektor-, illetve koordinátageometrián alapuló levezetés a szokásos, amelyből kiadódnak a forgatott pont koordinátáit meghatározó egyenletek, vagy ha mátrixalgebrai alakban írjuk ezeket fel, akkor megkapjuk a forgatási mátrixot. Ezek levezetése látható alább vektorábrával szemléltetve a forgatási eseteket.


Más módon is meghatározhatjuk a forgatott pontot, mégpedig a koordinátageometriával szoros kapcsolatba hozható komplex számokkal. Ennek levezetése a következő:

Itt két végképletet is láthatunk attól függően, hogy a képletben szereplő forgatási tényezőt exponenciális alakban, vagy trigonometrikus alakban fejezzük ki. Bár matematikailag ezek ekvivalensek, de kódolás szempontjából van különbség, mert míg az előbbihez a Python szabványos könyvtárából a cmath modul szükséges, addig az utóbbihoz a math modul.
A fentiek alapján tehát három különböző logikára, illetve képletre épülő függvényt tudunk definiálni a forgatott pont meghatározására. Ezek definícióit láthatjuk alább, ahol az egyes függvények törzsében szereplő kódok követik a levezetésekben kapott képleteket, ezért a működés bővebb magyarázatot nem igényel.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from collections import namedtuple from math import cos, sin, radians import cmath Point = namedtuple('Point', 'x y') def rotate_point_geom(point: Point, cr: Point, alpha): # Origóba tolás x, y = point.x - cr.x, point.y - cr.y # Origó körül forgatás xr0 = x * cos(alpha) - y * sin(alpha) yr0 = x * sin(alpha) + y * cos(alpha) # Visszatolás xr, yr = xr0 + cr.x, yr0 + cr.y return Point(xr, yr) def rotate_point_complex_exp(point: Point, cr: Point, alpha): p, cr = complex(*point), complex(*cr) pr = (p - cr) * cmath.exp(complex(0, alpha)) + cr return Point(pr.real, pr.imag) def rotate_point_complex_trig(point: Point, cr: Point, alpha): p, cr, j = complex(*point), complex(*cr), complex(0, 1) pr = (p - cr) * (cos(alpha) + sin(alpha) * j) + cr return Point(pr.real, pr.imag) class Rotator: def __init__(self, rotation_strategy): self.rotation_strategy = rotation_strategy def set_rotation_strategy(self, rotation_strategy): self.rotation_strategy = rotation_strategy def rotate_point(self, point_to_rotate: Point, center_of_rotation: Point, alpha: 'radians'): """Egy megadott pont, adott forgáspont körüli alpha szöggel történő elforgatott megfelelőjével tér vissza. A forgatás algoritmusát a hívás előtt a példányosításkor vagy be kell állítani. """ return self.rotation_strategy(point_to_rotate, center_of_rotation, alpha) |
A tényleges pontforgatás elvégzésére egy külön objektumot hozunk létre, amelynek Rotator nevű osztályának definíciója szintén az előbbi kódban látható. Ennek az az értelme, hogy a három, forgatott pontot meghatározó függvény közül bármelyiket alkalmazhatjuk, akár futási időben váltakozva úgy, hogy a kliens kódot ez nem érinti, mert ott mindig ugyanazt a nevű metódust kell meghívni. Ez a kialakítás a stratégia tervezési mintát (strategy design pattern) követi annyiban eltérve a klasszikus felépítéstől, hogy kihasználjuk azt, hogy a Pythonban a függvények objektumok, ami lehetővé teszi, hogy argumentumként átadhatók legyenek. Így az eredeti klasszikus stratégia mintában szereplő, az egyes algoritmusokra, stratégiákra vonatkozó osztályok létrehozása nem szükséges.
A következő néhány tesztesetben különböző megvalósítású forgatási algoritmusokkal használjuk a Rotator példányt. Nem meglepő módon mindegyik azonos eredményt produkál.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# TESZT rotation_data = [(Point(1, 1), Point(0, 0), radians(45)), (Point(1, 1), Point(0, 0), radians(-45)), (Point(2, 0), Point(1, 1), radians(90)), (Point(3, 7), Point(-1, -2), radians(30))] rotator = Rotator(rotate_point_geom) for rotation_strategy in (rotate_point_geom, rotate_point_complex_exp, rotate_point_complex_trig): rotator.set_rotation_strategy(rotation_strategy) print('\n{}'.format(rotation_strategy.__name__)) for args in rotation_data: print(rotator.rotate_point(*args)) # Eredmények: # rotate_point_geom # Point(x=0.0, y=1.4142135623730951) # Point(x=1.4142135623730951, y=0.0) # Point(x=2.0, y=2.0) # Point(x=-2.0358983848622443, y=7.794228634059948) # # rotate_point_complex_exp # Point(x=0.0, y=1.4142135623730951) # Point(x=1.4142135623730951, y=0.0) # Point(x=2.0, y=2.0) # Point(x=-2.0358983848622443, y=7.794228634059948) # # rotate_point_complex_trig # Point(x=0.0, y=1.4142135623730951) # Point(x=1.4142135623730951, y=0.0) # Point(x=2.0, y=2.0) # Point(x=-2.0358983848622443, y=7.794228634059948) |
E bejegyzés témájához a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak: az „Egymáshoz rendelve – függvények” fejezet „A függvény is objektum” alfejezete, valamint a „Készétel fogyasztás – a szabványos könyvtár moduljainak használata” fejezetben a „Speciális konténer típusok” alfejezet „Mezőneves tuple – namedtuple” cím, továbbá a „Matematikai számítások támogatása” című alfejezet „Valós és komplex változós függvények” címe, amelyben a math és cmath modulok leírása és alkalmazása olvasható.