Do węzłów wiążący (bindable nodes) należą: Background, Fog, NavigationInfo i Viewpoint. Niniejszy punkt ma na celu wytłumaczenie zasady przełączania między węzłami wiążącami tego samego rodzaju. W kodzie źródłowym możecie zdefiniować dowolną ilość każdego z tych węzłów jednak jednorazowo tylko jeden węzeł z danej grupy może być aktywny (podwiązany). W tym przypadku grupą nazywamy np. wszystkie węzły Background zdefiniowane w kodzie źródłowym, drugą grupą są wszystkie węzły Viewpoint itd.
Każda grupa węzłów tego samego rodzaju posiada w pamięci przeglądarki własny tzw. stos wiążący (bind stack). Węzeł aktywny znajduje się na górze takiego stosu. W pamięci przeglądarki istnieją w związku z tym cztery stosy wiążące, każdy z nich obsługuje jedną grupę węzłów wiążących, np. wszystkie węzły Fog.
Dzięki węzłom wiążącym możemy dowolnie zmieniać np. warunki atmosferyczne (Background, Fog) jakie panują w naszym świecie w zależności np. od czasu jaki upływa podczas wizyty. Możemy również spowodować, że użytkownikowi zbliżającemu się do ciemnej jaskini zapala się światło headlight (NavigationInfo).
Najprostszym przykładem demonstrującym podwiązywanie kolejnych węzłów wiążących jest opcja przeglądarki pozwalająca na przełączanie między kolejnymi węzłami Viewpoint. Nie jest to nic innego jak wysyłanie do kolejnych węzłów Viewpoint zdarzenia set_bind o wartości TRUE, które jest właśnie odpowiedzialne za uaktywnienie danego węzła wiążącego.
Do tej pory poznaliśmy wszystkie węzły wiążące oprócz jednego - NavigationInfo. Dlatego najpierw omówię szczegółowo ten węzeł a potem przejdę do wyjaśnienia zasad działania stosu wiążącego.
NavigationInfo {
eventIn
SFBool set_bind
exposedField MFFloat
avatarSize [0.25, 1.6, 0.75]
exposedField SFBool
headlight TRUE
exposedField SFFloat
speed 1.0
exposedField MFString
type
["WALK", "ANY"]
exposedField SFFloat
visibilityLimit 0.0
eventOut
SFBool isBound
}
Węzeł ten odpowiada za fizyczne cechy uczestnika świata wirtualnego (awatara), jak i również za sposób prezentacji obiektów w tym świecie się znajdujących.
Pole avatarSize typu MFFloat określa możliwości fizyczne uczetsnika świata VRML. Pierwsza wartość w tym polu odnosi się do odległości między użytkownikiem a obiektem, przy której następuje zderzenie. Druga wartość określa wysokość najniższego elementu świata, pod którym użytkownik może przejść (czyli wysokość). Trzecia wartość natomiast określa wysokości najwyższego elementu świata, przez który użytkownik może przejść (jeżeli w tym polu znajdzie się wartość 0.1, a na przykład stopnie schodów będą miały wysokość 0.2, to uczestnik nie będzie mógł na nie wejść).
Pole headlight typu SFBool odpowiada za włączenie (TRUE) lub wyłączenie (FALSE) źródła światła, które jest "przymocowane" do głowy uczestnika świata i oświetla wszystko co się przed nim znajduje. W zasadzie autorzy świata powinni samodzielnie definiować wszystkie światła na scenie, tak aby światło typu headlight nie było potrzebne. Często się zdarza, że autorzy zdefiniują światła, które oświetlają scenę, ale nie wyłączą światła headlight - wtedy łatwo zauważyć efekt "przeświecenia" sceny. Zachowaniem tego światła można również sterować podczas eksploracji świata korzystając z opcji dostępnej w przeglądarce VRML.
W polu speed typu SFFloat definiujemu prędkość, z jaką uczestnik świata będzie się poruszał. W polu tym nie powinna się znaleźć wartość ujemna. Ponadto jeżeli w polu type ustalimy wartość EXAMINE, działanie pola speed nie będzie przynosiło żadnego rezultatu.
Bardzo interesującym polem jest wspomniane pole type, w którym można narzucić sposób eksplorowania stworzonego świata. Wartości umieszczane w tym polu odnoszą się do przeglądarki VRML, zmieniają jej interfejs, włączając lub wyłączając różne jej opcje. W polu type mogą występować następujące wartości: ANY, WALK, EXAMINE, FLY, NONE. Ponieważ jest to pole typu MFString, możemy w nim umieścić dwie z powyższych wartości.
Wartość ANY oddaje uczestnikowi świata do dyspozycji wszystkie opcje interfejsu przeglądarki. Jeżeli w polu type oprócz wartości ANY znajdą się również inne, WALK, EXAMINE czy FLY, oznaczać do będzie, że uczestnik będzie mógł korzystać ze wszystkich opcji interfejsu przeglądarki, ale inne będą ustawienia tego interfejsu tuż po załadowaniu świata. Na przykład wartość ["EXAMINE", "ANY"] w polu type spowoduje, że po załadowaniu świata będziemy od razu w trybie examine.
Jeżeli wybierzemy wartość WALK, zostanie wyłączona możliwość wejścia w tryb obracania obiektów i wyłączenia grawitacji. Wartość EXAMINE w polu type spowoduje, że będziemy mogli daną sceną jedynie obracać bez możliwości chodzenia. Gdy w polu type umieścimy z kolei wartość FLY, zostanie wyłączona możliwość obracania obiektami oraz możliwość włączenia grawitacji. Wartość NONE w polu type spowoduje, że interfejs przeglądarki zostanie w ogóle wyłączony. Użytkownik zostanie w ogóle pozbawiony możliwości ruchu w świecie VRML.
W polu visibilityLimit typu SFFloat możemy określić jak duża część świata VRML będzie ukazywana jego uczestnikowi. Wartość domyślna (0.0) ukazuje całą scenę, natomiast wartość (10) - niewiele, a (100) całkiem spory kawałek, czyli po prostu 100 metrów. Pole to może być użyte przy budowaniu dużych przetsrzeni w celu optymalizacji - im mniejszy fragment świata będzie widoczny tym szybciej będzie on wyświetlany.
Poniższy przykład zmienia dzień w noc w zależności od pozycji uczestnika świata wirtualnego.
#VRML V2.0 utf8
DEF DzienNav NavigationInfo
{}
DEF NocNav NavigationInfo
{headlight FALSE}
DEF Dzien Background {
skyColor [
0.0 0.2 0.7,
0.0 0.5 1.0,
1.0 1.0 1.0 ]
skyAngle [ 1.3, 1.57 ]
}
DEF Rano Fog {
color 1 1 1
visibilityRange 20 }
DEF Noc Background {
skyColor [
1 1 1 ,
0 0 0,
0 0.25 0.5,
0 0 0 ]
skyAngle [ 0.5, 1, 2 ]
}
DEF Wieczor Fog {
color 1 1 1
visibilityRange 0 }
Shape {
appearance
Appearance { material Material {
diffuseColor 0 1 0 } }
geometry
Box {}
}
DEF DzienView Viewpoint {
description "Dzien"
position 0 0 10 }
DEF NocView Viewpoint {
description "Noc"
position 0 0 -10
orientation 0 1 0 3.14 }
DEF DzienProx ProximitySensor
{center 0 0 10
size 20 20 20 }
DEF NocProx ProximitySensor
{ center 0 0 -10
size 20 20 20 }
ROUTE DzienProx.isActive
TO DzienView.set_bind
ROUTE DzienProx.isActive
TO Dzien.set_bind
ROUTE DzienProx.isActive
TO Rano.set_bind
ROUTE DzienProx.isActive
TO DzienNav.set_bind
ROUTE NocProx.isActive TO
NocView.set_bind
ROUTE NocProx.isActive TO
Noc.set_bind
ROUTE NocProx.isActive TO
Wieczor.set_bind
ROUTE NocProx.isActive TO
NocNav.set_bind
Jak widzimy podwiązywanie węzłów nie jest takie trudne. Jednak zasady, które kierują tym całym mechanizmem są dosyć złożone.
Węzły wiążące posiadają unikalne zdarzenia, które mogą zostać uaktywnione w dowolnym momencie, ale tylko po jednym zdarzeniu z każego typu. Każdy z tych węzłów zawiera zdarzenie eventIn set_bind oraz zdarzenie eventOut isBound. Zdarzenie eventIn set_bind używane jest do przesuwania danego węzła po zakresie pamięci oferowanym przez przeglądarkę. Wartość TRUE dla zdarzenia eventIn set_bind przesuwa nasz węzeł na górę stosu, a wartość FALSE w ogóle usuwa węzeł ze stosu.
Zdarzenie isBound zostaje wysłane, gdy dany węzeł przesunie się na górę stosu, zostanie z niego usunięty lub zrzucony i zastąpiony przez inny węzeł. Węzeł, który znajduje się na górze stosu jest węzłem aktywnym dla reprezentowanego typu i używany jest przez przeglądarkę do określenia stanu sceny. Jeśli stos jest pusty, to do ustawienia stanu sceny użyta jest wartość domyślna. Rezultat tej operacji pozostaje niezdefiniowany, jeżeli więcej niż jeden spośród węzłów wiążących (przywołanych za pomocą mechanizmu DEF/USE) zostaje dowiązany do stosu.
Zachowania stosu wiążącego
1. Podczas czytania pliku:
a. Pierwszy znaleziony węzeł
wiążący jest umieszczany na górze stosu:
a.1. Węzły wiążące, które znajdują się w plikach dołączonych za pomocą
węzła Inline, nie mogą zostać jako pierwsze umieszczone ne górze stosu;
a.2.węzeł, który znajduje się wewnątrz prototypu, może zostać umieszczony
na górze stosu jako pierwszy;
b. Pierwszy napotkany węzeł
więżący wysyła zdarzenie isBound
o wartości TRUE.
2. Kiedy zdarzenie set_bind
węzła wiążącego przyjmie wartość TRUE:
a. jeśli węzeł wiążący nie
jest na górze stosu wiążącego:
a.1.węzeł, który aktualnie znajduje się na górze stosu, wysyła zdarzenie
isBound o wartości
FALSE;
a.2. nowy węzeł zostaje umieszczony na górze stosu (w danym momencie na
górze stosu może przebywać jedynie jeden węzeł);
a.3. nowy węzeł wysyła zdarzenie isBound
o wartości TRUE;
b. jeśli węzeł już jest
na górze stosu to zdarzenie nie wywołuje żadnego efektu.
3. Kiedy zdarzenie set_bind
węzła wiążącego przyjmie wartość FALSE:
a. węzeł zostaje usunięty
ze stosu wiążącego;
b. jeśli węzeł wiążący jest
na górze stosu:
a.1. wysyła zdarzenie isBound
o wartości FALSE;
a.2. następny węzeł znajdujący się na stosie wiążącym wchodzi na górę i
wysyła zdażenie isBound
o wartości TRUE.
4. Jeśli zdarzenie set_bind o wartości FALSE zostanie przyjęte przez węzeł, który nie należy aktualnie do stosu wiążącego, zdarzenie zostaje zignorowane i zdarzenie isBound nie jest wysłane.
5. Kiedy węzeł zastąpi inny węzeł na górze stosu, wtedy jeden węzeł wysyła zdarzenie o wartości TRUE, a drugi jednocześnie o wartości FALSE. Oba węzły w tym momencie mają identyczne etykiety czasowe.
6. Jeśli węzeł wiążący zostaje wykasowany, wtedy zachowuje się
on jakby wysłał zdarzenie set_bind
o wartości FALSE (patrz punkt 3).