![]() |
MAX Script jak sama nazwa wskazuje jest językiem skryptowym czyli w przeciwieństwie do np.C++ bardzo uproszczonym.
Napisane w nim skrypty można uruchamiać tylko pod MAX'em. Dodatkowo brak tu np.debuggera co niekiedy bardzo utrudnia
pisanie. Jednak mimo swoich ograniczeń bywa bardzo pomocny, chociażby dlatego, że wiele zadań można zautomatyzować,
oszczędzając sporo czasu. Obok widzicie rozwiniętą zakładkę Utilities, tutaj rezyduje MAX Script. Open Listener - przycisk ten otwiera okno MAX Script Listener'a. Wyświetlane są w nim wszystkie komunikaty, również te o błędach. New Script - otwiera nowe, puste okno edycji skryptu. Open Script - otwiera skrypt w oknie edycji. Run Script - Kompiluje (lub jak kto woli wykonuje) skrypt. Rozwijana lista Utilities - Tutaj pojawiają się nazwy wszystkich uruchomionych skryptów.
|
utility rollin "Rollin"
(
Ta pierwsza linijka definiuje nowy skrypt. Po słowie utility znajduje się nazwa skryptu (zmienna pod którą skrypt będzie
widoczny dla MAX'a) a w cudzysłowie nazwa która pojawi się w liście Utlities. Tutaj są one takie same, ale tak oczywiście
nie musi być.
Jak już wcześniej wspomniałem nie każdy skrypt działa jako Utility. Poniżej znajduje się pięć pierwszych linijek
bardzo popularnego skryptu HDRdomeLight.
Oznaczają one mniej więcej tyle: skrypt pojawi się w zakładce Create/Geometry w nowej kategorii CSLights (wszystkie
kategorie są w rozwijanej liście tam gdzie np. Standard Primitives). Będzie to przycisk z napisem HDRdomeLight. Linia
extends:GeoSphere oznacza, że skrypt jest rozszerzeniem obiektu GeoSphere. Na podobnej zasadzie, czyli
jako rozszerzenie działa skrypt SoftLight, z tą różnicą, że współpracuje on ze światłami.
classId:#(0x89bf888f, 0x86ae823b) definiuje unikalny identyfikator pluginu.Proste skrypty mogą się obejść bez interfejsu np. for i in 10 to 100 by 10 do (box position:[i*4,0,0] height:(random 10 100)).
Wpisanie tej linijki i skompilowanie (Ctrl+E) spowoduje utworzenie 10 box'ów o losowej wysokości od 10 do 100. Proponuję
żeby każdy kto zaczyna zabawę z MAX Script'em skopiował ją i spróbował jaki efekt dają zmiany poszczególnych parametrów,
można np. uzależnić wysokość od zmiennej i, dodać zmianę rotacji obiektów.
|
group "Settings"
(
Poleceniem group tworzymy ramkę w której znajdują się poszczególne elementy interfejsu przyciski, spinnery
itp. Nie jest to element konieczny ale pomaga elegancko rozplanować cały interfejs.
pickbutton selobj "Select object"
Pickbutton pozwala wybrać pojedynczy obiekt (mesh, światło ,kamera itd.). Po naciśnięciu pozostaje wciśnięty dopóki czegoś
nie wybierzemy.
label lab_rotaxis "Rotation axis: " across:2
Element label pozwala umieścić w interfejsie dowolny tekst.
radiobuttons rot_axis labels:#("X","Y","Z") default:3 align:#left across:1
Radiobuttons jest elementem z kilkoma opcjami do wyboru (może być wybrana tylko jedna z nich), rot_axis
jest zmienną pod jaką będzie widziany, natomiast "Rotation axis:" to nazwa jaka pojawi się
w interfejsie. labels:#("X","Y","Z") definiuje możliwe opcje wyboru, default:3 ustawia domyślnie
wybraną opcję, w tym wypadku wartość 3 oznacza oś Z czyli opcję trzecią, align:#left wyrównuje obiekt do lewej.
W tym konkretnym skrypcie umożliwi on wybór osi obrotu.across która pojawiła się przy ostatnich dwóch linijkach. Standardowo
MAX Script umieszcza elementy interfejsu jeden pod drugim, opcja across pozwala na rozmieszczenie ich obok
siebie.|
W przypadku wyrównywania dostępne są trzy możliwości: align:#left align:#center align:#right Istnieją również inne polecenia definiujące wygląd elementów interfejsu: offset - przesuwa element interfejsu o zadaną wartość wzdłuż osi X i Y np. button test "nazwa" offset:[10,12]width i height - definiują szerokość i wysokość elementu np. button test "nazwa" width:90 height:50pos - definiuje pozycję elementu np. button test "nazwa" pos:[10,15] |
spinner obj_radius "Radius: " type:#float range:[1,1000,48] fieldwidth:45 align:#left
Tworzymy pole w którym będzie można ustawiać żądaną wartość promienia obiektu. type:#float - rodzaj zmiennej
(do wyboru: float, integer lub worldunits), range:[1,100,48] - ustawia kolejno:
minimalną, maksymalną i domyślną wartość. fieldwidth:45 - definiuje szerokość tego pola (TYLKO pola a nie
całego elementu).
checkbox detect "Detect radius" enabled:false
Checkbox czyli pole które może być zaznaczone lub nie, zwracając odpowiednio wartość true lub false. Zaznaczenie pola
uruchomi funkcję, która automatycznie wyznaczy promień obracanego obiektu.
button center "Pivot to center of mass" width:140 enabled:false
Posłuży do umieszczenia Pivotu w środku masy animowanego obiektu. Ta opcja pojawiła się tutaj ponieważ w obiektach
tworzonych przy pomocy modyfikatora Lathe Pivot Point znajduje się przeważnie gdzieś z boku i nie może być wykorzystany
jako środek obrotu.
checkbox reverse "Reverse direction" align:#left
Posłuży do zmiany kierunku obrotu.
spinner calc_start "Calculation start: " type:#integer range:[1,1000,1] fieldwidth:47 align:#right
spinner calc_end "Calculation end: " type:#integer range:[2,1000,100] fieldwidth:47 align:#right
Powyższe spinnery posłużą do określenia początkowej i końcowej klatki zakresu działania skryptu. Zwróćcie uwagę, że są
one w przeciwieństwie do spinnera Radius typu integer (czyli liczbami całkowitymi tak samo jak numery klatek).
spinner step "Every Nth frame: " type:#integer range:[1,10,2] fieldwidth:47 align:#right
Określa co ile klatek skrypt będzie tworzył nowy klucz. Jest to dość ważne, gdyż duża ilość kluczy potrafi skutecznie
spowolnić pracę ze sceną.
button go "GO!"
Przycisk który uruchomi skrypt.
group "About"
(
label lab1 "Rollin'" align:#center
label lab2 "by Adam Wierzchowski" align:#center
label lab3 "E-mail: asd@grafik.3D.pl" align:#center
label lab4 "Web: www.asd.3D.pl" align:#center
)
Grupa zawierająca informacje o skrypcie.
![]() |
To by było na tyle jeśli chodzi o interfejs skryptu Rollin, poniżej zamieszczam przykładowy skrypt który umieszcza w
interfejsie wszystkie możliwe elementy. Kilka z nich zostało połączonych wzajemnymi zależnościami.
|
on rollin open do
(
Tutaj umieszczamy część skryptu która zostanie wykonana w momencie jego wykonania opcją Evaluate lub otwarcia rolety
(Rollout).
Jak się zapewne domyśliliście MAX Script obsługuje również zdarzenie zamknięcia rolety. Przyklad poniżej.
|
global xvector, yvector,zvector
global xradius, yradius,zradius
Deklaracja sześciu zmiennych globalnych. Zmienne xvector, yvector i zvector
przechowują jednostkowe wektory które umożliwią zrzutowanie wszystkich punktów obiektu na płaszczyzny YZ, XZ i XY.
W zmiennych xradius, yradius i zradius znajdzie się promień obiektu wyznaczony
względem każdej z trzech osi.
xvector = [0,1,1]
yvector = [1,0,1]
zvector = [1,1,0]
Nadajemy wartości zmiennym. Zapis xvector = [0,1,1] oznacza: zmiennej xvector zostaje przypisany punkt
w przestrzeni o współrzędnych (0,1,1).
)
Koniec obsługi zdarzenia on rollin open
on selobj picked obj do
(
Czas zrealizować obsługę PickButton'a "Select object". Po naciśnięciu i wybraniu obiektu zostanie on automatycznie
przypisany do zmiennej obj. Może to być światło, kamera, mesh, NURBS cokolwiek. Ponieważ skrypt
może obsłużyć tylko geometrię w dalszej częsci skryptu trzeba sprawdzić czy wybrany obiekt spełnia to kryterium.
if (superClassOf obj == geometryClass) then
(
Powyższa linia realizuje to o czym pisałem wcześniej, czyli sprawdzanie rodzaju obiektu. Zapis można rozszyfrować w
ten sposób: jeśli superklasa obiektu obj to geometryClass wtedy wykonaj... geometryClass - Box, Cone,
Editable_mesh, Nurbs, Sphere... light - Directionallight, freeSpot, Omnilight i tak dalej. Wymieniać
można bez końca. Zachęcam do przestudiowania poniższego przykładu.
Skrypt utworzy kilka obiektów. Należy nacisnąć przycisk "Wybierz obiekt" i następnie wybrać jeden z nich.
Zostanie wyświetlona jego superklasa i klasa.
|
global animated_object
Deklarujemy zmienną globalną w której będzie przechowywany animowany obiekt. Jak w każdym języku, tak i tutaj warto
nadawać zmiennym czytelne nazwy będące jednocześnie opisem przechowywanej wartości (lub obiektu).
animated_object = obj
Do zmiennej animated_object wpisujemy obiekt wybrany wcześniej przy pomocy PickButton'a.
selobj.text = obj.name
Nazwa wybranego obiektu zostaje wpisana do PickButton'a. Powyższa linijka mogłaby wyglądać również tak:
selobj.text = animated_object.name.
detect.enabled = true
center.enabled = true
go.enabled = true
Ponieważ obiekt został wybrany, można uaktywnić elementy skryptu które odwołują się do niego (są to kolejno wyznaczanie
promienia obiektu, ustawianie Pivota w środku masy i przycisk uruchamiający skrypt). Bez tego przypadkowe naciśnięcie
jednego z nich przed wybraniem obiektu spowodowałoby komunikat o błędzie.
detect.state = false
)
)
Odznaczamy checkbox'a "Detect radius". Jest to konieczne gdyż związana z nim funkcja mogła przechowywać promień poprzednio
wybranego obiektu.on selobj picked obj
on go pressed do
(
Najważniejsza część skryptu czyli to co dzieje się po naciśnięciu przycisku "GO!"
animate on
(
Polecenie animate on odpowiada naciśnięciu w MAX'ie przycisku Animate. Jak widać również w skryptach, jego
"naciśnięcie" jest konieczne.
cont = animated_object.rotation.controller
Przypisujemy kontroler rotacji obiektu animated_object do zmiennej cont. Kontroler to najprościej
mówiąc zawartość pojedynczej ścieżki w oknie Track View. Mogą to być klucze, dane z systemu motion capture, expression i
wiele innych rzeczy.
for frame in animationRange.start to animationRange.end do
(
Ta pętla zwróci kolejno numery wszystkich klatek w zmiennej frame. animationRange.start
i animationRange.end odpowiadają wartościom Start Time i End Time w oknie Time Configuration.
key_index = getkeyindex cont frame
Sprawdzamy jaki jest indeks klucza w aktualnej klatce animacji i wpisujemy go do zmiennej key_index.
Jeśli taki nie istnieje do zmiennej zostanie wpisana wartość 0 (indeksy kluczy zaczynają się od 1).
if (key_index) != 0 and (key_index) != 1 then (deleteKey cont key_index)
)
deleteKey cont key_index kasuje klucz o indeksie key_index w kontrolerze cont.
Nastąpi to tylko wtedy gdy indeks klucza jest różny od 0 (gdyż wtedy klucza po prostu nie ma, czyli nie ma czego
kasować) i różny od 1 (skasowanie pierwszego klucza powoduje błędne działanie skryptu).
for frame in calc_start.value to calc_end.value by step.value do
(
Ta pętla będzie zwracała numery klatek w których skrypt wyznacza rotację i tworzy nowe klucze. calc_start.value
zwraca wartość spinera "Calculation start" a calc_end.value spinera "Calculation end". step.value
czyli wartość spinera "Every Nth frame" określa co ile klatek powstanie nowy klucz.
if frame < step.value then (at time 0 (prev_pos = animated_object.pos))
else (at time (frame-step.value) (prev_pos = animated_object.pos))
Instrukcja za else wyznacza pozycję obiektu w poprzedniej iteracji pętli, natomiast warunek za if
jest zabezpieczaniem. Działa ono w momencie gdy np. "Calculation start" ma wartość 1 a "Every Nth frame" 4. Skrypt musiałby
odwołać się do klatki -3 a takiej oczywiście nie ma, zamiast tego odwołuje się do zerowej. Pozycja obiektu zostaje wpisana
do zmiennej prev_pos.
at time frame (current_pos = animated_object.pos)
Wyznaczamy pozycję obiektu w aktualnej klatce animacji (zmienna current_pos).
covered_distance = distance prev_pos current_pos
prev_pos i current_pos to punkty w przestrzeni 3D w których znajdował się i znajduje obiekt.
Polecenie distance pozwala wyznaczyć odległość między nimi (tą którą przebył nasz obiekt).
rotation = (180 * covered_distance)/(pi*obj_radius.value)
Teraz znając przebytą odległość covered_distance i promień obiektu obj_radius.value możemy
wyznaczyć rotację rotation, zwykła geometria. pi to jest stałą zdefiniowaną w systemie, więc
nawet nie trzeba znać jej wartości.
in coordsys local at time frame
(
Teraz bardzo ważna rzecz, przy pomocy in coordsys local przełączamy się na lokalny układ współrzędnych.
Aby dokładniej zrozumieć o co chodzi proponuję otworzyć plik before.max (w pliku ze skryptem, Rollin.zip), zaznaczyć
Torus01 i odegrać animację. Standartowo MAx ma włączony układ współrzędnych wievport'u. Najwyraźniej widać to w widoku
Top, obiekty zakręcają a mimo to układ współrzędnych nie zmienia orientacji. Teraz proponuję zmienić układ na Local.
Wyraźnie widać różnicę, oś Z cały czas pokrywa się z osiami torusów i właśnie o to chodzi. Teraz wystarczy odpowiednio
wokół nich obrócić obiekty i efekt toczenia gotowy.
case rot_axis.state of
(
Instrukcja case...of umożliwi podjęcie różnych akcji w zależności od osi wybranej w "Rotation axis".
rot_axis.state zwraca numer wybranej opcji.
1: if reverse.checked == false then (rotate animated_object (eulerangles -rotation 0 0))
else (rotate animated_object (eulerangles rotation 0 0))
Na początek sprawdzamy czy została zaznaczona opcja "Reverse direction". Jeśli tak to rot_axis.state zwróci
wartość true i zostanie wykonana instrukcja po then, w przeciwnym wypadku po else.
Zapis rotate animated_object (eulerangles rotation 0 0) oznacza: obróć obiekt animated_object wokół osi X
o tyle stopni ile wynosi wartość zmiennej rotation, a wokół osi Y i Z o zero stopni. Jeśli przed zmienną rotation pojawi
się minus obrót nastąpi w przeciwną stronę.
2: if reverse.checked == false then (rotate animated_object (eulerangles 0 -rotation 0))
else (rotate animated_object (eulerangles 0 rotation 0))
3: if reverse.checked == false then (rotate animated_object (eulerangles 0 0 -rotation))
else (rotate animated_object (eulerangles 0 0 rotation))
)
)
)
)
)
To samo dla osi Y i Z.
Skoro było o animacji proponuję dwa przykłady. W pierwszym, "Animacja kluczami", kulka animowana jest poprzez wstawianie
kluczy do kontrolera i nadawanie im wartości. W drugim, "Animacja pozycją", pozycja kulki jest ustalana co kilka klatek
przy pomocy właściwości position. Która metoda jest lepsza? Trudno powiedzieć, to jak zwykle zależy. Na pewno nie uciekniecie
od kluczy w wypadku gdy trzeba będzie wielokrotnie modyfikować tor ruchu.
Zwróćcie uwagę na kilka elementów. Po pierwsze konstrukcja as integer i as string, jest to zwykła
konwersja typów. W skryptach nigdy nie jest ona dokonywana automatycznie. Po drugie registertimecallback
current_frame, dzięki tej linijce funkcja current_frame zostanie wykonana za każdym razem gdy zmieni się aktualna
klatka animacji, czy to na skutek wciśnięcia przycisku "Play Animation", czy na skutek przesunięcia suwaka "Time Slider".
|
on center pressed do
(
Jeśli przycisk center (czyli "Pivot to center of mass") zostanie przyciśnięty wykonywane są instrukcje po
do.
obj_center = [0,0,0]
Inicjalizujemy zmienną obj_center. Później posłuży ona do obliczenia środka masy.
addModifier animated_object (edit_mesh())
Przypisujemy naszemu obiektowi modyfikator Edit Mesh. Umożliwi to dobranie się do poszczególnych vertex'ów.
for i in 1 to animated_object.numVerts do
(
Teraz rozpoczynamy pętlę która zwróci po kolei numery wszystkich vertex'ów. .numVerts to ich ilość w danym
obiekcie.
obj_center += getVert animated_object i
)
Funkcja getVert animated_object i zwraca położenie vertex'a o indeksie i należącego do obiektu
animated_object. Wszystkie pozycje sumujemy w zmiennej obj_center. Pamiętajcie, że jest ona typu Point3D zatem
sumowanie wygląda np. tak [1,2,3] + [7,0,-1] = [8,2,2], dla każdej osi osobno. Analogicznie wyglądają wszystkie działania
matematyczne na tym typie.
obj_center /= animated_object.numVerts
Tutaj wyznaczamy średnie położenie punktu, czyli suma przez ilość vertex'ów. Jak słusznie zauważyliście dzielimy zmienną
Point3D przez typ Integer. Wynikowy typ to oczywiście Point3D wygląda to np. tak: [8,16,2] / 2 = [4,8,1]. Czyli znowu wartość
dla każdej osi została podzielona osobno.
animated_object.pivot = obj_center
Przesuwamy Pivot obiektu w nowe położenie zdefiniowane w zmiennej obj_center
deleteModifier animated_object 1
)
Usuwamy modyfikator Edit Mesh. Jako ostatni nadany obiektowi modyfikator ma on indeks 1.
Skoro w powyższej procedurze odbywały się operacje ma vertex'ach proponuję wam krótki przykład na ten temat. Po uruchomieniu
skryptu wybierzcie dowolny obiekt. Zostanie wypisana całkowita ilość vertex'ów oraz pozycja jednego z nich wybranego przy
pomocy spinnera "Numer"
|
on detect changed new_state do
(
Ta instrukcja jest wywoływana w momencie gdy zmienia się stan elementu "Detect radius". Jeśli został on zaznaczony zmienna
new_state przyjmie wartość true, jeśli odznaczony - false.
if new_state == true then
(
Skrypt podejmie dalsze akcje jeśli "Detect radius" został zaznaczony, w przeciwnym wypadku nie zrobi nic.
addModifier animated_object (edit_mesh())
Podobnie jak to miało miejsce w funkcji przesuwającej Pivot, nadajemy naszemu obiektowi modyfikator Edit Mesh aby dobrać się
do poszczególnych vertexów.
obj_center = [0,0,0]
Inicjujemy nową zmienną obj_center typu Point3D, nadając jej jednocześnie wartość [0,0,0]. W niej będzie
przechowywana średnia arytmetyczna położenia wszystkich vertex'ów.
for i in 1 to animated_object.numVerts do
(
obj_center += in coordsys local getVert animated_object i
)
obj_center /= animated_object.numVerts
Wyznaczamy średnią arytmetyczną położenia wszystkich vertex'ów. Analogicznie jak chwilę wcześniej w funkcji ustawiającej
Pivot Point.
f_xvert_index = 1
f_yvert_index = 1
f_zvert_index = 1
Inicjalizujemy trzy zmienne w których będą przechowywane indeksy najdalszych vertex'ów, odpowiednio dla każdej z osi. Na
początek zakładamy, że najdalszym jest ten o indeksie 1.
for i in 2 to animated_object.numVerts do
(
Rozpoczynamy pętlę, która po kolei zwróci indeksy wszystkich vertex'ów aby można było sprawdzić ich odległość od środka
obiektu. Jak zauważyliście rozpoczyna się ona od 2. Gdyby zaczynała się od 1 to w pierwszej iteracji sprawdzalibyśmy
czy vertex o indeksie 1 nie znajduje się dalej od środka niż vertex o indeksie 1. Nie ma to żadnego sensu, lecz nie byłoby
błędem.
f_xvert_pos = in coordsys local getVert animated_object f_xvert_index
Odczytujemy pozycję najdalszego, znalezionego do tej pory vertex'a. Pamiętajcie, że nadal działamy w lokalnym układzie
współrzędnych - in coordsys local.
f_yvert_pos = in coordsys local getVert animated_object f_yvert_index
f_zvert_pos = in coordsys local getVert animated_object f_zvert_index
To samo dla pozostałych osi.
new_vert_pos = in coordsys local getVert animated_object i
Odczytujemy pozycję aktualnie porównywanego vertex'a. Jego indeks i zwracany jest przez pętlę for
którą rozpoczęliśmy kilka linijek wcześniej.
if (distance (obj_center*xvector) (new_vert_pos*xvector)) > (distance (obj_center*xvector) (f_xvert_pos*xvector))
then f_xvert_index = i
Jeżeli odległość między obj_center zrzutowanym na płaszczyznę YZ a new_vert_pos na YZ jest większa
od odległości między obj_center na YZ a aktualnie sprawdzanym punktem f_xvert_pos na YZ to
indeks aktualnego punktu staje się indeksem punktu najdalszego. Uff, teraz to samo po polsku. Właśnie tutaj odbywa się
rzutowanie o którym pisałem wcześniej. Załóżmy, że nasz obj_center ma współrzędne [7,-8,5], po rzutowaniu na
płaszczyznę YZ wyglądałby tak: [0,-8,5]. Zatem wystarczy przemnożyć go przez inny punkt [0,1,1] czyli ów
xvector (rozpisane działanie wygląda tak: [7,-8,5] * [0,1,1] = [7*0,-8*1,5*1] = [0,-8,5]). Gdybyście nie
pamiętali to xvector, jak również dwa pozostałe punkty, został zadeklarowany na samym początku w części
on rollin open do.
if (distance (obj_center*yvector) (new_vert_pos*yvector)) > (distance (obj_center*yvector) (f_yvert_pos*yvector))
then f_yvert_index = i
if (distance (obj_center*zvector) (new_vert_pos*zvector)) > (distance (obj_center*zvector) (f_zvert_pos*zvector))
then f_zvert_index = i
)
To samo dla pozostałych osi.
f_xvert_pos = in coordsys local getVert animated_object f_xvert_index
f_yvert_pos = in coordsys local getVert animated_object f_yvert_index
f_zvert_pos = in coordsys local getVert animated_object f_zvert_index
Ostatecznie wyznaczamy pozycję najdalszych vertex'ów dla każdej z osi. Ten krok jest konieczny na wypadek gdyby
interesujący nas punkt został znaleziony w ostatniej iteracji zakończonej pętli.
xradius = (distance (obj_center*xvector) (f_xvert_pos*xvector))
To co nas najbardziej interesuje, czyli wyznaczenie promienia. Odległość między zrzutowanym środkiem obiektu
obj_center a znalezionym przed chwilą najdalszym vertex'em f_xvert_pos, również zrzutowanym.
yradius = (distance (obj_center*yvector) (f_yvert_pos*yvector))
zradius = (distance (obj_center*zvector) (f_zvert_pos*zvector))
I znowu powtarzamy tą czynność dla pozostałych osi.
deleteModifier animated_object 1
Usuwamy już niepotrzebny modyfikator Edit Mesh.
case rot_axis.state of
(
1: obj_radius.value = xradius
2: obj_radius.value = yradius
3: obj_radius.value = zradius
)
)
)
W zależności od wybranej osi obrotu wpisujemy właściwą wartość do pola spinnera "Radius".
on calc_start changed val do
(
Instrukcja zostanie wywołana w momencie zmiany wartości spinera "Calculation start". Nowa wartość znajduje się w zmiennej
val.
if val > calc_end.value then calc_end.value = val
)
Jeśli nowa wartość wprowadzona do spinera "Calculation start" (czyli val) jest większa od wartości
"Calculation end" to "Calculation end" przyjmie tą nową wartość. W praktyce oznacza to, że "Calculation start" nigdy nie
będzie większa od "Calculation end".
on calc_end changed val do
(
if val < calc_start.value then calc_start.value = val
)
Dokładnie to samo co powyżej tylko w drugą stronę. "Calculation end" nigdy nie będzie większa od "Calculation start".
on rot_axis changed state do
(
Instrukcja zostanie wywołana w momencie zmiany dokonanej w elemencie "Rotation axis" (radiobuttons). Nowa wartość jest
przechowywana w zmiennej state.
if (state == 1) and (detect.state == true) then obj_radius.value = xradius
Jeśli wybrano opcję pierwszą (state == 1) i element "Detect raduis" jest zaznaczony
(detect.state == true), wpisz do spinera "Radius" wartość xradius.
if (state == 2) and (detect.state == true) then obj_radius.value = yradius
if (state == 3) and (detect.state == true) then obj_radius.value = zradius
)
To samo dla pozostałych dwóch opcji.
)
Koniec skryptu.