Zasada działania programów do rozwijania siatek brył przestrzennych
Zasada działania programów do rozwijania siatek brył przestrzennych
W dziale Download znajdują się programy Siatki i Kreski, ułatwiające rozwinięcia brył przestrzennych. Po podaniu wymiarów np. ostrosłupa, program tworzy siatkę bryły oraz umożliwia jej wydrukowanie na brystolu i sklejenie. Zaciekawiło mnie, jak taki program działa - krótko mówiąc: jak on to robi?. Trochę pokombinowałem sam, jak również postanowiłem zapytać u źródła i napisałem list do autora Kresek - Tomka Terleckiego - który wykazał życzliwość i umożliwił mi wgląd we "wnętrze" swojego programu, czyli tzw. kod źródłowy.
Program napisany jest w języku Borland Delphi i jego listing liczy kilkadziesiąt stron. Programowaniem zajmuję się tylko amatorsko i okazyjnie, zatem przedarcie się przez listing i zrozumienie "co autor miał na myśli" (pomimo licznych komentarzy) nie było łatwe. W końcu się udało: ogarnąłem całość.
W skrócie: dla danej bryły program posiada odpowiedni algorytm, który z podanych wymiarów podstawowych wylicza dodatkowe długości i kąty. Następnie przelicza wymiary na piksele i rysuje rozwinięcie. Robi to w specjalny, uniwersalny dla danej bryły sposób. Ostrosłup może mieć zarówno 4 jak i 12 ścian, więc musi to być metoda, z jednej strony konkretna dla ostrosłupa ale też nie ograniczająca sztywno liczby ścian bocznych Ponieważ okazało się, że takich metod rysowania (algorytmów) można wymyślić wiele, postanowiłem napisać swoją wersję programu, który ochrzciłem "Lump" (po angielsku znaczy to bryła, grudka).
Przy okazji pisania programu powstawał poniższy tekst, wyjaśniający zasadę działania użytych algorytmów. Pisałem go z myślą o osobach, które o programowaniu nie wiedzą nic, dlatego znajdzie się tu kilka krótkich wyjaśnień elementarnych spraw związanych z pisaniem programów. Prezentowane "listingi" nie są napisane w Delphi, lecz przerobione na prostszą postać, umożliwiającą samo zrozumienie działania danego algorytmu. Jednak same metody są w 100% zgodne z użytymi w moim programie, który można znaleźć w dziale Download albo na http://lump.republika.pl/. Zatem: jak to jest zrobione?
Jak komputer widzi rysunek.
Każdy w szkole spotkał się z układem współrzędnych XY. Na takim układzie można zaznaczyć kropką punkt P, znając jego współrzędne x i y. Jednak w tradycyjnym układzie liczby x i y mogą być dodatnie albo ujemne oraz mieć wartości ułamkowe, np. x=3,26 a y=-5,67. Programy posługują się podobnym układem, z tym że współrzędne mogą przybierać jedynie wartości całkowite i dodatnie. Operują więc tylko na jednej ćwiartce tego układu, gdzie x i y są dodatnie. Ćwiartka taka ma również takie ograniczenie, że nie jest nieskończenie wielka - programista musi określić z góry jej wymiary. "Jednostką długości" jest tzw. piksel.
rys.1
Zatem przed rysowaniem ustala się rozmiar "kartki", czyli jej rozmiar w pikselach oraz każe im przybrać np. kolor biały. Rysowanie na takiej wirtualnej kartce polega na zaczernianiu pikseli.
rys.2
Jaka jest długość piksela? Nie ma jej z góry narzuconej. Pisząc program sami decydujemy, jakie są jego wymiary. Na przykład ustalamy sobie, że 1 piksel będzie miał "długość" 0,1 mm. Jeśli więc na rysunku ustawimy obok siebie 100 pikseli, to będzie to odcinek o długości 10 mm. "Rysunek" dla komputera jest więc tabelką o wyznaczonej z góry liczbie kolumn i wierszy, gdzie jedna komórka to piksel.
Uwaga: z reguły w programach oś Y ma wartości dodatnie "w dół", czyli początek układu umieszczony jest w lewym górnym rogu ekranu a nie w dolnym. Jednak dla przejrzystości będę używał współrzędnych jak przy klasycznym układzie współrzędnych w I ćwiartce.
Pierwszy program.
Przy programowaniu używa się wielu poleceń, funkcji itp. Jednak nam do rysowania wystarczy znajomość tylko dwóch "rozkazów": 1. "Zaczernij piksel o współrzędnych x, y" - nazwijmy je roboczo Punkt(x,y). Wydanie polecenia Punkt(23, 67) zaczerni piksel o tych współrzędnych 2. "Narysuj linię do piksela x, y" - LiniaDo(x,y). Początkiem linii jest ostatni zaczerniony piksel albo koniec ostatniej linii. Wydanie komendy LiniaDo(123,48) spowoduje pojawienie się linii od ostatnio zaczernionego punktu, do punktu P(123,48).
Działanie programu przypomina sterowanie ręką trzymającą ołówek, poruszającą się nad kartką papieru: przesuń się tu czy tam i dziabnij ołówkiem w punkt. Albo: dziabnij, przyciśnij i rysuj do innego miejsca. I to jest cała wiedza potrzebna do tego, żeby rysować po wyznaczonym obszarze.
Oto przykładowy program w roboczym języku, którego efektem będzie rysunek prostej choinki (komendy wykonywane są po kolei).
rys.3
Zatem zadanie polega więc na wyznaczeniu punktów i sensownym ich połączeniu. Sensownym, bo program zawierającym błąd da np. taki efekt:
rys.4
W rzeczywistości istnieje wiele poleceń ułatwiających życie programiście, rysujących od razu gotowe prostokąty czy kółka, ale moje algorytmy generalnie wykorzystują tylko te dwa polecenia. Podsumowując: aby narysować cokolwiek należy znać współrzędne pikseli, z sensem je zaczernić czy kolejno połączyć. Kluczową sprawą jest więc sposób wyznaczania współrzędnych punktów oraz kolejności wykonywania połączeń między nimi.
Rozwijamy pierwszą bryłę - walec.
Program ma narysować rozwinięcie powierzchni bocznej walca o średnicy 32 mm i wysokości 54 mm. Rozwinięcie walca jest prostokątem: jeden wymiar to wysokość a drugi to obwód podstawy: -wysokość= 54 mm -obwód=PI*32mm=100,48 mm Zadanie to sprowadza się więc do narysowania prostokąta. Program przelicza te wymiary na pikselową długość (1 piksel=0,1 mm). Daje to wymiary 1005x540 pikseli. Jak widać dokładność rysowania zależy od ustalonej liczby pikseli na 1 mm. Im więcej, tym rysunek bardziej dokładny. Program jest zatem dziecinnie prosty:
rys.5
Podany poprzednio przykład ma ten feler, że rozwija tylko jeden walec o konkretnych wymiarach. Program ma być uniwersalny i umożliwić zrobienie tego dla różnych wysokości i średnic. Zmodyfikujemy go tak, aby był w stanie rozwinąć każdy walec. Użytkownik wprowadza do programu dane o wysokości i średnicy. Ponieważ nie wiemy jakie to będą liczby, muszą one zostać zapamiętane. Jak program je zapamiętuje? Służą do tego celu odpowiednie miejsca w pamięci zwane zmiennymi. Posiadają one dowolną nazwę i w programie operuje się tylko na niej. Np. komórka o nazwie Wysokosc przechowuje wartość wysokości walca. Jeśli ta definicja zbyt mało to wyjaśnia, to na końcu artykułu znajduje się Dodatek A poświęcony zmiennym i sposobom ich stosowania.
Użytkownik wprowadził więc swoje dane i program zapamiętał je w zmiennych: Wysokosc oraz Srednica. Przeliczmy je na piksele. Ponieważ rozdzielczość wynosi 1 piksel na 0,1 mm zapamiętamy ją też jako zmienną: Rozdz:=0,1 Zmienne "spikselowane" oznaczymy jako WysP oraz SzerP , aby nie pisać zbyt długich nazw (od razu obliczymy obwód walca, przyjmując że program zna wartość zmiennej "Pi"): WysP:=Wysokosc/Rozdz SzerP:=Pi*Srednica/Rozdz
Ważną rzeczą w programie są komentarze, które są ignorowane przez komputer ale programiście ułatwiają zapamiętanie co dany fragment robi. Przed komentarzem stawiamy znak //. Ustalamy, że rysowanie zaczniemy tym razem od punktu w lewym dolnym rogu obszaru rysunku. Oto wartości początkowe: Xp:=50 // Xp - współrzędna x początkowa Yp:=50 // Yp - współrzędna y początkowa
Ustaliliśmy na początku programu wartości trzech zmiennych: Rozdz, Xp, Yp. Ma to ogromną zaletę: jeśli zajdzie potrzeba zmiany rozdzielczości albo punktu początkowego, to wystarczy to zmienić tylko w tym miejscu programu. Cała reszta będzie operowała na zmiennych wyrażonych przez nazwę. Do narysowania uniwersalnego prostokąta potrzebujemy jedynie znajomości 4 punktów. Oto one:
rys.6
A oto nasz cały uniwersalny program, który narysuje rozwinięcie każdego walca (tym razem już bez numeracji linii):
rys.7
Graniastosłup foremny.
Wielkości niezbędne do wykonania rozwinięcia takiej bryły są trzy: wysokość, szerokość ściany bocznej oraz liczba boków:
rys.8
Ściana boczna graniastosłupa to prostokąt. Rozwinięcie składa się więc z tylu prostokątów połączonych bokami, ile ścian liczy bryła. Nasz program musi być uniwersalny, więc nie można na sztywno założyć ile prostokątów musi narysować. Wiemy jednak, że ma ich być "ileś tam" - wszystkie identyczne. Wystarczy więc ułożyć algorytm rysowania jednego prostokąta i potem wykonać go tyle razy, ile jest boków graniastosłupa. Oczywiście pilnując aby pojawiały się one w odpowiednich miejscach rysunku. Powtarzanie jakiegoś kawałka algorytmu kilka razy, najlepiej zrobić w tzw. pętli programowej. Pętla programowa w naszym roboczym języku, będzie się zaczynała poleceniem Powtorz od i:=k do n , zaś powtarzany fragment programu znajdzie się w nawiasach {...}. Jeśli pojęcie "pętla" nie mówi Ci zbyt wiele, to zapraszam do Dodatku B, na końcu artykułu.
Zacznijmy od prostszego, czyli rozwinięcia ścian bocznych. Składa się ono z szeregu prostokątów o wymiarach zależnych od wysokości i szerokości ściany. Można jednak nie rysować całych prostokątów, lecz tylko fragmenty w kształcie litery C:
rys.9
Do narysowania tych powtarzających się fragmentów wykorzystamy w programie pętlę. Liczba powtórzeń będzie zależna od wczytanej liczby boków LBok. Oto kawałek programu:
rys.10
Ściany boczne mamy załatwione: łatwizna :)
Podstawy są dwie takie same i są to wieloboki foremne, czyli każdy bok ma taką samą szerokość. Algorytm musi być uniwersalny i mieć możliwość narysowania wieloboku o dowolnej liczbie ścian, podanych przez użytkownika. Ciekawe, że łatwiejsza część programu, rysująca w pętli ściany boczne liczyła 4 wiersze poleceń. Część trudniejsza, rysująca wielobok liczy zaś ... tylko jeden wiersz.
Bardziej mozolna natomiast jest droga prowadząca do tej jednej linijki.
Przy rysowaniu wieloboku foremnego wykorzystamy 2 fakty: 1. Można go wpisać w okrąg, tzn. każdy wierzchołek leży na jego obwodzie. Do wykreślenia potrzebny będzie promień takiego okręgu. 2. Prowadząc linie od środka okręgu do wierzchołków otrzymuje się trójkąty równoboczne. Trójkątów tych jest tyle ile, boków ma graniastosłup a suma kątów ich wierzchołków wynosi 360 stopni Można więc łatwo wyznaczyć kąt Beta wierzchołka tych trójkątów oraz znaleźć promień okręgu R:
rys.11
Narysowanie dowolnego wieloboku jest więc proste: obieramy na okręgu jakiś punkt początkowy P1 i obracamy go - niczym wskazówki zegara - o wielokrotności kąta Beta: Przy pomocy cyrkla i kątomierza można łatwo narysować na kartce dowolny wielobok foremny. Trzeba tylko obliczyć wcześniej promień i kąt wierzchołka trójkąta.
rys.12
Program jednak nie dysponuje cyrklem i trzeba to przerobić na współrzędne punktów:
rys.13
Punkty "ślizgają" się po okręgu i łatwo wyznaczyć ich współrzędne wstawiając do wzorów wielokrotności kąta Beta:
rys.14
Wiadomo już, jak narysować wielobok o środku w początku układu współrzędnych. Program może jednak operować tylko na współrzędnych dodatnich, trzeba więc przesunąć figurę do I ćwiartki:
rys.15
Taki elaborat, żeby wyjaśnić sens jednej linijki programu :))
rys.16
Ostrosłup foremny.
Samo rozwinięcie jest proste co do zasady: ściany boczne są trójkątami stykającymi się bokami, a podstawą jest wielokąt foremny znany z poprzedniego odcinka. Podstawę więc pominę, skupiając się tylko na bokach:
rys.17
Po wyprowadzeniu:
rys.18
Uwaga: arcsin (czytaj: arkus sinus), to jest to funkcja odwrotna do sinusa. Idea tej odwrotności jest prosta: sam sinus mówi jaki jest stosunek boków przy danym kącie. Arcus zaś zwraca kąt przy danym stosunku boków (Alfa na rysunku poniżej jest kątem "ogólnym", nie z poprzedniego rysunku. Drobna niefortunność):
rys.19
Samo rozwinięcie można zrobić niemal identycznie, jak wyrysowanie wieloboku. Jedyna różnica jest taka, że kąt którym "obracamy" punkt nie dojdzie do wartości 360 stopni:
rys.20
Pozostało napisać kawałek programu:
rys.21
Ostrosłup ścięty płasko
Choć czubek będzie ścięty, to nadal będzie on bardzo potrzebny. Można powiedzieć, że aby rozwinąć ostrosłup ścięty, trzeba mu czubek założyć z powrotem. Kolejne obrazki wyjaśnią wszystko:
rys.22
rys.23
rys.24
Rysowanie jednej ściany bocznej:
rys.25
Gotowy fragment programu:
rys.26
O rozwijaniu podstawy i powierzchni górnej nie ma co pisać, bo wiadomo co i jak z wcześniejszych rozważań. Algorytm ten zawiera w sobie w pewnym sensie poprzedni. Jeśli przyjmie się szerokość ściany górnej równą zero, to powstanie rozwinięcie zwykłego ostrosłupa ze szpicem. Tak jest przyjęte w moim programie Lump.
Stożek
Na stożek można popatrzeć, jak na ostrosłup który ma baaaardzo wiele ścian bocznych (rysunki ostrosłupów pochodzą z programu Siatki 4):
rys.27
A oto dane stożka i wartości z nich obliczone:
rys.28
rys.29
Przyjęcie 1/5 długości piksela ma na celu jedynie to, aby w obwodzie nie powstała dziura. Program będzie więc czasami kilkukrotnie "dziobał" w ten sam punkt i go zaczerniał. Na szczęście piksele zaczerniane kilkukrotnie nie "rozlewają" się na boki niczym atrament z pióra, więc można to robić bezkarnie. Oczywiście wydłuża to czas rysowania, ale przy dzisiejszej szybkości komputerów...
rys.30
Podsumowanie
W podobny sposób można rozwijać bryły bardziej skomplikowane, np. ostrosłupy czy stożki ścięte ukośnie. Zasada działania będzie taka sama, natomiast same algorytmy i wyliczenia bardziej skomplikowane. Celem tego artykułu było jedynie wyjaśnienie ogólnej zasady działania programu do rozwijania siatek brył, na przykładzie kilku brył podstawowych - mam nadzieję, że powyższy opis to umożliwił. Oczywiście w rzeczywistym programie muszą być dodatkowe elementy umożliwiające wprowadzanie danych, sprawdzające ich poprawność, dbające o odpowiednie rozmieszczenie rysunku na kartce, możliwość zapisania go na dysku w formie pliku graficznego itp. To sprawia, że napisanie tego typu programu nie jest sprawą prostą i wymaga poświęcenia ogromnej ilości czasu na wymyślenie algorytmów, zapisanie ich w postaci kodu i wreszcie testowanie. Na zakończenie dziękuję autorowi programu Kreski - Tomkowi Terleckiemu - za życzliwe podejście i udostępnienie kodu źródłowego swojego programu. Zachęcam także do wypróbowania mojego programiku Lump, w którym użyte zostały dokładnie takie same algorytmy, jak opisane w artykule.
Dodatek A - zmienne
rys.31
Dodatek B - pętla programowa
Kilka słów wyjaśnienia "w pigułce" znajduje się poniżej.
rys.32
Uzbrojeni w pętlę możemy przystąpić do jej wykorzystania. Napiszmy program, który wykona następujący rysunek:
rys.33
Jest to jedna pozioma linia o długości 100 mm (1000 pikseli) a na niej 11 pionowych kresek o długości 20 mm (200 pikseli) w odstępach co 10 mm(100 pikseli). Rysunek pomocniczy:
rys.34
Oraz gotowy program wykorzystujący mechanizm działania pętli: