Węzły wiążące

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).