Macierze jako przekształcenia i krzywe Béziera

Wyświetlanie wektorów: vector_shower.py

Zanim zaczniemy przekształcać kształty, musimy umieć je wyświetlić. Do tego posłuży nam biblioteka vector_shower.py (i towarzyszący jej program w C++). Archiwum z biblioteką, potrzebnym do niej programem i jego kodem źródłowym możesz pobrać stąd.

Ważne

Rozpakuj pobranego zipa do tego samego folderu, w którym będziesz pisać programy na dzisiejszych zajęciach. W folderze z programami z dzisiejszych zajęć będą potrzebne:

  • folder vector_shower rospakowany z zipa,

  • plik matrices.py z implementacją klasy macierzy.

Biblioteka ta posiada dwie główne funkcje:

  • show_vector(vec, color) — rysuje pojedynczy wektor (punkt) na ekranie.

  • show_vector_cloud(vectors, color) — koloruje piksele tła zależnie od tego, jakie wektory znajdują się na obszarze ekranu pod tym pikselem. „Chmura” jest opisana za pomocą listy wektorów.

  • Tych funkcji możesz używać zarówno ze skryptu, jak i z trybu interaktywnego.

  • Kolejne wywołania będą rysować na tym samym obrazku; zamnkięcie okna spowoduje, że kolejne polecenia rysowania będą zaczynać od czystego obrazka.

Przykład użycia

Informacja

Biblioteka vector_shower odczytuje współrzędne z wektorów na dowolny z poniższych sposobów - wystarczy, żeby tylko jeden z nich był dostępny w klasie wektorów:

  1. x=v.x, y=v.y

  2. x=v[0], y=v[1] - dzięki temu można używać list jako wektorów.

  3. x=[0, 0], y=[1, 0]

  4. x=v.get(0,0), y=v.get(1,0) - z tego będziemy korzystać.

Po uruchomieniu okna z wizualizacją możesz z nim wchodzić w interakcję:

  • Strzałki — przesuwanie widoku.

  • Klawisze + / - — przybliżanie i oddalanie (zoom); przybliżanie powoduje, że pojedynczy piksel tła pokrywa coraz mniejszy obszar na ekranie, dlatego „chmury” wektorów tworzące spójny kształt przy większym oddaleniu, po przybliżeniu zaczną mieć widoczne dziury.

W parametrze color możesz podawać nazwy kolorów (np. "red", "blue") lub ich kody HTML (np. "#ff0000" dla czerwonego, "#123456" dla ciemnego niebieskiego).

Macierze jako wektory

Dzisiaj zamiast korzystać z klasy Vector2D tego, będziemy używać klasy do macierzy o wymiarach \(2 \times 1\). Dzięki temu będziemy mogli łatwo opisywać przekształcenia liniowe tych wektorów jako \(v' = Mv\).

Pomocna będzie też funkcja tworzenia, dodawania i mnożenia macierzy-wektorów:

def vec2d(x, y):
    v = Matrix(2, 1)
    v.set(0, 0, x)
    v.set(1, 0, y)
    return v

def vmul(a, v):
    return vec2d(a * v.get(0, 0), a * v.get(1, 0))

def vadd(v1, v2):
    return vec2d(v1.get(0, 0) + v2.get(0, 0), v1.get(1, 0) + v2.get(1, 0))

Budowanie kształtów z wektorów

Poniższy przykładowy kod wyświetla kwadrat z narożnikami:

from vector_shower import show_vector, show_vector_cloud
from matrices import Matrix

def vec2d(x, y):
    m = Matrix(2, 1)
    m.set(0, 0, x)
    m.set(1, 0, y)
    return m

def vmul(a, v):
    return vec2d(a * v.get(0, 0), a * v.get(1, 0))

def vadd(v1, v2):
    return vec2d(v1.get(0, 0) + v2.get(0, 0), v1.get(1, 0) + v2.get(1, 0))

unit_square = []
x = 0
while x <= 1:
    y = 0
    while y <= 1:
        unit_square.append(vec2d(x, y))
        y += 0.01
    x += 0.01
show_vector_cloud(unit_square, "#f00")
show_vector(vec2d(0,0))
show_vector(vec2d(1,0))
show_vector(vec2d(0,1))
show_vector(vec2d(1,1))

shift = vec2d(0.6, 0)
i = 0
while i < len(unit_square):
    unit_square[i] = vadd(shift, unit_square[i])
    i += 1
show_vector_cloud(unit_square, "#0f0")

shift = vec2d(-0.3, 0.6)
i = 0
while i < len(unit_square):
    unit_square[i] = vadd(shift, unit_square[i])
    i += 1
show_vector_cloud(unit_square, "#00f")

Przykład użycia

Mnożenie macierzy

Aby pomnożyć dwie macierze \(A\) i \(B\), element w \(i\)-tym wierszu i \(j\)-tej kolumnie macierzy wynikowej \(C\) obliczamy jako sumę iloczynów elementów z \(i\)-tego wiersza macierzy \(A\) i \(j\)-tej kolumny macierzy \(B\):

\[c_{i\,j} = a_{i\,1} b_{1\,j} + a_{i\,1} b_{1\,j} + \dots + a_{i\,1} b_{1\,j}\]

Przykład:

\[\begin{split}\begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} \cdot \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix} = \begin{pmatrix} 1\cdot 5 + 2\cdot 7 & 1\cdot 6 + 2\cdot 8 \\ 3\cdot 5 + 4\cdot 7 & 3\cdot 6 + 4\cdot 8 \end{pmatrix} = \begin{pmatrix} 19 & 22 \\ 43 & 50 \end{pmatrix}\end{split}\]

Przekształcanie wektorów

Macierz obrotu o kąt \(\alpha\) (w kierunku przeciwnym do ruchu wskazówek zegara) ma postać:

\[\begin{split}R = \begin{pmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{pmatrix}\end{split}\]

Składanie przekształceń

To zadanie przypomina, że składanie przekształceń nie jest przemienne - ważna jest kolejność.

Tak życiowo możemy to zilustrować następująco - obracanie działa jak zegar (zawsze przeciwnie do ruchu jego wskazówek), ale kiedy patrzymy na zegar w lustrze (odbiciu), to wskazówki ruszają się w złą stronę. Tak więc obrót następujący po odbiciu symetrycznym obraca w inną stronę, niż normalnie.

Krzywe Béziera i algorytm De Casteljau

Algorytm De Casteljau pozwala wyznaczyć punkt na krzywej dla parametru \(t \in [0, 1]\) poprzez wielokrotne wyznaczanie punktów pośrednich na odcinkach — podobnie do rysowania odcinka, ale obliczanie pojedynczego punktu jest bardziej skomplikowane:

  1. Masz listę punktów kontrolnych \([P_0, P_1, \dots, P_n]\).

  2. Jeśli na liście jest tylko jeden punkt, to jest on wynikiem.

  3. Jeśli jest więcej punktów, utwórz nową listę o jeden krótszą: \(Q_i = (1-t)P_i + tP_{i+1}\).

  4. Powtórz algorytm dla nowej listy punktów.

Zadania domowe

Chmury i przekształcenia

Krzywe