Tworzenie prototypów pozwala na zdefiniowanie dowolnej liczby nowych węzłów VRML. Raz utworzony prototyp może być póżniej używany jak węzeł wbudowany w specyfikację VRML.
Definicja prototypu składa się z następujących elementów:
PROTO nazwaprototypu [ eventIn
nazwaTypuZdarzenia nazwaZdarzenia
eventOut nazwaTypuZdarzenia nazwaZdarzenia
exposedField nazwaTypuPola nazwaPola wartośćDomyślna
pole nazwaTypuPola nazwaPola wartośćDomyślna
...] {
#Pierwszy węzeł (definiuje
typ węzła dla tego prototypu)
#Dowolna liczba tras i prototypów
#Dowolna liczba węzłów o dowolnym
typie, tras i prototypów
}
Nazwy pól, zdarzeń typu exposedField, eventIn i eventOut muszą być unikalne w obrębie definiowanego prototypu. Można natomiast używać tych samych nazw dla pól w różnych prototypach:
PROTO nazwaprototypu1 [field
SFBool nazwa1
eventOut SFColor nazwa2
eventIn SfVec3f nawzwa3
exposedField SFString nazwa4]
{...}
PROTO nazwaprototypu2 [ field
SFBool nazwa1
eventOut SFColor nazwa2
eventIn SFVec3f nawzwa3
exposedField SFString nazwa4]
{...}
Zdefiniowany prototyp nie jest węzłem - stanowi jedynie jego deklarację. Można się natomiast do niego odwołać w odpowiednim miejscu kodu źródłowego traktując go jako węzeł wbudowany w specyfikację. Aby obiekt określony przez prototyp był widoczny na scenie, po zdefiniowaniu prototypu musimy przywołać ten prototyp:
#VRML V2.0 utf8
PROTO nowaKula [ field SFFloat
nowyPromien 3.0] {
Sphere
{
radius IS nowyPromien #połączenie promienia
(radius)
#z nowymPromieniem
}
}#koniec
definicji prototypu
#teraz nastąpi uaktywnienie zdefiniowanego prototypu
Shape
{
geometry nowaKula {} #tworzy kulę o promieniu
3
}
Ważną funkcję w definicji prototypu pełni pierwszy węzeł, który został w nim zadeklarowany. Węzeł ten determinuje możliwość odwoływania się do prototypu w pliku *.wrl. W powyższym przykładzie pierwszym węzłem w prototypie jest Sphere, dlatego dany prototyp możemy uaktywnić tylko tam gdzie normalnie wstawilibyśmy węzeł Sphere (czyli w polu geometry węzła Shape). Wszystkie pozostałe węzły zdefiniowane w prototypie nie podlegają przekształceniom hierarchicznym, ale można się do nich odwołać tworząc trasy lub pisząc skrypty.
Słowo o komendzie IS
Jak powiedzieliśmy wcześniej, zdarzenia eventIn i eventOut występujące przy definiowaniu prototypów mogą przyjmować i wysyłać zdarzenia. Każde zdarzenie eventIn jest powiązane ze zdarzeniem eventIn lub exposedField zdefiniowanym w węźle prototypu poprzez komendę IS. Analogicznie, każde zdarzenie eventOut może zostać powiązane z polem eventOut lub exposedField. Deklaracja eventIn definiuje zdarzenie, które może być przez prototyp przyjęte. Deklaracja eventOut natomiast definiuje zdarzenie, które może być przez prototyp wysłane.
Poniżej przedstawiony przykład prezentuje zmianę nazwy zdarzenia set_translation występującego w węźle Transform, na set_position, która będzie używana w interfejsie prototypu.
PROTO zmianaTransform [ eventIn
SFVec3f set_position ] {
Transform { set_translation IS set_position }
}
Pola znajdujące się w deklaracji prototypu określają początkowy stan odpowiednio powiązanych pól w definicji prototypu. Pola prototypu mogą być powiązane z polami występującymi w węźle komendą IS. Przy deklarowaniu prototypu muszą być określone wartości domyślne dla pól. Na przykład:
PROTO BarTransform [ exposedField
SFVec3f position 42 42 42 ] {
Transform {
translation IS position
}
}
BarTransform określa początkowe wartości (42, 42, 42) dla pola exposedField. Pole position pochodzące z deklaracji prototypu jest powiązane komendą IS z polem translation, które występuje w węźle Transform znajdującym się w definicji prototypu.
Komenda IS może pojawić się wewnątrz definicji prototypu wszędzie tam, gdzie występują pola. Komenda ta musi odsyłać do pól lub zdarzeń opisanych w deklaracji prototypu. Błędem jest odesłanie komendy IS do nieistniejącej deklaracji. Błędem jest również powiązanie pól lub zdarzeń, które posiadają inne typy. Nie należy na przykład wiązać pól lub zdarzeń typu SFBool z typem SFVec3f.
Zasięg działania prototypu
Mechanizm DEF/USE może być stosowany również wewnątrz prototypu, jednak wtedy jego działanie obejmuje jedynie ten prototyp, w którym został zdefiniowany. Dlatego węzły poprzedzone komendą DEF wewnątrz prototypu nie mogą zostać przywołane komendą USE poza tym prototypem, a węzły poprzedzone komendą DEF poza prototypem, nie mogą zostać przywołane komendą USE wewnątrz prototypu.
Można definiować również prototyp w prototypie, ale wtedy deklaracja prototypu podrzędnego odnosi się jedynie do ram wyznaczonych przez prototyp nadrzędny. Spójrzmy na przykład:
PROTO pierwszy [ ... ] {
PROTO drugi [ ...
] { ... }
...
drugi { }
# powtórzenie prototypu „drugi" w pierwszym
}
# koniec definicji prototypu „pierwszy"
drugi { }
# błąd - prototyp „drugi" może zostać
przywołany ponownie
# jedynie wewnątrz prototypu „pierwszy"
Komendę IS możemy stosować pomiędzy prototypem drugim w hierarchi definicji prototypu a prototypem trzecim - jeszcze bardziej zagnieżdżonym. Nie wolno natomiast stosować komendy IS między prototypem drugim a prototypem pierwszym. Ponadto prototyp nie może zostać ponownie przywołany wewnątrz swojej własnej definicji, na przykład błędem byłoby zdefiniowanie następującego prototypu:
PROTO pierwszy [] {
pierwszy {}
}
Prototypy zewnętrzne ( komenda EXTERNPROTO)
Oprócz własnych zdefiniowanych prototypów możemy również w naszym świecie umieścić prototypy już przez kogoś kiedyś napisane, znajdujące się w dowolnym miejscu Sieci. Służy do tego komenda EXTERNPROTO, która pozwala na ściągnięcie prototypu i dołączenie go do modelowanej przez nas sceny.
Prototyp zewnętrzny definiujemy tak samo jak prototyp wewnętrzny, z dwoma wyjątkami. Po pierwsze, implementacja typu prototypu (czyli jego pierwszego węzła) znajduje się w pliku zewnętrznym. Po drugie, nie znamy wartości domyślnych do czasu, gdy zostaną one ściągnięte razem z prototypem. Plik, z którego ściągamy prototyp, musi być plikiem VRML zawierającym definicję tego prototypu utworzoną komendą PROTO. Prototyp zewnętrzny możemy w pliku VRML tak samo wielokrotnie przywoływać, jak robiliśmy to w przypadku prototypu wewnętrznego. Wszystkie opisy zdarzeń, pól i ich wartości domyślnych umieszczone są w prototypach, które są ściągane do bieżącego pliku VRML.
Kiedy odnosimy się do pliku *.wrl poprzez URL, do zdefiniowania EXTERNPROTO zostaje użyty pierwszy napotkany prototyp. Warto też zauważyć, że nazwy dla PROTO i EXTERNPROTO nie muszą być zgodne.
Jeżeli chcemy dotrzeć do konkretnego prototypu w pliku, w którym jest zdefiniowany więcej niż jeden prototyp (biblioteki prototypów), musimy znać nazwę prototypu docelowego. Aby powiedzieć przeglądarce, który prototyp z danego pliku nas interesuje, na końcu adresu URL powinniśmy dodać znak „#" oraz nazwę tego prototypu.
Powiedzmy, że plik VRML znajdujący się pod adresem http://www.enter.pol.pl/vrml/biblioteka.wrl, wygląda następująco:
#VRML V2.0 utf8
PROTO Gold [] { Material { ... } }
PROTO Silver [] { Material { ... } }
PROTO ...
Prototypy z tej biblioteki mogą być użyte w następujący sposób:
#VRML V2.0 utf8
EXTERNPROTO NewSilver []
„http://www.enter.pol.pl/vrml/biblioteka.wrl#Silver"
...
Shape {
apperance Apperance { material NewSilver {} }
geometry ...
}
Ujemną stroną tej operacji jest fakt, iż nawet jeśli będziemy chcieli wykorzystać jeden prototyp z całej grupy prototypów do wyboru, to i tak przez Sieć musimy ściągać plik ze wszystkimi prototypami, co oczywiście odbije się na szybkości ściągania świata.
Przykłady
Czas na trochę praktyki. Ponizej przedstawiam wirtualny stół, którego definicja została zawarta w prototypie.
#VRML V2.0 utf8
PROTO Stol [] {
Transform {
children [
Transform {
translation
0.39499 -0.01201 1.23595
rotation 1
0 0 3.14159
children [
DEF Noga
Shape {
appearance Appearance {
material Material {
diffuseColor 0 0 0.5
shininess 0.2
}
}
geometry Cone { bottomRadius 0.1 height 1 }
}
]
}
Transform {
translation
-0.32105 -0.01201 1.23595
rotation 1
-4.371139e-008 4.371139e-008 3.14159
children [
USE Noga ]
}
Transform {
translation
0.39584 -5.279573e-003 -1.23007
rotation 1
-4.371139e-008 4.371139e-008 3.14159
children [
USE Noga ]
}
Transform {
translation
-0.31267 -0.02194 -1.23007
rotation 1
-4.371139e-008 4.371139e-008 3.14159
scale 1 1 1
children [
USE Noga ]
}
Transform {
translation
0.03976 0.54297 0
rotation 0
0 -1 0
children [
DEF Blat
Shape {
appearance Appearance {
material Material {
diffuseColor 0.5 0.5 0.5
transparency 0.5
shininess 0.2
}
}
geometry Box { size 1.2 0.1 3 }
}
]
}
]
}
} # koniec definicji prototypu
# Teraz uaktywniamy nasz prototyp.
# Dopiero ponizsza linia powoduje,
ze przegladarka cos wyswietli
Stol {}
Sam Prototyp został zawarty w pliku http://www.enter.pol.pl/vrml/biblioteka.wrl. Jeżeli będziecie chcieli umieścić ten stół w swoim świecie to możecie użyć w tym celu komendy EXTERNPROTO.
#VRML V2.0 utf8
EXTERNPROTO StolProto []
„http://www.enter.pol.pl/vrml/kurs/biblioteka.wrl#Stol"
Transform {
translation
0 0 -5
rotation
0 1 0 1.2
children
[
StolProto {}
]
}
Zobaczmy czy to działa. Pamiętajcie, że musicie być podłączeni do Sieci, żeby przykład działał prawidłowo.
Teraz przedstawię bardzo przydatny prototyp, który umożliwia wystartowanie dowolnego węzeła TimeSensor tuż po załadowaniu świata za pomocą zdarzenia set_startTime (bez konieczności ustawienia wartości TRUE w polu loop). Oznacza to, że węzeł TimeSensor wystartuje odrazu po załadowaniu świata ale wykona tylko jeden cykl.
PROTO StartWithoutLoop [
eventOut SFTime loadTime ]
{
DEF TS TimeSensor {
loop TRUE
time IS loadTime
}
DEF S Script {
eventIn SFTime time
eventOut SFBool enoughAlready
url "javascript:
function time(t) {
enoughAlready = false;
}
"
}
ROUTE TS.time TO S.time
ROUTE S.enoughAlready TO TS.enabled
}
StartWithoutLoop {}