Planowanie projektów
Firma CrystalPoint jest do Państwa dyspozycji w zaplanowaniu działalności internetowej, analizie potrzeb i wymagań, opracowaniu funkcjonalności czy grafiki. Prosimy o kontakt z nami.
Napisz do nas...Oferta
CrystalPoint specjalizuje się w tworzeniu zaawansowanych aplikacji internetowych, tworzonych z użyciem sprawdzonych technologii.
- Aplikacje internetowe
- Strony i wizytówki WWW
- Hosting
- Serwery VPS
Portfolio
Nasze usługi mają potwierdzenie w zrealizowanych i utrzymywanych projektach. Projektowaliśmy:
- Sklepy internetowe
- Aplikacje dedykowane
- Serwisy społecznościowe
- Narzędzia dla programistów
Papervision 3D: galeria
Ostatnimi czasy bardzo popularne stało się tworzenie animacji Flash z wykorzystaniem Papervision 3D. Można tworzyć bardzo złożone i ciekawe efekty umieszczone w przestrzeni trójwymiarowej zamiast typowego dla Flash rozkladu 2D. Wiele osób zapewne zastanawia się, co tak właściwie daje nam możliwość wykorzystania 3 wymiaru w sferze zastosowań do jakiej Flash został zaprojektowany. Odpodzwiedź jest bardzo prosta, możemy wziąść większość rzeczy, które do tej pory zawsze były tworzone w sposób tradycyjny i dodać im kolejny wymiar, zarówno przestrzenny jak i twórczy.
Weźmy na płótno tak częsty element stron www jak galeria. Wiele stron posiada galerie przygotowane przy użyciu Flash/Actionscript, ponieważ można dodać do elementu czysto graficznego, dodatkowe, przyjemne dla oczu efekty. Zobaczmy w takim razie co można zrobić z galerią jeśli nagle pojawi nam się możliwość wykorzystania trzeciego wymiaru: (pomysł zaporzyczony z Aero)
Efekt całkiem fajny i co najważniejsze, przy wykorzystaniu PP3D całkiem łatwy do uzyskania. Nasz projekt będzie się składał z 4 plików. Głownego pliku FLA, Main.as - głowna klasa projektu, flipController.as - klasa odpowiedzialna za inicjowanie elementów, i flipElements.as - klasa reprezentująca poszczególne fotografie w galerii. Użyjemy w projekcie klasy Tweener, której opisywać nie będę - leży to poza zakresem tego artykułu.
Main.as
/* PaperVision 3D */
import org.papervision3d.scenes.*;
import org.papervision3d.cameras.*;
import org.papervision3d.objects.*;
import org.papervision3d.objects.special.*;
import org.papervision3d.objects.primitives.*;
import org.papervision3d.materials.*;
import org.papervision3d.materials.special.*;
import org.papervision3d.materials.shaders.*;
import org.papervision3d.materials.utils.*;
import org.papervision3d.lights.*;
import org.papervision3d.render.*;
import org.papervision3d.view.*;
import org.papervision3d.events.*;
import org.papervision3d.core.utils.*;
import org.papervision3d.core.utils.virtualmouse.VirtualMouse;
/* Tweener */
import caurina.transitions.Tweener;
/* Flash Libraries */
import flash.display.MovieClip
import flash.utils.Timer;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
/* Project specific Classes */
import flip.flipElement;
import flip.flipController;
public class Main extends MovieClip
{
public var renderer:BasicRenderEngine;
public var viewport:Viewport3D;
public var scene:Scene3D;
public var cam:Camera3D;
private var c:flipController;
/* config variables */
private var vV:int = 2;
private var hV:int = 2;
private var eW:int = 320;
private var eH:int = 200;
private var numOfElements = 5;
private var numOfRenderedElements = 4;
public function Main()
{
/* papervision init */
this.renderer = new BasicRenderEngine();
this.viewport = new Viewport3D(
stage.stageWidth,
stage.stageHeight,
false,
true
);
this.addChild(this.viewport);
this.scene = new Scene3D();
this.cam = new Camera3D();
this.cam.zoom = 80;
/* controller init */
this.c = new flipController(
0,
numOfRenderedElements-1,
numOfElements,
numOfRenderedElements
);
/* element init */
for (var i:uint=0; i < this.numOfElements; i++)
{
var e:flipElement = new flipElement(
this.eH,
this.eW,
i,
this.hV,
this.vV,
this.c,
i+1
);
e.init(i*-200,i*150,i*800,this.scene);
this.c.addElement(e);
}
/* init reRender for every frame */
this.addEventListener(Event.ENTER_FRAME, this.reRender);
/* init fliping options */
this.addEventListener(MouseEvent.MOUSE_WHEEL, this.flip);
}
private function flip(evt:MouseEvent)
{
this.removeEventListener(MouseEvent.MOUSE_WHEEL, this.flip);
var mouseWheelBug = new Timer(1);
mouseWheelBug.addEventListener(
TimerEvent.TIMER,
this.reEnableMouse
);
mouseWheelBug.start();
if (evt.delta > 0)
this.c.goUp();
else
this.c.goDown();
}
/* flash player 10 firefox hack (bug workaround) */
private function reEnableMouse(evt:TimerEvent)
{
this.addEventListener(MouseEvent.MOUSE_WHEEL, this.flip);
}
private function reRender(evt:Event)
{
renderer.renderScene(this.scene, this.cam, this.viewport);
}
}
Cały plik Main.as umieściłem powyżej, myślę jednak, że poszczególne fragmenty mogą wymagać pewnych wyjaśnień. Zacznijmy więc od deklarowanych zmiennych:
Main.as
/* config variables */
private var vV:int = 2;
private var hV:int = 2;
private var eW:int = 320;
private var eH:int = 200;
private var numOfElements = 5;
private var numOfRenderedElements = 4;
Zmienne vV i hV okreslają ilość segmentów użytych do opisania obiektów typu Plane. Im więcej tym lepiej będą odwzorowane niektóre przekształcenia ale niestety znacząco spada wydajność.
Na nasze potrzeby spokojnie wystarczy reprezentacja 2x2. eW i eH przechowują wymiary planów (eW - szerokość, eH - wysokość). Pozostałe dwie zmienne definiują ile elementów zostanie utworzonych
i ile elementów będzie w jednym momencie wyświetlone. Kod został przygotowany z założeniem: numOfElements >= numOfRenderedElements + 1.
Poniżej zamieszczony jest przykład dla zmiennych o wartościach:
numOfElements = 9;
numOfRenderedElements = 3;
Uważne osoby zauważą kilka zmian w zachowaniu. Kliknięcie na element zawsze przesyła o jeden w dół zamiast bezpośrednio do klikniętego. Zdjęcia zostały zastąpione monochromatycznymi planami. Dodatkowo istnieje możliwość powiększenia tylko pierwszego zdjęcia a nie tak jak w pierwszym podglądzie automatycznego przeskoku z powiększeniem jak po podwójnym kliknięciu. Zmiany te wprowadziłem specjalnie, Dodanie brakujących funkcji pozostawiam czytelnikom jako ćwiczenie. Nie mają one wpływu na omawianą mechanikę animacji a pozwolą chętnym troszkę rozruszać szare komórki.
Przejdźmy do konstruktora. Pierwszym etapem jest inicjalizacja wszystkich niezbędnych elementów PP3D. Nie będę się rozwodził nad tymi elementami ponieważ istnieje już mnóstwo źródeł informacji na ten temat. Przejdźmy do inicjalizacji kontrolera.
Main.as
/* controller init */
this.c = new flipController(
0,
numOfRenderedElements-1,
numOfElements,
numOfRenderedElements
);
Dwa ostatnie parametry myślę, że są oczywiste. Pierwszy parametr określa indeks pierwszego wyświetlonego elementu a parametr drugi określa indeks ostatniego wyświetlonego elementu. Oczywiście można założyć, że na start wyświetlany będzie pierwszy element a ostatnim wyświetlonym będzie pierwszy + ilość wyświetlonych na raz, pozwoliło by to pominąć podawanie tych dwóch wartości do kontruktora. Ja osobiście preferuję jak takie rzeczy są podawane.
Main.as
/* element init */
for (var i:uint=0; i < this.numOfElements; i++)
{
var e:flipElement = new flipElement(
this.eH,
this.eW,
i,
this.hV,
this.vV,
this.c,
i+1
);
e.init(i*-200,i*150,i*800,this.scene);
this.c.addElement(e);
}
Następnie tworzymy i inicjalizujemy elementy. Co dokładnie robi metoda init() zobaczymy przy okazji omawiania kodu klasy flipElement. Konstruktor flipElement przyjmuje następujące parametry: (w kolejności podawania) szerokość płaszczyzny, wysokość płaszczyzny, indeks elementu, ilość segmentów reprezentujących płaszczyznę w poziomie, ilość segmentów reprezentujących płaszczyznę w pionie, obiekt kontrollera, aktualna pozycja wyświetlenia elementu. Na sam koniec dodajemy element do kontrollera.
Main.as
/* init reRender for every frame */
this.addEventListener(Event.ENTER_FRAME, this.reRender);
/* init fliping options */
this.addEventListener(MouseEvent.MOUSE_WHEEL, this.flip);
Podłączamy obsługę odpowiednich zdarzeń. ENTER_FRAME jest potrzebne do renderowania sceny a MOUSE_WHEEL pozwoli nam wykonywać zmianę obrazków przy użyciu scrolla. Metoda reRender() jest dość oczywista, zerknijmy jednak na flip().
Main.as
private function flip(evt:MouseEvent)
{
this.removeEventListener(MouseEvent.MOUSE_WHEEL, this.flip);
var mouseWheelBug = new Timer(1);
mouseWheelBug.addEventListener(
TimerEvent.TIMER,
this.reEnableMouse
);
mouseWheelBug.start();
if (evt.delta > 0)
this.c.goUp();
else
this.c.goDown();
}
Flash Player 10 dla Firefox 3 ma bug, który powoduje że zdarzenie poruszenie kółka myszki jest uruchamiane dwa razy. Niestety efektem tego jest wywołanie tej funkcji dwukrotnie a więc automatyczny przeskok o 2 elementy.
Bardzo możliwe, że Adobe poprawił już ten problem, jednak w momencie pisania kodu nadal on występował. Aby temu zapobiec wprowadzamy dodatkowy fragment kodu, który usuwa event handler dla poruszenia kółka w momencie gdy
sam handler zostanie uruchomiony. Następnie tworzymy Timer z bardzo małym czasem trwania i ustawiamy kolejną funkcję, która się wykona gdy Timer upłynie. Funkcja ta nie robi nic ponad ponowne ustanowienie handlera na MOUSE_WHEEL.
Tym sposobem drugie/zbędne uruchomienie metody flip() wpadnie w ten okres kiedy handler nie istnieje.
Ostatecznie na podstawie wartości delta określamy, w którą stronę kółko zmieniło swoją pozycję i wywołujemy odpowiednią metodę kontrolera.
Najwyższy czas zerknąć na kod klasy flipController.
flipController.as
public class flipController
{
public var current:int;
public var lastShown:int;
public var allElements:int;
public var rendered:int;
private var elementA:Array;
public function flipController(
current:int=0, lastShown:int=0, all:int=0, rend:int=0
)
{
this.elementA = new Array();
this.current = current;
this.lastShown = lastShown;
this.allElements = all;
this.rendered = rend;
}
public function addElement(e:flipElement)
{
this.elementA.push(e);
}
public function goDown()
{
for(var i:uint=0; i<this.elementA.length; i++)
{
this.elementA[i].goDown(1);
this.elementA[i].removeClickEvens();
}
this.current++;
this.lastShown++;
if (this.lastShown >= this.allElements)
this.lastShown = 0;
if (this.current >= this.allElements)
this.current = 0;
this.elementA[lastShown].appearAway();
this.elementA[current].setupAsCurrent();
}
public function goUp()
{
for(var i:uint=0; i<this.elementA.length; i++)
{
this.elementA[i].goUp(1);
this.elementA[i].removeClickEvents();
}
this.current--;
this.lastShown--;
if (this.lastShown < 0)
this.lastShown = this.allElements-1;
if (this.current < 0)
this.current = this.allElements-1;
this.elementA[current].appearClose();
this.elementA[current].setupAsCurrent();
}
}
Kontruktor robi tylko jedną rzecz. Przyjmuje parametry a następnie zamienia je na stałe atrybuty obiektu. Metoda addElement() nie wymaga wyjaśnień. Dopiero metody goDown() i goUp() trzeba przeanalizować. Zerknijmy na goDown().
flipController.as
for(var i:uint=0; i<this.elementA.length; i++)
{
this.elementA[i].goDown(1);
this.elementA[i].removeClickEvens();
}
Przechodzimy przez wszystkie elementy i na każdym z nich wykonujemy metodę odpowiedzialną za przejścia w dół. Następnie usuwamy na tych elementach handlery zdarzeń pozwalających na powiększenie/zmniejszenie ponieważ chcemy, żeby ta funkcja działała tylko dla aktualnie pierwszego elementu.
flipController.as
this.current++;
this.lastShown++;
Aktualizujemy parametry przechowujące aktualnie pierwszy i ostatni wyświetlony element. Jeśli pokazywaliśmy elementy od 1 do 3 to po zejściu pokazujemy od 2 do 4;
flipController.as
if (this.lastShown >= this.allElements)
this.lastShown = 0;
if (this.current >= this.allElements)
this.current = 0;
Dokonujemy ponownej aktualizacji tych samych parametrów, tym razem uwzględniając, że animacja powinna się zapętlać. Jeśli po poprzedniej aktualizacji indeks ostatniego elementu wksazuje na element, który nie istnieje to oczywiste jest, że nowym ostatnim elementem będzie pierwszy indeks tabeli elementów. Podobnie jest z aktualnie pierwszym elementem. Jeśli pierwszym elementem miałby być nieistniejący element to pokazany na froncie jest element zerowy. Na podstawie tak uzyskanych wartości będziemy mogli dokonać ostatnich zmian:
flipController.as
this.elementA[lastShown].appearAway();
this.elementA[current].setupAsCurrent();
Wiemy już, który element będzie pokazany ostatni, więc możemy na nim wywołać metodę, która materializuje go w oddali razem z przemieszczeniem go na poprawną pozycję ostatniego wyświetlonego elementu.
Ostatnim krokiem jest wywołanie metody ustanawiającej na pierwszym elemencie handlerow pozwalający na jego powięszkenie i pomniejszenie.
goUp() działa w analogiczny sposób.
Teraz gdy mamy już wyjaśniony kontroler pora rzucić okiem na najciekawszy fragment kodu - klasę flipElement.as:
flipElement.as
public class flipElement extends MovieClip
{
private var index:int;
private var eHeight:int;
private var eWidth:int;
private var hVertices:int;
private var vVertices:int;
private var plane:Plane;
private var mate:ColorMaterial;
private var controller:flipController;
private varcurrentPos:int;
public function flipElement(
h:int,
w:int,
index:int,
hV:int,
vV:int,
c:flipController,
currentPos:int
)
{
this.eWidth = w;
this.eHeight = h;
this.hVertices = hV;
this.vVertices = vV;
this.index = index;
this.currentPos = currentPos;
this.controller = c;
}
public function init(xPos:int, yPos:int, zPos:int, scene:Scene3D)
{
this.mate = new ColorMaterial(
Math.random()*0xeeeeee+0x111111);
this.mate.interactive = true;
this.plane = new Plane(
mate,
this.eWidth,
this.eHeight,
this.vVertices,
this.hVertices
);
this.plane.x = xPos;
this.plane.y = yPos;
this.plane.z = zPos;
this.plane.useOwnContainer = true;
scene.addChild(this.plane);
if (this.index >= this.controller.rendered)
this.plane.alpha = 0;
if (this.index == 0)
this.setupAsCurrent();
else
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
}
public function clicked(evt:InteractiveScene3DEvent)
{
this.controller.goDown();
}
public function function setupAsCurrent()
{
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
}
public function function removeClickEvents()
{
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
}
public function goDown(times:int)
{
this.currentPos--;
if (
( this.currentPos > 0)
&&
( this.currentPos < this.controller.rendered)
)
{
Tweener.addTween(this.plane, {x:(this.currentPos*-200)+200*times, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:(this.currentPos*150)-150*times, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:(this.currentPos*800)-800*times, time:2, transition:"easeOutExpo"} );
}
else
{
Tweener.addTween(this.plane, {x:200, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:-150, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:-800, time:2, transition:"easeOutExpo"} )
Tweener.addTween(this.plane, {alpha:0, time:2, transition:"easeOutExpo"} );
}
}
public function goUp(times:int)
{
if (
(this.currentPos > 0)
&&
(this.currentPos < this.controller.rendered)
)
{
Tweener.addTween(this.plane, {x:((this.currentPos-1)*-200)-200*times, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:((this.currentPos-1)*150)+150*times, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:((this.currentPos-1)*800)+800*times, time:2, transition:"easeOutExpo"} );
}
else
{
Tweener.addTween(this.plane, {x:(this.controller.rendered)*-200, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:(this.controller.rendered)*150, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:(this.controller.rendered)*800, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {alpha:0, time:2, transition:"easeOutExpo"} );
}
this.currentPos++;
}
public function appearAway()
{
this.currentPos = this.controller.rendered;
this.plane.x = (this.controller.rendered)*-200;
this.plane.y = (this.controller.rendered)*150;
this.plane.z = (this.controller.rendered)*800;
Tweener.addTween(this.plane, {x:this.plane.x+200, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:this.plane.y-150, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:this.plane.z-800, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {alpha:1, time:2, transition:"easeOutExpo"} );
}
public function appearClose()
{
this.currentPos = 1;
this.plane.alpha = 0;
this.plane.x = 200;
this.plane.y = -150;
this.plane.z = -800;
Tweener.addTween(this.plane, {x:this.plane.x-200, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {y:this.plane.y+150, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {z:this.plane.z+800, time:2, transition:"easeOutExpo"} );
Tweener.addTween(this.plane, {alpha:1, time:2, transition:"easeOutExpo"} );
}
private function enlarge(evt:InteractiveScene3DEvent)
{
Tweener.addTween(this.plane, {z:-400, time:1, transition:"easeOutExpo"} );
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
}
private function shrink(evt:InteractiveScene3DEvent)
{
Tweener.addTween(this.plane, {z:0, time:1, transition:"easeOutExpo"} );
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
}
}
Deklaracje zmiennych nie wymagają wyjaśnień. Podobnie konstruktor, którego jedyną rolą jest przypisanie parametrów do lokalnych atrybutów. Znacznie ważniejsza jest metoda init()
flipElement.as
public function init(xPos:int, yPos:int, zPos:int, scene:Scene3D)
{
this.mate = new ColorMaterial(
Math.random()*0xeeeeee+0x111111);
this.mate.interactive = true;
this.plane = new Plane(
mate,
this.eWidth,
this.eHeight,
this.vVertices,
this.hVertices
);
this.plane.x = xPos;
this.plane.y = yPos;
this.plane.z = zPos;
this.plane.useOwnContainer = true;
scene.addChild(this.plane);
if (this.index >= this.controller.rendered)
this.plane.alpha = 0;
if (this.index == 0)
this.setupAsCurrent();
else
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
}
Najpierw tworzymy nowy materiał. W przypadku podanego powyżej kodu jest to zwykła jednokolorowa powierzchnia. Dla urozmaicenia sam kolor jest wybierany losowo w taki sposób aby uniknąć kolorów zbyt ciemnych.
Aby uzyskać taki efekt jak w pierwszym przykładzie koniecznie jest użycie innego materiału, np. BitmapFileMaterial. Musimy również aktywować interaktywność materiału, co pozwili na rejestrowanie zdarzeń.
Kolejnym krokiem jest utworzenie nowej płaszczyzny, na która wcześniej przygotowany materiał zostanie nałożony i dodanie jej do renderowanjej sceny. Jeśli indeks elementu przekracza ilość wyświetlanych elementów to element staje się niewidoczny.
Następnie ustawiamy odpowienią obsługę zdarzeń w zależności od pozycji elementu. Pierwszy element ma się powiększać i zmniejszać jeśli zostanie kliknięty, każdy inny ma wywołać funkcję przejścia o jeden w dół.
flipElement.as
public function clicked(evt:InteractiveScene3DEvent)
{
this.controller.goDown();
}
Metoda clicked() jest wywoływana tylko jeśli kliknięty został element, który nie jest aktualnie pierwszym w kolejce wyświetlania. Podobnie wywoływane są następujące funkcje, jeśli kliknięty zostanie pierwszy element:
flipElement.as
private function enlarge(evt:InteractiveScene3DEvent)
{
Tweener.addTween(this.plane, {z:-400, time:1, transition:"easeOutExpo"} );
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
}
private function shrink(evt:InteractiveScene3DEvent)
{
Tweener.addTween(this.plane, {z:0, time:1, transition:"easeOutExpo"} );
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
}
Jak widać funkcje te ustanawiają się wzajemnie jako handlery zdarzenia kliknięcia. Jeśli obrazek jest powiększony to następne kliknięcie go pomniejszy i vice versa.
flipElement.as
public function function setupAsCurrent()
{
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
}
public function function removeClickEvents()
{
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.enlarge
);
this.plane.removeEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.shrink
);
this.plane.addEventListener(
InteractiveScene3DEvent.OBJECT_PRESS,
this.clicked
);
}
Powyższe funkcje były używane w metodach goUp() i goDown() klasy flipController. removeClickEvents() jest wywołane na każeym elemencie razem z wykonaniem przesunięcia. Dopiero po zakończeniu tego procesu ustalane jest, który element jest aktualnie wyświetlony jako pierwszy i na nim wywołana jest metoda setupAsCurrent(), która ustanawia zachowania speceyficzne dla tego jednego elementu.
Pozostałe metody odpowiedzialne są za odpowiednie animacje - przesunięcia w dół, w góre a także pojawienie się elementu w oddali jak i blisko. Mogą one wygladać dość skomplikowanie jednak nie ma tam nic ponad odpowienie manipulowanie klasą Tweener. Konkretnych animacji nie będę już wyjaśniał, każdy powinien być sam w stanie zrozumieć, skąd wzięły się poszczególne wartości.
Mam nadzieję, że artykuł ten przybliżył trochę temat Papervision 3D i możliwych jego zastosowań. Przykłady galerii tego typu można by mnożyć a to dopiero początek. Nic nie stoi na przeszkodzie aby wykorzystująć takie możliwości wykonać cały interfejs aplikacji w trzech wymiarach.