Przypuszczalnie potrafisz zmusić swój komputer do wykonywania mniej lub bardziej wyrafinowanych rysunków na ekranie. Nie znajdziesz więc w tym opracowaniu opisu działania elementarnych procedur grafiki żółwia. Nie będziemy też omawiać takich czynności, jak nagrywanie na taśmę i ładowanie swego programu, albo korzystanie z edytora. Zatrzymamy się tylko na chwilę przy niektórych, rzadziej być może używanych a użytecznych procedurach Logo.
Zajmiemy się (jeżeli, oczywiście, masz na to ochotę) rzutowaniem, zwanym w rysunku technicznym aksonometrią. Program nie jest trudny, ale do jego pełnego zrozumienia potrzebna jest znajomość podstaw geometrii analitycznej przestrzeni dwu- i trójwymiarowej z zakresu szkoły średniej. Jeżeli zdecydujesz się na trochę głębszą analizę tego, co wprowadzisz na swój komputer, zwróć uwagę na jedną z możliwych metod pracy z Logo, sposób tworzenia "narzędzi" do dalszej pracy i możliwość stawiania sobie coraz trudniejszych zadań bez potrzeb pisania nowego programu, a jedynie przez rozszerzenie tego, co już mamy.
We wszystkich tekstach Logo będziemy używali pełnego alfabetu polskiego, abyś nie musiał tracić czasu np. na domyślanie się, że „BLAD" to to samo, co po polsku „BŁĄD". Rzecz jasna, używając Spectrum czy Commodore napiszemy „a" zamiast "ą", "I" zamiast "ł" itd.
Zaczynamy...
...od prostego ćwiczenia, którego efekty mogą być nam w przyszłości przydatne. Nietrudno się domyślić, że nasze rzutowanie będzie miało wiele wspólnego z rysunkiem technicznym. Wiemy, że w rysunku tym używa się różnych rodzajów linii; w szczególności dotyczy to ich grubości. Ma to znaczenie estetyczne i praktyczne — w ten sposób rysunek staje się bardziej czytelny. Ponieważ rozdzielczość ekranu ZX Spectrum jest mimo wszystko dość niewielka, rysunki wykonywane liniami jednakowej grubości mogą nie dawać właściwego wyobrażenia o kształcie rzutowanej bryły. Wobec tego spróbujmy umożliwić programiście (a zatem sobie) kreślenie na ekranie linii o wybranej grubości. Przyda się to również i w Commodore Logo. Przyjrzyjmy się takiej procedurze:
TO NAPRZÓD :odl
FORWARD :odl
END
Wykonuje ona dokładnie to samo, co FORWARD — zatem kreśli linię, którą nazwalibyśmy „cienką" (cieńszej na naszym komputerze już nie da się otrzymać). Chcielibyśmy jednak, aby ta sama procedura NAPRZÓD wytwarzała na nasze życzenie linie grubsze. Możemy oczywiście w takiej sytuacji zmienić jej treść posługując się edytorem. To rozwiązanie nie jest jednak zadowalające, szczególnie gdy np. mamy nakreślić figurę złożoną z 30 odcinków, z których każdy jest innej grubości niż jego poprzednik. Na szczęście zmiany definicji procedury można dokonać przez wykonanie odpowiedniego fragmentu programu.
TO NOWE: procedura
IF NAMEP: procedura [ERA-
SE: procedura]
END
TO L. CIENKA
NOWE „naprzód
DEFINE „naprzód [[:odl] FD
:odl]]
END
To już znamy — napisanie L. CIENKA powoduje, że definiowana jest od nowa procedura NAPRZÓD o postaci już nam znanej. Jak widać, parametrem DEFINE jest lista list, stanowiących kolejne wiersze tworzonej procedury. Za pierwszy wiersz przyjmuje się listę parametrów. W przypadku ich braku lista ta jest pusta, ale musi również zostać zapisana. Jeżeli masz wątpliwości, do czego właściwie służy NOWE i czy nie wystarczyłoby użycie ERASE, napisz:
ER “Franuszek
Spowoduje to pojawienie się komunikatu
ER doesn't like FRANUSZEK as input
Stanowi to odpowiedź na twoje pytanie. Po prostu nie mamy całkowitej pewności, czy w momencie wywołania NOWE „naprzód istnieje już procedura o podanej nazwie. Przejdźmy do linii grubszych.
TO L. GRUBA NOWE "naprzód DEFINE "naprzód [[:odl] [REPEAT 2 [FD odl RT 90 FD 1 RT 90]] [FD :odl]] END
TO L.B. GRUBA NOWE "naprzód
DEFINE "naprzód [odl][FD
:odl RT 90 FD 1 RT 90] [FD :odl RT 90 FD 2 RT 90][FD :odl RT 90 FD 1 LT 90]]
END
Być może taki zapis treści procedury nie jest dla ciebie wystarczająco czytelny. W takim razie napisz:
L.B. GRUBA PO „naprzód
Łatwiej ci będzie w ten sposób zrozumieć, w jaki sposób otrzymujemy linię podwójnej (L. GRUBA) i potrójnej (L.B. GRUBA) grubości. Ważne jest, aby po wykonaniu NAPRZÓD żółw zawsze znajdował się w tej samej pozycji, co po wykonaniu FORWARD, a nie np. o 1 krok w lewo. Zależy nam przecież, aby można było używać tych dwóch procedur zamiennie.
Obejrzyjmy teraz efekt naszej pracy. Napisz:
CS L. GRUBA NAPRZÓD 70
a potem np.
HOME LT 45 NAPRZÓD 100
Przekonasz się, że NAPRZÓD nie działa dokładnie tak, jak byśmy chcieli: linia się w pewnym miejscu rozdwaja. Jest to związane z rozdzielczością ekranu, wobec czego zadowolimy się tym, co mamy. Oczywiście rysowanie linii pogrubionych trwa znacznie dłużej niż linii zwykłych. Pewne zwiększenie tempa można uzyskać przez uprzednie ukrycie żółwia — HT.
Na tym kończymy wstęp.
Przypomnijmy, sobie nasze szkolne wiadomości o rzutach aksonometrycznych. Poniższy rysunek przedstawia najczęściej spotykane rodzaje tych rzutów. Wykonując rzut aksonometrycz-ny pewnego przedmiotu postępujemy według kilku zasad:
— umieszczamy przedmiot w przestrzeni tak, aby możliwie największa liczba krawędzi i ścian była równoległa do osi układu współrzędnych.
— jeżeli krawędź przedmiotu jest w rzeczywistości równoległy do którejś z osi układu, to długość jej rzutu jest równa długości rzeczywistej tej krawędzi przemnożonej przez odpowiednią po-działkę, określoną dla danej osi.
— jeżeli krawędź przedmiotu jest w rzeczywistości równoległa do którejś z osi układu, to jej rzut jest równoległy do rzutu odpowiedniej osi. Jest to zawężenie pewnej ogólnej zasady, dotyczącej rzutów równoległych.
Inaczej mówiąc, w przestrzeni trójwymiarowej wyróżniamy trzy wzajemnie prostopadłe kierunki, odpowiadające osiom układu współrzędnych. Do jednoznacznego określenia rzutu aksono-metrycznego wystarczy nam sześć wartości liczbowych: kierunki rzutów osi układu na płaszczyźnie rzutowej oraz podziałki przypisane tym osiom. Zapiszmy to w Logo.
TO OSIE :x :y :z
MAKE “kątx :x
MAKE “kąty :y
MAKE “kątz :z END
TO PODZIAŁKI :x :y :z
MAKE “podzx :x
MAKE “podzy :y
MAKE “podzz :z END
Przy pomocy procedur OSIE i PODZIAŁKI możemy teraz w łatwy sposób przypisać wartości zmiennym „kątx, „kąty, „kątz, „podzx, „podzy, „podzz. Na przykład układ jednomiarowy określimy przez
OSIE 120 0 240 PODZIAŁKI 1 1 1
Parametry procedury OSIE podawać będziemy jako wartości
kątów, jakie tworzą rzuty osi układu z “pionem” ekranu, liczone zgodnie z ruchem wskazówek zegara: tak samo jest w przypadku instrukcji SETH i wygodnie będzie trzymać się jednej konwencji. Oczywiście wartości te podane są w stopniach. Pozostałe dwa znane nam rzuty aksonome-tryczne zdefiniujemy przez
OSIE 135 0 225 PODZIAŁKI 1 0.5 1
OSIE 90 0 225 PODZIAŁKI 1 1 0.5
Zanim zajmiemy się przełożeniem powyższego na język konkretnego rysunku, napiszmy jedną procedurę RZUT, której parametr stanowić będzie nazwę żądanego rzutu aksonometryczne-go. Chcemy, aby np. napisanie
RZUT “jednomiarowy
spowodowało przypisanie odpowiednich wartości naszym zmiennym.
TO RZUT :nazwa
IF MEMBERP :nazwa
[prostokątny p][OSIE 90 0 225 PODZIAŁKI1 1 .5 CS STOP]
IF MEMBERP :nazwa
[jednowiarowy j][OSIE 120 0 240 PODZIAŁKI 1 1 1 CS STOP]
IF MEMBERP :nazwa [wojskowy w][OSIE 135 0 225 PODZIAŁKI 1
.5 1 CS STOP]
IF MEMBERP nazwa [górny g][OSIE 90 0 180 PODZIAŁKI 1 0 1 CS STOP]
IF MEMBERP :nazwa [boczny b][OSIE 0 0 90 PODZIAŁKI 0 1 1 SC STOP]
IF MEMBERP nazwa [z. przodu z][OSIE 90 0 0 PODZIAŁKI 1 1 0 CS STOP]
IF MAMBERP :nazwa [trzy.czwarte t][OSIE 100 0 235 PODZIAŁKI 1 1 .65 CS STOP]
PRINT [Nie znam takiego
rzutu!] TOPLEVEL
END
Przy pisaniu tej procedury wygodnie jest się posłużyć klawiszem .EXTENDED MODE R. Powoduje to, że jako bieżąca linia zostaje wypisany ostatni wprowadzony wiersz Logo. Rodzaj rzutu możemy teraz ustalić przez podanie jego nazwy, np.
RZUT “boczny
albo używając tylko jej jednoliterowego skrótu:
RZUT„b
Wywołanie RZUT „p, RZUT „j, RZUT „w daje nam jeden ze znanych rzutów aksonometrycznych. Rzuty „górny, „boczny, „z. przodu to standartowo używane ruchy prostokątne. Zauważ, że jedną z osi eliminujemy z rysunku przez przypisanie jej podziałki 0. Wreszcie rzut „trzy.czwarte da nam nieco inny widok rysowanej bryły. W przypadku napisania np.
RZUT „lotniczy
na ekranie pojawi się komunikat „Nie znam takiego rzutu!, a program, w którym błędne wywołanie wystąpiło, zostanie przerwany.
Zacznijmy od rysowania odcinków równoległych do osi układu. Teraz przydadzą nam się wartości sześciu wcześniej zadeklarowanych zmiennych.
TO PRAWO :odl SETH :kątx
NAPRZÓD :odl * :podzx END
TO LEWO :odl PRAWO — :odl END
LEWO mogliśmy też zapisać tak:
TO LEWO :odl SETH :kątx +180
NAPRZÓD :odl * :podzx END
Nie czyni to zbyt dużej różnicy, użyj więc wersji, która ci bardziej odpowiada. Postaraj się teraz samodzielnie zdefiniować procedury GÓRA, DÓŁ, PRZÓD, TYŁ. Porównaj wynik swojej pracy z poniższym.
TO GÓRA :odl SETH :kąty
NAPRZÓD :odl * :podzy END
TO DÓŁ :odl GÓRA — :odl END
TO PRZÓD :odl SETH :kątz
NAPRZÓD odl * podzz END
TO TYŁ :odl PRZÓD — :odl END
Mamy już do dyspozycji narzędzia, pozwalające na wykonywanie rysunków. Na początek nakreślimy na ekranie układ współrzędnych, co da nam wstępne wyobrażenie o postaci przyszłych rzutów. Przedtem jednak napiszemy krótką procedurę, przydatną w każdej grafice żółwia, powodującą podniesienie “pióra” i opuszczenie go po wykonaniu pewnej sekwencji czynności.
TO HOP :co.zrobić
PENUP
RUN xo.zrobic
PENDOWN
END
Parametrem użytego tu RUN jest lista czynności do wykonania, a zatem tekst Logo ujęty w nawiasy kwadratowe. Przykład zastosowania poniżej.
TO UKŁAD
L.CIENKA HOP [HOME]
PRAWO 200 HOP [HOME]
GÓRA 200 HOP [HOME]
PRZÓD 200 HOP [HOME]
END
Napiszmy teraz np.
RZUT „prostokątny UKŁAD
No, tak: nie dało to pożądanego wyniku, ponieważ ekran znajduje się w trybie WRAP. Żółw po przekroczeniu granicy ekranu pojawia się na jego przeciwległej krawędzi, co psuje efekt trójwymiarowości. Do naszych celów najbardziej przydatny jest tryb WINDOW. Poprawmy nasz rysunek.
WINDOW CS RZUT „p UKŁAD
Jeżeli chcesz, obejrzyj też układ współrzędnych w pozostałych rzutach.
Być może jedną z pierwszych przez ciebie zdefiniowanych procedur Logo była ta rysująca prostokąt. Dokonajmy tego samego w trzech wymiarach. Ponieważ na razie umiemy poruszać żółwia tylko w kierunkach osi układu, nasz prostokąt może leżeć w jednej z płaszczyzn równoległych do xz lub xy. Spróbuj sam napisać procedury PROST.XY PROST .YZ, a dopiero potem ewentualnie popraw je według poniższego wzoru:
TO PROST .XY :x :y
GÓRA :y
PRAWO :x
DÓŁ :y
LEWO :x
END
TO PROST .XZ :x :z
PRZÓD :z
PRAWO :x
TYŁ :z
LEWO :x
END
TO PROST .YZ :y :z
GÓRA :y
PRZÓD :z
DÓŁ :y
TYŁ :z
END
Warto zwrócić uwagę, że treść tych procedur podlega pewnym konwencjom. W przeciwnym wypadku mogłoby się zdarzyć, że napisawszy.
PROST.XY 50 10
nie będziemy wiedzieli, czy rysowany prostokąt ma wysokość 50, a szerokość 10, czy odwrotnie. W tym przypadku zdecydowaliśmy się na podawanie parametrów w porządku alfabetycznym.
Można oczywiście ustalić inną regułę, trzeba jednak się jej trzymać konsekwentnie w całym programie.
Przy okazji warto też poruszyć kwestię tzw. nazw znaczących. Staraliśmy się dotychczas, aby wszystkie występujące w programie nazwy procedur i zmiennych były adekwatne do ich zawartości. Nie było to postępowanie przypadkowe. Oczywiście, z punktu widzenia Logo jest obojętne, czy nazwiesz pewną procedurę NAPRZÓD czy HUHU. HAHA. Ta druga nazwa może się nawet wydać śmieszniejsza, natomiast niesie ze sobą inną niedogodność. Gdy nagrasz cały program na taśmę i powrócisz do niego, powiedzmy, za miesiąc, wiele czasu zajmie ci przypomnienie sobie, co właściwie HUHU. HAHA wykonuje a co HIHI. HOHO? .Należy unikać również dość rozpowszechnionej praktyki nazywania zmiennych „jak leci”, tzn. kolejnymi literami alfabetu, gdy nie są one bezpośrednio związane z treścią programu.
Z naszych prostokątów możemy już składać rysunki trójwymiarowe. Oto prościutki przykład.
RZUT „jednomiarowy
L.CIENKA HT
REPEAT 10 [PROST.XZ 30 50
HOP [GÓRA 3]]
Otrzymujemy taki rysunek: Otwiera się przed tobą pole do własnych doświadczeń. Oto jeszcze jeden przykład figury przestrzennej złożonej z kwadratów...
Następnym etapem pracy będzie rysowanie prostopadłościanu o zadanej długości boków.
TO KOSTKA :x :y :z
PROST .XY :x :y
PROST .XZ :x :z
HOP [PRZÓD :z]
PROST .XY :x:y
HOP [TYŁ :z GÓRA :y]
PROST .XZ :x :z
HOP [DÓŁ :y]
END
Warto poćwiczyć trochę wyobraźnię przestrzenną, analizując działanie tego fragmentu. Interesujące, że nie użyliśmy ani razu PROST.YZ. Ostatnia instrukcja HOP nie ma znaczenia dla samego rysunku. Powoduje za to, że po wykonaniu procedury żółw znajdzie się dokładnie w pozycji wyjściowej. Powiemy, że pozycja żółwia jest niezmiennikiem procedury KOSTKA. Jest to zasada, której warto przestrzegać.
Narysuj teraz kilka prostopadłościanów w różnych rzutach. Gdy to ci się znudzi, napisz
RZUT „t KOSTKA 40 40 40
Na ekranie ukaże się sześcian. Niemniej jednak rzut otrzymany w wyniku
CS OSIE 90 20 200 PODZIAŁ-Kl 1.2 1 .85 KOSTKA 40 40 40
mało ów sześcian przypomina. Wnioskujemy, że przez użycie procedur OSIE i PODZIAŁKI z różnymi parametrami możemy dość dowolnie deformować rzutowane bryły. Zrób kilka doświadczeń tych deformacji. Jeżeli otrzymany rzut odpowiada ci, możesz go dołączyć do procedury RZUT.
Mamy teraz do dyspozycji dość narzędzi, aby popróbować swych sił przy rzutowaniu różnego typu brył prostopadłościen-nych. Na początek możesz spróbować np.
TO PRZYKŁAD
L.CIENKA KOSTKA 30 70 50
HOP [GÓRA 70]
L.B.GRUBA
KOSTKA 30 15 50
HOP [DÓŁ 70 PRAWO 30]
L.GRUBA
KOSTKA 40 45 50
END
Oczywiście proste bryły można składać w coraz bardziej skomplikowane ich układy, aż do utraty czytelności rysunku. Kilka przykładów możesz obejrzeć poniżej.
No, tak — mógłby ktoś powiedzieć — dobrze, ale przecież nie wszystkie krawędzie brył muszą być do siebie prostopadłe. Jak narysować dowolny odcinek w naszym rzucie?
Przyjrzyjmy się poniższemu rysunkowi.
Przedstawia on pewien odcinek w przestrzeni trójwymiarowej. Stanowi on jakby przekątną pewnego prostopadłościanu o bokach, dajmy na to, dx, dy, dz. Krawędzie tego prostopadłościanu potrafimy już przebyć przy pomocy żółwia. Nasuwa się więc następujący algorytm.
TO ODC :dx :dy :dz
MAKE „x XCOR
MAKE „y YCOR
HOP [PRAWO :dx GÓRA :dy PRZÓD :dz] SETPOS SE :x :y
END
Nie jest to jednak dobre rozwiązanie. Po pierwsze wykonujemy kilka niepotrzebnych ruchów żółwiem, po drugie po zakończeniu wykonywania ODC znajduje się on w punkcie wyjścia, a nie. jak chcielibyśmy, na końcu rysowanego odcinka. Lepiej będzie użyć matematyki w celu wyliczenia współrzędnych rzutu tego końca, a następnie „przywołać” żółwia do tego punktu.
TO ODC :dx :dy :dz
MAKE „now.xcor (SUM XCOR :podzx * :dx * SIN :kątx)
podzy * :dy * SIN :kąty :podzz * :dz * SIN: kątz)
MAKE „now.ycor (SUM YCOR :podzx * :dx * COS :kątz
podzy * :dy * COS :kąty :podzz * :dz * COS :kątz
SETH TOWARDS SE :now.xcor :now.ycor
NAPRZÓD
ODLEGŁOŚĆ :now.xcor now.ycor
END
TO ODLEGŁOŚĆ :x :y
MAKE „deltax :x — XCOR
MAKE „deltay :y — YCOR
OUTPUT SORT [:deltax *
:deltax + :deltay * :deltay] END
Zamiast poprzedniego SETPOS użyliśmy sekwencji, która pozwą la na użycie NAPRZÓD a zatem i kreślenie linii grubych. Zauważ, że używając TOWARDS i SETPOS należy napisać
SETPOS SE :x :y
a nie
SETPOS [:x :y]
Sprawdzić działanie ODC możemy np. przez
RZUT „t KOSTKA 60 60 60 ODC 60 60 60
Otrzymamy sześcian i jego przekątną. Zauważymy też, że ODC działa bardzo powoli w porównaniu z np. PRAWO. Spowodowane jest to powolnym obliczaniem funkcji SIN i COS przez ZX Spectrum. Gdyby nie ten fakt, moglibyśmy zmienić definicje
PRAWO, GÓRA i PRZÓD
według poniższego wzoru:
TO GÓRA :odl ODC 0 :odl 0
END
Możemy teraz łatwo narysować prostopadłościan np. z przekątnymi ścian. Napisanie jej procedury to ćwiczenie dla ciebie.
Na zakończenie omówimy krótko kilka procedur, które wzbogacą naszą grafikę o ciekawe możliwości.
TO OBRÓCONE :kąt MAKE „katx :kątx + :kąt MAKE „kąty :kąty + :kąt MAKE „kątz :kątz + :kąt END
OBRÓCONE powoduje, iż rysunek (lub jego część) zostaje obrócona o podany kąt.
TO SYMETR.XY MAKE „kątz :kątz t 180 END
PRAWO, GÓRA i PRZÓD według poniższego wzoru:
TO GÓRA :odl
ODC 0 :odl 0
END
Możemy teraz łatwo narysować prostopadłościan np. z przekątnymi ścian. Napisanie jej procedury to ćwiczenie dla ciebie.
Na zakończenie omówimy krótko kilka procedur, które wzbogacą naszą grafikę o ciekawe możliwości.
TO OBRÓCONE :kąt
MAKE „katx :kątx + :kąt
MAKE „kąty :kąty + :kąt
MAKE „kątz :kątz + :kąt END
OBRÓCONE powoduje, iż rysunek (lub jego część) zostaje obrócona o podany kąt.
TO SYMETR.XY
MAKE „kątz :kątz t 180
END
SYMETR.XY daje nam lustrzane odbicie przedmiotu, względem płaszczyzny xy.’Analogicznie działają poniższe procedury:
TO SYMETR.XZ
MAKE „kąty :kąty + 180
END
TO SYMETR.YZ
MAKE „kątx :kątx + 180
END
Wiemy, że złożeniem dwóch symetrii jest obrót. Możemy zatem obejrzeć nasz przedmiot „z drugiej strony’.
TO OD. TYŁU
SYMETR.XY
SYMETR.YZ
END
TO OD.SPODU
SYMETR.XZ
SYMETR.YZ
END
Możemy także nasz rysunek dowolnie powiększyć albo zmniejszyć przez proporcjonalne zwiększenie podziałek na wszystkich osiach.
TO ZBLIŻENIE :krotność PODZIAŁKI :krotnosć * :podzx: krotność *: podzy: krotność * podzz END
Daje to nam możliwość powiększenia dowolnego elementu rysunku. Usprawnimy to przy pomocy dwóch poniższych procedur:
TO WOKÓŁ.PUNKTU :x :y :z MAKE „x.0 (— :x) MAKE
„y« (- :y)
MAKE „z.0 (— :z) END
TC ZERO
HOP [HOME GÓRA :y.0 PRAWO :x.0 PRZÓD :z.0] END
Sposób posługiwania się nimi jest następujący: listę procedur realizujących rysunek poprzedzamy wywołaniem ZERO. Usta-Ie; to niejako początek układu współrzędnych w punkcie, w którym rozpoczyna się rysowanie bryły. Parametrami WOKÓŁ.PUNKTU są współrzędne przestrzeni trójwymiarowej liczone od tego właśnie punktu. Oto prosty przykład.
TO PROSTY.PRZ ZERO
KOSTKA 10 10 10 END
Jeżeli teraz chcemy obejrzeć z bliska jeden z wierzchołków sześcianu, piszemy
RZUT „j ZBLIŻENIE 200 WOKÓŁ.PUNKTU 10 10 10 PROSTY.PRZ
Trzeba pamiętać, że użycie RZUT przywraca standartową sytuację, tzn. usuwa działanie procedur ZBLIŻENIE, OBRÓCONE, OD.SPODU itp.
Zastosowanie otrzymane, w ten sposób grafiki trójwymiarowej zależy już od twojej inwencji. Daje się odczuć brak możliwości „zasłaniania linii”, tzn. pomijania w rysunku linii, które normainie nie są widoczne. Ze względii na elementarny charakter ninieisze go opracowania pominęliśmy rozwiązanie tego problemu. Może też cię niecierpliwić powolność działania Sinclair Logo — nie ma na to jednak rady. Miejrm nadzieję, że otrzymane rzuty interesujących brył zrekompensują ci te niedostatki. Oto przykład rzutu domu i kolejne przybliżenia jego fragmentu — klamki okiennej. Wymyśl coś lepszego!
Marcin Waligórski