Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Redaktor prowadzący: Michał Mrowiec Projekt okładki: Studio Gravite / Olsztyn Obarek, Pokoński, Pazdrijowski, Zaprucki Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail:
[email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie?flacpo_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Kody źródłowe wybranych przykładów dostępne są pod adresem: ftp://ftp.helion.pl/przyklady/flacpo.zip ISBN: 978-83-283-1132-9 Copyright © Helion 2015
Poleć książkę na Facebook.com Kup w wersji papierowej Oceń książkę
Księgarnia internetowa Lubię to! » Nasza społeczność
Dla Joli
4
Flash i ActionScript. Aplikacje 3D od podstaw
Spis treści Rozdział 1. Wprowadzenie ...............................................................................13 Dostępne biblioteki 3D ...................................................................................................................14 Away3D ......................................................................................................................................14 Alternativa3D ............................................................................................................................16 Papervision3D ...........................................................................................................................18 FIVe3D .......................................................................................................................................20 Flare3D .......................................................................................................................................21 Sandy3D .....................................................................................................................................23 Sophie3D ....................................................................................................................................24 Wybór biblioteki 3D .......................................................................................................................25 Pobieranie silnika Away3D ............................................................................................................26 Away3D FP9, FP10 czy może FP11? ......................................................................................27 Pobieranie Away3D 3.6.0 z Away3D.com .............................................................................27 Pobieranie Away3D 3.6.0 z github.com .................................................................................28 Pobieranie Away3D 3.6.0 z SVN .............................................................................................28 Instalacja biblioteki Away3D .........................................................................................................31 Konfiguracja w Adobe Flash CS4 i CS5 .................................................................................31 Konfiguracja we FlashDevelop ................................................................................................32 Konfiguracja w Adobe Flash Builder 4 ..................................................................................35 Podsumowanie ................................................................................................................................37
Rozdział 2. Podstawy biblioteki Away3D .......................................................... 39 Podstawowe komponenty ..............................................................................................................39 View3D .......................................................................................................................................40 Scene3D ......................................................................................................................................43 Camera3D ..................................................................................................................................45 Object3D ....................................................................................................................................46 ObjectContainer3D ...................................................................................................................49 Podstawowa budowa aplikacji w Away3D ..................................................................................50 Położenie obiektów w przestrzeni .................................................................................................53 Układ współrzędnych w Away3D .................................................................................................54 Podsumowanie ................................................................................................................................59
6
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 3. Obiekty .........................................................................................61 Base ....................................................................................................................................................62 Vertex ..........................................................................................................................................62 Mesh ............................................................................................................................................65 Segment ......................................................................................................................................68 Face .............................................................................................................................................72 Sprites ................................................................................................................................................76 Klasa bazowa ..............................................................................................................................77 Sprite3D ......................................................................................................................................81 MovieClipSprite ........................................................................................................................83 DirectionalSprite .......................................................................................................................85 Primitives ..........................................................................................................................................87 Przykład ......................................................................................................................................88 AbstractPrimitive ......................................................................................................................94 LineSegment ............................................................................................................................100 Trident ......................................................................................................................................101 Triangle .....................................................................................................................................102 Plane ..........................................................................................................................................104 GridPlane .................................................................................................................................106 RegularPolygon .......................................................................................................................107 Cube ..........................................................................................................................................109 RoundedCube ..........................................................................................................................111 Sphere .......................................................................................................................................112 GeodesicSphere .......................................................................................................................114 Torus .........................................................................................................................................115 TorusKnot ................................................................................................................................116 Cylinder ....................................................................................................................................118 Cone ..........................................................................................................................................120 SeaTurtle ...................................................................................................................................122 Arrow ........................................................................................................................................123 WirePlane .................................................................................................................................125 WireCube .................................................................................................................................126 WireCone .................................................................................................................................128 WireCylinder ...........................................................................................................................129 WireSphere ..............................................................................................................................130 WireRegularPolygon ..............................................................................................................132 WireTorus ................................................................................................................................133 Skybox .............................................................................................................................................134 Skybox .......................................................................................................................................135 Skybox6 .....................................................................................................................................138 Podsumowanie ..............................................................................................................................139
Spis treści
7
Rozdział 4. Materiały ....................................................................................143 Przygotowanie zasobów dla aplikacji .........................................................................................144 Dodawanie plików do zasobów w programie Adobe Flash ..............................................144 Odwoływanie się do zasobów w kodzie źródłowym aplikacji ..........................................147 Przykład ..........................................................................................................................................150 Linie .................................................................................................................................................164 WireframeMaterial .................................................................................................................164 WireColorMaterial .................................................................................................................165 Kolor ...............................................................................................................................................167 ColorMaterial ..........................................................................................................................167 EnviroColorMaterial ..............................................................................................................168 Bitmapy ...........................................................................................................................................170 BitmapMaterial ........................................................................................................................170 BitmapFileMaterial .................................................................................................................172 EnviroBitmapMaterial ............................................................................................................174 GlassMaterial ...........................................................................................................................175 TransformBitmapMaterial .....................................................................................................178 Animowane ....................................................................................................................................181 MovieMaterial .........................................................................................................................181 AnimatedBitmapMaterial ......................................................................................................183 VideoMaterial ..........................................................................................................................185 Mieszanie materiałów ...................................................................................................................187 Podsumowanie ..............................................................................................................................188
Rozdział 5. Światło ....................................................................................... 191 Rodzaje świateł ..............................................................................................................................192 Klasa bazowa dla przykładów ................................................................................................192 AmbientLight3D .....................................................................................................................194 PointLight3D ...........................................................................................................................196 DirectionalLight3D .................................................................................................................199 Materiały reagujące na światło ....................................................................................................202 Klasa bazowa dla przykładów ................................................................................................202 PhongColorMaterial ...............................................................................................................204 ShadingColorMaterial ............................................................................................................206 PhongBitmapMaterial ............................................................................................................207 WhiteShadingBitmapMaterial ..............................................................................................209 PhongMovieMaterial ..............................................................................................................210 Dot3BitmapMaterial ...............................................................................................................212 Dot3BitmapMaterialF10 ........................................................................................................214 Dot3MovieMaterial ................................................................................................................216 PhongMultiPassMaterial ........................................................................................................218 Podsumowanie ..............................................................................................................................220
8
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 6. Modele i animacje ......................................................................... 223 3ds Max ....................................................................................................................................224 Maya ..........................................................................................................................................224 LightWave ................................................................................................................................225 Blender ......................................................................................................................................226 MilkShape 3D ..........................................................................................................................227 CharacterFX .............................................................................................................................228 Google SketchUp .....................................................................................................................229 PreFab3D ..................................................................................................................................230 Low poly i High poly .....................................................................................................................231 Format plików obsługiwanych w Away3D ................................................................................232 3DS ............................................................................................................................................232 ASE ............................................................................................................................................232 OBJ ............................................................................................................................................232 DAE ...........................................................................................................................................232 MD2 ..........................................................................................................................................233 AWD .........................................................................................................................................233 AS ..............................................................................................................................................233 Konwertowanie modelu do klasy ActionScript ........................................................................233 Klasa AS3Exporter ..................................................................................................................233 Eksport z programu PreFab3D .............................................................................................241 Eksport z programu Blender .................................................................................................244 Eksport z programu Autodesk 3ds Max ..............................................................................246 Stosowanie modeli 3D w Away3D ..............................................................................................248 Przykładowa aplikacja ............................................................................................................248 3DS ............................................................................................................................................263 OBJ ............................................................................................................................................266 ASE ............................................................................................................................................268 DAE ...........................................................................................................................................269 MD2 ..........................................................................................................................................271 AWD .........................................................................................................................................273 Stosowanie animacji modeli ........................................................................................................275 AnimationData i AnimationDataType ................................................................................275 AnimationLibrary ...................................................................................................................277 Animator i AnimatorEvent ...................................................................................................277 Zastosowanie animacji w przykładzie ..................................................................................279 Podsumowanie ..............................................................................................................................280
Spis treści
9
Rozdział 7. Praca z obiektami ........................................................................ 283 Biblioteki do animacji ...................................................................................................................284 Pobieranie i instalacja biblioteki GreenSock Tweening Platform ....................................284 Wybór biblioteki GreenSock Tweening Platform ..............................................................285 Implementacja TweenMax ....................................................................................................285 Klasa bazowa ..................................................................................................................................286 Przemieszczanie .............................................................................................................................294 Właściwości ..............................................................................................................................294 Metody ......................................................................................................................................295 Obracanie .......................................................................................................................................301 Właściwości ..............................................................................................................................302 Metody ......................................................................................................................................303 Skalowanie ......................................................................................................................................309 Właściwości ..............................................................................................................................309 Metody ......................................................................................................................................310 Macierz transformacji ...................................................................................................................311 Czym jest macierz transformacji? .........................................................................................311 Tworzenie macierzy transformacji .......................................................................................312 Modyfikowanie powierzchni obiektu .........................................................................................313 PathExtrusion ..........................................................................................................................313 HeightMapModifier ................................................................................................................319 Elevation i SkinExtrude ..........................................................................................................325 Explode i Merge .......................................................................................................................331 Podsumowanie ..............................................................................................................................337
Rozdział 8. Interaktywność ........................................................................... 339 Używanie klawiatury .....................................................................................................................340 Klasa KeyboardEvent ..............................................................................................................340 Klasa Keyboard ........................................................................................................................341 Klasa KeyLocation ...................................................................................................................343 Sterowanie statkiem kosmicznym w przestrzeni ................................................................344 Używanie myszy ............................................................................................................................352 MouseEvent .............................................................................................................................352 Obracanie i skalowanie statku kosmicznego .......................................................................353 MouseEvent3D ........................................................................................................................359 Metody dla zdarzeń MouseEvent3D ....................................................................................361 Malowanie na trójwymiarowych obiektach ........................................................................361 Rozmieszczanie obiektów na planszy ...................................................................................367 Podsumowanie ..............................................................................................................................384
10
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 9. Kamery ...................................................................................... 387 Podstawy działania kamer w Away3D .......................................................................................388 Rejestrowanie sceny w Away3D ............................................................................................388 Podstawowe pojęcia i właściwości kamer w Away3D ........................................................389 Rodzaje soczewek ..........................................................................................................................406 Przykład ....................................................................................................................................406 AbstractLens ............................................................................................................................414 ZoomFocusLens i PerspectiveLens .......................................................................................415 SphericalLens ...........................................................................................................................415 OrthogonalLens .......................................................................................................................417 Rodzaje kamer ...............................................................................................................................418 Camera3D ................................................................................................................................418 TargetCamera3D .....................................................................................................................420 HoverCamera3D .....................................................................................................................421 SpringCam ...............................................................................................................................422 Przykładowe zastosowania kamer ...............................................................................................425 Kamera z perspektywy pierwszej osoby ...............................................................................425 Kamera z perspektywy osoby trzeciej ...................................................................................443 Kamera stosowana w grach typu action RPG .....................................................................451 Podsumowanie ..............................................................................................................................461
Rozdział 10. Dźwięk ..................................................................................... 465 Klasy obsługujące dźwięk .............................................................................................................465 Sound3D ...................................................................................................................................465 SimplePanVolumeDriver .......................................................................................................467 Przykłady zastosowań ...................................................................................................................468 Dźwięk 3D ................................................................................................................................468 Kontrolowanie obiektów dźwiękiem ....................................................................................477 Podsumowanie ..............................................................................................................................487
Rozdział 11. Tekst ........................................................................................ 489 Przygotowanie czcionki ................................................................................................................490 Sposoby wyświetlania tekstu ........................................................................................................492 Tekst płaski ..............................................................................................................................492 Tekst przestrzenny ..................................................................................................................496 Materiały dla tekstu ................................................................................................................497 Tekst jako BitmapData ...........................................................................................................498 Tekst w obiekcie MovieClip ..................................................................................................499 Deformowanie tekstu ....................................................................................................................499 Podsumowanie ..............................................................................................................................503
Spis treści
11
Rozdział 12. Optymalizacja ............................................................................ 505 Statystyki aplikacji .........................................................................................................................506 Away3D Project stats ..............................................................................................................506 AwayStats .................................................................................................................................508 Hi-ReS-Stats .............................................................................................................................509 Wyświetlanie zawartości ..............................................................................................................510 Sposoby przycinania widoku .................................................................................................510 Stosowanie filtrów ...................................................................................................................516 Ogólne wskazówki ..................................................................................................................521 Obiekty ............................................................................................................................................522 Stosowanie obiektów LOD ....................................................................................................522 Stosowanie narzędzia Weld ...................................................................................................527 Ogólne wskazówki ..................................................................................................................530 Tekstury i światło ..........................................................................................................................531 Ogólne wskazówki ..................................................................................................................531
Rozdział 13. Więcej zabawy z 3D .................................................................. 533 Fizyka i kolizje ...............................................................................................................................534 Wykrywanie kolizji w Away3D .............................................................................................534 Dodatkowe silniki fizyki .........................................................................................................538 Rzeczywistość rozszerzona ...........................................................................................................541 FLARToolKit ...........................................................................................................................543 FLARManager .........................................................................................................................544 IN2AR .......................................................................................................................................545 Kontrolery ......................................................................................................................................546 Xbox Kinect .............................................................................................................................546 Wii Remote ..............................................................................................................................547 Stage3D ...........................................................................................................................................548 O przygotowaniu środowiska do Stage3D słów kilka ........................................................548 Co to jest Stage3D? ..................................................................................................................555 Gdzie umieszczony jest Stage3D? .........................................................................................556 Ograniczenia związane ze Stage3D .......................................................................................557 Jak korzystać ze Stage3D? ......................................................................................................558 Podstawowe pojęcia związane ze Stage3D ...........................................................................559 Podstawowy szkielet dla aplikacji korzystającej ze Stage3D .............................................563 AGAL ..............................................................................................................................................565 Co to jest AGAL? .....................................................................................................................566 Podstawowe komendy języka AGAL ...................................................................................566
12
Flash i ActionScript. Aplikacje 3D od podstaw Rejestry w języku AGAL ........................................................................................................567 Zastosowanie kodu AGAL wraz z ActionScript .................................................................570 Away3D 4.x ....................................................................................................................................578 Instalacja biblioteki .................................................................................................................579 Podstawowy przykład z kulą ziemską ..................................................................................579
Skorowidz ....................................................................................................591
Rozdział 1. Wprowadzenie Grafika trójwymiarowa to jedna z głównych dziedzin grafiki komputerowej. Jest powszechnie stosowana dzisiaj i stale rozwijana; ma jeden cel — jak najbardziej zadziwić widza i zacierać granice między światem rzeczywistym a wirtualnym. Technika oraz postęp w dziedzinie grafiki komputerowej często zapierają dech w piersiach. Od początku istnienia animowanych filmów twórcy stale podnoszą sobie poprzeczkę, nam, widzom, sprawiając frajdę podczas oglądania ich dzieł. Kinematografia to niejedyna dziedzina, w której grafika trójwymiarowa robi ogromne postępy. W produkcji gier komputerowych producenci również ciągle konkurują, tworząc coraz lepsze silniki 3D. Efekty ich pracy w większości przypadków budzą prawdziwy zachwyt. Dodatkowo rozwojowi grafiki w grach sprzyja taniejący sprzęt komputerowy. Postęp nie ogranicza się do konkretnych dziedzin i obejmuje nowe obszary. Jednym z nich stały się strony internetowe oraz gry. Sprzyjające warunki sprzętowe w połączeniu z kilkoma ścisłymi umysłami i pasją zaowocowały szeregiem nowych silników umożliwiających wyświetlanie trójwymiarowych obiektów w przeglądarkach internetowych. Między innymi za pomocą technologii Flash. W tym podrozdziale: Poznasz niektóre dostępne biblioteki 3D na platformę Adobe Flash Player. Poznasz podstawowe możliwości tych bibliotek. Dowiesz się, jakie czynniki brać pod uwagę przy wyborze biblioteki 3D do swoich projektów. Dowiesz się, skąd można pobrać przedstawione biblioteki. Nauczysz się importować bibliotekę Away3D do swoich projektów w różnych narzędziach.
14
Flash i ActionScript. Aplikacje 3D od podstaw
Dostępne biblioteki 3D Wraz z rozwojem technologii Flash przyszedł czas na wprowadzenie trzeciego wymiaru. W internecie dostępnych jest kilkanaście mniej lub bardziej znanych bibliotek służących do tworzenia aplikacji, w których grafika jest trójwymiarowa. Rozbieżność między silnikami jest całkiem spora, jeśli weźmiemy pod uwagę kilka aspektów. Po pierwsze, różnice w możliwościach. Licencje, z których korzystają, definiują obszary, w których można je zastosować. Po ustaleniu priorytetów projektu wybór właściwej biblioteki w zasadzie i tak zależy od upodobań programisty oraz doświadczenia, jakie zdobył we wcześniejszych projektach. Skoro interesujesz się tematem tworzenia aplikacji 3D w technologii Flash, warto byłoby, żebyś zapoznał się z kilkoma bibliotekami 3D i sam zgodnie ze swoimi upodobaniami i wiedzą w przyszłości wybierał silnik. W chwili obecnej, jeżeli nie masz takiego doświadczenia, przedstawię kilka bibliotek i wybiorę jedną z nich do omawiania późniejszych zagadnień. Kolejne decyzje będą należały do Ciebie.
Away3D
Away3D to jeden z najbardziej znanych silników 3D dostępnych na platformę Adobe Flash, jest też darmowy. Napisany został w języku ActionScript 3.0, umożliwia tworzenie aplikacji dla przeglądarek z zainstalowaną wtyczką Adobe Flash Player oraz Adobe AIR. Prace nad tą biblioteką rozpoczęli Alexander Zadorozhny i Rob Bateman w 2007 roku. Away3D ma wiele funkcji, dzięki którym można budować rozmaite aplikacje. Rysunek 1.1 przedstawia grę społecznościową Cafe World stworzoną przez firmę Zynga. W poniższych podpunktach wyszczególniono główne aspekty biblioteki Away3D 3.6: Ma kilkanaście wbudowanych obiektów 3D — od najprostszego punktu w przestrzeni do złożonego modelu żółwia morskiego (traktowany jest jako maskotka Away3D). Jako jedna z nielicznych ma w swoich zasobach kilka rodzajów kamer. Umożliwia oświetlanie i cieniowanie obiektów na kilka sposobów. Umożliwia pokrywanie obiektów różnymi materiałami. Między innymi kolorem oraz teksturą z zewnętrznych plików graficznych.
Rozdział 1. Wprowadzenie
15
Rysunek 1.1.
Gra społecznościowa Cafe World wykonana za pomocą Away3D
Pozwala na modyfikowanie powierzchni obiektów 3D przy tworzeniu ukształtowania terenu. Umożliwia generowanie niewidocznych ścieżek, które mogą posłużyć do wyznaczania toru przemieszczania obiektu 3D lub tworzenia wstęg i łańcuchów obiektów 3D. Obsługuje kilka formatów modeli wygenerowanych w znanych aplikacjach do grafiki 3D, między innymi: 3DS, MD2, COLLADA, OBJ. Umożliwia wyeksportowanie obiektu do klasy AS 3.0 plików AWD oraz OBJ. Umożliwia zwiększenie wydajności przy wykorzystaniu różnych metod wyświetlania obiektów. Ma wbudowane zdarzenia umożliwiające między innymi interakcje użytkownika z obiektami 3D. Pozwala na korzystanie z obiektów typu LOD (ang. Level of Detail — poziom szczegółów). Wraz z Away3D można stosować wiele innych znanych bibliotek napisanych w ActionScript 3.0. Aktualnie stabilną i oficjalną wersją jest Away3D 3.6. W wydaniu dla Adobe Flash Player 10 korzysta z ograniczonego dostępu do procesora GPU (ang. Graphics Processing Unit — procesor graficzny). Znaczna większość operacji wykonywanych jest za pomocą procesora CPU (ang. Central Processing Unit — procesor), przez co Away3D nie może w pełni korzystać z możliwości nowoczesnych kart graficznych — do czasu oficjalnej premiery Adobe Flash Player 11 i kolejnej odsłony silnika Away3D 4.0. Programiści nadal pracują nad wykorzystaniem Stage3D API
16
Flash i ActionScript. Aplikacje 3D od podstaw
(zwanego Molehill). Pozwoli to na wyświetlanie obiektów, łączna liczba trójkątów będzie liczona nie w tysiącach, lecz w milionach. Na rysunku 1.2 przedstawiono zrzut ekranu strony: http://www.nissan-stagejuk3d.com/, w której wykorzystano najnowszą wersję biblioteki Away3D. Rysunek 1.2.
Zrzut ekranu ze strony projektu Stage Juk3D
Away3D można pobrać ze strony: http://away3d.com/.
Alternativa3D
Alternativa3D to wytwór rosyjskiej firmy AlternativaPlatform. Biblioteka ta otrzymała wiele nagród za rozwiązania, które oferuje. Jest to niepodważalnie najlepszy płatny silnik. Wersji darmowej można używać jedynie do nauki i prywatnych niekomercyjnych projektów, podobnie jest w przypadku pozostałych omawianych silników. Alternativa3D została napisana do kreowania trójwymiarowych aplikacji na platformę Adobe Flash Player. Możliwości, które daje ta biblioteka, pozwalają na pisanie atrakcyjnych stron internetowych, serwisów oraz gier wieloosobowych. Flagowym produktem AlternativaPlatform jest gra Tanki Online. Rysunek 1.3 przedstawia zrzut ekranu z gry.
Rozdział 1. Wprowadzenie
17
Rysunek 1.3.
Zrzut ekranu z gry Tanki Online
Z kolei na rysunku 1.4 przedstawiono zrzut ekranu z prezentacji technicznej najnowszej wersji gry Tanki Online, która aktualnie tworzona jest na bazie silnika Alternativa3D 8. Rysunek 1.4.
Zrzut ekranu z gry Tanki Online bazującej na silniku Alternativa3D 8
Alternativa3D w wersji siódmej pozwala na wyświetlanie obiektów złożonych z 12 000 wielokątów. Jest to ogromna liczba w porównaniu z pozostałymi dostępnymi na rynku bibliotekami 3D, w tym Adobe Flash. Dzięki zaawansowanym algorytmom i optymalnemu wykorzystaniu zasobów silnik ten stoi na czele pod względem wydajności. Główne cechy biblioteki Alernativa3D:
18
Flash i ActionScript. Aplikacje 3D od podstaw
Ma szybki i efektywny render, umożliwia wyświetlanie obiektów złożonych nawet z 12 000 wielokątów. ♦ Umożliwia oświetlanie i cieniowanie obiektów na kilka sposobów. Ma intuicyjny dla użytkownika API. Hierarchia i właściwości obiektów 3D przypominają standardowe obiekty DisplayObject, znane z języka ActionScript 3.0. Umożliwia zastosowanie różnego rodzaju sortowania do wyświetlania obiektów — w zależności od potrzeb użytkownika. Umożliwia zwiększenie wydajności przy wykorzystaniu różnych metod wyświetlania obiektów w widoku. Ma kilka rodzajów kamer, które można wykorzystać w aplikacji jednocześnie. Ma wbudowane zdarzenia umożliwiające między innymi interakcje użytkownika z obiektami 3D. Pozwala na korzystanie z obiektów LOD. Ma wbudowany system do analizy i systematycznego redukowania błędów. Umożliwia korzystanie z modeli wyeksportowanych z innych programów. Obsługuje modele statyczne oraz dynamiczne, takie jak Collada. Aktualnie AlternativaPlatform pracuje nad kolejną wersją swojego silnika. W swojej ósmej odsłonie ma ona być przeznaczona dla platformy Adobe Flash Player 11 i obsługiwać Stage3D. Poza tym programiści AlternativaPlatform pracują nad autorskim systemem fizyki w ich silniku.
Papervision3D
Papervision3D jest kolejną obok Away3D darmową biblioteką 3D. Została napisana w języku ActionScript 3.0 przez kilkuosobową grupę (Carlos Ulloa, John Grden, Tim Knip, Andy Zupko) oraz programistów wspierających ten projekt. Przy użyciu Papervision3D można tworzyć proste i złożone projekty, na przykład strony internetowe, gry lub kampanie reklamowe. Do momentu kiedy
Rozdział 1. Wprowadzenie
19
Away3D stał się popularnym silnikiem, Papervision3D był wiodącą biblioteką 3D typu open source. Z wykorzystaniem Papervision3D powstało wiele interesujących i nagradzanych projektów — od stron internetowych znanych marek i osobistości po innowacyjne aplikacje i gry internetowe. Rysunek 1.5 przedstawia grę zręcznościową stworzoną dla koncernu Pepsi. Rysunek 1.5.
Zrzut ekranu z gry Pepsi Music Challenge
Warto wspomnieć, że Papervision3D nie korzysta z możliwości Adobe Flash Player 10. Tę lukę miał zapełnić projekt o nazwie PapervisionX, ale brak do tej pory jakichkolwiek informacji na temat tej wersji. Na pytanie, czy w przyszłości biblioteka Papervision3D będzie obsługiwała Adobe Flash Player 11 i Stage3D, nadal nie ma odpowiedzi. Główne cechy Papervision3D: Ma kilkanaście rodzajów obiektów 3D. Wyposażona jest w kilka rodzajów kamer. Umożliwia pokrywanie obiektów różnymi rodzajami materiałów. Obsługuje kilka formatów modeli wygenerowanych w znanych aplikacjach do grafiki 3D. Umożliwia zwiększenie wydajności przy wykorzystaniu różnych metod wyświetlania obiektów. Ma wbudowane zdarzenia umożliwiające między innymi interakcje użytkownika z obiektami 3D.
20
Flash i ActionScript. Aplikacje 3D od podstaw
Pozwala na korzystanie z metody LOD. Działa z innymi bibliotekami napisanymi w ActionScript 3.0, między innymi FLARToolkit lub JiglibFlash. Biblioteka Papervision3D dostępna jest do ściągnięcia pod adresem: http://code. google.com/p/papervision3d/.
FIVe3D
Bibliotekę FIVe3D napisał programista Mathieu Badimon w 2008 roku. Jest to darmowy silnik; wyświetlane w nim obiekty oraz animacje bazują na grafice wektorowej. FIVe3D składa się z kilkunastu klas. W porównaniu z innymi bibliotekami 3D nie jest to dużo, co nie oznacza, że za pomocą tego silnika nie można zbudować solidnych i przyjaznych dla oka aplikacji. Małe rozmiary są w istocie atutem FIVe3D, a możliwości, które oferuje, można sprawdzić na oficjalnej stronie biblioteki: http://five3d.mathieu-badimon.com/. Na rysunku 1.6 przedstawiono przykład trójwymiarowej klawiatury. Rysunek 1.6.
Przykład wirtualnej klawiatury wykonany w FIVe3D
Gdy przeglądamy przykłady FIVe3D, łatwo można poznać, do jakich projektów biblioteka nadaje się najlepiej. Jeżeli aplikacja wymaga umieszczenia obiektów MovieClip, Graphics oraz zwykłych tekstów w przestrzeni 3D, to FIVe3D jak najbardziej sprosta temu zadaniu bez zbędnego obciążania aplikacji dodatkowymi klasami. Poniżej wypisano główne cechy tego silnika. Nie jest ich wiele, ale wpływa to na korzyść tej biblioteki: Umożliwia łatwe osadzenie w przestrzeni 3D obiektów MovieClip oraz Graphics.
Rozdział 1. Wprowadzenie
21
Nie wymaga konfigurowania widoków oraz kamer. Głównym komponentem jest scena, na której dodawane są elementy. Umożliwia tworzenie trójwymiarowych napisów oraz obiektów zawsze zwróconych w stronę kamery. Pozwala na animowanie i modyfikowanie obiektów. Umożliwia odtwarzanie filmów FLV oraz wyświetlanie bitmap w przestrzeni 3D. Umożliwia korzystanie z innych bibliotek przeznaczonych dla języka ActionScript 3.0, na przykład JiglibFlash.
Flare3D
Flare3D jest częścią całego pakietu o nazwie Flare3DStudio. Jest to środowisko służące głównie do tworzenia gier i animacji na platformę Adobe Flash Player. Dzięki dostępnym narzędziom, budowanie podstaw aplikacji we Flare3D — w porównaniu z Away3D czy też Papervision3D — jest prostsze i bardziej intuicyjne. Nadal rozwijany przez ekipę firmy Flare3D silnik będzie wspierał platformę Adobe Flash Player 11 i komponent Scene3D. W chwili obecnej proces ten jest w fazie testów. Na rysunku 1.7 umieszczono zrzut ekranu z gry Almax Race stworzonej za pomocą Flare3DStudio. Rysunek 1.7.
Zrzut ekranu z gry Almax Race
Z kolei na rysunku 1.8 przedstawiono zrzut ekranu z gry Yellow Planet wykonanej za pomocą Flare3D 2.0.
22
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 1.8.
Zrzut ekranu z gry Yellow Planet
Najważniejsze cechy biblioteki Flare3D: Autorskie wtyczki umożliwiają współpracę z zewnętrznymi programami. Przykładem może być plugin do programu 3DSMax, który pozwala na stosowanie w nim tekstur pochodzących z Flare3D. Ma kilka wbudowanych obiektów 3D, między innymi obiekt lustra, w którym odbija się otaczające go środowisko. Umożliwia korzystanie ze statycznych i animowanych modeli stworzonych w programach typu 3DSMax. Pozwala na animowanie i modyfikowanie obiektów. Umożliwia oświetlanie i cieniowanie obiektów na kilka sposobów. Umożliwia pokrywanie obiektów różnymi materiałami, między innymi kolorem oraz teksturą z zewnętrznych plików graficznych. Umożliwia malowanie na powierzchni obiektów 3D. Umożliwia korzystanie z innych bibliotek przeznaczonych dla języka ActionScript 3.0, takich jak Stardust, FLARToolkit czy JiglibFlash. Ma bogaty zestaw narzędzi ułatwiających pracę nad aplikacją. Aby móc ściągnąć Flare3D i korzystać z niego, trzeba zarejestrować się na stronie internetowej producentów: http://www.flare3d.com/. Korzystanie z biblioteki po okresie próbnym wymaga wykupienia licencji.
Rozdział 1. Wprowadzenie
23
Sandy3D
Biblioteka Sandy3D to jeden z najwcześniejszych darmowych silników 3D pisanych na platformę Adobe Flash Player. Pierwszą wersję stworzył programista Thomas Pfeiffer w 2005 roku. W następnych latach nad rozwojem tego projektu pracowała już większa liczba osób. W swoich początkowych fazach Sandy3D przeznaczony był do użytku w języku ActionScript 2.0. Dopiero w kolejnych wersjach wraz z powstaniem ActionScript 3.0 przepisano kod Sandy3D. Wraz z rozwojem silników Papervision3D oraz Away3D silnik Sandy3D zszedł na drugi plan. Mimo to prace trwały nad nim do końca 2010 roku, a efekty można zobaczyć na rysunku 1.9. Przedstawia ona kadr z gry Logoleptic napisanej w Sandy3D. Rysunek 1.9.
Zrzut ekranu z gry Logoleptic
Mimo mniejszego zainteresowania tą biblioteką ma ona podstawowe i porównywalne z innymi właściwości. Ważniejsze cechy wypisano poniżej: Ma kilka wbudowanych obiektów 3D. Umożliwia korzystanie z kilku kamer jednocześnie. Umożliwia oświetlanie i cieniowanie obiektów na kilka sposobów. Umożliwia pokrywanie obiektów trzema głównymi materiałami: kolorem, bitmapą oraz plikiem wideo.
24
Flash i ActionScript. Aplikacje 3D od podstaw
Obsługuje kilka formatów modeli wygenerowanych w znanych aplikacjach do grafiki 3D, między innymi 3DS, MD2, COLLADA. Umożliwia zwiększenie wydajności przy wykorzystaniu różnych metod wyświetlania obiektów. Ma wbudowane zdarzenia umożliwiające między innymi interakcje użytkownika z obiektami 3D. Umożliwia korzystanie z innych bibliotek przeznaczonych dla języka ActionScript 3.0, takich jak AS3Dmod, FLARManager lub WOW. Biblioteka Sandy3D dostępna jest do ściągnięcia pod adresem: http://www. flashsandy.org.
Sophie3D
Sophie3D to niewielka biblioteka 3D. Jest niezwykle szybka i pozwala na wyświetlanie obiektów złożonych nawet z 80 000 wielokątów. Niewielki jej rozmiar oraz możliwość wyświetlania skomplikowanych modeli idealnie pasują do prezentacji produktów różnych marek w przestrzeni trójwymiarowej na stronach internetowych lub prezentacjach. Na rysunku 1.10 pokazano przykład zastosowania Sophie3D w promocji modelu samochodu marki Mini. Biblioteka ta w wersji podstawowej wymaga wyświetlania logo twórców oraz pozwala na korzystanie z Sophie3D jedynie w projektach niekomercyjnych. Licencja na wersję Sophie3D PRO zwalnia z obowiązku wyświetlania logo i pozwala na użycie biblioteki na wskazanej domenie. Z kolei licencja Sophie3D PRO FULL pozwala klientowi na swobodne korzystanie z silnika we wszystkich jego projektach. Aktualnie trwają prace nad wersją Sophie3D dla Adobe Flash Player 11. Na chwilę obecną silnik ma następujące cechy: Umożliwia import modeli w formacie Collada oraz Wavefront OBJ. Umożliwia kompresję plików do szybszego ładowania zawartości. Umożliwia poruszanie kamery oraz obiektu za pomocą zewnętrznych bibliotek, na przykład TweenMax.
Rozdział 1. Wprowadzenie
25
Rysunek 1.10. Zrzut ekranu z kreatora Mini ze strony http://www.miniby.me/
Pozwala na dodanie nieskończonej liczby źródeł światła na scenie. Pozwala na wykorzystanie modeli złożonych nawet z 80 000 wielokątów. Umożliwia zwiększenie wydajności przy wykorzystaniu różnych metod wyświetlania obiektów. Umożliwia wyświetlanie trójwymiarowej sceny z użyciem stereoskopicznych okularów. Umożliwia wyeksportowanie sceny 3D do obrazu. Przed pobraniem biblioteki Sophie3D wymagane jest podanie adresu e-mail na stronie: http://www.sophie3d.com. Po weryfikacji tego adresu e-mail wysyłany jest specjalny link, pod którym można znaleźć komponent Sophie3D.
Wybór biblioteki 3D W poprzednim podrozdziale krótko przedstawiłem poszczególne biblioteki 3D dostępne na dzień dzisiejszy. Mają one wiele wspólnych cech, ale też różnią się od siebie możliwościami. Niektóre są wręcz przeznaczone do konkretnych celów. Przy wyborze biblioteki musisz dobrze zaplanować projekt oraz wyznaczyć sobie cele, które chcesz osiągnąć, korzystając z silnika 3D. Dobrze byłoby, gdybyś zapoznał się z każdą z wymienionych bibliotek. To znacznie pomoże Ci w wyborze odpowiedniej przy następnym projekcie. Istotną kwestią jest również to, czy
26
Flash i ActionScript. Aplikacje 3D od podstaw
przy realizacji projektu będziesz mógł skorzystać z płatnych silników takich jak Alternativa3D. Pamiętaj również, że pisanie aplikacji 3D ma sprawiać w większej mierze radość niż bóle głowy. Dlatego wybieraj biblioteki, które najbardziej przypadły Ci do gustu, a w wolnych chwilach eksperymentuj i poznawaj nowe. Do realizacji przykładów w tej książce wybrałem bibliotekę Away3D, ponieważ jest ona jednym z najpopularniejszych silników 3D. Przede wszystkim jest darmowym i stale rozwijanym projektem pozwalającym na kreowanie prostych i skomplikowanych aplikacji 3D. Książkę tę potraktuj jako przewodnik, który zapozna Cię z zasadami oraz sposobami tworzenia aplikacji 3D. Zaczynając od podstaw, dowiesz się, jaką wersję biblioteki i w jaki sposób pobrać z internetu. W kolejnych krokach przedstawię, jak zintegrować Away3D z poszczególnymi programami: Adobe Flash, FlashDevelop oraz Flash Builder. Gdy opanujesz tę część i będziesz potrafił skonfigurować silnik 3D w swoich narzędziach, przejdziemy do poznawania podstaw tworzenia aplikacji w Away3D. W pierwszej kolejności poznamy fundamenty każdej z tego typu aplikacji. Dowiesz się, czym są widok, scena, jakie rodzaje kamer ma Away3D. Będziesz wiedział, jak należy rozumieć stworzoną przestrzeń oraz jak należy się w niej poruszać. Poznasz różnice między układami współrzędnych oraz punktami odniesienia w poszczególnych elementach aplikacji. Gdy poznasz najbardziej podstawowe części, pójdziemy krok dalej i będziemy poszerzali wiedzę o ciekawsze i bardziej złożone komponenty. Różnego rodzaju obiekty 3D, materiały obiektów, animowanie modeli — to tylko niektóre z tematów, jakie poruszymy. Każdy z działów zawierać będzie przykłady ilustrujące implementację omawianych zagadnień. Niektóre z tych przykładów mogą zawierać elementy, których jeszcze nie poznałeś we wcześniej przeczytanych rozdziałach. Nie przejmuj się tym, Twoim zadaniem będzie skupienie się na aktualnie omawianym temacie. Po zapoznaniu się z komponentami silnika Away3D nauczysz się kilku sposobów poprawiania wydajności działania aplikacji. Poza tym połączymy Away3D z innymi bibliotekami, aby otrzymać nowe ciekawe możliwości, takie jak chociażby rzeczywistość rozszerzona bądź system fizyki.
Pobieranie silnika Away3D Zanim zaczniesz się uczyć Away3D oraz tworzenia aplikacji 3D, musisz zdobyć potrzebne do tego narzędzia. Bibliotekę Away3D można ściągnąć z kilku źródeł w różnych wersjach. Na początku może się to wydawać kłopotliwe, jednak po przeczytaniu tego podrozdziału sprawy się wyjaśnią. Dowiesz się, jakie są rodzaje silników Away3D oraz skąd można pobrać wybraną bibliotekę.
Rozdział 1. Wprowadzenie
27
Away3D FP9, FP10 czy może FP11? Którą z wersji wybrać? Away3D w chwili pisania tych słów udostępnia wczesną wersję beta silnika o numeracji 4.0, która nie ma jeszcze wszystkich komponentów oraz pełnej stabilizacji w działaniu, ale pozwala doświadczonym użytkownikom tego silnika sprawdzić jego potencjalnie nowe możliwości oraz poszerzać swoje umiejętności. Nie jest to wersja oficjalna, dlatego można się spodziewać jeszcze wielu zmian. Jedno jest pewne — będzie ona w pełni kompatybilna z klasą Stage3D, która przede wszystkim wykorzystuje GPU, a nie CPU, jak to było w poprzednich wersjach. Do czasu premiery nowej odsłony programu Adobe Flash Player i oficjalnego wsparcia ze strony przeglądarek Away3D 4.0 beta zostaje w obszarze badań, testów i rozwoju, co nie oznacza, że po przeczytaniu tej książki nie będzie warto po niego sięgnąć. Wręcz przeciwnie, mając większe pole do popisu i wiedzę z tej książki, śmiało będziesz mógł próbować swoich sił na nowym terenie. W ostatnim rozdziale „Więcej zabawy z 3D” stworzymy prosty przykład z zastosowaniem Away3D 4.0. Dziesiąta odsłona wtyczki Adobe Flash Player wprowadziła wiele ulepszeń, w tym przeznaczonych dla silników 3D. Stało się tak z uwagi na wzrost popularności tego typu stron i aplikacji. Poprzednia wersja nakładała duże ograniczenia w możliwości wyświetlania obiektów 3D i zarządzania pamięcią. Rozwój wymusił powstanie nowej wersji, która wręcz podwoiła możliwości starej dziewiątki. Away3D w wersji 3.6.0 przeznaczone jest do odtwarzaczy Flash Player 10 i nie może być stosowane w niższych jego wersjach. Jeżeli z jakichś powodów Twój projekt będzie wymagał dostosowania się do starszych odsłon wtyczki Adobe Flash Player, będziesz musiał skorzystać z Away3D 2.5.2, ostatniej odsłony przeznaczonej dla Adobe Flash Player 9. Z uwagi na to, że aktualnie Away3D 3.6.0 jest oficjalną stabilną wersją, wszystkie elementy tego silnika i przytoczone przykłady kodów źródłowych bazują na tej wersji.
Pobieranie Away3D 3.6.0 z Away3D.com Źródła biblioteki Away3D można pobrać z oficjalnej strony projektu: http:// away3d.com/download/. Na pierwszym planie pojawi się dział biblioteki w wersji Away3D 4.0. Żeby pobrać wersję 3.6.0, należy: 1. Po prawej stronie w dziale Releases kliknąć link show, jeżeli nie jest wyświetlona lista dostępnych wersji. 2. Kliknąć link 3.6.0.
28
Flash i ActionScript. Aplikacje 3D od podstaw
W nowo otwartej stronie są linki do plików ZIP zawierających źródła biblioteki, przykłady wraz z plikami programu Flash, przykłady kodów ActionScript 3.0 oraz dokumentacji. Klikając na link Source files: Download, pobierzesz źródła biblioteki w postaci pliku ZIP. Pod linkami znajduje się również informacja o alternatywnych źródłach dostępu do biblioteki.
Pobieranie Away3D 3.6.0 z github.com Źródła Away3D 3.6.0 dostępne są również w znanym serwisie GitHub pod adresem: https://github.com/away3d/. Po wejściu na stronę zauważysz kilkanaście linków do różnych źródeł bibliotek oraz przykładów do nich. Nas interesuje link o nazwie away3d-core-fp10. Po załadowaniu strony pojawi się lista z folderem o nazwie src. Aby pobrać wszystkie pliki, należy wykonać następujące kroki: 1. Kliknąć na przycisk Downloads, który mieści się w górnym panelu po prawej stronie. 2. W otwartym oknie wybrać rodzaj pliku, na przykład ZIP, i kliknąć Download.
Pobieranie Away3D 3.6.0 z SVN Repozytorium SVN biblioteki Away3D umożliwia szybki dostęp do najnowszej wersji tego silnika. Plusem korzystania z repozytorium jest stały dostęp do najbardziej aktualnej wersji plików. Dla przykładu: jeżeli zanotowano jakiś błąd w działaniu konkretnej klasy, można się spodziewać, że zostanie on rozwiązany i automatycznie udostępniony użytkownikom (pomijając okres wyczekiwania, aż autor podmieni pliki na głównym serwerze). Istnieje też druga strona medalu. W przypadku gdy zastosujemy pewne rozwiązania w naszej aplikacji i zaktualizujemy bibliotekę z repozytorium, może się okazać, że nowa wersja nie zawiera klas bądź metod, których wcześniej używaliśmy. Aby pobrać bibliotekę poprzez SVN, trzeba skorzystać z dowolnego klienta SVN i jako źródło repozytorium podać następujący adres: http://away3d.googlecode.com/ svn/trunk/fp10/Away3D/src. 1. Jeżeli nie masz zainstalowanego klienta SVN, możesz skorzystać z programu TortoiseSVN. W poniższych krokach omówiony został sposób jego instalacji oraz zastosowanie przy pobieraniu biblioteki Away3D. Jeżeli nie masz klienta TortoiseSVN, należy go pobrać ze strony: http://tortoisesvn.net/downloads.html.
Rozdział 1. Wprowadzenie
29
2. Po zakończeniu pobierania uruchom instalator i stosując standardowe ustawienia, zainstaluj w wybranej lokalizacji. 3. Po zakończeniu instalacji zresetuj komputer. Teraz, gdy klient TortoiseSVN został zainstalowany, możesz przystąpić do pobierania biblioteki Away3D. 1. Stwórz nowy folder o dowolnej nazwie w wybranym miejscu. Dla przykładu może być to folder o nazwie libs w głównym katalogu Twojego projektu. 2. Kliknij prawym przyciskiem myszy na utworzonym katalogu i wybierz opcję SVN Checkout…, tak jak to pokazano na rysunku 1.11. Rysunek 1.11.
Przedstawia wybór opcji SVN Checkout
3. Otworzy się nowe okno Checkout. W tym oknie w polu URL of repository: należy wpisać następujący adres: http://away3d.googlecode.com/svn/ trunk/fp10/Away3D/src, a w polu Checkout directory: miejsce docelowe dla biblioteki. Po wypełnieniu tych pól kliknij przycisk OK. Na rysunku 1.12 pokazano okno ustawień pobierania danych repozytorium. 4. Pojawi się nowe okno, które zawiera dziennik aktualnie pobieranych zasobów repozytorium. Jeżeli wszystko przebiegło pomyślnie, to na samym dole okna ujrzysz napis Completed. Oznacza to, że biblioteka jest gotowa do użytku. Kliknij przycisk OK i zamknij okno. Na rysunku 1.13 przedstawiono okno procesu pobierania plików.
30
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 1.12.
Przedstawia ustawienia pobierania danych repozytorium
Rysunek 1.13.
Proces pobierania plików
5. Na ikonach pobranych folderów pojawi się zielony znaczek symbolizujący, że zawartość jest aktualna, tak jak to pokazano na rysunku 1.14. Rysunek 1.14.
Zsynchronizowane foldery biblioteki Away3D
Rozdział 1. Wprowadzenie
31
Instalacja biblioteki Away3D Konfiguracja w Adobe Flash CS4 i CS5 Produkty Adobe Flash to pierwotne narzędzia do tworzenia aplikacji dla Adobe Flash Player. Korzystając z tego programu, można tworzyć elementy wizualne aplikacji, a za pomocą wbudowanego edytora i debuggera pisać, a zarazem kontrolować kod ActionScript 3.0. W następujących krokach przedstawiono, jak należy zintegrować bibliotekę Away3D ze środowiskiem Adobe Flash w wersjach CS4 i CS5. 1. Po włączeniu programu Adobe Flash stwórz nowy plik w ActionScript 3.0, wybierając opcję ActionScript 3.0, tak jak to pokazano na rysunku 1.15. Rysunek 1.15.
Przedstawia okno powitalne w programie Adobe Flash Professional CS5 z zaznaczoną opcją tworzenia projektu ActionScript 3.0
2. Po otwarciu okna wybierz zakładkę File w górnym menu. Następnie kliknij opcję Publish Settings… z listy. Alternatywnie można użyć kombinacji klawiszy Ctrl+Shift+F12. Na rysunku 1.16 pokazano wybór opcji Publish Settings… 3. Kliknij w zakładkę Flash, a następnie przycisk Settings…, jak pokazano na rysunku 1.17. 4. W otwartym oknie wybierz zakładkę Source path. Następnie kliknij ikonkę + i wpisz bezpośrednią względem projektu ścieżkę do biblioteki. Możesz również kliknąć ikonę folderu i wskazać lokalizację z drzewa katalogów. Gdy umieścisz już odwołanie do Away3D, kliknij przycisk OK i zamknij to okno. Na rysunku 1.18 pokazano dodawanie ścieżki do potrzebnych bibliotek. 5. Po wykonaniu wszystkich kroków zapisz ustawienia, klikając przycisk OK w oknie Publish Settings.
32
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 1.16.
Wybór opcji ustawień publikacji projektu
Rysunek 1.17.
Uruchamianie ustawień dla języka ActionScript 3.0
Rysunek 1.18.
Dodawanie ścieżki z potrzebnymi bibliotekami
Konfiguracja we FlashDevelop FlashDevelop jest darmowym edytorem kodu napisanym przez członków FlashDevelop.org. Dzięki Adobe Flex SDK, możliwe jest pisanie w nim kodu w językach ActionScript 2.0, ActionScript 3.0 oraz MXML. Korzystając w tym edytorze z wieloplatformowego języka haXe, można również tworzyć projekty
Rozdział 1. Wprowadzenie
33
oparte na językach JavaScript, PHP czy też C++. FlashDevelop dzięki możliwościom, jakie oferuje, jest bardzo dobrą alternatywą dla edytora kodu wbudowanego w programie Adobe Flash. Jeżeli do realizacji przykładów z tej książki oraz przyszłych projektów związanych z biblioteką Away3D zamierzasz korzystać z programu FlashDevelop, to musisz wykonać kilka czynności. Po pierwsze, upewnij się, że masz zainstalowane środowisko Flex SDK. Jeżeli nie, to pobierz aktualną wersję ze strony: http://opensource. adobe.com/wiki/display/flexsdk/Flex+SDK. Jeżeli masz zainstalowane środowisko Flex SDK, to upewnij się, że w edytorze FlashDevelop przypisane jest poprawne odwołanie do niego. Czynność tę wykonuje się w następujących krokach: 1. Wejdź w zakładkę Tools w górnym menu. Następnie wybierz opcję Program Settings… Możesz również od razu wcisnąć klawisz F10. Na rysunku 1.19 pokazano uruchomienie konfiguracji projektu w programie FlashDevelop. Rysunek 1.19.
Uruchamianie konfiguracji projektu w programie FlashDevelop
2. W oknie Settings z listy po lewej stronie wybierz opcję AS3Context, a następnie poszukaj w prawej kolumnie wiersza o nazwie Installed Flex SDKs. Jeżeli to pole jest puste, musisz na nie kliknąć i wybrać katalog, w którym zainstalowałeś środowisko Flex SDK, tak jak to pokazano na rysunku 1.20. Po wykonaniu powyższych kroków trzeba kliknąć przycisk Close. Wszystkie zmiany zostaną zapisane. Teraz możesz swobodnie dodać bibliotekę Away3D do zasobów projektu w programie FlashDevelop. 1. W pierwszej kolejności kliknij na ikonę Properties w panelu Project. Możesz również kliknąć przycisk Project w górnym menu i wybrać opcję Properties… z listy, tak jak to pokazano na rysunku 1.21.
34
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 1.20. Ustawienia lokalizacji środowiska Flex SDK
Rysunek 1.21. Wybór ustawień projektu
2. W oknie Properties należy wybrać zakładkę Classpaths, a w niej kliknąć przycisk Add Classpath… Z drzewa katalogów wybierz lokalizację, która zawiera bibliotekę Away3D. Na rysunku 1.22 pokazano okno z dołączonymi lokalizacjami plików projektu. 3. Po wykonaniu tych czynności należy kliknąć OK w oknie Properties. Od tej chwili można korzystać z biblioteki Away3D. Dostępne będą również podpowiedzi i podświetlanie składni.
Rozdział 1. Wprowadzenie
35
Rysunek 1.22.
Lista lokalizacji, w których umieszczone są potrzebne źródła
Konfiguracja w Adobe Flash Builder 4 Adobe Flash Builder, wcześniej znany jako Adobe Flex Builder, jest środowiskiem bazującym na platformie Eclipse. Służy do tworzenia aplikacji typu RIA, które opierają się na technologii Adobe Flash. Znając język ActionScript 3.0 oraz MXML, można stworzyć za pomocą tego edytora złożone aplikacje. Dużo przykładów krążących w sieci wykonanych jest za pomocą tego edytora, dlatego warto wiedzieć, jak należy zintegrować bibliotekę Away3D z tym środowiskiem. Adobe Flash Builder 4 oraz 4.5 w standardzie korzystają z Flex SDK 3.6, 4.5 i 4.5.1. Wszystkie wymienione współpracują z platformą Adobe Flash Player 10. Jeżeli masz wcześniejszą wersję Flash Builder, upewnij się, że korzystasz z aktualnego Flex SDK. Pliki Flex SDK można pobrać ze strony: http://opensource.adobe.com/ wiki/display/flexsdk/Flex+SDK. Integracja biblioteki Away3D z edytorem Adobe Flash Builder przebiega następująco: 1. Pierwszym krokiem jest stworzenie projektu i wybranie odpowiedniego Flex SDK. W tym celu wybierz opcję File w górnym menu, a następnie New/Flex Project, tak jak to pokazano na rysunku 1.23. 2. W oknie New Flex Project wpisz nazwę projektu i wybierz wersję Flex SDK. Ważne jest, aby wersja ta obsługiwała Adobe Flash Player 10, na którym bazuje Away3D 3.6.0. Jeżeli w edytorze nie ma dodanego odpowiedniego Flex SDK, możesz dodać go manualnie, klikając łącze Configure Flex SDKs… Na rysunku 1.24 pokazano wybór wersji środowiska SDK.
36
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 1.23. Tworzenie nowego projektu Flex Rysunek 1.24.
Wybóri konfiguracja środowiska Flex SDK
3. Po wpisaniu nazwy projektu oraz wybraniu środowiska Flex SDK kliknij przycisk Finish w oknie New Flex Project. Edytor zacznie tworzyć całą strukturę Twojego projektu. 4. Zaznacz trzy katalogi z pobranych źródeł biblioteki Away3D: away3d, nochump i wumedia. Skopiuj je, wciskając kombinację klawiszy Ctrl+C. 5. W oknie Package Explorer należy wybrać katalog src, kliknąć prawym przyciskiem myszy i wybrać opcję Paste. Można również użyć kombinacji klawiszy Ctrl+V. Na rysunku 1.25 pokazano opcję kopiowania źródeł biblioteki. Rysunek 1.25.
Kopiowanie potrzebnych bibliotek
6. Po krótkiej chwili zawartość katalogu src projektu powinna wyglądać tak, jak pokazano na rysunku 1.26.
Rozdział 1. Wprowadzenie
37
Rysunek 1.26.
Poprawnie skopiowane biblioteki do naszego projektu
Podsumowanie Po zapoznaniu się z tym rozdziałem wiemy już, za pomocą jakich bibliotek możemy aplikacje Flash wprowadzić w trzeci wymiar. Każdy z przedstawionych tutaj silników ma swoje właściwości wyróżniające go spośród innych. Sprawia to, że niektóre z nich przeznaczone są do konkretnych celów. Dla przykładu biblioteka Sophie3D stosowana jest głównie w trójwymiarowych prezentacjach szczegółowych modeli 3D. Poznaliśmy część najpopularniejszych bibliotek 3D stosowanych w technologii Adobe Flash. Niektóre z nich nadal są rozwijane, a nad innymi zaprzestano prac lub znacznie zwolniono tempo ich rozwoju. Poza wymienionymi silnikami istnieją jeszcze inne, takie jak Minko, Yogurt3D czy też ND3D. Nieustannie powstają nowe, coraz bardziej zaawansowane narzędzia służące do tworzenia aplikacji 3D. Co ciekawe, firma Epic Games zadeklarowała możliwość obsługi silnika Unreal Engine 3 dla Adobe Flash Player 11. Można przypuszczać, że przez coraz większą popularność gier przeglądarkowych technologia Adobe Flash będzie niedługo ukierunkowana tylko w stronę produkcji aplikacji 3D. Silniki Away3D, Papervision3D oraz Sandy3D są w pełni darmowymi bibliotekami. Pozostałe wymagają wykupienia licencji do użytku komercyjnego. Wyjaśniliśmy w tym rozdziale również sposoby integracji Away3D z różnymi narzędziami służącymi do tworzenia aplikacji opartych na technologii Adobe Flash. Wiesz już również, czym należy się kierować przy wyborze biblioteki oraz dlaczego w tej książce korzysta się z Away3D.
38
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 2. Podstawy biblioteki Away3D Skoro czytasz te słowa, wiesz już, jak należy zaimportować silnik 3D do projektu. Zanim jednak zaczniesz tworzyć w pełni profesjonalne aplikacje z użyciem Away3D, musisz zapoznać się z podstawami tej biblioteki oraz zasadami tworzenia aplikacji trójwymiarowych w technologii Flash. W tym rozdziale zajmiemy się kluczowymi zagadnieniami, których znajomość pozwoli Ci stworzyć aplikacje na poziomie podstawowym. Czytając ten rozdział, dowiesz się: Jakie są podstawowe komponenty biblioteki Away3D, niezbędne do stworzenia aplikacji 3D. Jak interpretowana jest przestrzeń trójwymiarowa w Away3D. Co sprawia, że widzimy to, co widzimy, w aplikacjach 3D. Jakie właściwości oraz metody mają kluczowe klasy. Czym jest kontener w przestrzeni trójwymiarowej. Jak umieszcza się obiekty w konkretnym miejscu na scenie. Jakie rozróżniamy przestrzenie układów współrzędnych w Away3D. Jak wygląda podstawowa budowa aplikacji z użyciem biblioteki Away3D.
Podstawowe komponenty Każda biblioteka składa się z podstawowych i rozszerzających klas. W Away3D podstawowymi są te, które dają możliwość wyświetlenia trójwymiarowego obrazu. Do tego celu służą następujące klasy i komponenty: View3D. Camera3D.
40
Flash i ActionScript. Aplikacje 3D od podstaw
Scene3D. ObjectContainer3D. Object3D. Każdy z tych składników spełnia konkretne zadanie, udostępnia równocześnie pozostałym komponentom potrzebne im informacje. W szczególności klasy widoku, kamery i sceny tworzą jedną całość, którą możemy obserwować na naszych ekranach. W tym podrozdziale poznamy wszystkie te klasy. Zacznijmy od widoku.
View3D Dosłownie i w przenośni widok jest oknem na świat w przestrzeni trójwymiarowej. Stwierdzenie to może wydawać się nieco zawiłe, ale rysunek 2.1 powinien ułatwić zrozumienie pojęcia widoku. Rysunek 2.1.
Wizualizacja pojęcia widoku w bibliotece Away3D
W bibliotekach 3D takich jak Away3D widok to prostokątna powierzchnia przedstawiająca wybraną część trójwymiarowej sceny. Należy traktować ją jak obszar roboczy programu Flash lub maskę warstw z obiektami. W Away3D widok reprezentuje klasa View3D, która znajduje się w pakiecie away3d.containers. Tak jak inne obiekty w programie Flash, View3D dodaje się poprzez użycie metody addChild(). To oznacza, że należy traktować widok jak każdy inny element stosowany w aplikacjach Flash. Pozycję widoku zmienia się za pomocą podstawowych właściwości x i y. Przeważnie widok umieszcza się w centrum okna, stosując współrzędne stage.stageWidth*.5 na osi X oraz stage.stageHeight*.5 na osi Y.
Rozdział 2. Podstawy biblioteki Away3D
41
Tworzenie i dodawanie widoku odbywa się następująco: var view:View3D = new View3D( { x:stage.stageWidth*.5, y:stage.stageHeight*.5} ); addChild(view);
Miejsce, w którym należy zapisać powyższe dwie linijki kodu, zależy od struktury całej aplikacji, przeważnie obiekt klasy View3D tworzony jest w głównej klasie aplikacji. Jeżeli poza biblioteką Away3D stosujemy również wzorzec projektowy, na przykład MVC (ang. Model View Controller – Model Widok Kontroler), to obiekt klasy View3D powinien być tworzony w klasie odpowiedzialnej za generowanie i wyświetlanie elementów graficznych aplikacji. Standardowo widok zajmuje całą przestrzeń okna Flash. Nie można zmienić jego wymiarów, stosując standardowe właściwości wysokości i szerokości. Jeżeli trzeba zmienić wymiary wyświetlania trójwymiarowej sceny, należy skorzystać ze specjalnych metod ograniczania widoku, ale tym tematem zajmiemy się w rozdziale 12. „Optymalizacja”. W aplikacji nie musimy ograniczać się do jednego widoku, możemy stworzyć ich więcej. Jedną z sytuacji, w których istnieje taka potrzeba, jest prezentowanie obiektu z różnych perspektyw w tym samym czasie. Aby wyświetlić scenę oraz zawarte w niej trójwymiarowe obiekty, trzeba użyć metody o nazwie render(), dostępnej w klasie View3D. Pojedyncze wywołanie tej metody zadziała jak zrobienie zdjęcia, dlatego do uzyskania animacji należy ją uruchamiać, bez przerwy stosując obiekt klasy Timer bądź zdarzenie Event.ENTER_FRAME. W przykładach zawartych w tej książce do wywoływania metody render() będziemy wykorzystywali wspomniane zdarzenie. Przy okazji pamiętaj o ustawieniu w projekcie aplikacji odpowiedniej liczby klatek wyświetlanych na sekundę. W naszych przykładach wartość FPS ustawimy na 30 klatek na sekundę. Co prawda stosowanie dwa razy większej prędkości sprawia, że animacja staje się płynniejsza, ale z drugiej strony od procesora wymaga to znacznie większej liczby wykonywanych operacji. Wartość FPS powinna zależeć od stosowanej wersji biblioteki Away3D oraz od samej aplikacji, tak aby była ona jak najbardziej optymalna. Do tematu wyboru liczby FPS powrócimy w rozdziale 12. „Optymalizacja”. Tabele 2.1 i 2.2 zawierają najważniejsze właściwości oraz metody obiektu klasy View3D. Tabela 2.1. Właściwości klasy View3D
Nazwa
Rodzaj
Wartość domyślna Opis
background
Sprite
Obiekt typu Sprite traktowany jako tło sceny
camera
Camera3D
Standardowa kamera typu Camera3D, używana do wyświetlania sceny
clipping
Clipping
Wycięty obszar wyświetlania sceny
42
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 2.1. Właściwości klasy View3D (ciąg dalszy)
Nazwa
Rodzaj
Wartość domyślna Opis
forceUpdate
Boolean
false
foreground
Sprite
Obiekt typu Sprite nałożony na wyświetlaną scenę
mouseEvents
Boolean
Włącza i wyłącza możliwość korzystania ze zdarzeń myszki
mouseZeroMove
Boolean
Wymusza, by zdarzenia mouseMove były uruchamiane nawet wtedy, gdy kursor nie zmienia swojej pozycji
overlay
Sprite
Obiekt typu Sprite wyświetlany na samym wierzchu sceny
renderer
Renderer
scene
Scene3D
stats
Boolean
statsPanel
Stats
Określa, czy widok ma być stale odświeżany, czy ma odświeżać tylko to, co zostało zmienione
Renderer.BASIC
Obiekt typu Renderer definiuje sposób wyświetlania obiektów na scenie Standardowy obiekt klasy Scene3D. Reprezentuje scenę biblioteki Away3D
false
Określa, czy w projekcie ma być wyświetlany panel statystyk Away3D Obiekt typu Stats służy do wyświetlania panelu statystyk
Tabela 2.2. Metody klasy View3D
Nazwa
Opis
View3D(init:Object = null)
Konstruktor
addOnMouseDown(listener:Function):void
Standardowa metoda służąca do dodania obiektu nasłuchującego zdarzenia MouseDown
addOnMouseMove(listener:Function):void
Standardowa metoda służąca do dodania obiektu nasłuchującego zdarzenia MouseMove
addOnMouseOut(listener:Function):void
Standardowa metoda służąca do dodania obiektu nasłuchującego zdarzenia MouseOut
addOnMouseOver(listener:Function):void
Standardowa metoda służąca do dodania obiektu nasłuchującego zdarzenia MouseOver
addOnMouseUp(listener:Function):void
Standardowa metoda służąca do dodania obiektu nasłuchującego zdarzenia MouseUp
clear():void
Czyści wcześniej wyświetlony widok
getBitmapData():BitmapData
Zwraca obiekt BitmapData wyświetlanej sceny
Rozdział 2. Podstawy biblioteki Away3D
43
Tabela 2.2. Metody klasy View3D (ciąg dalszy)
Nazwa
Opis
getContainer():DisplayObject
Zwraca obiekt DisplayObject wyświetlanej sceny
removeOnMouseDown(listener:Function):void
Standardowa metoda usuwania obiektu nasłuchującego zdarzenia MouseDown
removeOnMouseMove(listener:Function):void
Standardowa metoda usuwania obiektu nasłuchującego zdarzenia MouseMove
removeOnMouseOut(listener:Function):void
Standardowa metoda usuwania obiektu nasłuchującego zdarzenia MouseOut
removeOnMouseOver(listener:Function):void
Standardowa metoda usuwania obiektu nasłuchującego zdarzenia MouseOver
removeOnMouseUp(listener:Function):void
Standardowa metoda usuwania obiektu nasłuchującego zdarzenia MouseUp
render():void
Wyświetla pojedynczy obraz sceny
Scene3D Gdy umieszczamy elementy na stole montażowym w programie Adobe Flash, dodawane są one również do kontenera obiektów wyświetlanych, dzięki temu elementy te widoczne są po uruchomieniu aplikacji. W przypadku biblioteki Away3D, aby obiekty trójwymiarowe były wyświetlane, należy dodać je do sceny, którą tworzy się za pomocą klasy Scene3D. Klasa ta, zlokalizowana w pakiecie away3d.containers, dziedziczy po klasie ObjectContainer3D, o której jeszcze wspomnimy w dalszych podpunktach tego podrozdziału. Scenę należy traktować jako przestrzeń z ustalonym miejscem zerowym, nieposiadającą granic. Na rysunku 2.2 przedstawiono umieszczony na scenie zbiór obiektów różnych klas dostępnych w bibliotece Away3D. Gdy wykonujemy zwykłą aplikację w programie Flash, nie ma potrzeby tworzenia głównego kontenera obiektów wyświetlanych, ponieważ jest on generowany wraz z uruchomieniem aplikacji. W Away3D istnieją dwie możliwości inicjalizacji sceny. Pierwsza polega na stosowaniu zdefiniowanej sceny utworzonego widoku View3D. Do takiej sceny odwołujemy się poprzez właściwość scene, tak jak to pokazano w poniższym kodzie: view.scene.addChild(obj); trace(view.scene.children);
44
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 2.2.
Obiekty umieszczone na scenie Scene3D
Drugim sposobem jest stworzenie obiektu klasy Scene3D i dodanie go do zawartości Stage przy użyciu metody addChild(): var scene2:Scene3D = new Scene3D(); addChild(scene2); view.scene = scene2;
Scene3D jako potomna klasy ObjectContainer3D korzysta z dostępnych w niej metod oraz właściwości do modyfikowania i kontrolowania swojej zawartości. W klasie Scene3D jest kilka metod służących do umieszczania obiektów na scenie: addChild(), addSprite(), addSegment(), addFace() oraz addLight(), których zastosowanie ogranicza się do konkretnego rodzaju dodawanego obiektu. Z kolei do usuwania poszczególnych obiektów ze sceny służą metody: removeChild(), removeSprite(), removeSegment(), removeFace() oraz removeLight().
Spis najistotniejszych właściwości oraz metod klasy Scene3D zawierają tabele 2.3 i 2.4. Tabela 2.3. Właściwości klasy Scene3D
Nazwa
Rodzaj
ambientLights
Vector.
autoUpdate
Boolean
Wartość Opis domyślna Zwraca tablicę dodanych obiektów światła typu AmbientLight3D true
Określa, czy zdarzenia sceny są automatycznie wywoływane przez widok, czy też poprzez zastosowanie metody updateScene()
Rozdział 2. Podstawy biblioteki Away3D
45
Tabela 2.3. Właściwości klasy Scene3D (ciąg dalszy)
Nazwa
Rodzaj
Wartość Opis domyślna
directionalLights
Vector.
numLights
uint
pointLights
Vector.
Zwraca tablicę dodanych obiektów światła typu DirectionalLight3D 0
Liczba źródeł dodanych obiektów światła Zwraca tablicę dodanych obiektów światła typu PointLight3D
viewDictionary
Dictionary
Obiekt klasy Dictionary; jego kluczami są obiekty widoków, w których zastosowano wybraną scenę
Tabela 2.4. Metody klasy Scene3D
Nazwa
Opis
Scene3D(... initarray)
Konstruktor; można w nim podać argument w postaci tablicy obiektów 3D, które mają być dodane do sceny wraz z jej utworzeniem. Zamiast tablicy obiektów 3D można również podać obiekt inicjalizacyjny
updateTime(time:int = -1):void
Wywołana ręcznie odświeży obiekty 3D, które wykonują aktualizację metodą tick()
Camera3D Kamery w bibliotekach takich jak Away3D to niewidoczne i niezawierające jakiejkolwiek siatki geometrycznej obiekty umieszczone w przestrzeni trójwymiarowej. Dokładniej ujmując, są to punkty o współrzędnych x, y oraz z. Z tych punktów przedstawiana jest scena oraz zawarte w niej elementy. Kamera jest — obok widoku oraz sceny — trzecim podstawowym składnikiem bibliotek 3D. W zasadzie trudno wyobrazić sobie działanie silników 3D bez niej. Kamery dostępne w silnikach 3D w swoim funkcjonowaniu odzwierciedlają kamery, których możemy używać na co dzień. Jako operator decydujemy o tym, co chcemy przedstawić i z której strony. Ponieważ Away3D ma kilka rodzajów kamer, zostały one opisane w osobnym rozdziale 9. „Kamery”. Znajdują się tam również ważniejsze kwestie związane z używaniem kamer w Away3D oraz przykłady użycia.
46
Flash i ActionScript. Aplikacje 3D od podstaw
Podobnie jak w przypadku sceny, obiekt stworzonego widoku ma zdefiniowaną podstawową kamerę typu Camera3D. Odwołać się do tej kamery można poprzez właściwość camera dostępną w klasie View3D.
Object3D Zaczynając pracę z silnikiem Away3D, warto znać jego podstawy i główne komponenty. Takim komponentem jest właśnie klasa Object3D. Jest to klasa bazowa dla wszystkich trójwymiarowych obiektów dostępnych w tej bibliotece. Tych widocznych i tych, których nie widzimy bezpośrednio na scenie, lecz zauważamy ich działanie. Z klasy Object3D przeważnie korzysta się w sytuacjach, w których dany obiekt nie może się ograniczać do jednego typu trójwymiarowego obiektu. Listę ważniejszych właściwości i niektórych metod klasy Object3D przedstawiono w tabelach 2.5 oraz 2.6. Tabela 2.5. Właściwości klasy Object3D
Nazwa
Rodzaj
Wartość domyślna
Opis
alpha
Number
1
Określa stopień widoczności obiektu w przedziale od 0 do 1, gdzie 0 oznacza, że obiekt jest niewidoczny
collider
Boolean
false
Określa, czy dany obiekt używany jest do wykrywania kolizji między obiektami
filters
Array
Opcjonalna tablica filtrów nałożonych na obiekt 3D
inverseScene Transform
Matrix3D
Zwraca odwrotność sceneTransform
mouseEnabled
Boolean
objectDepth
Number
Zwraca wartość głębokości siatki obiektu
objectHeight
Number
Zwraca wartość wysokości siatki obiektu
objectWidth
Number
Zwraca wartość szerokości siatki obiektu
ownCanvas
Boolean
true
false
Określa, czy zdarzenia myszy będą aktywne dla obiektu 3D
Określa, czy obiekt ma być renderowany z wykorzystaniem własnej sesji renderowania
Rozdział 2. Podstawy biblioteki Away3D
47
Tabela 2.5. Właściwości klasy Object3D (ciąg dalszy)
Nazwa
Rodzaj
Wartość domyślna
Opis
ownSession
AbstractSession
Określa odrębną sesję wyświetlania dla obiektu 3D
parent
ObjectContainer3D
Określa kontener dla obiektu 3D
pivotPoint
Vector3D
Vector3D(0,0,0)
Określa punkt w lokalnym układzie współrzędnych, wokół którego będzie się obracał obiekt
position
Vector3D
Vector3D(0,0,0)
Określa pozycję obiektu w przestrzeni
rotationX
Number
0
Określa stopień nachylenia obiektu, relatywny do współrzędnych rodzica względem osi X
rotationY
Number
0
Określa stopień nachylenia obiektu, relatywny do współrzędnych rodzica względem osi Y
rotationZ
Number
0
Określa stopień nachylenia obiektu, relatywny do współrzędnych rodzica względem osi Z
scaleX
Number
1
Określa skalę wymiaru obiektu na osi X
scaleY
Number
1
Określa skalę wymiaru obiektu na osi Y
scaleZ
Number
1
Określa skalę wymiaru obiektu na osi Z
sceneTransform Matrix3D
Zwraca macierz transformacji obiektu 3D, relatywnego do globalnych współrzędnych obiektu sceny
transform
Matrix3D
Określa macierz transformacji obiektu 3D
useHandCursor
Boolean
false
Określa, czy po najechaniu na obiekt myszką ma być wyświetlony kursor dłoni
visible
Boolean
true
Określa, czy obiekt ma być widoczny
x
Number
0
Określa pozycję obiektu na osi X
y
Number
0
Określa pozycję obiektu na osi Y
z
Number
0
Określa pozycję obiektu na osi Z
48
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 2.6. Metody klasy Object3D
Nazwa
Opis
Object3D(init:Object = null)
Konstruktor
addOnDimensionsChange()
Metoda inicjująca nasłuchiwanie zdarzenia dimensionsChanged
addOnMouseDown()
Metoda inicjująca nasłuchiwanie zdarzenia mouseDown3D
addOnMouseMove()
Metoda inicjująca nasłuchiwanie zdarzenia mouseMove3D
addOnMouseOut()
Metoda inicjująca nasłuchiwanie zdarzenia mouseOut3D
addOnMouseOver()
Metoda inicjująca nasłuchiwanie zdarzenia mouseOver3D
addOnMouseUp()
Metoda inicjująca nasłuchiwanie zdarzenia mouseUp3D
addOnPositionChange()
Metoda inicjująca nasłuchiwanie zdarzenia positionChanged
addOnRollOut()
Metoda inicjująca nasłuchiwanie zdarzenia rollOut3D
addOnRollOver()
Metoda inicjująca nasłuchiwanie zdarzenia rollOver3D
addOnScaleChange()
Metoda inicjująca nasłuchiwanie zdarzenia scaleChanged
centerPivot()
Metoda reguluje pozycję punktu obrotu tak, aby znajdował się on w centrum geometrii obiektu
clone()
Metoda powiela właściwości obiektu do innego obiektu typu Object3D
removeOnDimensionsChange()
Metoda usuwająca nasłuchiwanie zdarzenia dimensionsChanged
removeOnMouseDown()
Metoda usuwająca nasłuchiwanie zdarzenia mouseDown3D
removeOnMouseMove()
Metoda usuwająca nasłuchiwanie zdarzenia mouseMove3D
removeOnMouseOut()
Metoda usuwająca nasłuchiwanie zdarzenia mouseOut3D
removeOnMouseOver()
Metoda usuwająca nasłuchiwanie zdarzenia mouseOver3D
removeOnMouseUp()
Metoda usuwająca nasłuchiwanie zdarzenia mouseUp3D
removeOnPositionChange()
Metoda usuwająca nasłuchiwanie zdarzenia positionChanged
removeOnRollOut()
Metoda usuwająca nasłuchiwanie zdarzenia rollOut3D
removeOnRollOver()
Metoda usuwająca nasłuchiwanie zdarzenia rollOver3D
removeOnScaleChange()
Metoda usuwająca nasłuchiwanie zdarzenia scaleChanged
distanceTo(obj:Object3D):Number
Metoda zwraca w postaci liczbowej odległość od wskazanego obiektu klasy Object3D
tick()
Metoda, która może być nadpisana w celu uruchomienia aktualizacji obiektu na podstawie indywidualnych wywołań renderowania z renderera
toString()
Zwraca ciąg reprezentujący określony obiekt
Rozdział 2. Podstawy biblioteki Away3D
49
ObjectContainer3D Wspomnieliśmy wcześniej o tym, że klasa Scene3D jest głównym kontenerem dla obiektów trójwymiarowych. W Away3D istnieje możliwość tworzenia dodatkowych kontenerów, które są obiektami i pochodnymi klasy ObjectContainer3D. Klasa ta, zlokalizowana w pakiecie away3d.containers, służy w głównej mierze do grupowania elementów o podobnych właściwościach lub mających stanowić część większej całości. Często obiekt klasy ObjectContainer3D wykorzystuje się do połączenia kilku trójwymiarowych obiektów w jedną całość. Może łatwiej byłoby stworzyć kompletny model, ale bywają takie sytuacje, w których wymagane jest, by niezwiązane budową obiekty zachowywały się tak, jakby stanowiły jeden złożony obiekt. Obiekt klasy ObjectContainer3D nie ma widocznych ścian ani krawędzi. Jego powierzchnia zmienia się wraz ze zmianą zawartości i pozycji poszczególnych jego obiektów wewnętrznych. Wizualizację ObjectContainer3D przedstawiono na rysunku 2.3. Rysunek 2.3.
Wizualizacja obiektu ObjectContainer3D
Jako kontener dla innych obiektów ObjectContainer3D musi mieć właściwości oraz metody pozwalające na manipulowanie jego zawartością. Przedstawiono je w tabelach 2.7 oraz 2.8. Tabela 2.7. Właściwości klasy ObjectContainer3D
Nazwa
Rodzaj
Wartość domyślna Opis
children
Vector.
Zwraca zawartość kontenera w postaci tablicy obiektów 3D (Object3D)
polyCount
int
Zwraca liczbę obiektów znajdujących się w kontenerze
50
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 2.8. Metody klasy ObjectContainer3D
Nazwa
Opis
ObjectContainer3D(init:Object = null)
Konstruktor
addChild(child:Object3D):void
Metoda dodaje obiekt pochodny od Object3D do zawartości kontenera
addChildren(... childarray):void
Metoda dodaje do zawartości kontenera tablicę zawierającą obiekty
addLight(light:AbstractLight):void
Metoda dodaje źródła światła w postaci obiektów klas dziedziczących po AbstractLight
applyPosition(dx:Number, dy:Number, dz:Number):void
Metoda modyfikuje pozycję każdego z obiektów umieszczonych w kontenerze
centerMeshes():void
Metoda ustawia punkt pivot każdego z obiektów w jego środku geometrycznym
getChildByName(childName:String):Object3D
Metoda zwraca wyszukany za pomocą nazwy obiekt object3D
removeChild(child:Object3D):void
Metoda usuwa obiekt pochodny od Object3D z zawartości kontenera
removeChildByName(name:String):void
Metoda usuwa wyszukany przez nazwę obiekt 3D z zawartości kontenera
removeLight(light:AbstractLight):void
Metoda usuwa wybrane źródło światła z zawartości kontenera
Podstawowa budowa aplikacji w Away3D Nadszedł czas, aby poznaną teorię podeprzeć przykładem. Zbudujemy w tym miejscu najprostszą aplikację z użyciem biblioteki Away3D. Żeby nie wprowadzać zbyt wielu nowych pojęć, na scenie umieścimy kulę, która będzie się obracała wokół własnej osi. Obiekty 3D oraz wprawianie ich w ruch poznasz w dalszych rozdziałach tej książki. Na razie istotne dla Ciebie ma być to, jak korzysta się z silnika 3D. Na rysunku 2.4 pokazano efekt, jaki uzyskamy po stworzeniu i skompilowaniu omawianego w tym podpunkcie przykładu. Aby osiągnąć przedstawiony na rysunku 2.4 efekt, w pierwszej kolejności należy stworzyć nową klasę ActionScript 3.0. Po stworzeniu i zapisaniu pliku w wybranej lokalizacji trzeba w zależności od wybranego edytora zintegrować bibliotekę z projektem aplikacji.
Rozdział 2. Podstawy biblioteki Away3D Rysunek 2.4.
Efekt skompilowania i uruchomienia przykładu BasicExample
Kod źródłowy klasy BasicExample tego przykładu przedstawiono poniżej: package { import import import import
away3d.containers.View3D; away3d.primitives.Sphere; flash.display.Sprite; flash.events.Event;
public class BasicExample extends Sprite { private var view:View3D; private var sphere:Sphere; public function BasicExample() { view = new View3D( { x:stage.stageHeight*.5, y:stage.stageWidth*.5 } ); sphere = new Sphere(); view.scene.addChild(sphere); addChild(view); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(e:Event):void { sphere.rotationX += 5; sphere.rotationY -= 5; view.render(); } } }
51
52
Flash i ActionScript. Aplikacje 3D od podstaw
Na samym wstępie dodaliśmy biblioteki niezbędne do działania przykładu. import import import import
away3d.containers.View3D; away3d.primitives.Sphere; flash.display.Sprite; flash.events.Event;
Pierwsze dwie należą do biblioteki Away3D. View3D służy do stworzenia sceny, natomiast Sphere to obiekt kuli, o którym jeszcze wspomnimy w rozdziale 3. „Obiekty”. Klasa Sprite potrzebna nam jest do umieszczenia i wyświetlenia widoku w oknie Flash. Wszystkie główne klasy aplikacji Away3D dziedziczą z klas Sprite bądź MovieClip. Na końcu dodaliśmy klasę zdarzenia Event. Kiedy dziedziczymy z klasy Sprite, a kiedy z klasy MovieClip Możesz się spotkać z przykładami, w których klasy będą dziedziczyły nie ze Sprite, tylko MovieClip. W obu przypadkach kod będzie działał, jedyna różnica polega na tym, że klasa MovieClip jest rozszerzeniem Sprite. Ma ona właściwości oraz metody służące do operowania na linii czasowej. Dlatego kiedy w Twojej klasie będziesz miał zamiar wykorzystać animacje składające się z klatek, należy dziedziczyć z MovieClip. W przeciwnym razie wystarczy Sprite.
Po zaimportowaniu potrzebnych klas tworzymy naszą klasę BasicExample, dziedzicząc z klasy Sprite. public class basicExample extends Sprite
W ciele klasy BasicExample znajdują się dwie metody, konstruktor oraz funkcja reagująca na wystąpienie zdarzenia Event.ENTER_FRAME. W obu metodach korzystamy z obiektów widoku i kuli, dlatego jako właściwości klasy definiujemy obiekty klas View3D oraz Sphere. Klasa BasicExample to jedyna klasa w tym przykładzie, ale i tak zastosowaliśmy modyfikator private, by ograniczyć zakres dostępności tych obiektów do ciała klasy bazowej. private var view:View3D; private var sphere:Sphere;
W konstruktorze w pierwszej kolejności stworzyliśmy obiekt widoku. Standardowo okno aplikacji Flash ma wymiary 550 na 400 pikseli. Chcąc umieścić widok na środku okna, jego właściwościom x i y nadaliśmy odpowiednio połowę wymiarów okna Flash. view = new View3D( { x:stage.stageHeight*.5, y:stage.stageWidth*.5 } );
W kolejnym kroku stworzyliśmy obiekt kuli Sphere. Nie nadajemy mu jakichkolwiek właściwości, więc wyświetlony zostanie ze swoimi standardowymi ustawieniami i pokryty losowo wybranym kolorem. Po stworzeniu obiektu sphere, odwołując się do domyślnej sceny widoku, dodaliśmy kulę do zasobów sceny.
Rozdział 2. Podstawy biblioteki Away3D
53
sphere = new Sphere(); view.scene.addChild(sphere);
Na końcu ciała konstruktora dodaliśmy widok i ustawiliśmy rejestrator zdarzenia Event.ENTER_FRAME. Za jego pomocą będziemy mogli wprawić w ruch nasz obiekt kuli. addChild(view); addEventListener(Event.ENTER_FRAME, onEnterFrame);
Drugą, a zarazem ostatnią w klasie BasicExample metodą jest onEnterFrame, której zadaniem jest wykonanie swojego bloku kodu za każdym razem, gdy wykryte zostanie zdarzenie Event.ENTER_FRAME. Aby przedstawić prostą animację obrotu wykonaną na obiekcie sphere, zapisaliśmy dwie linijki kodu zmieniające wartości właściwości rotationX oraz rotationY. Poza tym umieściliśmy również w metodzie onEnterFrame() wywołanie z widoku metody render(); bez tego nie ujrzelibyśmy obiektu i jego animacji. sphere.rotationX += 5; sphere.rotationY -= 5; view.render();
Położenie obiektów w przestrzeni W bibliotece Away3D każdy obiekt 3D ma cztery właściwości służące do określenia jego pozycji na scenie. Są nimi: x, y, z oraz position. Pierwsze trzy tworzą główną metodę ustalania pozycji. Jest ona częściej stosowana w sytuacjach, gdy niewymagana jest zmiana położenia względem wszystkich trzech osi. Poniższy kod źródłowy zajmuje aż cztery linijki tylko po to, aby ustalić pozycję początkową. var obj:Object3D = new Object3D(); obj.x = 100; obj.y = 50; obj.z = 0;
Kolejnym sposobem ustalenia pozycji obiektu jest skorzystanie z właściwości position, która przyjmuje jako wartość obiekt typu Vector3D. W obiekcie tym wystarczy podać jako argumenty wartości dla współrzędnych: x, y oraz z, tak jak to pokazano w poniższym kodzie źródłowym. var obj:Object3D = new Object3D(); obj.position = new Vector3D(100, 50, 0);
Jak widać, ten sposób określania położenia obiektu w przestrzeni wymaga zapisania mniejszej ilości kodu. Więcej informacji na temat zmiany pozycji obiektu poznasz, czytając rozdział 7. „Praca z obiektami”.
54
Flash i ActionScript. Aplikacje 3D od podstaw
Układ współrzędnych w Away3D Według definicji układ współrzędnych to funkcja przypisująca każdemu punktowi przestrzeni skończony ciąg liczb. Liczby te nazywamy współrzędnymi. Standardowy układ osi to układ kartezjański, przedstawiony na rysunku 2.5. Rysunek 2.5.
Podstawowy układ współrzędnych
Programy Adobe Flash do wersji CS3 miały układ współrzędnych również złożony z dwóch osi: X i Y. Jednak swoje miejsce zerowe miały w lewym górnym rogu. Dodatkowo wartości osi Y zwrócone są ku dołowi. Oznacza to, że obiekty niżej położne mają większą wartość y. Wraz z odsłoną Adobe Flash Player 10 rozszerzono układ współrzędnych o trzecią oś Z. Programy Adobe Flash od wersji CS4 miały do dyspozycji trzy osie, pozwalają tym samym na tworzenie obiektów w przestrzeni trójwymiarowej. Różnicą między układem współrzędnych w programach Flash a układem w bibliotekach 3D takich jak Away3D jest kierunek, w którym rosną wartości osi Y. Flash nadal stosuje swój system, z kolei silniki 3D używają układu z osią Y zwróconą ku górze. Kierunki osi w Away3D łatwo można przedstawić za pomocą lewej dłoni. Kciuk zwrócony ku górze określa kierunek osi Y. Wyprostowany palec wskazujący reprezentuje kierunek osi Z. Palec środkowy skierowany w prawą stronę oznacza oś X. Rysunek 2.6 przedstawia porównanie układu współrzędnych znanego z programu Flash z układem stosowanym w bibliotece Away3D. Linią przerywaną przedstawiono odwzorowanie na lewej dłoni kierunków osi układu współrzędnych stosowanego w przestrzeni sceny Away3D.
Rozdział 2. Podstawy biblioteki Away3D
55
Rysunek 2.6.
Porównanie układów współrzędnych programu Flash i biblioteki Away3D
W Away3D scena ma swój układ współrzędnych. Środek tego układu jest głównym punktem odniesienia dla każdego z elementów bezpośrednio w nim umieszczonych. Nazywamy to globalnym układem współrzędnych. Każdy z obiektów umieszczonych na scenie poza swoim punktem środkowym, który określa jego pozycję, ma również swój własny układ współrzędnych. Nazywamy go lokalnym układem współrzędnych. Z układu tego korzysta się między innymi przy modyfikacji geometrii, kąta nachylenia bądź pozycji danego obiektu. Dla przykładu: chcąc obrócić obiekt i pokazać wcześniej niewidoczne jego części, stosujemy oś obiektu, a nie sceny. Stosując oś sceny przy obrocie obiektu, stworzymy efekt orbitowania wokół punktu zerowego sceny. Zagadnienie to przedstawia poniższy rysunek 2.7. Rysunek 2.7.
Układ współrzędnych sceny oraz obiektu
56
Flash i ActionScript. Aplikacje 3D od podstaw
W podpunkcie „ObjectContainer3D” tego rozdziału wspomnieliśmy o tym, że w Away3D istnieje możliwość tworzenia pojedynczych kontenerów wewnątrz sceny. Każdy z takich kontenerów jako obiekt trójwymiarowy również ma swój układ współrzędnych. W sytuacji gdy mamy umieszczone obiekty wewnątrz ciała kontenera i chcemy zmienić ich położenie, zmiana właściwości określających pozycję nie odnosi się do środka sceny, lecz środka kontenera. Dla przykładu w poniższym kodzie źródłowym stworzymy dwie kule i kontener. Obie kule mimo tych samych wartości właściwości pozycji będą umieszczone w różnych miejscach na scenie. Pierwszą kulę umieścimy bezpośrednio na scenie i nadamy konkretne wartości jej właściwościom x, y i z. Następnie stworzymy kontener i umieścimy w nim drugą kulę o tych samych właściwościach co pierwsza. Dodatkowo dla lepszego zobrazowania sytuacji umieścimy na scenie obiekty Trident, które przedstawiają układ współrzędnych. Mniejszy układ po lewej stronie jest układem znajdującym się w punkcie zerowym kontenera. Większy układ dodamy bezpośrednio na środek sceny Away3D. Taką sytuację przedstawia rysunek 2.8. Rysunek 2.8.
Rozmieszczenie dwóch kul o tych samych wartościach pozycji
Jak przedstawiono na rysunku 2.8, kule znajdują się w różnych pozycjach na scenie, mimo że wartości ich współrzędnych są identyczne. Aby przedstawić te wartości, skorzystamy z funkcji trace(), która w konsoli edytora wyświetli następujący fragment logu: sphere1: sphere2:
100 50 0 100 50 0
Aby uzyskać efekt przedstawiony na rysunku 2.8 i w poprzednim wycinku logu, przepisz i skompiluj następujący kod źródłowy:
Rozdział 2. Podstawy biblioteki Away3D package { import import import import import
57
away3d.containers.ObjectContainer3D; away3d.containers.View3D; away3d.primitives.Sphere; away3d.primitives.Trident; flash.display.Sprite;
public class CoordinateSystemExample extends Sprite { public function CoordinateSystemExample() { var view:View3D = new View3D( { x:stage.stageHeight*.5, y:stage.stageWidth*.5 } ); // var globalCoor:Trident = new Trident(500); view.scene.addChild(globalCoor); // var sphere1:Sphere = new Sphere(); sphere1.radius = 50; sphere1.x = 100; sphere1.y = 50; sphere1.z = 0; view.scene.addChild(sphere1); // var container:ObjectContainer3D = new ObjectContainer3D(); container.x = -200; container.y = -50; container.z = 100; view.scene.addChild(container); // var containerLocalCoor:Trident = new Trident(100, true); container.addChild(containerLocalCoor); // var sphere2:Sphere = new Sphere(); sphere2.radius = 50; sphere2.x = 100; sphere2.y = 50; sphere2.z = 0; container.addChild(sphere2); // addChild(view); view.render(); // trace('sphere1: ', sphere1.x, sphere1.y, sphere1.z); trace('sphere2: ', sphere2.x, sphere2.y, sphere2.z); } } }
Żeby pokazać różnicę między lokalnym a globalnym systemem układu współrzędnych, potrzebowaliśmy trzech rodzajów obiektów. Pierwszy z nich to znany z poprzedniego przykładu obiekt kuli – sphere. Drugi to obiekt klasy ObjectContainer3D, służący jako kontener dla drugiej kuli. Trzeci to obiekt klasy Trident, która tworzy układ współrzędnych. Poza tymi klasami dodaliśmy również inne, standardowe, niezbędne do uruchomienia aplikacji.
58
Flash i ActionScript. Aplikacje 3D od podstaw import import import import import
away3d.containers.ObjectContainer3D; away3d.containers.View3D; away3d.primitives.Sphere; away3d.primitives.Trident; flash.display.Sprite;
Po stworzeniu klasy coordinateSystemExample w jej ciele zdefiniowaliśmy tylko konstruktor. Nie korzystaliśmy z jakichkolwiek animacji, dlatego nie było potrzeby nasłuchiwania zdarzenia Event.ENTER_FRAME. W tym przykładzie odświeżyliśmy widok 3D tylko raz po stworzeniu i dodaniu wszystkich elementów. W konstruktorze w pierwszym kroku utworzyliśmy obiekt widoku View3D umieszczonego w centrum okna aplikacji Flash. var view:View3D = new View3D( { x:stage.stageHeight*.5, y:stage.stageWidth*.5 } );
Po stworzeniu widoku dodaliśmy główny układ współrzędnych o nazwie globalCoor, któremu długość osi ustawiliśmy na 500. var globalCoor:Trident = new Trident(500); view.scene.addChild(globalCoor);
Kolejnym krokiem było stworzenie obiektu sphere1 i umieszczenie go bezpośrednio na scenie widoku View3D. Następnie zmieniliśmy jego standardowe wartości właściwości x, y oraz z, tak aby kula pojawiła się z prawej strony, a nie na środku okna. Ponieważ obiekt kuli jest duży i przeszkadzałoby to w pokazaniu różnicy, zmniejszyliśmy promień kuli, przypisując właściwości radius wartość 50. var sphere1:Sphere = new Sphere(); sphere1.radius = 50; sphere1.x = 100; sphere1.y = 50; sphere1.z = 0; view.scene.addChild(sphere1);
Po dodaniu kuli utworzyliśmy obiekt klasy ObjectContainer3D, który nazwaliśmy container i ustawiliśmy jego pozycję na każdej z trzech osi. var container:ObjectContainer3D = new ObjectContainer3D(); container.x = -200; container.y = -50; container.z = 100; view.scene.addChild(container);
Żeby móc dostrzec punkt zerowy obiektu container, dodaliśmy kolejny, mniejszy układ współrzędnych o nazwie containerLocalCoor. Ustawiliśmy długość jego osi na 100 pikseli i wyświetliliśmy etykiety każdej z osi, podając właściwości showLetters wartość true. var containerLocalCoor:Trident = new Trident(100, true); container.addChild(containerLocalCoor);
Rozdział 2. Podstawy biblioteki Away3D
59
Przyszedł czas na dodanie obiektu kuli sphere2 do zawartości kontenera. W tym celu wywołując na obiekcie container metodę addChild(), umieściliśmy sphere2 w kontenerze. Jak było w założeniach tego przykładu, obiektowi sphere2 przypisaliśmy identyczne wartości właściwości x, y oraz z z tymi, jakie posiada sphere1. var sphere2:Sphere = new Sphere(); sphere2.radius = 50; sphere2.x = 100; sphere2.y = 50; sphere2.z = 0;
Na koniec po dodaniu i wyświetleniu widoku w oknie programu Adobe Flash zastosowaliśmy funkcję trace, aby pokazać współrzędne obiektów sphere1 i sphere2. trace('sphere1: ', sphere1.x, sphere1.y, sphere1.z); trace('sphere2: ', sphere2.x, sphere2.y, sphere2.z);
Podsumowanie Najważniejszymi komponentami biblioteki Away3D są View3D, Scene3D, Camera3D, Object3D oraz ObjectContainer3D. Widok, czyli okno na trójwymiarową przestrzeń, traktowany jest w programie Flash jako zwykły element. Dlatego musi on być dodany do zawartości Stage metodą addChild(). Widok swoimi wymiarami pokrywa całą powierzchnię okna aplikacji. Widok standardowo umieszcza się w centrum okna, stosując konkretne wartości bądź właściwości stageWidth *.5 i stageHeight *.5. Przy budowaniu widoku domyślnie tworzone są również scena i kamera. Można odwołać się do nich poprzez właściwości scene i camera. Do wyświetlenia zawartości sceny służy metoda widoku o nazwie render(). Powoduje ona pojedyncze odświeżenie obrazu. Scena to główny kontener dla obiektów 3D. Jako potomna klasy ObjectContainer3D korzysta z jej metod oraz właściwości do modyfikowania zawartości. Kamera w Away3D to niewidoczny obiekt umieszczony w przestrzeni trójwymiarowej. Zadaniem kamery jest przedstawienie trójwymiarowej sceny z konkretnej pozycji w przestrzeni. Away3D ma kilka rodzajów kamer.
60
Flash i ActionScript. Aplikacje 3D od podstaw
Object3D jest podstawową klasą dla wszystkich obiektów trójwymiarowych. ObjectContainer3D to niewidoczny obiekt, który służy do grupowania innych obiektów o podobnych właściwościach lub mających stanowić część większej całości. Główna klasa aplikacji w Away3D może dziedziczyć z klas Sprite oraz MovieClip. Wybór zależy od tego, czy obiekt widoku będzie osadzony bezpośrednio na stole montażowym projektu w programie Adobe Flash oraz czy na obiekcie widoku wykonywane będą animacje zapisane na linii czasowej projektu. Adobe Flash od wersji CS4 obsługuje trzy osie, pozwala tym samym na tworzenie obiektów 3D. Układ współrzędnych stosowany w Away3D różni się od standardowego w programie Adobe Flash kierunkiem osi Y. W przypadku biblioteki Away3D wartości dodatnie osi Y skierowane są ku górze. Ponieważ każdy z obiektów w Away3D ma swój układ współrzędnych, rozróżniamy dwa rodzaje układów: globalny układ współrzędnych oraz lokalny układ współrzędnych.
Rozdział 3. Obiekty Kluczowym elementem każdej aplikacji 3D są wyświetlane w niej elementy. Niezależnie od tego, czy są one prostymi obiektami, czy bardzo złożonymi, bez nich scena jest tylko pustą przestrzenią. Wraz z powstaniem pierwszego programu wyświetlającego grafikę wyrosły nowe gałęzie przemysłu IT, które nieprzerwanie rozwijają się w różnych kierunkach. Weźmy za przykład przemysł gier komputerowych. Jedna z pierwszych gier, jakie powstały, Spacewar!, składała się jedynie z kilkunastu punktów i linii, a dzisiejsze tytuły oferują nieprzeciętne obrazy złożone z tysięcy wielokątów oprawionych w szczegółowe tekstury. Z użyciem biblioteki Away3D w wersji 3.6 można stworzyć aplikacje, których poziom wygenerowanego obrazu można przyrównać do poziomu gier pisanych na konsole PlayStation. Podobnie jak inne biblioteki, Away3D jest stale rozwijany i jego możliwości będą nadal poszerzane. Niezależnie od tego trzeba mieć wiedzę o podstawowych obiektach, jakie są dostępne w Away3D, i tym właśnie zajmiemy się na kolejnych kilkudziesięciu stronach. Ponieważ wiele jest tych obiektów, dla utrzymania pewnego porządku i odseparowania różnych od siebie kategorii klas zostały one podzielone na następujące grupy: Base, Sprites, Primitives oraz Skybox. We wstępie każdego z tych podrozdziałów umieszczone są ogólna charakterystyka i objaśnienie danej grupy. Poza tym czytając ten rozdział, dowiesz się: Co jest podstawą każdego wyświetlanego obiektu. Jak tworzyć i wyświetlać bryły w Away3D. Jak modyfikować budowę poszczególnych obiektów. W jakich sytuacjach można wykorzystać omawiane obiekty. Jakie właściwości oraz metody mają poszczególne klasy generujące bryły.
62
Flash i ActionScript. Aplikacje 3D od podstaw
Base Klasy opisane w tym podrozdziale umieszczone są w pakiecie away3d.core.base. Uznałem, że przed poznaniem konkretnych klas tworzących gotowe obiekty warto dowiedzieć się, z czego tak naprawdę są one zbudowane, który element odpowiada za budowę siatki (zwanej też szkieletem), a który za wypełnienie powierzchni boków. Wiedza zawarta w tym podrozdziale może się okazać pomocna w sytuacji, gdy zaistnieje potrzeba zmodyfikowania struktury jednego ze standardowych obiektów lub wręcz stworzenia całkiem nowego i niestandardowego.
Vertex Podstawą wszystkich brył — niezależnie od złożoności ich budowy — są punkty. W przestrzeni trójwymiarowej punkty nie przybierają kształtu ani nie są nawet widoczne. Istnieją one dla nas jako pozycja w przestrzeni określona względem osi X, Y oraz Z. W Away3D punkt reprezentowany jest przez klasę Vertex, która znajduje się w pakiecie away3d.core.base. Poniższy kod źródłowy prezentuje sposób tworzenia punktu Vertex oraz wykorzystania kilku jego właściwości i metod. Po skompilowaniu kodu w oknie aplikacji wyświetli się tylko białe tło. To, co jest interesujące, pojawi się w zakładce Output edytora. package { import import import import
away3d.containers.View3D; away3d.core.base.Vertex; flash.geom.Vector3D; flash.display.Sprite;
public class vertexExample extends Sprite { public function vertexExample() { var view:View3D = new View3D(); view.x = 320; view.y = 240; addChild(view); var v1:Vertex = new Vertex(); var v2:Vertex = new Vertex(100, 100, 100); trace("v1.position: ", v1.position); trace("v2.position: ", v2.position); trace("Vertex.distanceSqr: ", Vertex.distanceSqr(v1, v2)); trace("Vertex.median: ", Vertex.median(v1, v2)); trace("Vertex.weighted: ", Vertex.weighted(v1, v2, 10, 1));
Rozdział 3. Obiekty
63
trace("\noperacje: v1.add() i v2.adjust()"); v1.add(new Vector3D(-50, -50, -50)); v2.adjust(10, 10, 10, 3); trace("v1.position: ", v1.position); trace("v2.position: ", v2.position); trace("\noperacje: v1.reset()"); v1.reset(); trace("v1.position: ", v1.position); trace("\noperacje: v1.setValue()"); v1.setValue(10, 10, 10); trace("v1.position: ", v1.position); } } }
W powyższym kodzie źródłowym najpierw zaimportowaliśmy potrzebne biblioteki, z których najważniejszymi w tym przypadku są: import away3d.core.base.Vertex; import flash.geom.Vector3D;
W konstruktorze stworzyliśmy obiekt widoku o nazwie view oraz dwa obiekty klasy Vertex o nazwach v1 oraz v2: pierwszy z nich z domyślnymi ustawieniami, a w drugim zmieniliśmy położenie względem wszystkich osi, w konstruktorze podając wartość równą 100 trzem argumentom: x, y oraz z. var v1:Vertex = new Vertex(); var v2:Vertex = new Vertex(100, 100, 100);
W kolejnych linijkach konstruktora, stosując funkcję trace(), wyświetlamy wyniki poszczególnych metod statycznych dostępnych w klasie Vertex. Jako pierwszą zapisaliśmy metodę distanceSqr(), która zwraca w postaci liczby sumę dodanych do siebie i podniesionych do kwadratu współrzędnych podanych dwóch punktów. Vertex.distanceSqr(v1, v2);
Metoda median() zwraca nowy obiekt klasy Vertex, którego współrzędne są wartościami środkowymi pozycji podanych punktów v1 i v2. Vertex.median(v1, v2);
Metoda weighted() zwraca nowy obiekt klasy Vertex, który reprezentuje średnią ważoną dwóch punktów: v1 i v2. Jako argumenty dla tej metody podaje się obiekty wierzchołków oraz ich wagę. Vertex.weighted(v1, v2, 10, 1);
Do zmiany położenia punktu służą metody: add(), adjust(), setValue() oraz reset(). W metodzie add() jako argument należy podać obiekt klasy Vector3D z konkretnymi współrzędnymi x, y i z. Te wartości zostaną dodane do współrzędnych obiektu, na którym wywołano metodę add().
64
Flash i ActionScript. Aplikacje 3D od podstaw v1.add(new Vector3D(-50, -50, -50));
Metoda adjust() modyfikuje aktualne położenie punktu o przypisane w argumentach wartości x, y i z. Dodatkowy argument k=1 określa wielokrotność zmiany stosowanej według wzoru: [aktualna pozycja na osi]*(1 – k) + [wartość argumentu]*k
Metoda setValue() zastępuje wartość pozycji na wszystkich osiach tymi podanymi jako argumenty x, y i z. v1.setValue(10, 10, 10);
Metoda reset() powoduje ustawienie punktu w pozycji zerowej otoczenia, w jakim się znajduje. Uruchomienie tego przykładu spowoduje wyświetlenie następującego wyniku: v1.position: v2.position: Vertex.distanceSqr: Vertex.median: Vertex.weighted: 9.090909090909092)
Vector3D(0, 0, 0) Vector3D(100, 100, 100) 30000 new Vertex(50, 50, 50) new Vertex(9.090909090909092, 9.090909090909092,
operacje: v1.add() i v2.adjust() v1.position: Vector3D(-50, -50, -50) v2.position: Vector3D(-170, -170, -170) operacje: v1.reset() v1.position: Vector3D(0, 0, 0) operacje: v1.setValue() v1.position: Vector3D(10, 10, 10) Vertex — skoro jest niewidoczny — może się wydawać niepozornym elementem, jednak w grupie punktów jako wierzchołek określający budowę bryły ma fundamentalne znaczenie. Umiejętność posługiwania się punktami daje możliwość modyfikowania powierzchni obiektu w dowolny sposób oraz kształtowania relacji i zależności między nimi.
Tabele 3.1 oraz 3.2 zawierają właściwości i metody klasy Vertex, których znajomość przyda się przy okazji przykładów transformacji obiektu w przestrzeni.
Rozdział 3. Obiekty
65
Tabela 3.1. Właściwości klasy Vertex
Nazwa
Rodzaj
Wartość domyślna
Opis
x
Number
0
Określa pozycję punktu względem osi X
y
Number
0
Określa pozycję punktu względem osi Y
z
Number
0
Określa pozycję punktu względem osi Z
position
Vector3D
Vector3D(0,0,0)
Określa pozycję punktu w przestrzeni
extra
Object
null
Obiekt zawierający zdefiniowane przez użytkownika właściwości
Tabela 3.2. Metody klasy Vertex
Nazwa
Opis
Vertex(x:Number, y:Number, z:Number)
Konstruktor
add(value:Vector3D)
Sumuje odpowiednio każdą z wartości x, y oraz z aktualnej pozycji z wartościami podanymi w argumencie typu Vector3D
distanceSqr(a:Vertex,b:Vertex)
Zwraca w postaci liczby sumę dodanych do siebie i podniesionych do kwadratu współrzędnych z podanych punktów a i b
median(a:Vertex, b:Vertex)
Zwraca obiekt typu Vertex, którego współrzędne są wartościami środkowymi podanych punktów
weighted(a:Vertex, b:Vertex, aw:Number, bw:Number)
Zwraca obiekt klasy Vertex reprezentujący średnią ważoną podanych punktów
Mesh Obiekt klasy Mesh spełnia ważną funkcję między innymi dla obiektów klas Segment, Face czy Sprite3D. Mesh jest kontenerem oraz wizualną powłoką dla obiektów posiadających współrzędne i odpowiednie dane, ale nieposiadających warstwy widocznej dla użytkownika. Za pomocą odpowiednich metod (addSegment(), addFace() i addSprite()) możemy uzupełnić zawartość obiektu klasy Mesh. Wybór metody uzależniony jest od rodzaju elementu, jaki chcemy dodać. Poznamy je w kolejnych podpunktach, a na razie przyjrzyjmy się tabelom 3.3 i 3.4, które zawierają właściwości i metody klasy Mesh. Wiele z nich na chwilę obecną nie będzie nam potrzebnych, jednak z czasem, przy bardziej złożonych aplikacjach, mogą się okazać bardzo przydatne.
66
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.3. Właściwości klasy Mesh
Nazwa
Rodzaj
Wartość Opis domyślna
elements
Vector.
Zwraca tablicę wszystkich elementów zawartych w obiekcie Mesh
faces
Vector.
Zwraca tablicę wszystkich elementów typu Face zawartych w obiekcie Mesh
segments
Vector.
Zwraca tablicę wszystkich elementów typu Segment zawartych w obiekcie Mesh
sprites
Vector.
Zwraca tablicę wszystkich elementów typu Sprite3D zawartych w obiekcie Mesh
vertices
Vector.
Zwraca tablicę wszystkich punktów Vertex zawartych w obiekcie Mesh
indexes
Array
Tablica indeksów klatek w modelach typu MD2
bothsides
Boolean
material
Material
Definiuje rodzaj materiału do pokrycia elementów typu Face, Segment lub Sprite3D
back
Material
Określa rodzaj materiału, jaki ma być wykorzystany do pokrycia powierzchni odwróconych od kamery
outline
Material
Określa rodzaj materiału, jaki ma być wykorzystany do pokrycia linii obiektu
geometry
Geometry
Definiuje obiekt typu Geometry wykorzystany w obiekcie Mesh
type
String
url
String
false
mesh
Definiuje, czy elementy obiektu odwrócone od kamery mają być renderowane
Nazwa rodzaju klasy wykorzystanej do stworzenia obiektu Adres stworzonego obiektu w postaci obiektu String.
Tabela 3.4. Metody klasy Mesh
Nazwa
Opis
Mesh
Konstruktor
addFace(face:Face):void
Dodaje obiekt typu Face
addSegment(segment:Segment):void
Dodaje obiekt typu Segment
addSprite(sprite3d:Sprite3D):void
Dodaje obiekt typu Sprite3D
Rozdział 3. Obiekty
67
Tabela 3.4. Metody klasy Mesh (ciąg dalszy)
Nazwa
Opis
asAS3Class(classname:String, packagename:String, round:Boolean, animated:Boolean):String
Zwraca dane obiektu w postaci klasy AS 3.0, którą można wykorzystać do powielania obiektu
asXML():XML
Zwraca dane obiektu w postaci XML
applyPosition(dx:Number, dy:Number, dz:Number):void
Aktualizuje pozycję obiektu Mesh bez ingerencji w położenie zawartych w nim elementów
applyRotations():void
Aktualizuje kąty nachylenia geometrii obiektu bez ingerencji w wygląd całego obiektu Mesh
splitFace(face:Face, side:int = 0):void
Dzieli wybrany obiekt klasy Face na dwie części
splitFaces(side:int = 0):void
Dzieli na dwie części wszystkie obiekty klasy Face wewnątrz obiektu Mesh
triFace(face:Face):void
Dzieli na trzy części wybrany obiekt klasy Face wewnątrz obiektu Mesh
triFaces():void
Dzieli na trzy części wszystkie obiekty klasy Face wewnątrz obiektu Mesh
quarterFace(face:Face):void
Dzieli na cztery równe części wybrany obiekt klasy Face wewnątrz obiektu Mesh
quarterFaces():void
Dzieli na cztery równe części wszystkie obiekty klasy Face wewnątrz obiektu Mesh
invertFaces():void
Odwraca wszystkie element typu Face wewnątrz obiektu Mesh
removeFace(face:Face):void
Usuwa wybrany obiekt typu Face wewnątrz obiektu Mesh
removeSegment(segment:Segment):void
Usuwa wybrany obiekt typu Segment wewnątrz obiektu Mesh
removeSprite(sprite3d:Sprite3D):void
Usuwa wybrany obiekt typu Sprite3D wewnątrz obiektu Mesh
updateMesh(view:View3D):void
Aktualizuje w wybranym widoku wygląd obiektu Mesh
updateVertex(v:Vertex, x:Number, y:Number, z:Number, refreshNormals:Boolean):void
Aktualizuje pozycję wybranego obiektu klasy Vertex
clone(object:Object3D):Object3D
Kopiuje właściwości obiektu Mesh do innego elementu 3D
cloneAll(object:Object3D):Object3D
Kopiuje całą strukturę obiektu Mesh do innego elementu 3D
68
Flash i ActionScript. Aplikacje 3D od podstaw
Segment W Away3D obiekt Segment to nic innego jak linia określona dwoma punktami Vertex. W przestrzeni trójwymiarowej możemy narysować ją jako linię prostą lub krzywą, stosując metody lineTo(), curveTo() czy też defineSingleCurve(). Należy pamiętać, że samo utworzenie obiektu Segment nie sprawi, że będzie on widoczny na scenie. W pierwszej kolejności należy zdefiniować obiekt typu Mesh, a następnie — posługując się metodą addSegment() — dodać do niego obiekt Segment. Zastosowań dla Segment nie ma zbyt wielu, aczkolwiek jak zawsze wszystko zależy od naszej wyobraźni. Można wykorzystać go jako linię wskazującą na jakiś element lub traktować jako obiekt pomocniczy do ustalenia odległości między obiektami, toru lotu lub granic relacji między obiektami. My jako przykład zastosowania każdej z metod rysowania stworzymy kilka zwykłych linii, jak pokazano na rysunku 3.1. Rysunek 3.1.
Wygenerowane różne obiekty klasy Segment
Aby uzyskać efekt przedstawiony na rysunku 3.1, przepisz i skompiluj następujący kod źródłowy: package { import import import import import
away3d.core.base.Mesh; away3d.core.base.Segment; away3d.core.base.Vertex; away3d.containers.View3D; away3d.materials.ShadingColorMaterial;
Rozdział 3. Obiekty
69
import flash.events.Event; import flash.display.Sprite; import flash.geom.Vector3D; public class segmentExample extends Sprite { private var view:View3D; private var mesh:Mesh; public function segmentExample() { view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); mesh = new Mesh(); view.scene.addChild(mesh); drawLines(); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function drawLines():void { var s1:Segment = new Segment(new Vertex(0,0,0),new Vertex(-100,100,0), new ShadingColorMaterial(null,{wireColor:0x000000})); mesh.addSegment(s1); var s2:Segment = new Segment(); s2.material = new ShadingColorMaterial(null, { wireColor:0x8218DF } ); s2.moveTo(0, 0, 0); s2.lineTo(100, 100, 0); mesh.addSegment(s2); var s3:Segment = new Segment(); s3.material = new ShadingColorMaterial(0x39DF18, { wireColor:0xFF0000 } ); s3.curveTo(100, 0, 0, 100, -100, 0); s3.curveTo(100, -200, 0, 200, -200, 0); mesh.addSegment(s3); } private function onEnterFrame(e:Event):void { mesh.rotationY -= 1; view.render(); } } }
W pierwszej kolejności zaimportowaliśmy potrzebne biblioteki. W tym przypadku najbardziej interesują nas następujące: import import import import
away3d.core.base.Segment; away3d.core.base.Mesh; away3d.materials.ShadingColorMaterial; flash.geom.Vector3D;
70
Flash i ActionScript. Aplikacje 3D od podstaw
Z wyżej wymienionych klas Segment reprezentuje obiekty linii. Aby były one widoczne, potrzebowaliśmy również obiektu klasy Mesh. Żeby rozróżnić kolorami poszczególne linie, zastosowaliśmy materiał ShadingColorMaterial, który w swoich właściwościach ma wypełnienie dla krawędzi. Więcej o materiałach dowiesz się w rozdziale 4. „Materiały”. Klasa Vector3D posłużyła nam do określania pozycji w przestrzeni. Następnym krokiem, jaki wykonaliśmy, było zdefiniowanie ogólnodostępnych wewnątrz klasy obiektów klasy Mesh oraz widoku View3D: private var view:View3D; private var mesh:Mesh;
W ciele konstruktora naszej klasy w pierwszej kolejności utworzyliśmy obiekt wcześniej zdefiniowany jako view i nadaliśmy mu nowe położenie w oknie aplikacji. Stosując metodę addChild(), dodaliśmy widok do zasobów stołu montażowego. view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view);
W następnej kolejności stworzyliśmy obiekt o nazwie mesh i dodaliśmy go do zasobów sceny widoku, stosując referencję do obiektu scene i wywołując jego metodę addChild(). mesh = new Mesh(); view.scene.addChild(mesh);
Na końcu konstruktora wywołaliśmy metodę rysującą linie drawLines() oraz zarejestrowaliśmy obiekt nasłuchujący zdarzenie Event.ENTER_FRAME, który odpowiada za wywoływanie metody onEnterFrame() po każdym odświeżeniu klatki. drawLines(); addEventListener(Event.ENTER_FRAME, onEnterFrame);
W kodzie metody drawLines() stworzyliśmy trzy różne segmenty. Robimy tak, ponieważ chcemy sprawdzić działanie poszczególnych metod klasy Segment i wyróżnić każdą innym kolorem. Pierwszy segment s1 to linia prosta. Dodaliśmy ją, określając punkty początkowy, końcowy oraz ustalając materiał linii. W kolejnej linijce za pomocą wcześniej wspomnianej metody addSegment() dodaliśmy linię do obiektu mesh. Dzięki temu linia jest dla nas widoczna. var s1:Segment = new Segment(new Vertex(0,0,0), new Vertex(-100,100,0), new ShadingColorMaterial(null,{wireColor:0xFFFFFF}) ); mesh.addSegment(s1);
Rozdział 3. Obiekty
71
W przypadku drugiego segmentu s2 zastosowaliśmy metody, które w pewnym stopniu mają swoje odpowiedniki w standardowym kodzie ActionScript 3.0. Mowa o metodzie moveTo(), której użyliśmy do wyznaczenia punktu początkowego, oraz o metodzie lineTo(), rysującej linię do wyznaczonego punktu końcowego. Zarówno moveTo(), jak i lineTo() potrzebują trzech współrzędnych do narysowania obiektu klasy Segment. var s2:Segment = new Segment(); s2.material = new ShadingColorMaterial(null, { wireColor:0x8218DF } ); s2.moveTo(0, 0, 0); s2.lineTo(100, 100, 0); mesh.addSegment(s2);
Trzeci segment zawiera dwie krzywe tworzące falę. Krzywe tworzy się za pomocą metody curveTo(). Jako właściwości w pierwszej kolejności podaliśmy pozycje x, y oraz z dla punktu kontrolnego, inaczej zwanego też koordynatem. Punkt ten, podobnie jak przy rysowaniu krzywych 2D, odpowiada za wygięcie linii. Po podaniu pozycji tego punktu podajemy współrzędne dla punktu, w którym linia ma się zakończyć. W tym segmencie pokazaliśmy, że możemy dodawać więcej niż jedną krzywiznę. var s3:Segment = new Segment(); s3.material = new ShadingColorMaterial(0x39DF18, { wireColor:0xFF0000 } ); s3.curveTo(100, 0, 0, 100, -100, 0); s3.curveTo(100, -200, 0, 200, -200, 0); mesh.addSegment(s3);
W metodzie onEnterFrame() zapisaliśmy obracanie obiektem mesh i odświeżanie widoku. Spis najistotniejszych właściwości oraz metod klasy Segment zawierają tabele 3.5 i 3.6. Tabela 3.5. Właściwości klasy Segment
Nazwa
Rodzaj
Wartość domyślna
Opis
v0
Vertex
null
Punkt v0 obiektu Segment
v1
Vertex
null
Punkt v1 obiektu Segment
material
Material
null
Pokrycie powierzchni obiektu Segment
Tabela 3.6. Metody klasy Segment
Nazwa
Opis
Segment(v0:Vertex, v1:Vertex, material:Material)
Konstruktor
moveTo(x:Number, y:Number, z:Number):void
Ustawienie punktu początkowego dla linii
lineTo(x:Number, y:Number, z:Number):void
Rysowanie linii do wyznaczonego punktu
72
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.6. Metody klasy Segment (ciąg dalszy)
Nazwa
Opis
curveTo(cx:Number, cy:Number, cz:Number, ex:Number, ey:Number, ez:Number):void
Rysowanie krzywej przez podanie współrzędnych punktu kontrolnego i końcowego
drawPath(path:Path):void
Rysowanie ścieżki przez użycie obiektu Path
Face Obiekt Face zlokalizowany w pakiecie away3d.core.base jest zbiorem trzech punktów Vertex, które połączone tworzą trójkąt. Powierzchnia Face może zostać wypełniona dostępnymi w Away3D materiałami. Więcej o materiałach dowiesz się w rozdziale 4. „Materiały”. Podobnie jak obiekty klasy Segment, Face sam w sobie nie jest widoczny, do tego potrzebuje warstwy wizualnej, jaką jest obiekt klasy Mesh. Jak wspomnieliśmy wcześniej przy omawianiu klasy Mesh, zawiera ona metodę addFace(), która służy do dodawania obiektów Face tak, aby były widoczne na scenie. Przy tworzeniu obiektów klasy Face najważniejsze są trzy pierwsze argumenty konstruktora, które tworzą podstawowe wierzchołki trójkąta. Argumenty te nazwane są kolejno v0, v1 oraz v2 i są obiektami klasy Vertex. Położenie tych wierzchołków w podstawowej formie przedstawiono na rysunku 3.2. Rysunek 3.2.
Ułożenie wierzchołków obiektu klasy Face
W przykładzie zastosowania obiektów klasy Face stworzymy obiekt o nazwie mesh, złożony z ośmiu trójkątów, które połączymy tak, aby całość przypominała gwiazdę, tak jak to pokazano na rysunku 3.3.
Rozdział 3. Obiekty Rysunek 3.3.
Wykorzystanie obiektów Face
package { import import import import import import
away3d.core.base.Face; away3d.core.base.Mesh; away3d.core.base.Vertex; away3d.containers.View3D; flash.events.Event; flash.display.Sprite;
public class faceExample extends Sprite { private var view:View3D; private var mesh:Mesh; public function faceExample() { view = new View3D(); view.x = stage.stageWidth * 0.5; view.y = stage.stageHeight * 0.5; addChild(view); drawFaces(); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function drawFaces():void { mesh = new Mesh(); mesh.bothsides = true; view.scene.addChild(mesh);
73
74
Flash i ActionScript. Aplikacje 3D od podstaw
}
}
}
var f1:Face = new Face(new Vertex(0, 0, 0), new Vertex(-100, -100, 50), new Vertex(100, -100, 50)); var f2:Face = new Face(new Vertex(0, 0, 0), new Vertex(100, -100, 50), new Vertex(100, 100, 50)); var f3:Face = new Face(new Vertex(0, 0, 0), new Vertex(100, 100, 50), new Vertex(-100, 100, 50)); var f4:Face = new Face(new Vertex(0, 0, 0), new Vertex(-100, 100, 50), new Vertex(-100, -100, 50)); var f5:Face = new Face(new Vertex(0, -200, 0), new Vertex(-100, -100, 50), new Vertex(100, -100, 50)); var f6:Face = new Face(new Vertex(200, 0, 0), new Vertex(100, -100, 50), new Vertex(100, 100, 50)); var f7:Face = new Face(new Vertex(0, 200, 0), new Vertex(100, 100, 50), new Vertex(-100, 100, 50)); var f8:Face = new Face(new Vertex(-200, 0, 0), new Vertex(-100, 100, 50), new Vertex(-100, -100, 50)); mesh.addFace(f1); mesh.addFace(f2); mesh.addFace(f3); mesh.addFace(f4); mesh.addFace(f5); mesh.addFace(f6); mesh.addFace(f7); mesh.addFace(f8); private function onEnterFrame(e:Event):void { mesh.rotationY -= 1; view.render(); }
Do stworzenia efektu pokazanego na rysunku 3.3 wykorzystaliśmy przede wszystkim klasę Face, której osiem obiektów odpowiednio ułożonych tworzy kształt gwiazdy. Do przedstawienia tego elementu na scenie potrzebowaliśmy również klasy Mesh, z kolei aby ustalić pozycję każdego z wierzchołków, zastosowaliśmy znany nam obiekt Vertex. Poza tymi klasami dodaliśmy również widok View3D, klasę zdarzenia Event i kontener Sprite. W ciele klasy faceExample zdefiniowaliśmy obiekt widoku view oraz kontener mesh poza konstruktorem. Dzięki temu są one dostępne dla wszystkich metod umieszczonych w klasie. W konstruktorze klasy w pierwszej kolejności stworzyliśmy widok oraz ustawiliśmy jego pozycję na środku okna aplikacji, przypisując właściwości x wartość równą połowie szerokości okna aplikacji stage.stageWidth * 0.5, a właściwości y wartość równą połowie wysokości okna stage.stageHeight * 0.5. W następnej kolejności, stosując metodę addChild(), dodaliśmy widok do zasobów stołu montażowego.
Rozdział 3. Obiekty
75
Później wywołaliśmy metodę drawLines() i dodaliśmy rejestrator zdarzenia Event. ENTER_FRAME.
W metodzie drawLines() na samym początku stworzyliśmy obiekt klasy Mesh i dodaliśmy go do sceny widoku, ustawiliśmy również wartość właściwości bothsides jako true, tak aby podczas obrotu były widoczne wszystkie ściany umieszczonych wewnątrz elementów. mesh = new Mesh(); mesh.bothsides = true; view.scene.addChild(mesh);
W kontenerze mesh umieściliśmy osiem obiektów klasy Face, w argumentach podając położenie każdego z jego wierzchołków. Współrzędne te nie odnoszą się do swojej przestrzeni, lecz do układu współrzędnych obiektu, w którym się znajdują. W naszym przypadku obiektem tym jest mesh. var f1:Face = new Face(new Vertex(0, 0, 0), new Vertex(-100, -100, 50), new Vertex(100, -100, 50)); var f2:Face = new Face(new Vertex(0, 0, 0), new Vertex(100, -100, 50), new Vertex(100, 100, 50)); var f3:Face = new Face(new Vertex(0, 0, 0), new Vertex(100, 100, 50), new Vertex(-100, 100, 50)); var f4:Face = new Face(new Vertex(0, 0, 0), new Vertex(-100, 100, 50), new Vertex(-100, -100, 50)); var f5:Face = new Face(new Vertex(0, -200, 0), new Vertex(-100, -100, 50), new Vertex(100, -100, 50)); var f6:Face = new Face(new Vertex(200, 0, 0), new Vertex(100, -100, 50), new Vertex(100, 100, 50)); var f7:Face = new Face(new Vertex(0, 200, 0), new Vertex(100, 100, 50), new Vertex(-100, 100, 50)); var f8:Face = new Face(new Vertex(-200, 0, 0), new Vertex(-100, 100, 50), new Vertex(-100, -100, 50));
Po stworzeniu wszystkich obiektów Face oraz nadaniu im odpowiednich właściwości dodaliśmy je do obiektu mesh, stosując metodę addFace(). mesh.addFace(f1); mesh.addFace(f2); mesh.addFace(f3); mesh.addFace(f4); mesh.addFace(f5); mesh.addFace(f6); mesh.addFace(f7); mesh.addFace(f8);
Podobnie jak w przypadku klasy Vertex, znajomość metod i właściwości klasy Face jest potrzebna do lepszego zrozumienia sposobu funkcjonowania i wyświetlania bardziej złożonych brył w przestrzeni trójwymiarowej. Z tego powodu w tabelach 3.7 i 3.8 wypisane zostały najważniejsze właściwości i metody klasy Face.
76
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.7. Właściwości klasy Face
Nazwa
Rodzaj
Wartość domyślna Opis
area
Number
NaN
Zwraca wyliczoną wartość płaskiej powierzchni obiektu Face
material
Material
null
Definiuje pokrycie powierzchni obiektu Face
back
Material
null
Definiuje pokrycie tylnej części obiektu Face
isBack
Boolean
false
Określa, czy obiekt Face będzie miał tylną stronę
normal
Vector3D
uvs
Vector
v0
Vertex
null
Punkt v0 obiektu Face
v1
Vertex
null
Punkt v1 obiektu Face
v2
Vertex
null
Punkt v2 obiektu Face
Zwraca wektor normalny prostopadły do obiektu Face
Zwraca tablicę użytych obiektów typu UV na obiekcie Face
Tabela 3.8. Metody klasy Face
Nazwa
Opis
Face(v0:Vertex, v1:Vertex, v2:Vertex, material:Material, uv0:UV, uv1:UV, uv2:UV)
Konstruktor
curveTo(cx:Number, cy:Number, cz:Number, ex:Number, ey:Number, ez:Number):void
Od pozycji obiektu Face do ustalonego położenia rysuje krzywą
lineTo(x:Number, y:Number, z:Number):void
Od pozycji obiektu Face do ustalonego położenia rysuje prostą
moveTo(x:Number, y:Number, z:Number):void
Zmiana pozycji obiektu Face
invert():void
Zmienia punkty v1, v2 oraz uv1 i uv2, odwracając w ten sposób obiekt Face
Sprites Zanim zajmiemy się przestrzennymi obiektami dostępnymi w Away3D, przyjrzymy się obiektom, które na pierwszy rzut oka sprawiają wrażenie, jakby były stworzone bezpośrednio w kodzie ActionScript bez jakiejkolwiek biblioteki 3D. Klasy umieszczone w pakiecie away3d.sprites tworzą dwuwymiarowe obiekty osadzone w przestrzeni trójwymiarowej, które niezależnie od pozycji kamery są zawsze skierowane w jej stronę. Obiekty klas, które omówimy w tym podrozdziale, przeważnie wykorzystuje się w celu optymalizacji i poprawienia wydajności aplikacji. Przyjmując jako przykład scenę przestrzeni kosmicznej, na której tle przemieszczają się komety, nie trzeba stosować trójwymiarowych obiektów do
Rozdział 3. Obiekty
77
ich wyświetlenia. Zamiast tego można przygotować płaski element graficzny i osadzić go na obiekcie klasy z pakietu away3d.sprites. W tym podrozdziale poznamy klasy Sprite3D, MovieClipSprite oraz DirectionalSprite. Poza nimi istnieje jeszcze klasa DepthOfFieldSprite, ale o niej wspomnimy przy okazji omawiania głębi ostrości w rozdziale 9. „Kamery”. Na początek jednak napiszemy klasę bazową, w której wykorzystamy wszystkie wyżej wymienione klasy z pakietu away3d.sprites.
Klasa bazowa Jak wspomniałem, elementy typu Sprite często wykorzystuje się, aby zoptymalizować kod aplikacji, na przykład w sytuacjach, gdy dany obiekt występuje w dużych ilościach i można zastąpić go elementem graficznym takim jak bitmapa. Poza takimi operacjami Sprite nadaje się do tworzenia gier klimatem nawiązujących do czasów Doom, Duke Nukem 3D, Shadow Warrior bądź Blood. Nawiązując do tych tytułów, w naszym przykładzie przedstawimy głównego bohatera otoczonego przez zgraję potworów, tak jak to zaprezentowano na rysunku 3.4. W tym celu posłużyliśmy się obiektami Sprite3D, MovieClipSprite oraz DirectionalSprite, poza tym dodamy również obiekt klasy GridPlane, którego zadaniem będzie zaznaczenie tego, że wszystko dzieje się w przestrzeni 3D, a nie bezpośrednio na stole montażowym aplikacji. Rysunek 3.4.
Obiekty z pakietu away3d.sprites rozmieszczone na planszy
78
Flash i ActionScript. Aplikacje 3D od podstaw
Aby osiągnąć taki efekt jak na rysunku 3.4, przepisz i skompiluj następujący kod źródłowy: package { import import import import import import import import import import import import import
away3d.containers.View3D; away3d.core.base.Mesh; away3d.sprites.Sprite3D; away3d.sprites.MovieClipSprite; away3d.sprites.DirectionalSprite; away3d.materials.BitmapFileMaterial; away3d.primitives.GridPlane; away3d.core.base.Vertex; flash.display.StageAlign; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D;
public class spritesExample extends Sprite { private var view:View3D; private var hero:Mesh; public function spritesExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; view = new View3D(); view.camera.position = new Vector3D(0, 600, -1000); view.camera.lookAt(new Vector3D(0, 0, 0)); addChild(view); var grid:GridPlane = new GridPlane( { width:1000, height:1000, segmentsH:10, segmentsW:10 } ); view.scene.addChild(grid); onResize(); initSprite3DObjects(); initMovieClipSpriteObjects(); initDirectionalSpriteObject(); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function initSprite3DObjects():void { var s3d:Sprite3D = new Sprite3D(new BitmapFileMaterial('../../ resources/sprites/doom/monster.png'), 91, 74); var monster1:Mesh = new Mesh( { x: -300, z: 300 } ); monster1.addSprite(s3d);
Rozdział 3. Obiekty
79
var monster2:Mesh = monster1.clone() as Mesh; monster2.x = 300; var monster3:Mesh = monster2.clone() as Mesh; monster3.z = -300; var monster4:Mesh = monster3.clone() as Mesh; monster4.x = -300; view.scene.addChild(monster1); view.scene.addChild(monster2); view.scene.addChild(monster3); view.scene.addChild(monster4); } private function initMovieClipSpriteObjects():void { var monster1:Mesh = new Mesh( { z: 300 } ); monster1.addSprite(new MovieClipSprite(new Monster())); var monster2:Mesh = new Mesh( { z: -300 } ); monster2.addSprite(new MovieClipSprite(new Monster())); var monster3:Mesh = new Mesh( { x: 300 } ); monster3.addSprite(new MovieClipSprite(new Monster())); var monster4:Mesh = new Mesh( { x: -300 } ); monster4.addSprite(new MovieClipSprite(new Monster())); view.scene.addChild(monster1); view.scene.addChild(monster2); view.scene.addChild(monster3); view.scene.addChild(monster4); } private function initDirectionalSpriteObject():void { hero = new Mesh(); view.scene.addChild(hero); var ds:DirectionalSprite = new DirectionalSprite(null, 210, 216); ds.addDirectionalMaterial(new Vertex(0, 0, -1), new BitmapFileMaterial('../../resources/sprites/duke/f.png')); ds.addDirectionalMaterial(new Vertex(0, 0, 1), new BitmapFileMaterial('../../resources/sprites/duke/b.png')); ds.addDirectionalMaterial(new Vertex(1, 0, 0), new BitmapFileMaterial('../../resources/sprites/duke/r.png')); ds.addDirectionalMaterial(new Vertex(-1, 0, 0), new BitmapFileMaterial('../../resources/sprites/duke/l.png')); ds.addDirectionalMaterial(new Vertex(-.75, 0, -.75), new BitmapFileMaterial('../../resources/sprites/duke/fl.png')); ds.addDirectionalMaterial(new Vertex(.75, 0, -.75), new BitmapFileMaterial('../../resources/sprites/duke/fr.png')); ds.addDirectionalMaterial(new Vertex(-.75, 0, .75), new BitmapFileMaterial('../../resources/sprites/duke/bl.png')); ds.addDirectionalMaterial(new Vertex(.75, 0, .75), new BitmapFileMaterial('../../resources/sprites/duke/br.png')); hero.addSprite(ds); }
80
Flash i ActionScript. Aplikacje 3D od podstaw private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { hero.rotationY++; view.render(); } } }
Niektóre fragmenty kodu, takie jak metody initSprite3DObjects(), initMovieClip SpriteObjects(), initDirectionalSpriteObject(), zostaną wyjaśnione w dalszych częściach tego podrozdziału, teraz przyjrzymy się ogólnie jego zawartości. Głównym celem tego przykładu było pokazanie, w jaki sposób i do jakich sytuacji można wykorzystać elementy pakietu away3d.sprites. My umieściliśmy głównego bohatera na środku sceny i otoczyliśmy go ośmioma wrogami. Aby każdy z tych elementów był widoczny na scenie, musieliśmy zastosować obiekt klasy Mesh jako kontener dla każdej postaci z osobna. Z klas biblioteki Away3D dodaliśmy również klasę Vertex do ustalenia punktów w przestrzeni, klasę GridPlane generującą planszę, jeden z rodzajów materiałów BitmapFileMaterial oraz trzy klasy billboardów: Sprite3D, MovieClipSprite i DirectionalSprite. W ciele klasy poza wszystkimi metodami zadeklarowaliśmy dwa obiekty: widok Away3D pod nazwą view oraz obiekt klasy Mesh o nazwie hero, który będzie przechowywał obiekt klasy DirectionalSprite głównego bohatera. W konstruktorze klasy spritesExample w pierwszej kolejności ustawiliśmy wyrównanie stołu montażowego względem okna oraz to, że elementy w nim umieszczone nie będą skalowane wraz ze zmianą rozmiaru okna. stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE;
Następnie stworzyliśmy widok View3D i ustawiliśmy pozycję kamery tak, aby widok pasował mniej więcej do grafiki głównego bohatera. Po zamianie pozycji musieliśmy zastosować metodę lookAt() na obiekcie kamery, aby skorygować kierunek, w którym jest zwrócona. Po ustaleniu wszystkich opcji związanych z widokiem dodaliśmy go do listy wyświetlanych obiektów w naszej aplikacji, stosując metodę addChild(). view = new View3D(); view.camera.position = new Vector3D(0, 600, -1000); view.camera.lookAt(new Vector3D(0, 0, 0)); addChild(view);
Rozdział 3. Obiekty
81
Do celów pomocniczych stworzyliśmy planszę, która jest obiektem klasy GridPlane; więcej o tym dowiemy się w dalszych częściach tego rozdziału. var grid:GridPlane = new GridPlane({width:1000,height:1000,segmentsH:10,segmentsW:10}); view.scene.addChild(grid);
Po stworzeniu widoku i planszy wywołaliśmy cztery różne metody i zarejestrowaliśmy nasłuch dwóch zdarzeń: Event.ENTER_FRAME i zmiany rozmiaru okna Event.RESIZE. onResize(); initSprite3DObjects(); initMovieClipSpriteObjects(); initDirectionalSpriteObject(); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame);
W kodzie metody onResize(), który wywoływany jest przy każdorazowej zmianie wielkości okna, zapisaliśmy wycentrowanie widoku. Aby móc wykorzystać tę metodę w innym miejscu niż tylko przy wystąpieniu zdarzenia Event.RESIZE, musieliśmy argumentowi e przypisać wartość null. Dzięki temu podczas wywołania metody onResize() w konstruktorze klasy spritesExample nie pojawi się błąd braku argumentu typu Event. view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5;
W metodzie wywoływanej zdarzeniem Event.ENTER_FRAME zapisaliśmy odświeżenie zawartości sceny oraz nieustanny obrót obiektu hero. hero.rotationY++; view.render();
O ogólnej zawartości kodu to wszystko, teraz przyjrzyjmy się poszczególnym typom obiektów zastosowanych do wyświetlania dwuwymiarowych grafik.
Sprite3D Obiekt klasy Sprite3D jest prostokątną figurą 2D umieszczoną w przestrzeni trójwymiarowej. Skierowany jest zawsze w stronę kamery i ma tylko jeden punkt określający jego pozycję. Jedynym sposobem oddziaływania Sprite3D na otaczające go środowisko jest zmienienie swojej wielkości względem odległości od kamery. Dla przykładu: dostępna w tej klasie metoda rotation() nie definiuje obrotu obiektu względem trzech osi, tylko samej osi Z.
82
Flash i ActionScript. Aplikacje 3D od podstaw
Mimo że nie jest to element typowo trójwymiarowy, zakres jego zastosowań jest całkiem duży. W naszym przykładzie zastosowaliśmy go do wyświetlenia potwora, którego przedstawiono na rysunku 3.5. Rysunek 3.5.
Element graficzny osadzony w obiekcie klasy Sprite3D
W kodzie źródłowym naszego przykładu dodawanie obiektów Sprite3D zapisaliśmy w metodzie initSprite3DObjects(). W pierwszej kolejności stworzyliśmy obiekt s3d, podając jako argument nowy obiekt BitmapFileMaterial z określoną lokalizacją pliku graficznego. W kolejnych dwóch argumentach podaliśmy wymiary zgodne z szerokością i wysokością pliku PNG. var s3d:Sprite3D = new Sprite3D(new BitmapFileMaterial('../../resources/sprites/doom/monster.png'),91,74);
Następnie stworzyliśmy obiekt klasy Mesh o nazwie monster1, podając w jego argumentach położenie na scenie. Metodą addSprite() do jego zawartości dodaliśmy wcześniej utworzony element Sprite3D. var monster1:Mesh = new Mesh( { x: -300, z: 300 } ); monster1.addSprite(s3d);
Kolejne obiekty: monster2, monster3 i monster4, są klonami swoich poprzedników, chociaż ustawiliśmy je w innych pozycjach. Do stworzenia klonów wybranego obiektu stosuje się metodę clone(); należy ją wywołać na obiekcie, który ma być powielony. var monster2:Mesh = monster1.clone() as Mesh; monster2.x = 300; var monster3:Mesh = monster2.clone() as Mesh; monster3.z = -300; var monster4:Mesh = monster3.clone() as Mesh; monster4.x = -300;
Na końcu metody initSprite3DObjects(), po ustawieniu wszystkich obiektów, umieściliśmy je metodą addChild() na obiekcie sceny widoku. view.scene.addChild(monster1); view.scene.addChild(monster2); view.scene.addChild(monster3); view.scene.addChild(monster4);
Rozdział 3. Obiekty
83
W tabelach 3.9 i 3.10 przedstawiono właściwości i metody klasy Sprite3D. Tabela 3.9. Właściwości klasy Sprite3D
Nazwa
Rodzaj
Wartość domyślna
Opis
align
String
"center"
Określa wyrównanie grafiki osadzonej w obiekcie klasy Sprite3D
x
Number
Określa położenie obiektu względem osi X
y
Number
Określa położenie obiektu względem osi Y
z
Number
Określa położenie obiektu względem osi Z
width
Number
10
Określa szerokość obiektu
height
Number
10
Określa wysokość obiektu
material
Material
null
Definiuje rodzaj materiału pokrywającego obiekt
distanceScaling
Boolean
true
Określa, czy obiekt ma zmieniać swoją skalę proporcjonalnie do odległości
rotation
Number
0
Określa kąt obrotu obiektu
scaling
Number
1
W przypadku użycia materiału typu bitmapMaterial określa skalę obiektu
Tabela 3.10. Metody klasy Sprite3D
Nazwa
Opis
Sprite3D (material:Material = null, width:Number = 10, height:Number = 10, rotation:Number = 0, align:String = "center", scaling:Number = 1, distanceScaling:Boolean = true)
Konstruktor
MovieClipSprite Obiekt klasy MovieClipSprite jest bardzo podobny do poznanego wcześniej Sprite3D, różnica między nimi polega na rodzaju wyświetlanego źródła grafiki. W MovieClip Sprite jako grafikę należy podać odwołanie do elementu DisplayObject, którym może być między innymi MovieClip. Dzięki temu istnieje możliwość stosowania dwuwymiarowych animowanych obiektów w przestrzeni trójwymiarowej. W naszym przykładzie zastosowaliśmy prostą animację, w której potwór przedstawiony na rysunku 3.6 w błyskawicznym tempie zmienia swój kolor na czerwony. Tego rodzaju animacja może posłużyć jako wizualizacja trafienia przeciwnika.
84
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.6.
Obiekt klasy Monster zastosowany w MovieClipSprite
W metodzie initMovieClipSpriteObjects() zapisaliśmy tworzenie obiektu pokazanego na rysunku 3.6. Najpierw stworzyliśmy obiekt klasy Mesh o nazwie monster1 i nadaliśmy mu pozycję w przestrzeni. W następnej kolejności dodaliśmy element MovieClipSprite metodą addSprite(). Każdy z tych obiektów tworzyliśmy bezpośrednio w tej metodzie i jako źródło grafiki zastosowaliśmy odwołania do klasy Monster, która w zasadzie służy jako połączenie do obiektu klasy MovieClip, osadzonego w zasobach aplikacji. var monster1:Mesh = new Mesh( { z: 300 } ); monster1.addSprite(new MovieClipSprite(new Monster()));
Następne trzy obiekty potworów stworzyliśmy w identyczny sposób, stosując następujący zapis: var monster2:Mesh = new Mesh( { z: -300 } ); monster2.addSprite(new MovieClipSprite(new Monster())); var monster3:Mesh = new Mesh( { x: 300 } ); monster3.addSprite(new MovieClipSprite(new Monster())); var monster4:Mesh = new Mesh( { x: -300 } ); monster4.addSprite(new MovieClipSprite(new Monster()));
Tak samo jak w metodzie initSprite3DObjects(), po ustawieniu wszystkich potworów dodaliśmy je do sceny, stosując metodę addChild(). view.scene.addChild(monster1); view.scene.addChild(monster2); view.scene.addChild(monster3); view.scene.addChild(monster4);
W tabelach 3.11 i 3.12 przedstawiono właściwości i metody klasy MovieClipSprite. Tabela 3.11. Właściwości klasy MovieClipSprite
Nazwa
Rodzaj
movieClip
DisplayObject
Wartość domyślna
Opis Określa obiekt stosowany jako wyświetlane źródło grafiki
Rozdział 3. Obiekty
85
Tabela 3.12. Metody klasy MovieClipSprite
Nazwa
Opis
MovieClipSprite(movieClip:DisplayObject, align:String, scaling:Number, distanceScaling:Boolean)
Konstruktor
DirectionalSprite Jak widać, po skompilowaniu przykładu spritesExample główny bohater obraca się nieustannie wokół własnej osi, przynajmniej my tak to widzimy. Faktycznie obiektowi o nazwie hero nadaliśmy opcję obrotu rotationY++, ale nie wprawia ona w ruch osadzonej grafiki, tylko służy jako wyznacznik aktualnego kąta obrotu obiektu, w którym umieszczono obiekt klasy DirectionalSprite. Pamiętajmy, że obiekty DirectionalSprite są dwuwymiarowe i zawsze skierowane w stronę kamery, poza tym nie mogą się obracać wokół własnej osi Y. W rzeczywistości wyświetlona animacja obrotu postaci bazuje na kilku specjalnie przygotowanych grafikach przedstawiających postać z kilku różnych stron. Tego typu praktyki są dobrą metodą optymalizacji w grach, w których świat przedstawiony jest w rzucie izometrycznym. Stosując klasę DirectionalSprite, można zastąpić skomplikowany model kilkoma dwuwymiarowymi grafikami, aby uzyskać złudzenie generowania trójwymiarowego obiektu. Na rysunku 3.7 przedstawiono osiem różnych plików graficznych o stałych wymiarach; każdy z tych rysunków odpowiada za zwrot obiektu w konkretnym kierunku. Rysunek 3.7.
Elementy graficzne wykorzystane w obiekcie DirectionalSprite
Tworząc obiekt klasy DirectionalSprite, określamy jego podstawowe źródło wyświetlanej grafiki, planowaną szerokość i wysokość obiektu. Kolejne argumenty są takie same jak w poprzednio poznanych klasach Sprite3D i MovieClipSprite.
86
Flash i ActionScript. Aplikacje 3D od podstaw
Najważniejsza w tej klasie jest metoda addDirectionalMaterial(), która służy do dodawania źródła grafiki. W jej argumentach w pierwszej kolejności podaje się punkt orientacyjny Vertex, a następnie odpowiedni dla niego materiał. Punkty te są wyznacznikiem stopnia obrotu kamery bądź samego obiektu DirectionalSprite. W naszym przypadku zastosowaliśmy osiem różnych kątów, które zmieniają się co mniej więcej 45 stopni. Podstawowe cztery kierunki oznaczyliśmy na osiach X oraz z liczbami –1 i 1, z kolei dla pozostałych czterech zastosowaliśmy wartość 0.75, ponieważ ta dawała najlepsze rezultaty. Ponieważ obiekt DirectionalSprite jest zwrócony w stronę kamery, wartości na osiach Z we wszystkich punktach obrazków są ustawione odwrotnie. Dla przykładu zwróć uwagę na ujemną wartość osi Z dla obrazka f.png. Dzięki temu w początkowej fazie bohater jest ustawiony plecami do kamery. Alternatywnie można zmienić kąt całego obiektu mesh o 180 stopni.
W metodzie initDirectionalSpriteObject() naszego przykładu obiekt głównego bohatera stworzyliśmy, stosując następujący kod źródłowy: hero = new Mesh(); view.scene.addChild(hero); var ds:DirectionalSprite = new DirectionalSprite(null, 210, 216); ds.addDirectionalMaterial(new Vertex(0, 0, -1), new BitmapFileMaterial('../../resources/sprites/duke/f.png')); ds.addDirectionalMaterial(new Vertex(0, 0, 1), new BitmapFileMaterial('../../resources/sprites/duke/b.png')); ds.addDirectionalMaterial(new Vertex(1, 0, 0), new BitmapFileMaterial('../../resources/sprites/duke/r.png')); ds.addDirectionalMaterial(new Vertex(-1, 0, 0), new BitmapFileMaterial('../../resources/sprites/duke/l.png')); ds.addDirectionalMaterial(new Vertex(-.75, 0, -.75), new BitmapFileMaterial('../../resources/sprites/duke/fl.png')); ds.addDirectionalMaterial(new Vertex(.75, 0, -.75), new BitmapFileMaterial('../../resources/sprites/duke/fr.png')); ds.addDirectionalMaterial(new Vertex(-.75, 0, .75), new BitmapFileMaterial('../../resources/sprites/duke/bl.png')); ds.addDirectionalMaterial(new Vertex(.75, 0, .75), new BitmapFileMaterial('../../resources/sprites/duke/br.png')); hero.addSprite(ds);
W tym bloku kodu w pierwszej kolejności stworzyliśmy obiekt klasy Mesh o nazwie hero, następnie odwołując się do obiektu sceny, dodaliśmy go do jej zawartości, stosując metodę addChild(). Obiekt klasy DirectionalSprite nazwaliśmy ds i ustawiliśmy w konstruktorze null dla materiału, wartość liczbową 210 jako szerokość oraz wysokość równą 216. Po utworzeniu tego obiektu osiem razy wywołaliśmy metodę addDirectionalMaterial(), gdzie jako argumenty podaliśmy różne kierunki i odpowiadające im obrazki w formie materiału BitmapFileMaterial. Sam rodzaj tego materiału na chwilę obecną
Rozdział 3. Obiekty
87
nie jest ważny, istotne jest jedynie to, że odwoływaliśmy się w nim do plików graficznych o formacie PNG. Na końcu do obiektu hero dodaliśmy obiekt ds, stosując metodę addSprite(). Dzięki temu nasz bohater stał się widoczny na scenie. W tabelach 3.13 i 3.14 przedstawiono właściwości i metody klasy DirectionalSprite. Tabela 3.13. Właściwości klasy DirectionalSprite
Nazwa
Rodzaj
Wartość Opis domyślna
materials
Array
null
align
String
"center"
Domyślne ustawienie grafiki w obiekcie
scaling
Number
1
Poziom skalowania wyświetlanego źródła grafiki
distanceScaling
Boolean
true
Określa, czy obiekt ma być skalowany w zależności od odległości od kamery
material
Material
null
Definiuje rodzaj materiału pokrywającego obiekt
width
Number
10
Określa szerokość obiektu
height
Number
10
Określa wysokość obiektu
Tablica materiałów użytych w obiekcie klasy DirectionalSprite
Tabela 3.14. Metody klasy DirectionalSprite
Nazwa
Opis
DirectionalSprite(material:Material, width:Number, height:Number, rotation:Number, align:String, scaling:Number, distanceScaling:Boolean)
Konstruktor
addDirectionalMaterial(vertex:Vertex, material:Material):void
Metoda dodaje materiał do tablicy materiałów
Primitives Zawarte w pakiecie primitives klasy napisane zostały przez ekipę Away3D po to, by zaoszczędzić trochę czasu programistom na generowanie podstawowych i najczęściej stosowanych brył. Z czasem kolekcja ta była poszerzana o kolejne rozmaite klasy, na przykład taką, której obiekt ma formę żółwia morskiego. W przeciwieństwie do obiektów klas Sprite3D, MovieClipSprite i DirectionalSprite obiekty klas zawartych w pakiecie primitives są przestrzenne. Klasy umieszczone w tym pakiecie dziedziczą po klasie AbstractPrimitive, którą poznamy jako pierwszą, ale zanim to nastąpi, napiszemy klasę bazową dla tego podrozdziału.
88
Flash i ActionScript. Aplikacje 3D od podstaw
Przykład Aby przedstawić podstawowe obiekty z pakietu away3d.primitives, napiszemy aplikację, w której umieszczone zostaną wszystkie interesujące nas obiekty. Do ich wybrania i wyświetlenia posłużymy się komponentem ComboBox wraz z prostym panelem użytkownika. Wspomniany panel będzie obiektem klasy PrimitivesPanel, którą zapiszemy w pliku o tej samej nazwie. Poza umieszczeniem obiektu typu ComboBox napiszemy również metodę primitiveChange(), wysyłającą zdarzenia informujące o wybranej z pola wyboru opcji. Dopiszemy również metodę draw(), której celem będzie przerysowanie tła panelu przy zmianach rozmiaru okna aplikacji. Teraz przyjrzyj się poniższemu kodowi źródłowemu i przepisz go do nowego pliku PrimitivesPanel.as, a później zajmiemy się klasą właściwą dla tego przykładu. package { import import import import import import
flash.display.Sprite; flash.events.Event; fl.core.UIComponent; fl.controls.Button; fl.controls.ComboBox; fl.data.DataProvider;
public class PrimitivesPanel extends Sprite { public function PrimitivesPanel() { var dp:DataProvider = new DataProvider(); dp.addItem( { label: 'AbstractPrimitive', data: 'AbstractPrimitive' } ); dp.addItem( { label: 'Arrow', data: 'Arrow' } ); dp.addItem( { label: 'Cone', data: 'Cone' } ); dp.addItem( { label: 'Cube', data: 'Cube' } ); dp.addItem( { label: 'Cylinder', data: 'Cylinder' } ); dp.addItem( { label: 'GeodesicSphere', data: 'GeodesicSphere' } ); dp.addItem( { label: 'GridPlane', data: 'GridPlane' } ); dp.addItem( { label: 'LineSegment', data: 'LineSegment' } ); dp.addItem( { label: 'Plane', data: 'Plane' } ); dp.addItem( { label: 'RegularPolygon', data: 'RegularPolygon' } ); dp.addItem( { label: 'Sphere', data: 'Sphere' } ); dp.addItem( { label: 'SeaTurtle', data: 'SeaTurtle' } ); dp.addItem( { label: 'Torus', data: 'Torus' } ); dp.addItem( { label: 'TorusKnot', data: 'TorusKnot' } ); dp.addItem( { label: 'Triangle', data: 'Triangle' } ); dp.addItem( { label: 'Trident', data: 'Trident' } ); dp.addItem( { label: 'RoundedCube', data: 'RoundedCube' } ); dp.addItem( { label: 'WireTorus', data: 'WireTorus' } ); dp.addItem( { label: 'WireSphere', data: 'WireSphere' } ); dp.addItem( { label: 'WireRegularPolygon', data: 'WireRegularPolygon' } );
Rozdział 3. Obiekty dp.addItem( dp.addItem( dp.addItem( dp.addItem(
{ { { {
label: label: label: label:
89
'WirePlane', data: 'WirePlane' } ); 'WireCylinder', data: 'WireCylinder' } ); 'WireCube', data: 'WireCube' } ); 'WireCone', data: 'WireCone' } );
var primitiveSelect:ComboBox = new ComboBox(); primitiveSelect.dataProvider = dp; primitiveSelect.width = 150; primitiveSelect.x = 15; primitiveSelect.y = 13; addChild(primitiveSelect); primitiveSelect.addEventListener(Event.CHANGE, primitiveChange); } private function primitiveChange(e:Event):void { dispatchEvent(new Event(e.target.value + 'Event')); } public function draw(_width:Number, _height:Number, _color:uint = 0xF1F1F1, _alpha:int = 1):void { graphics.clear(); graphics.beginFill(_color,_alpha); graphics.drawRect(0, 0, _width, _height); graphics.endFill(); } } }
Gdy masz gotową klasę panelu użytkownika, przepisz poniższy kod do pliku PrimitivesExample.as. Miej na uwadze to, że klasa panelu użytkownika Primitives Panel nie została w tym kodzie zaimportowana, co oznacza, że znajduje się ona w tym samym miejscu co klasa właściwa primitivesExample. package { import import import import import import import import import
away3d.containers.View3D; away3d.core.base.Vertex; away3d.primitives.*; away3d.containers.ObjectContainer3D; flash.display.StageAlign; flash.display.StageScaleMode; flash.display.Sprite; flash.events.*; flash.geom.Vector3D;
public class primitivesExample extends Sprite { private var panel:PrimitivesPanel; private var view:View3D; private var container:ObjectContainer3D;
90
Flash i ActionScript. Aplikacje 3D od podstaw private private private private private private private private private private private private private private private private private private private private private private private private private
var var var var var var var var var var var var var var var var var var var var var var var var var
arrowhead:Arrowhead; arrow:Arrow; cone:Cone; cone2:Cone; cube:Cube; cylinder:Cylinder; geodesicSphere:GeodesicSphere; grid:GridPlane; line:LineSegment; plane:Plane; polygon:RegularPolygon; roundedCube:RoundedCube; sphere:Sphere; turtle:SeaTurtle; torus:Torus; torusKnot:TorusKnot; triangle:Triangle; trident:Trident; wireCone:WireCone; wireCube:WireCube; wireCylinder:WireCylinder; wirePlane:WirePlane; wirePolygon:WireRegularPolygon; wireSphere:WireSphere; wireTorus:WireTorus;
public function primitivesExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; view = new View3D(); view.camera.position = new Vector3D(0, 600, -1000); view.camera.lookAt(new Vector3D(0, 0, 0)); addChild(view); initPrimitives(); addPanel(); onResize(); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function addPanel():void { panel = new PrimitivesPanel(); addChild(panel); panel.addEventListener('ArrowEvent', primitiveChangeEventHandler); panel.addEventListener('ConeEvent', primitiveChangeEventHandler); panel.addEventListener('CubeEvent', primitiveChangeEventHandler); panel.addEventListener('CylinderEvent', primitiveChangeEventHandler); panel.addEventListener('GeodesicSphereEvent', primitiveChangeEventHandler);
Rozdział 3. Obiekty
91
panel.addEventListener('GridPlaneEvent', primitiveChangeEventHandler); panel.addEventListener('LineSegmentEvent', primitiveChangeEventHandler); panel.addEventListener('PlaneEvent', primitiveChangeEventHandler); panel.addEventListener('RegularPolygonEvent', primitiveChangeEventHandler); panel.addEventListener('SphereEvent', primitiveChangeEventHandler); panel.addEventListener('SeaTurtleEvent', primitiveChangeEventHandler); panel.addEventListener('TorusEvent', primitiveChangeEventHandler); panel.addEventListener('TorusKnotEvent', primitiveChangeEventHandler); panel.addEventListener('TriangleEvent', primitiveChangeEventHandler); panel.addEventListener('TridentEvent', primitiveChangeEventHandler); panel.addEventListener('RoundedCubeEvent', primitiveChangeEventHandler); panel.addEventListener('WireTorusEvent', primitiveChangeEventHandler); panel.addEventListener('WireSphereEvent', primitiveChangeEventHandler); panel.addEventListener('WireRegularPolygonEvent', primitiveChangeEventHandler); panel.addEventListener('WirePlaneEvent', primitiveChangeEventHandler); panel.addEventListener('WireCylinderEvent', primitiveChangeEventHandler); panel.addEventListener('WireCubeEvent', primitiveChangeEventHandler); panel.addEventListener('WireConeEvent', primitiveChangeEventHandler); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = panel.height + (stage.stageHeight - panel.height) *.5; panel.draw(stage.stageWidth, 50); } private function onEnterFrame(e:Event):void { container.rotationY++; view.render(); } private function initPrimitives():void { container = new ObjectContainer3D(); view.scene.addChild(container);
92
Flash i ActionScript. Aplikacje 3D od podstaw //miejsce na pozostałe obiekty //container.addChild(arrowhead); } private function primitiveChangeEventHandler(e:Event = null):void { while(container.children.length > 0) container.removeChild(container.children[0]); if (e != null) { switch(e.type) { default:break; } } } } }
Jak widać, kod nie jest skomplikowany. Zaczęliśmy od dodania potrzebnych klas zarówno ze standardowych pakietów języka ActionScript 3.0, jak i z biblioteki Away3D. Ponieważ celem przykładu jest przedstawienie większości dostępnych w Away3D trójwymiarowych obiektów, zaimportowaliśmy cały pakiet away3d. primitives, aby zaoszczędzić sobie trochę czasu i linijek kodu. import away3d.primitives.*;
Poza tym dodaliśmy również klasy widoku View3D, punkty Vertex oraz kontener ObjectContainer3D, który będzie pomocny przy zmianie wyświetlanych obiektów. Standardowo dostępnych klas użyliśmy między innymi do kontrolowania wymiarów okna aplikacji i ustalenia pozycji na trzech osiach. import import import import import
flash.display.StageAlign; flash.display.StageScaleMode; flash.display.Sprite; flash.events.*; flash.geom.Vector3D;
W klasie PrimitiveExample zadeklarowaliśmy obiekt panel napisanej przez nas klasy PanelPrimitives, view jako obiekt klasy widoku View3D oraz obiekt klasy Object Container3D, w którym umieszczane będą wyświetlane trójwymiarowe obiekty. private var panel:PrimitivesPanel; private var view:View3D; private var container:ObjectContainer3D;
W następnej kolejności zadeklarowaliśmy szereg obiektów klas dostępnych w pakiecie away3d.primitives.
Rozdział 3. Obiekty
93
W konstruktorze klasy w pierwszej kolejności ustawiliśmy odpowiednie wartości skalowania okna oraz wyrównania, stosując następujący zapis: stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE;
Następnie stworzyliśmy widok i ustawiliśmy nową pozycję dla kamery, tak aby spoglądała ona na obiekty umieszczone w centrum sceny z innej perspektywy: view = new View3D(); view.camera.position = new Vector3D(0, 600, -1000); view.camera.lookAt(new Vector3D(0, 0, 0)); addChild(view);
W kolejnych linijkach odwołaliśmy się do metod, które mają swoje osobne zadania do wykonania, oraz dodaliśmy detektory zdarzeń Event.RESIZE i Event.ENTER_FRAME. initPrimitives(); addPanel(); onResize(); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame);
Metoda initPrimitives() jest jedną z dwóch najważniejszych metod w tym przykładzie. W tym właśnie miejscu będziemy tworzyli poszczególne obiekty i nadawali ich właściwościom odpowiednie wartości. W chwili obecnej zawiera ona jedynie kod tworzący obiekt kontenera i dodający go do sceny Away3D. Zwróć uwagę, że na końcu tej metody umieszczony w komentarzu został zapis container.add Child(arrowhead). Celem dodania tej linijki kodu jest spowodowanie, by obiekt arrowhead był pierwszym wyświetlanym obiektem po uruchomieniu aplikacji. W chwili obecnej nie jest potrzebny, ale po utworzeniu obiektu arrowhead należy usunąć komentarz z tej linijki. W metodzie addPanel() stworzyliśmy i dodaliśmy do listy wyświetlanych obiektów aplikacji obiekt panelu użytkownika. panel = new PrimitivesPanel(); addChild(panel);
W kolejnych linijkach kodu metody addPanel() zapisaliśmy detektory dla zdarzeń wyboru każdej z dostępnych opcji pola wyboru zawartego w panelu użytkownika. W razie wystąpienia któregokolwiek z tych zdarzeń wywoływana jest metoda primitiveChangeEventHandler(). Ponieważ we wszystkich detektorach dodanych w metodzie addPanel() wywoływana jest metoda primitiveChangeEventHandler(), musieliśmy w jej bloku kodu umieścić instrukcję warunkową switch(), która uruchamia odpowiedni kod w zależności od przechwyconego zdarzenia. Przed zapisaniem wspomnianej instrukcji
94
Flash i ActionScript. Aplikacje 3D od podstaw
warunkowej dodaliśmy również pętlę while, w której usuwany jest pierwszy obiekt z tablicy children. Dzięki temu możemy łatwo i skutecznie usunąć całą dotychczasową zawartość obiektu container, a dopiero wtedy dodać nowe elementy. while(container.children.length > 0) container.removeChild(container.children[0]); if (e != null) { switch(e.target.value) { default:break; } }
W metodzie onResize() zapisaliśmy zmianę pozycji widoku uwarunkowaną wymiarami okna całej aplikacji, tak aby view zawsze było w samym środku okna. Ustawienie domyślnej wartości null dla argumentu e pozwala na uruchomienie metody również niezależnie od zdarzenia Event.RESIZE. Dzięki temu nie musieliśmy wcześniej w konstruktorze powielać tych linijek kodu: view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5;
Zadaniem metody onEnterFrame() jest odświeżanie zawartości widoku przez zastosowanie metody render() oraz wywoływanie obrotu kontenera względem osi Y, aby wybrane figury były widoczne z każdej strony. container.rotationY++; view.render();
AbstractPrimitive Klasa AbstractPrimitive sama w sobie nie tworzy obiektu o konkretnej formie, jest ona bazą dla wszystkich klas z pakietu away3d.primitives. Dzięki niej wybrany obiekt można wyświetlić, ustawić w przestrzeni trójwymiarowej i zmodyfikować jego budowę tak, aby odpowiadał naszym oczekiwaniom. Do tego celu wystarczy wiedza z poprzednich podpunktów tego rozdziału, znajomość kilku właściwości i metod klasy AbstractPrimitive oraz znajomość budowy klasy dziedziczącej z AbstractPrimitive. Klasa AbstractPrimitive jest potomną klasy Mesh, co sprawia, że staje się ona warstwą wizualną dla zawartych w niej obiektów Face, Sprite3D oraz Segment. Poniżej w tabelach 3.15 oraz 3.16 wypisane są ważniejsze właściwości oraz metody klasy AbstractPrimitive, które przydadzą się do stworzenia niestandardowego obiektu.
Rozdział 3. Obiekty
95
Tabela 3.15. Właściwości klasy AbstractPrimitive
Nazwa
Rodzaj
Opis
boundingRadius Number
Zwraca długość promienia granic obiektu
elements
Vector.
Zwraca tablicę wszystkich elementów zawartych w obiekcie
faces
Vector.
Zwraca tablicę wszystkich elementów typu Face zawartych w obiekcie
segments
Vector.
Zwraca tablicę wszystkich elementów typu Segment zawartych w obiekcie
sprites
Vector. Zwraca tablicę wszystkich elementów typu Sprite3D
zawartych w obiekcie Vertices
Vector.
Zwraca tablicę wszystkich punktów Vertex zawartych w obiekcie
geometry
Geometry
Definiuje obiekt typu Geometry wykorzystany w obiekcie
objectDepth
Number
Zwraca wartość liczbową głębi obiektu
objectHeight
Number
Zwraca wartość liczbową wysokości obiektu
objectWidth
Number
Zwraca wartość liczbową szerokości obiektu
maxX
Number
Zwraca maksymalną wartość na osi X obiektu
minX
Number
Zwraca minimalną wartość na osi X obiektu
maxY
Number
Zwraca maksymalną wartość na osi Y obiektu
minY
Number
Zwraca minimalną wartość na osi Y obiektu
maxZ
Number
Zwraca maksymalną wartość na osi Z obiektu
minZ
Number
Zwraca minimalną wartość na osi Z obiektu
Tabela 3.16. Metody klasy AbstractPrimitive
Nazwa
Opis
AbstractPrimitive(init:Object = null)
Konstruktor
buildPrimitive():void
Metoda pomocnicza przy tworzeniu obiektu, czyści zawartość obiektu
updatePrimitive():void
Metoda zmienia strukturę geometryczną obiektu
createVertex(x:Number, y:Number, z:Number):Vertex
Tworzenie obiektu Vertex w wewnętrznej przestrzeni obiektu
createUV(u:Number, v:Number):UV
Tworzenie współrzędnych uv w wewnętrznej przestrzeni obiektu
createFace(v0:Vertex, v1:Vertex, v2:Vertex, material:Material, uv0:UV, uv1:UV, uv2:UV):Face
Tworzenie obiektu Face w wewnętrznej przestrzeni obiektu
createSegment(v0:Vertex, v1:Vertex, material:Material):Segment
Tworzenie obiektu Segment w wewnętrznej przestrzeni obiektu
96
Flash i ActionScript. Aplikacje 3D od podstaw
Metody przedstawione w tabeli 3.16 poza buildPrimitive() umieszczone są w przestrzeni nazw arcane, zlokalizowanej w pakiecie away3d.
Gdy znamy najważniejsze metody oraz właściwości dostępne w klasie Abstract Primitive, możemy zbudować nasz pierwszy niestandardowy obiekt. Na rysunku 3.8 przedstawiono figurę przestrzenną przypominającą wyglądem grot strzały złożony z kilku ścian. Rysunek 3.8.
Obiekt arrowhead
Aby uzyskać taki efekt, musimy skonstruować klasę, która będzie generowała takie obiekty, umożliwiając przy okazji decydowanie o liczbie ścian, z jakiej mają się składać. Stworzymy klasę, w której ustalimy trzy właściwości: width, height oraz sides. Pierwsze dwie będą definiowały wymiary całej figury przestrzennej, a trzecia właściwość określać będzie liczbę trójkątów. Przepisz następujący kod źródłowy, a następnie zajmiemy się omawianiem jego zawartości. package { import away3d.primitives.AbstractPrimitive; import away3d.arcane; import away3d.core.base.UV; use namespace arcane; public class Arrowhead extends AbstractPrimitive { private var _height:Number; private var _width:Number; private var _sides:Number;
Rozdział 3. Obiekty private var _ang:Number; public function Arrowhead(init:Object = null) { super(init); width = ini.getNumber("width", 100, { min:1 } ); height = ini.getNumber("height", 150, {min:1}); sides = ini.getInt("sides", 3, { min:1 } ); ang = 360 / _sides; } protected override function buildPrimitive():void { super.buildPrimitive(); var i:Number = _sides; while(i--) { var currAng = i * _ang; var a = createVertex(0, _height, 0); var b = createVertex(0, 0, 0); var c = createVertex(Math.cos(currAng / 180 * Math.PI) * (_width * .5), 0, Math.sin(currAng / 180 * Math.PI) * (_width * .5)); addFace(createFace(a, b, c, null, new UV(0, 0), new UV(1, 0), new UV(0, 1))); } } public function get width():Number { return _width; } public function set width(val:Number):void { if (_width == val) return; width = val; primitiveDirty = true; } public function get height():Number { return _height; } public function set height(val:Number):void { if (_height == val) return; _height = val; _primitiveDirty = true; }
97
98
Flash i ActionScript. Aplikacje 3D od podstaw public function get sides():Number { return _sides; } public function set sides(val:Number):void { if (_sides == val) return; sides = val; ang = 360 / val; primitiveDirty = true; } } }
Schemat klasy Arrowhead został zapożyczony z klas znajdujących się w pakiecie away3d.primitives, które poznamy w kolejnych podpunktach. Na początku zaimportowaliśmy potrzebną klasę AbstractPrimitive, z której dziedziczymy, klasę UV potrzebną do ustalenia współrzędnych uv w obiektach Face oraz przestrzeń nazw arcane. Aby móc korzystać z metod i właściwości umieszczonych w arcane, musieliśmy poza samym zaimportowaniem również ją uruchomić, stosując use namespace. W ciele klasy Arrowhead zdefiniowaliśmy cztery prywatne zmienne: _height, _width, _sides oraz _ang. Pierwsze dwie określają wymiary obiektu, trzecia zawierać będzie liczbę ścian, z kolei _ang to wyliczony kąt pomiędzy każdą ze ścian. W konstruktorze jako argument wpisaliśmy obiekt init klasy Object, w którym można zadeklarować odpowiednie wartości dla poszczególnych właściwości. Domyślnie argument ten jest równy null, co oznacza, że nie jest on konieczny. W pierwszej linijce konstruktora odwołaliśmy się do rodzica, podając w argumencie wcześniej omówiony obiekt init. W kolejnych linijkach bloku konstruktora ustawiliśmy wartości dla właściwości potrzebnych do skonstruowania obiektu. Skorzystaliśmy w tym miejscu z obiektu init, który jest instancją klasy Init dostępnej we wszystkich obiektach klas dziedziczących po Object3D. Stosując metody getNumber() oraz getInt(), ustaliliśmy wartości domyślne oraz minimalne dla właściwości _width, _height i _sides. Dzięki temu podanie nieodpowiednich wartości spowoduje zastosowanie domyślnych. _width = ini.getNumber("width", 100, { min:1 } ); _height = ini.getNumber("height", 150, { min:1 } ); _sides = ini.getInt("sides", 3, { min:1 } );
Do określenia kąta między obiektami klasy Face zastosowaliśmy prosty wzór, w którym wartość 360 podzieliliśmy przez ustaloną liczbę ścian. _ang = 360 / _sides;
Rozdział 3. Obiekty
99
Klasa Init umieszczona w pakiecie away3d.core.utils generuje obiekt pomocniczy dla ustawiania różnego rodzaju wartości dla właściwości. W swoich zasobach ma ona między innymi następujące metody: getArray(), getBitmap(), getBoolean(), getColor(), getInt(), getMaterial(), getNumber(), getString().
Po konstruktorze nadpisaliśmy metodę buildPrimitive(), w której zaprogramowaliśmy proces tworzenia figury przestrzennej. W pierwszej linijce odwołaliśmy się do tej samej metody w klasie AbstractPrimitive, stosując przedrostek super. super.buildPrimitive();
Następnie zdefiniowaliśmy lokalną zmienną i o wartości równej _sides, po czym zastosowaliśmy ją w pętli while. W pętli tej stworzyliśmy nową zmienną, która określa aktualny kąt dla jednego z wierzchołków trójkąta. var i:Number = _sides; while(i--) { var currAng = i * _ang;
W następnych trzech linijkach zapisaliśmy tworzenie trzech punktów Vertex, korzystając z metody createVertex(). W argumentach tej metody podaliśmy odpowiednie wartości dla wierzchołków, tak aby trójkąt miał właściwą wysokość, szerokość i był skierowany pod stosownym kątem względem pozostałych ścian. var a = createVertex(0, _height, 0); var b = createVertex(0, 0, 0); var c = createVertex(Math.cos(currAng / 180 * Math.PI) * (_width * .5), 0, Math.sin(currAng / 180 * Math.PI) * (_width * .5));
Na końcu pętli wszystkie utworzone punkty wykorzystaliśmy do stworzenia obiektu Face. W tej jednej linijce użyliśmy metody addFace(), której zadaniem jest dodanie do przestrzeni elementu stworzonego przez użycie metody createFace(). W argumentach tej metody w pierwszej kolejności podaliśmy stworzone wcześniej punkty Vertex, wartość null określa materiał, a kolejne obiekty UV oznaczają współrzędne uv dla punktów a, b i c. addFace(createFace(a, b, c, null, new UV(0, 0), new UV(1, 0), new UV(0, 1))); }
Kolejne metody są metodami ustawiającymi i pobierającymi wartości dla zdefiniowanych właściwości prywatnych. Stosowanie tego typu metod jest przydatne w sytuacjach, gdy przy okazji zmiany wartości prywatnej trzeba wykonać dodatkowe operacje. Dla przykładu: wywołując metodę ustawiającą height(), poza samym przypisaniem nowej wartości właściwości _height przypisaliśmy wartość true właściwości _primitiveDirty, bez której wszelkie zmiany nie byłyby widoczne.
100
Flash i ActionScript. Aplikacje 3D od podstaw
Z kolei przy zmianie wartości właściwości _sides dodatkowo wyliczyliśmy nową wartość kąta _ang, tak aby był on jednakowy między wszystkimi ścianami. To tyle w przypadku klasy Arrowhead. Aby uzyskać efekt z rysunku 3.8, musieliśmy również dodać odpowiedni kod w klasie bazowej. Do bloku kodu metody initPrimitives(), w której tworzone są wszystkie potrzebne obiekty, należy wpisać poniższy kod źródłowy: arrowhead = new Arrowhead(); arrowhead.y = -150; arrowhead.height = 300; arrowhead.width = 300; arrowhead.bothsides = true; arrowhead.sides = 3;
W ten sposób wygenerowany zostanie grot o trzech ścianach i wymiarach równych 300 pikselom. Dodatkowo aby przy obracaniu figury były widoczne wszystkie trójkąty, ustawiliśmy we właściwościach bothsides wartość true. Jak wspomniano wcześniej, na końcu metody initPrimitives() umieszczono linijkę kodu, który powoduje dodanie obiektu arrowhead do kontenera i wyświetlenie go przy uruchomieniu przykładu. container.addChild(arrowhead);
Ponieważ obiekt arrowhead pojawia się jako pierwszy po uruchomieniu przykładu, również na liście wyboru primitiveSelect musieliśmy w pierwszej kolejności ustawić opcję określającą ten obiekt. Jako etykiety i wartości tej opcji użyliśmy nazwy klasy AbstractPrimitive, ponieważ klasa Arrowhead nie jest integralną częścią biblioteki Away3D, a jedynie klasą, którą napisaliśmy, bazując na klasie AbstractPrimitive. Do instrukcji switch zawartej w metodzie primitiveChangeEventHandler() dodaliśmy warunek AbstractPrimitiveEvent, w którym zapisaliśmy umieszczenie obiektu arrowhead w kontenerze container. Tym właśnie sposobem przy zmianie opcji w liście wyboru na AbstractPrimitive na scenie pojawia się obiekt arrowhead: case 'AbstractPrimitiveEvent': container.addChild(arrowhead); break;
LineSegment Klasa LineSegment jest bardzo podobna do klasy Segment i również służy do rysowania linii prostych, chociaż nie potrzebuje do tego żadnych dodatkowych kontenerów. Obiekt klasy LineSegment można umieścić bezpośrednio na scenie, korzystając z zapisu view.scene.addChild(). Aby stworzyć taką linię, musimy podać
Rozdział 3. Obiekty
101
dwa obiekty klasy Vertex określające jej początek i koniec. Punkty te zdefiniowane są właściwościami start oraz end. Aby stworzyć obiekt klasy LineSegment, można się posłużyć następującym kodem: line = new LineSegment(); line.start = new Vertex(0, 0, 0); line.end = new Vertex(110, 110, 110);
W przypadku naszej klasy bazowej wystarczy ten kod wpisać do ciała metody initPrimitives(). Z kolei w metodzie primitiveChangeEventHandler() wewnątrz instrukcji switch należy dodać taki oto warunek: case 'LineSegmentEvent': container.addChild(line); break;
Dzięki temu po wybraniu opcji LineSegment z listy ComboBox na ekranie pojawi się obiekt tej klasy. Poniżej zamieszczono tabele 3.17 i 3.18, które zawierają podstawowe właściwości oraz metody tej klasy. Tabela 3.17. Właściwości klasy LineSegment
Nazwa
Rodzaj
Wartość domyślna
Opis
start
Vertex
Vertex(-50, 0, 0)
Położenie początku linii
end
Vertex
Vertex(50, 0, 0)
Położenie końca linii
Tabela 3.18. Metody klasy LineSegment
Nazwa
Opis
LineSegment(init:Object = null)
Konstruktor
Trident Trident
jest w zasadzie narzędziem pomocniczym, ponieważ obiekt tej klasy to układ współrzędnych złożony z trzech osi: X, Y oraz Z. W swoich zasobach ma standardowe metody oraz właściwości dostępne dla obiektów trójwymiarowych. Poza konstruktorem, w którym argumenty decydują o wyglądzie układu, nie ma żadnych specjalnych metod i właściwości. Tworząc obiekt tej klasy, można zmienić wartości dwóm argumentom: len oraz showLetters. Pierwszy określa długość ramion, która standardowo równa jest 1000 pikseli, drugi zaś określa, czy litery oznaczające oś mają być widoczne na ich końcach, czy nie. Wartość tej opcji domyślnie ustawiona jest na false. Na rysunku 3.9 przedstawiono obiekt klasy Trident z niestandardowymi ustawieniami.
102
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.9.
Obiekt klasy Trident
Aby uzyskać taki efekt, w klasie bazowej wewnątrz metody initPrimitives() należy dopisać następującą linijkę kodu: trident = new Trident(250, true);
W ten sposób ustawiliśmy długość ramion na 250 pikseli i pokazaliśmy ich nazwy. Poza tym w kodzie instrukcji switch wewnątrz metody primitiveChangeEventHandler() należy dodać następujący warunek: case 'TridentEvent': container.addChild(trident); break;
Teraz po wybraniu z listy ComboBox opcji Trident na ekranie pojawi się taki układ jak na rysunku 3.9. Poniżej w tabeli 3.19 zamieszczono konstruktor klasy jako dostępną metodę, poza nią klasa Trident nie ma dodatkowych własnych. Tabela 3.19. Metody klasy Trident
Nazwa
Opis
Trident(len:Number, showLetters:Boolean)
Konstruktor
Triangle Triangle, jak nazwa wskazuje, jest klasą, której obiekt przyjmuje kształt ustalonego trójkąta. Do wygenerowania takiego obiektu właściwościom a, b i c należy podać trzy punkty Vertex, które będą określały wierzchołki trójkąta. Na rysunku 3.10
Rozdział 3. Obiekty
103
przedstawiono wygenerowany trójkąt oraz litery poszczególnych punktów. Litery te nie stanowią części wyświetlanego obiektu, dodałem je tylko w celu oznaczenia wierzchołków. Rysunek 3.10.
Obiekt klasy Triangle z zaznaczonymi wierzchołkami
Aby po wybraniu opcji Triangle wyświetlić podobny obiekt klasy Triangle, należy w metodzie initPrimitives() dopisać następujący fragment kodu: triangle = new Triangle(); triangle.bothsides = true; triangle.a = new Vertex(0, 200, 0); triangle.b = new Vertex(200, -150, 0); triangle.c = new Vertex(-200, -150, 0);
W pierwszej linijce tworzony jest obiekt, następnie ustawiliśmy wartość bothsides na true, co umożliwi wyświetlanie trójkąta z obu stron. W kolejnych trzech linijkach każdej z właściwości wierzchołka przypisaliśmy punkty Vertex z wymyślonymi wartościami. Następnie w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch: case 'TriangleEvent': container.addChild(triangle); break;
Poniżej w tabelach 3.20 oraz 3.21 wypisane są podstawowe właściwości i metody klasy Triangle.
104
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.20. Właściwości klasy Triangle
Nazwa
Rodzaj
Wartość domyślna
Opis
a
Vertex
Vertex(0, 0, 57.73502691896258)
Pierwszy wierzchołek trójkąta
b
Vertex
Vertex(50, 0, -28.86751345948129)
Drugi wierzchołek trójkąta
c
Vertex
Vertex(-50, 0, -28.86751345948129)
Trzeci wierzchołek trójkąta
Tabela 3.21. Metody klasy Triangle
Nazwa
Opis
Triangle(init:Object = null)
Konstruktor
Plane Obiekt klasy Plane ma postać płaszczyzny złożonej z dwóch trójkątów, które tworzą pojedynczy segment siatki geometrycznej. Liczbę segmentów możemy kontrolować za pomocą właściwości segmentsW oraz segmentsH. Pierwsza z nich określa liczbę segmentów w rzędzie, a druga w kolumnie. Pierwotnie powierzchnia obiektu klasy Plane jest płaska, ale z uwagi na swoją budowę bez większego problemu można ingerować w strukturę siatki geometrycznej tego obiektu. Zmieniając współrzędne poszczególnych wierzchołków, można wygenerować zniekształcenia terenu, o czym jeszcze wspomnimy w dalszych rozdziałach tej książki. Standardowo stworzony obiekt klasy Plane leży na scenie. Jeśli chcemy ustawić go w pozycji pionowej, trzeba właściwości yUp przypisać wartość false. Jeżeli już tak zrobimy, to należy pamiętać o zmianie wartości właściwości bothsides na true, ponieważ standardowo wartość ta równa jest false. Trzeba pamiętać, że liczba trójkątów, z których złożony jest obiekt Plane, wpływa na wydajność aplikacji oraz poprawność wyświetlania nałożonych na niego materiałów. Im więcej segmentów, tym lepiej tekstura prezentuje się na płaszczyźnie, ale równocześnie wymaga większej liczby obliczeń. Jak zawsze trzeba znaleźć złoty środek pomiędzy estetyką a wydajnością. Na rysunku 3.11 przedstawiono obiekt Plane w najprostszej postaci. W celu stworzenia obiektu Plane w klasie bazowej wewnątrz metody initPrimitives() należy dodać następujący kod źródłowy, w którym tworzymy obiekt i zmieniamy jego standardowe wymiary i położenie na scenie: plane = new Plane(); plane.width = 350; plane.height = 350; plane.yUp = false; plane.bothsides = true;
Rozdział 3. Obiekty
105
Rysunek 3.11.
Obiekt klasy Plane
Aby obiekt był dostępny po zmianie w komponencie ComboBox opcji na Plane, należy dodatkowo umieścić następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'PlaneEvent': container.addChild(plane); break;
W tabelach 3.22 oraz 3.23 wypisane zostały podstawowe właściwości i metody klasy Plane. Tabela 3.22. Właściwości klasy Plane
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Określa wysokość obiektu Plane
width
Number
100
Określa szerokość obiektu Plane
segmentsH
Number
1
Określa liczbę segmentów w kolumnie
segmentsW
Number
1
Określa liczbę segmentów w wierszu
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Tabela 3.23. Metody klasy Plane
Nazwa
Opis
Plane(init:Object = null)
Konstruktor
106
Flash i ActionScript. Aplikacje 3D od podstaw
GridPlane Tak samo jak w poprzednio omawianej klasie, obiekt klasy GridPlane przyjmuje kształt prostokąta, chociaż jego powierzchnia nie jest wypełniona jednolitym kolorem, lecz zbudowana jest ze skrzyżowanych linii. Całość tworzy siatkę, w której każdy z prostokątów nazywany jest segmentem. Ich liczbę można zmieniać za pomocą właściwości segmentsW oraz segmentsH. Podobnie jak w przypadku obiektu klasy Plane, rozmiar każdego segmentu zależy od wymiarów całej siatki oraz liczby kolumn i wierszy. Domyślnie obiekt leży na scenie, więc jeśli chcemy to zmienić, należy ustawić wartość właściwości yUp na false. Ponieważ GridPlane składa się z linii, nie trzeba się martwić o widoczność obiektu z obu stron. Na rysunku 3.12 przedstawiono obiekt klasy GridPlane złożony ze stu segmentów. Tego typu obiekty najczęściej stosuje się do przedstawiania siatki na wybranej osi, tak jak ma to miejsce w edytorach graficznych. Rysunek 3.12.
Obiekt klasy GridPlane
Aby uzyskać taki efekt, w klasie bazowej wewnątrz metody initPrimitives() trzeba dodać następujący fragment kodu: grid = new GridPlane(); grid.width = 450; grid.height = 450; grid.segmentsH = 10; grid.segmentsW = 10;
Rozdział 3. Obiekty
107
Jak widać, ustawiliśmy dla obiektu o nazwie grid odpowiednią wysokość oraz szerokość i zmieniliśmy domyślną liczbę segmentów zarówno w rzędach, jak i kolumnach. W metodzie primitiveChangeEventHandler(), odpowiedzialnej za reakcje na zmiany w liście wyboru, należy dodać następujący warunek: case 'GridPlaneEvent': container.addChild(grid); break;
Poniżej w tabelach 3.24 oraz 3.25 wypisane są podstawowe właściwości i metody klasy GridPlane. Tabela 3.24. Właściwości klasy GridPlane
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Określa wysokość obiektu GridPlane
width
Number
100
Określa szerokość obiektu GridPlane
segmentsH
Number
1
Określa liczbę segmentów w kolumnie
segmentsW
Number
1
Określa liczbę segmentów w wierszu
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Tabela 3.25. Metody klasy GridPlane
Nazwa
Opis
GridPlane(init:Object = null)
Konstruktor
RegularPolygon Obiekt klasy RegularPolygon to kolejna figura, którą można przedstawić w przestrzeni trójwymiarowej, a jej kształt domyślnie przyjmuje formę ośmiokąta foremnego. Liczbę boków można zmienić za pomocą właściwości sides. Każdy z trójkątów będących częścią RegularPolygon można podzielić na mniejsze części i w tym celu trzeba zmienić wartość właściwości subdivision, która standardowo równa jest 1. RegularPolygon ma niektóre cechy znane z klasy Plane, to znaczy w pierwotnych ustawieniach figura leży na scenie oraz powierzchnia odwrócona od kamery nie jest wyświetlana. Czyli jeśli chcemy zmienić któryś z tych stanów, należy odwołać się do właściwości yUp i bothsides. Do ustalenia wielkości obiektu służy właściwość radius, która określa długość linii tworzących boki trójkątów wewnątrz powierzchni RegularPolygon. Na rysunku 3.13 przedstawiono obiekt tej klasy ze standardową liczbą trójkątów.
108
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.13.
Obiekt klasy RegularPolygon
Obiekt z rysunku 3.13 został wygenerowany dzięki zastosowaniu w metodzie initPrimitives() następującego kodu: polygon = new RegularPolygon(); polygon.radius = 200; polygon.yUp = false; polygon.bothsides = true;
Zmieniając standardowe wartości, postawiliśmy obiekt, zwiększyliśmy jego rozmiar i ustawiliśmy jego widoczność z obu stron. Z kolei w metodzie primitiveChange EventHandler() wewnątrz instrukcji switch należy dodać taki oto warunek: case 'RegularPolygonEvent': container.addChild(polygon); break;
Dzięki temu po wybraniu opcji RegularPolygon z listy ComboBox na ekranie pojawi się obiekt tej klasy. Poniżej zamieszczono tabele 3.26 i 3.27, które zawierają podstawowe właściwości oraz metody RegularPolygon. Tabela 3.26. Właściwości klasy RegularPolygon
Nazwa
Rodzaj
Wartość domyślna Opis
radius
Number
100
Określa promień
subdivision
Number
1
Określa liczbę podziału trójkąta
sides
Number
8
Określa liczbę ścian
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Rozdział 3. Obiekty
109
Tabela 3.27. Metody klasy RegularPolygon
Nazwa
Opis
RegularPolygon(init:Object = null)
Konstruktor
Cube Klasa Cube tworzy obiekt sześcianu, którego ściany standardowo składają się z dwóch trójkątów, czyli jednego segmentu. Wymiary boków zmienia się za pomocą właściwości width, height oraz depth. Liczbę segmentów każdej ze ścian można zmienić, używając właściwości segmentsW (względem osi X), segmentsH (względem osi Y) oraz segmentsD (względem osi Z). Domyślnie zewnętrzna strona każdego z boków jest rysowana, ale gdyby umieścić kamerę wewnątrz bryły, to nie byłyby one widoczne. Jeżeli sytuacja wymaga umieszczenia kamery wewnątrz sześcianu, to należy posłużyć się właściwością flip i przypisać jej wartość true. Warto wspomnieć, że wypełnianie materiałem obiektu klasy Cube można wykonać w dwojaki sposób. Pierwszy z nich to zwykłe przypisanie wybranego materiału dla wszystkich ścian. O tym dowiesz się w rozdziale 4. „Materiały”. Drugi zaś polega na zastosowaniu specjalnej klasy o nazwie CubeMaterialsData. Na rysunku 3.14 przedstawiono obiekt klasy Cube z losowo wygenerowanym materiałem. Klasa CubeMaterialsData to klasa zlokalizowana w pakiecie away3d.primitives.data; przyjmuje ona rolę tablicy, której indeksy nazwane są odpowiednio dla poszczególnych boków sześcianu: back, bottom, front, left, right, top. Każdemu indeksowi można przypisać materiał niezwiązany z pozostałymi, co poszerza możliwości stosowania obiektu klasy Cube. Rysunek 3.14.
Obiekt klasy Cube
110
Flash i ActionScript. Aplikacje 3D od podstaw
Od strony kodu klasy bazowej w metodzie initPrimitives() należy dopisać następujący fragment kodu: cube = new Cube(); cube.width = 250; cube.height = 250; cube.depth = 250;
W pierwszej linijce tworzony jest obiekt, a w następnych trzech zmienione zostały jego wymiary. Aby obiekt cube był dostępny po wybraniu jego opcji z listy wyboru, w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch: case 'CubeEvent': container.addChild(cube); break;
Poniżej w tabelach 3.28 oraz 3.29 wypisane są podstawowe właściwości i metody klasy Cube. Tabela 3.28. Właściwości klasy Cube
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Wysokość obiektu
width
Number
100
Szerokość obiektu
depth
Number
100
Głębokość obiektu
flip
Boolean
false
Określa, czy ściany mają być odwrócone do wewnątrz sześcianu
segmentsW
Number
1
Liczba segmentów względem osi X
segmentsH
Number
1
Liczba segmentów względem osi Y
segmentsD
Number
1
Liczba segmentów względem osi Z
cubeMaterials
CubeMaterialsData null
Obiekt klasy CubeMaterialsData służący do nakładania osobnych materiałów na każdą ze ścian
Tabela 3.29. Metody klasy Cube
Nazwa
Opis
Cube(init:Object = null)
Konstruktor
Rozdział 3. Obiekty
111
RoundedCube Wygenerowany przez klasę RoundedCube obiekt ma formę sześcianu z zaokrąglonymi krawędziami, co sprawia, że jego struktura jest znacznie bardziej złożona od zwykłego sześcianu. Dodanie owalnych kształtów wymagało wykorzystania większej liczby trójkątów. Obiekt klasy RoundedCube, wyświetlany nawet w najprostszej formie, wymaga więcej obliczeń niż zwykły Cube. Za kontrolowanie tych zaokrągleń odpowiadają właściwości radius oraz subdivision. Pierwsza z nich określa promień rogów sześcianu, a druga definiuje liczbę podziału każdej krawędzi. Na rysunku 3.15 widać obiekt klasy RoundedCube, w którym zaokrąglone krawędzie mają swoje standardowe ustawienia. Rysunek 3.15.
Obiekt klasy RoundedCube
W tym przypadku promień zaokrągleń równy jest 33.3, a podział — jak widać — liczy dwa segmenty w pionie i poziomie. Żeby uzyskać taki efekt, w klasie bazowej wewnątrz metody initPrimitives() trzeba dodać następujący fragment kodu: roundedCube = new RoundedCube(); roundedCube.width = 250; roundedCube.height = 250; roundedCube.depth = 250;
Jak widać, nadaliśmy obiektowi roundedCube jedynie nowe wartości określające jego wymiary, pozostawiając resztę standardowych ustawień. W metodzie primitiveChangeEventHandler(), odpowiedzialnej za reakcje na zmiany w liście wyboru, należy dodać następujący warunek: case 'RoundedCubeEvent': container.addChild(roundedCube); break;
112
Flash i ActionScript. Aplikacje 3D od podstaw
Poza radius i subdivision klasa RoundedCube ma znane nam już właściwości. Wszystkie najważniejsze wraz z metodami zostały wypisane poniżej w tabelach 3.30 oraz 3.31. Tabela 3.30. Właściwości klasy RoundedCube
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Wysokość obiektu
width
Number
100
Szerokość obiektu
depth
Number
100
Głębokość obiektu
subdivision
Number
2
Określa podział krawędzi
radius
Number
33.3
Promień krawędzi
cubicmapping
Boolean
false
Określa, czy materiał pokrywa cały sześcian, czy każdy z boków, uwzględniając promień zaokrągleń
cubeMaterials
CubeMaterialsData null
Obiekt klasy CubeMaterialsData służący do nakładania osobnych materiałów na każdą ze ścian
Tabela 3.31. Metody klasy RoundedCube
Nazwa
Opis
Cube(init:Object = null)
Konstruktor
Sphere W bibliotece Away3D do stworzenia obiektu kuli służy klasa Sphere, chociaż z uwagi na domyślną liczbę segmentów siatki geometrycznej wyświetlony obiekt tej klasy niewiele ma wspólnego z kulą. Na rysunku 3.16 przedstawiono obiekt klasy Sphere. Jak widać, nie jest on idealnie kulisty. Podobnie jak Cube, obiekt klasy Sphere złożony jest z segmentów, które domyślnie nie formują okrągłej bryły, jedynie jej namiastkę. Dzięki właściwościom segmentsW oraz segmentsH liczbę segmentów można kontrolować, nadając im większe wartości. Obiekt klasy Sphere staje się bardziej wypukły, ale przy okazji pogarsza się wydajność aplikacji. Jeśli pójdziemy w drugą stronę — nadamy mu mniej segmentów — obiekt stanie się lżejszy dla aplikacji, ale i mniej atrakcyjny dla oka jako kula. Jak zwykle należy znaleźć złoty środek. Podobnie jak w przypadku poznanej klasy RegularPolygon, rozmiar obiektu klasy Sphere określa się właściwością radius, która oznacza długość promienia kuli.
Rozdział 3. Obiekty
113
Rysunek 3.16.
Obiekt klasy Sphere
Aby wygenerować obiekt kuli w takiej postaci, jak pokazano na rysunku 3.16, należy dodać w metodzie initPrimitives() następujący kod: sphere = new Sphere(); sphere.radius = 200;
Poza tym aby obiekt ten był widoczny, po wybraniu opcji Sphere w komponencie ComoBox wewnątrz instrukcji switch w metodzie primitiveChangeEventHandler() należy dodać taki oto warunek: case 'SphereEvent': container.addChild(sphere); break;
Poniżej zamieszczono tabele 3.32 i 3.33, które zawierają podstawowe właściwości oraz metody klasy Sphere. Tabela 3.32. Właściwości klasy Sphere
Nazwa
Rodzaj
Wartość domyślna
Opis
radius
Number
100
Długość promienia kuli
segmentsW
Number
8
Liczba segmentów w poziomie
segmentsH
Number
6
Liczba segmentów w pionie
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Tabela 3.33. Metody klasy Sphere
Nazwa
Opis
Sphere(init:Object = null)
Konstruktor
114
Flash i ActionScript. Aplikacje 3D od podstaw
GeodesicSphere GeodesicSphere to kolejna klasa dostępna w bibliotece Away3D, której obiekt ma formę kuli. Różnica w stosunku do klasy Sphere polega na budowie siatki geometrycznej wygenerowanego obiektu. W przypadku GeodesicSphere siatka geometryczna tworzona jest na wzór kopuły Fullera, zbudowanej z trójkątów równoramiennych ułożonych tak, aby odwzorować powierzchnię kuli. Z uwagi na konstrukcję tworzonego obiektu klasa GeodesicSphere nie ma właściwości kontrolujących liczbę segmentów w pionie i poziomie, jak to ma miejsce na przykład w klasie Sphere. Zastąpiono je jedną właściwością fractures, której wartość określa liczbę złamań każdej z krawędzi bryły. Każde ze złamań tworzy nowe trójkąty równoramienne, które pokrywając powierzchnię bryły, uwypuklają ją. Domyślnie wartość tego właściwości równa jest liczbie 2. Dla lepszego zrozumienia całego mechanizmu na rysunku 3.17 przedstawiono trzy wersje obiektu klasy GeodesicSphere, w których zastosowano różne wartości fractures. Rysunek 3.17.
Obiekty klasy GeodesicSphere o różnych wartościach właściwości fractures
Rozmiar całej powierzchni zależy od wartości właściwości radius, która określa długość promienia. Aby uzyskać obiekt o budowie odpowiadającej trzeciej bryle przedstawionej na rysunku 3.17, w klasie bazowej przykładu wewnątrz metody initPrimitives() należy dodać następujący kod: geodesicSphere = new GeodesicSphere(); geodesicSphere.radius = 200;
Aby po wybraniu opcji GeodesicSphere z listy wyboru na ekranie pojawiła się bryła kuli geodezyjnej, należy dopisać następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'GeodesicSphereEvent': container.addChild(geodesicSphere); break;
Wszystkie najważniejsze właściwości wraz z metodami dostępnymi w klasie GeodesicSphere zostały wypisane poniżej w tabelach 3.34 oraz 3.35.
Rozdział 3. Obiekty
115
Tabela 3.34. Właściwości klasy GeodesicSphere
Nazwa
Rodzaj
Wartość domyślna
Opis
radius
Number
100
Długość promienia kuli
fractures
Number
2
Liczba załamań na krawędziach kuli
Tabela 3.35. Metody klasy GeodesicSphere
Nazwa
Opis
GeodesicSphere(init:Object = null)
Konstruktor
Torus Torus w bardzo ogólnym pojęciu matematycznym to powierzchnia powstała przez obrót okręgu wokół prostej leżącej na tej samej płaszczyźnie. Klasa Torus generuje obiekt, którego forma ma właśnie taki kształt, często porównywany z oponką. Obiekt ten przeważnie wykorzystuje się w celu testowania wydajności aplikacji. Z uwagi na swój kształt najlepiej sprawdza się przy ustalaniu światła, cienia oraz refleksów. O budowie całej bryły decydują wartości kilku właściwości; poznany radius określa promień wyznaczony ze środka powierzchni do zewnętrznej krawędzi bryły. Za grubość torusa odpowiada właściwość tube, która określa długość promienia obracanego okręgu. Poza tym kształt nadawany jest również przez określenie odpowiedniej liczby segmentów, a do tego celu służą segmentsR oraz segmentsT. Pierwszy z nich można potraktować tak, jakby definiował liczbę okręgów, które służą do wyznaczenia całej figury, z kolei druga właściwość wyznacza liczbę krawędzi każdego z tych okręgów. Na rysunku 3.18 przedstawiono obiekt klasy Torus o standardowej liczbie segmentów i zmienionych wartościach właściwości tube oraz radius. Aby uzyskać efekt przedstawiony na rysunku 3.18, należy w metodzie initPrimitives() dopisać następujący kod: torus = new Torus(); torus.tube = 60; torus.radius = 200;
Aby obiekt torus był dostępny po wybraniu jego opcji z pola wyboru, w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch: case 'TorusEvent': container.addChild(torus); break;
116
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.18.
Obiekt klasy Torus
Poniżej zamieszczono tabele 3.36 i 3.37, które zawierają podstawowe właściwości oraz metody klasy Torus. Tabela 3.36. Właściwości klasy Torus
Nazwa
Rodzaj
Wartość domyślna
Opis
radius
Number
100
Długość promienia całego obiektu
segmentsR
Number
8
Liczba segmentów w poziomie
segmentsT
Number
6
Liczba segmentów w pionie
tube
Number
40
Promień ścian obiektu Torus
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Tabela 3.37. Metody klasy Torus
Nazwa
Opis
Torus(init:Object = null)
Konstruktor
TorusKnot TorusKnot
to węzeł, który jeszcze lepiej sprawdza się w testach wydajności aplikacji, oświetlenia i refleksów niż wspomniana wcześniej klasa. Budowa tego obiektu opiera się na kilku znanych nam właściwościach: radius, tube, segmentsR oraz segmentsT, poza nimi w klasie TorusKnot stosuje się również heightScale, p oraz q. Właściwość heightScale określa poziom rozciągnięcia węzła w pionie. Im wyższa wartość jest podana, tym większa odległość powstaje między wartościami mini-
Rozdział 3. Obiekty
117
malnymi a maksymalnymi na lokalnej osi Y obiektu TorusKnot. Właściwości p oraz q to liczby względnie pierwsze, które określają liczbę owinięć w przeciwnych kierunkach. Najbardziej znana forma węzła to węzeł koniczyny, z kolei na rysunku 3.19 przedstawiono obiekt klasy TorusKnot z domyślnymi wartościami właściwości p i q. Rysunek 3.19.
Obiekt klasy TorusKnot
Aby uzyskać taki efekt, w przykładzie w metodzie initPrimitives() trzeba stworzyć obiekt o nazwie torusKnot i nadać jego właściwościom odpowiednie wartości, podobnie jak w poniższym kodzie źródłowym: torusKnot = new TorusKnot(); torusKnot.p = 2; torusKnot.q = 3; torusKnot.segmentsT = 8; torusKnot.segmentsR = 64; torusKnot.tube = 20; torusKnot.radius = 150; torusKnot.rotationX = 90;
Jak widać, ustawiliśmy dla całego węzła odpowiednią liczbę segmentów, zmieniliśmy jego promienie i obróciliśmy względem osi X. W metodzie primitiveChange EventHandler(), odpowiedzialnej za reakcje na zmiany w liście wyboru, należy dodać następujący warunek: case 'TorusKnotEvent': container.addChild(torusKnot); break;
Poniżej w tabelach 3.38 oraz 3.39 wypisane są podstawowe właściwości i metody klasy TorusKnot.
118
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.38. Właściwości klasy TorusKnot
Nazwa
Rodzaj
Wartość domyślna Opis
radius
Number
200
Długość promienia całego obiektu
segmentsR
Number
15
Liczba okręgów formująca węzeł
segmentsT
Number
6
Liczba krawędzi okręgów formujących węzeł
tube
Number
10
Promień ścian węzła
heightScale
Number
1
Poziom rozciągnięcia węzła w pionie
p
Number
2
Liczba owinięć wokół koła wewnątrz obiektu Torus
q
Number
3
Liczba owinięć wokół linii przez otwór w obiekcie Torus
Tabela 3.39. Metody klasy TorusKnot
Nazwa
Opis
TorusKnot(init:Object = null)
Konstruktor
Cylinder Obiekt tej klasy ma kształt walca, którego podstawy domyślnie są ośmiokątami foremnymi. Standardowo bryła ta jest zamknięta, ale podstawę oraz górną część można schować, pozostawiając tym samym walec otwarty. Do tego celu służy właściwość openEnded, która przyjmuje wartości typu Boolean, gdzie true oznacza walec otwarty, a false zamknięty u podstaw. Poza tą właściwością o wyglądzie bryły decydują również wartości właściwości takich jak: height, radius, segmentsH, segmentsW oraz yUp. Do wyznaczenia szerokości podstaw cylindra służy właściwość radius, z kolei wysokość jego ścian wyznacza wartość właściwości height. Podobnie jak w większości poznanych brył, liczbę segmentów w kolumnie i rzędzie określają segmentsH i segmentsW. Żeby osiągnąć bardziej owalne kształty cylindra, zwiększa się wartość segmentsW, a segmentsH jest użyteczny przy dokładniejszym odwzorowaniu materiału pokrywającego bryłę. Na rysunku 3.20 przedstawiono obiekt klasy Cylinder z domyślnymi ustawieniami segmentów. Od strony kodu klasy bazowej w metodzie initPrimitives() należy dopisać następujący fragment kodu: cylinder = new Cylinder(); cylinder.radius = 150; cylinder.height = 250;
Rozdział 3. Obiekty
119
Rysunek 3.20.
Obiekt klasy Cylinder
Aby po wybraniu opcji Cylinder z listy wyboru na ekranie pojawił się obiekt tej klasy, należy dopisać następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'CylinderEvent': container.addChild(cylinder); break;
Poniżej w tabelach 3.40 oraz 3.41 wypisane są podstawowe właściwości i metody klasy Cylinder. Tabela 3.40. Właściwości klasy Cylinder
Nazwa
Rodzaj
Wartość domyślna Opis
height
Number
200
Wysokość cylindra
openEnded
Boolean
false
Określa, czy cylinder ma zawierać podstawy, czy nie
radius
Number
100
Promień podstaw cylindra
segmentsH
Number
1
Liczba segmentów na ścianach cylindra
segmentsW
Number
8
Liczba krawędzi podstaw cylindra
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
120
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.41. Metody klasy Cylinder
Nazwa
Opis
Cylinder(init:Object = null)
Konstruktor
Cone Klasa Cone generuje obiekt stożka prostego, którego podstawą jest ośmiokąt foremny. Liczbę krawędzi podstawy można regulować, tak samo jak w przypadku klasy Cylinder, zmieniając wartość właściwości segmentsW. Naturalnie wpływa to również na liczbę ścian, z których złożona jest bryła, a ich podział — tym razem w kolumnie — reguluje właściwość segmentsH. Szerokość podstawy określa właściwość radius, a to, czy w ogóle ma być ona wyświetlana, zależy od wartości właściwości openEnded. Wysokość obiektu Cone określa właściwość height, przy czym warto zwrócić uwagę na to, że punkt zerowy — tak samo jak w innych obiektach — nie jest ulokowany przy podstawie bryły, tylko na środku jej powierzchni. Służy to do obracania bryły wokół jej osi, ale przy umieszczaniu obiektu na scenie należy o tym pamiętać. Jak wiadomo, mamy kilka rodzajów stożków: prosty, ścięty i pochyły. Manipulując odpowiednio punktami Vertex umieszczonymi w obiekcie, można standardowy stożek zamienić w dowolny z wymienionych wcześniej rodzajów. Na rysunku 3.21 przedstawiono stożek standardowy oraz zmodyfikowany — jego forma jest stożkiem ściętym. Rysunek 3.21.
Dwa obiekty klasy Cone
Rozdział 3. Obiekty
121
Aby wygenerować obiekty stożków w takich formach, jak pokazano na rysunku 3.21, należy zapisać w metodzie initPrimitives() następujący kod: cone = new Cone(); cone.radius = 150; cone.bothsides = true; cone.height = 300; cone.x = -150; cone2 = new Cone(); cone2.radius = 150; cone2.x = 150; cone2.bothsides = true; cone2.segmentsH = 2; cone2.height = 300; (cone2.vertices[cone2.vertices.length - 1] as Vertex).y = 0;
Stożkowi o nazwie cone zmienione zostały wysokość, promień podstawy oraz położenie względem osi X. Ostatnia linijka tego kodu powoduje zmianę pozycji ostatniego punktu Vertex, tak aby czubek obiektu cone2 stanowił drugą podstawę. Aby obiekty te były widoczne, po wybraniu opcji Cone w komponencie ComoBox wewnątrz instrukcji switch w metodzie primitiveChangeEventHandler() należy dodać taki oto warunek: case 'ConeEvent': container.addChild(cone); container.addChild(cone2); break;
Poniżej zamieszczono tabele 3.42 i 3.43, które zawierają podstawowe właściwości oraz metody Cone. Tabela 3.42. Właściwości klasy Cone
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
200
Wysokość stożka
openEnded
Boolean
false
Określa, czy podstawa stożka jest wyświetlana, czy nie
radius
Number
100
Promień podstawy stożka
segmentsH
Number
1
Liczba segmentów ścian stożka
segmentsW
Number
8
Liczba krawędzi podstawy stożka
yUp
Boolean
true
Określa, czy stożek jest w pozycji pionowej, czy poziomej
122
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.43. Metody klasy Cone
Nazwa
Opis
Cone(init:Object = null)
Konstruktor
SeaTurtle SeaTurtle to model żółwia morskiego, który został wyeksportowany i zapisany w pliku *.as jako klasa ActionScript 3.0. Swój żywot zaczął jako obiekt stosowany w demonstracjach możliwości silnika Away3D, ale z czasem twórcy Away3D oraz sami użytkownicy tak się przywiązali do jego wizerunku, że stał się integralną częścią całej biblioteki i swego rodzaju znakiem rozpoznawczym. W swoich zasobach klasa SeaTurtle nie ma własnych publicznych właściwości ani metod, ma tylko te, które odziedziczyła po klasie Mesh i tym samym Object3D. Na rysunku 3.22 przedstawiono obiekt tej klasy bez nałożonej tekstury, aby można było zobaczyć jego złożoną strukturę. Rysunek 3.22.
Obiekt klasy SeaTurtle
Aby stworzyć obiekt turtle, w klasie bazowej wewnątrz metody initPrimitives() należy dodać następujący kod źródłowy, w którym tworzymy obiekt, zmieniając jego skalę i kąt pochylenia, tak aby był w pełni widoczny: turtle = new SeaTurtle(); turtle.rotationX = 45; turtle.scale(.75);
Rozdział 3. Obiekty
123
Aby obiekt był dostępny, po zmianie w komponencie ComboBox opcji na SeaTurtle należy dodatkowo umieścić następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'SeaTurtleEvent': container.addChild(turtle); break;
Jak wspomniano, klasa SeaTurtle nie ma własnych właściwości oraz dodatkowych metod, dlatego poniżej umieszczona tabela 3.44 zawiera tylko konstruktor. Tabela 3.44. Metody klasy SeaTurtle
Nazwa
Opis
SeaTurtle(init:Object = null)
Konstruktor
Arrow Obiekt klasy Arrow ma formę strzałki, która w swojej domyślnej budowie jest płaskim trójkątem widocznym tylko z jednej strony. Aby nadać mu pełne kształty, należy wykorzystać wszystkie przypisane mu właściwości oraz kilka znanych z klasy Mesh, po której dziedziczy. W pierwszej kolejności trzeba ustalić długość ogona strzałki, którą określa właściwość tailLength, a następnie ustawić jego szerokość, korzystając z właściwości tailWidth. Po wykonaniu tych czynności będziemy mieli formę strzałki, tyle że nadal w postaci płaskiej. Aby to zmienić, trzeba użyć właściwości thickness i określić grubość całego obiektu. Warto zwrócić uwagę na to, że punkt pivotPoint obiektu klasy Arrow nie jest umiejscowiony na środku siatki geometrycznej, lecz tuż przy najwyżej umieszczonej krawędzi. Na rysunku 3.23 przedstawiono obiekt klasy Arrow po nadaniu konkretnych ustawień każdej ze wspomnianych właściwości. Od strony kodu klasy bazowej w metodzie initPrimitives() należy dopisać następujący fragment kodu: arrow = new Arrow(); arrow.height = 400; arrow.width = 400; arrow.tailLength = 250; arrow.tailWidth = 100; arrow.thickness = 100; arrow.bothsides = true; arrow.rotationX = -90; arrow.y = 150;
124
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.23.
Obiekt klasy Arrow
Aby obiekt arrow był dostępny, po wybraniu jego opcji z pola wyboru w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch: case 'ArrowEvent': container.addChild(arrow); break;
Poniżej w tabelach 3.45 oraz 3.46 wypisane są podstawowe właściwości i metody klasy Arrow. Tabela 3.45. Właściwości klasy Arrow
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Określa wysokość obiektu Arrow
width
Number
100
Określa szerokość obiektu Arrow
tailLength
Number
0
Określa długość promienia obiektu Arrow
tailWidth
Number
0
Określa szerokość promienia obiektu Arrow
thickness
Number
0
Określa grubość, a w zasadzie wysokość obiektu Arrow
sideMaterial
Material
null
Określa pokrycie powierzchni obiektu Arrow
bottomMaterial
Material
null
Określa pokrycie spodu powierzchni obiektu
yUp
Boolean
true
Arrow
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Rozdział 3. Obiekty
125
Tabela 3.46. Metody klasy Arrow
Nazwa
Opis
Arrow(init:Object = null)
Konstruktor
WirePlane Klasa WirePlane to odpowiednik klasy Plane, a raczej GridPlane, którego obiekt złożony jest z samych linii. Parametrami nie różni się od swojego pierwowzoru. Stosując ten obiekt, również można ustalić jego wysokość, szerokość, liczbę segmentów oraz to, czy ma być w pozycji pionowej, czy poziomej. Na rysunku 3.24 przedstawiono obiekt klasy WirePlane z przykładowymi ustawieniami. Rysunek 3.24.
Obiekt klasy WirePlane
Aby wygenerować taki obiekt, należy dodać w metodzie initPrimitives() następujący kod: wirePlane = new WirePlane(); wirePlane.width = 350; wirePlane.height = 350; wirePlane.segmentsH = 4; wirePlane.segmentsW = 4; wirePlane.yUp = false;
Poza tym aby był on widoczny, po wybraniu opcji WirePlane wewnątrz instrukcji switch w metodzie primitiveChangeEventHandler() należy dodać taki oto warunek: case 'WirePlaneEvent': container.addChild(wirePlane); break;
126
Flash i ActionScript. Aplikacje 3D od podstaw
Poniżej w tabelach 3.47 oraz 3.48 wypisane są podstawowe właściwości i metody klasy WirePlane. Tabela 3.47. Właściwości klasy WirePlane
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Określa wysokość obiektu WirePlane
width
Number
100
Określa szerokość obiektu WirePlane
segmentsH
Number
1
Określa liczbę segmentów w kolumnie
segmentsW
Number
1
Określa liczbę segmentów w wierszu
yUp
Boolean
true
Definiuje, czy współrzędne są zorientowane względem osi Y, czy Z
Tabela 3.48. Metody klasy WirePlane
Nazwa
Opis
WirePlane(init:Object = null)
Konstruktor
WireCube Klasa WireCube tworzy obiekt złożony z punktów Vertex oraz linii tworzących siatkę sześcianu bez przekątnych przechodzących przez każdy z jego boków. Umieszczony na scenie prezentuje się w postaci krawędzi zaznaczających powierzchnię sześcianu otwartego z każdej strony. Do ustalenia jego wymiarów stosuje się właściwości znane z pierwowzoru Cube, a są nimi width, height oraz depth. Właściwości wyznaczające liczbę segmentów w tym przypadku są zbędne, ale zamiast nich pojawiły się nowe, określające każdy z wierzchołków. Nazwy tych właściwości składają się z litery v i odpowiedniego numeru z przedziału od 0 do 7, zapisanego w kodzie binarnym. Pierwsza z nich oznaczona jest więc jako v000, a ostatnia jako v111. Na rysunku 3.25 przedstawiono obiekt WireCube z odpowiednio zaznaczonymi właściwościami wierzchołków: v000 ... v111. W klasie bazowej wewnątrz metody initPrimitives() należy dodać następujący kod, aby uzyskać siatkę sześcianu z rysunku 3.25. wireCube = new WireCube(); wireCube.width = 250; wireCube.height = 250; wireCube.depth = 250;
Rozdział 3. Obiekty
127
Rysunek 3.25.
Obiekt klasy WireCube z zaznaczonymi właściwościami wierzchołków
Aby na ekranie pojawił się obiekt tej klasy, należy dopisać następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'WireCubeEvent': container.addChild(wireCube); break;
Wszystkie najważniejsze właściwości wraz z metodami zostały wypisane poniżej w tabelach 3.49 oraz 3.50. Tabela 3.49. Właściwości klasy WireCube
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
100
Wysokość obiektu
width
Number
100
Szerokość obiektu
depth
Number
100
Głębokość
v000
Vertex
Vertex(-50,-50,-50)
Współrzędne wierzchołka
v001
Vertex
Vertex(-50,-50,50)
Współrzędne wierzchołka
v010
Vertex
Vertex(-50,50,-50)
Współrzędne wierzchołka
v011
Vertex
Vertex(-50,50,50)
Współrzędne wierzchołka
v100
Vertex
Vertex(50,-50,-50)
Współrzędne wierzchołka
v101
Vertex
Vertex(50,-50,50)
Współrzędne wierzchołka
v110
Vertex
Vertex(50,50,-50)
Współrzędne wierzchołka
v111
Vertex
Vertex(50,50,50)
Współrzędne wierzchołka
128
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.50. Metody klasy WireCube
Nazwa
Opis
WireCube(init:Object = null)
Konstruktor
WireCone Klasa WireCone tworzy obiekt stożka prostego, którego podstawą jest ośmiokąt foremny. Podobnie jak kilka wcześniej omówionych klas, ta również generuje tylko siatkę bez boków i podstaw. Do wyznaczenia formy obiektu WireCone służą właściwości znane z klasy Cone, czyli tutaj również można regulować promień podstawy, liczbę kątów, wysokość całej siatki i pozycję jej punktów Vertex. Na rysunku 3.26 przedstawiono obiekt klasy WireCone. Rysunek 3.26.
Obiekt klasy WireCone
Od strony kodu klasy bazowej w metodzie initPrimitives() należy dopisać następujący fragment kodu: wireCone = new WireCone(); wireCone.radius = 150; wireCone.height = 300;
Aby obiekt wireCone był dostępny, po wybraniu jego opcji z pola wyboru w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch: case 'WireConeEvent': container.addChild(wireCone); break;
Rozdział 3. Obiekty
129
Tabele 3.51 oraz 3.52 zawierają listę właściwości oraz metod dostępnych dla obiektów klasy WireCone. Tabela 3.51. Właściwości klasy WireCone
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
200
Wysokość obiektu WireCone
radius
Number
100
Promień podstawy obiektu
segmentsH
Number
1
Liczba segmentów w pionie
segmentsW
Number
8
Liczba krawędzi podstawy
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
Tabela 3.52. Metody klasy WireCone
Nazwa
Opis
WireCone(init:Object = null)
Konstruktor
WireCylinder Klasa WireCylinder tworzy obiekt siatki walca, którego podstawami domyślnie są ośmiokąty foremne. Tak samo jak w klasie Cylinder, liczbę krawędzi podstaw można zmienić, stosując właściwość segmentsW, a wysokość całej siatki reguluje się właściwością height. Pozostałe właściwości — radius, segmentsW oraz yUp — mają takie same zadania jak ich odpowiedniki w klasie Cylinder. Jeżeli projekt wymaga umieszczenia na scenie obiektu walca, który nie ma wypełnionych boków, klasa WireCylinder jest do tego zadania stworzona. Na rysunku 3.27 przedstawiono ją z domyślnymi ustawieniami liczby kątów u podstaw. Aby stworzyć obiekt WireCylinder, w klasie bazowej wewnątrz metody initPrimitives() należy dodać następujący kod źródłowy, w którym tworzymy obiekt i zmieniamy jego standardowe wymiary i położenie na scenie: wireCylinder = new WireCylinder(); wireCylinder.radius = 150; wireCylinder.height = 250;
Aby obiekt był dostępny, po zmianie w komponencie ComboBox opcji na WireCylinder należy dodatkowo umieścić następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'WireCylinderEvent': container.addChild(wireCylinder); break;
130
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.27.
Obiekt klasy WireCylinder
Tabele 3.53 oraz 3.54 zawierają listę właściwości oraz metod dostępnych dla obiektów klasy WireCylinder. Tabela 3.53. Właściwości klasy WireCylinder
Nazwa
Rodzaj
Wartość domyślna
Opis
height
Number
200
Wysokość obiektu WireCylinder
radius
Number
100
Promień podstaw obiektu WireCylinder
segmentsH
Number
1
Liczba segmentów w pionie
segmentsW
Number
8
Liczba krawędzi podstaw obiektu
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
Tabela 3.54. Metody klasy WireCylinder
Nazwa
Opis
WireCylinder(init:Object = null)
Konstruktor
WireSphere W tym rozdziale omówione zostały dwie klasy, które przy odpowiedniej liczbie segmentów generują obiekty w postaci kul. WireSphere jest kolejną klasą, której obiekt może przyjąć taką formę, jedyna różnica polega na tym, że nie ma ona wypełnionych wielokątów, a jedynie samą siatkę bryły. Właściwości udostępnione w tej klasie są takie same jak właściwości klasy Sphere. Dla przykładu na rysunku
Rozdział 3. Obiekty
131
3.28 przedstawiono obiekt klasy WireSphere z takimi samymi ustawieniami, jakie miał obiekt klasy Sphere z rysunku 3.16. Rysunek 3.28.
Obiekt klasy WireSphere
Aby wygenerować obiekt siatki kuli w takiej postaci, jak pokazano na rysunku 3.28, należy w metodzie initPrimitives() dodać dwie linijki kodu: wireSphere = new WireSphere(); wireSphere.radius = 200;
Poza tym aby obiekt wireSphere pojawił się na scenie, po wybraniu jego opcji z pola wyboru wewnątrz instrukcji switch w metodzie primitiveChangeEventHandler() należy dodać taki oto warunek: case 'WireSphereEvent': container.addChild(wireSphere); break;
Tabele 3.55 oraz 3.56 zawierają listę właściwości oraz metod dostępnych dla obiektów klasy WireSphere. Tabela 3.55. Właściwości klasy WireSphere
Nazwa
Rodzaj
Wartość domyślna
Opis
radius
Number
100
Promień kuli
segmentsH
Number
1
Liczba segmentów w pionie
segmentsW
Number
8
Liczba segmentów w poziomie
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
132
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.56. Metody klasy WireSphere
Nazwa
Opis
WireSphere(init:Object = null)
Konstruktor
WireRegularPolygon Obiekt stworzony z klasy WireRegularPolygon wyświetlany jest jako wielokąt foremny, standardowo złożony z ośmiu krawędzi. Wewnątrz wygenerowanego obiektu nie ma nic — ani pokrytej powierzchni, ani nawet linii łączących punkty Vertex na krawędziach z punktem centralnym całego obiektu. WireRegularPolygon ma następujące właściwości: radius ustawiający długość jego promienia, sides wyznaczający liczbę krawędzi oraz yUp, który określa, czy obiekt ma być w pozycji pionowej, czy też poziomej. Na rysunku 3.29 przedstawiono przykładowy obiekt tej klasy. Rysunek 3.29.
Obiekt klasy WireRegularPolygon
Od strony kodu klasy bazowej w metodzie initPrimitives() należy dopisać następujący fragment kodu: wirePolygon = new WireRegularPolygon(); wirePolygon.radius = 200; wirePolygon.yUp = false;
Aby obiekt wirePolygon był dostępny, po wybraniu jego opcji z pola wyboru w metodzie primitiveChangeEventHandler() należy dodać następujący warunek do instrukcji switch:
Rozdział 3. Obiekty
133
case 'WireRegularPolygonEvent': container.addChild(wirePolygon); break;
Tabele 3.57 oraz 3.58 zawierają listę właściwości oraz metod dostępnych dla obiektów klasy WireRegularPolygon. Tabela 3.57. Właściwości klasy WireRegularPolygon
Nazwa
Rodzaj
Wartość domyślna
Opis
sides
Number
200
Liczba krawędzi wielokąta
radius
Number
100
Promień wielokąta foremnego
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
Tabela 3.58. Metody klasy WireRegularPolygon
Nazwa
Opis
WireRegularPolygon(init:Object = null)
Konstruktor
WireTorus WireTorus
to ostatnia omawiana klasa w tym rozdziale, której obiekt ma postać siatki, w tym przypadku poznanej wcześniej bryły o kształcie obwarzanka. Pod względem właściwości nie różni się ona od klasy Torus, tutaj również istnieje możliwość ustawienia szerokości całego obiektu poprzez zmianę wartości właściwości radius. Promień okręgów, których połączenie tworzy tę siatkę, również kontroluje się poprzez właściwość tube, a za liczbę segmentów odpowiadają segmentsR oraz segmentsT. Na rysunku 3.30 przedstawiono obiekt klasy WireTorus z takimi samymi ustawieniami jak w przypadku obiektu klasy Torus z rysunku 3.18. Żeby uzyskać taki obiekt, wewnątrz metody initPrimitives() należy dodać następujący kod: wireTorus = new WireTorus(); wireTorus.tube = 60; wireTorus.radius = 200;
Aby po wybraniu opcji WireTorus z pola ComboBox na ekranie pojawił się obiekt wireTorus, należy dopisać następujący warunek w instrukcji switch wewnątrz metody primitiveChangeEventHandler(): case 'WireTorusEvent': container.addChild(wireTorus); break;
134
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 3.30.
Obiekt klasy WireTorus
Tabele 3.59 oraz 3.60 zawierają listę właściwości oraz metod dostępnych dla obiektów klasy WireTorus. Tabela 3.59. Właściwości klasy WireTorus
Nazwa
Rodzaj
Wartość domyślna
Opis
radius
Number
100
Długość promienia torusa
segmentsR
Number
8
Liczba segmentów w poziomie
segmentsT
Number
6
Liczba segmentów w pionie
tube
Number
40
Średnica ścian obiektu WireTorus
yUp
Boolean
true
Określa, czy obiekt ma być w pozycji pionowej, czy poziomej
Tabela 3.60. Metody klasy WireTorus
Nazwa
Opis
WireTorus(init:Object = null)
Konstruktor
Skybox W tym podrozdziale poznamy klasy Skybox oraz Skybox6, które generują obiekty specjalnie skonstruowane do pełnienia funkcji tła lub panoramy. Do ich wykorzystania potrzebna jest wiedza związana ze stosowaniem materiałów, którą zdobędziemy dopiero w następnym rozdziale, ale ponieważ obie te klasy znajdują się w pakiecie away3d.primitives, nie będziemy specjalnie ich rozłączali od pozostałych.
Rozdział 3. Obiekty
135
Skybox W punkcie dotyczącym obiektu klasy Cube wspomniano o możliwości umieszczenia kamery wewnątrz bryły i odwrócenia sposobu wyświetlania ścian z zewnętrznej warstwy obiektu na wewnętrzną. Warunkiem, aby to się udało, jest przypisanie właściwości flip wartości true, dzięki czemu ze zwykłego obiektu klasy Cube można stworzyć warstwę tła. Aby jednak pominąć to całe zamieszanie, powinno się wykorzystać obiekt typu Skybox, który został zaprojektowany specjalnie do takich celów. Obiekt tej klasy to nic innego jak duży sześcian Cube, którego ściany są od razu skierowane do wewnątrz, a ich wymiary odpowiadają scenie aplikacji. W konstruktorze tej klasy argumentami nazywa się materiały, które mają zostać użyte na każdym z boków. Łatwo się nimi posługiwać, zważywszy na to, że ich nazwy odpowiadają bokom sześcianu. Na rysunku 3.31 przedstawiono obiekt klasy Skybox z przykładowymi teksturami, które można znaleźć w internecie. Rysunek 3.31.
Obiekt klasy Skybox
Aby stworzyć panoramę, często sięgano po obiekty Sphere, ale jest to mniej wydajne rozwiązanie z uwagi na liczbę trójkątów, jaka jest potrzebna do wygenerowania kuli. Przepisz poniższy kod, aby sprawdzić, jak działa obiekt klasy Skybox: package { import import import import import
away3d.containers.View3D; away3d.primitives.Skybox; away3d.materials.BitmapFileMaterial; flash.display.StageAlign; flash.display.StageScaleMode;
136
Flash i ActionScript. Aplikacje 3D od podstaw import flash.display.Sprite; import flash.events.*; public class skyboxExample extends Sprite { private var view:View3D; private var background:Skybox; public function skyboxExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; view = new View3D(); addChild(view); background = new Skybox( new BitmapFileMaterial('../../resources/bitmaps/skybox/ front.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/ left.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/ back.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/ right.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/up.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/ down.jpg') ); view.scene.addChild(background); onResize(); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { view.camera.rotationX += .25; view.camera.rotationY -= .1; view.render(); } } }
W powyższym kodzie na początku zaimportowaliśmy potrzebne nam klasy, ze strony biblioteki użyliśmy widoku View3D, omawianego Skybox oraz materiału BitmapFileMaterial, którym na razie nie będziemy zaprzątali sobie głowy. Do ob-
Rozdział 3. Obiekty
137
sługi zdarzeń oraz stołu montażowego aplikacji wykorzystaliśmy klasy: Sprite, StageAlign, StageScaleMode oraz Event. Wewnątrz klasy skyboxExample zdefiniowaliśmy dwa obiekty niezbędne do działania aplikacji. Są nimi view oraz background. W konstruktorze, podobnie jak w poprzednich przykładach, w pierwszej kolejności nadaliśmy odpowiednie ustawienia obiektowi stage: stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE;
Następnie stworzyliśmy widok i dodaliśmy go do stołu montażowego. W kolejnych kilku linijkach stworzyliśmy wcześniej zadeklarowany jako background obiekt klasy Skybox, nadając mu kolejno w argumentach obiekty materiałów korzystające z zewnętrznych plików graficznych. Każdy z tych materiałów pokrywa osobną ścianę, a użyte nazwy plików odpowiadają ich nazwom argumentów. Dzięki temu można zachować porządek w tego typu obiektach. background = new Skybox( new BitmapFileMaterial('../../resources/bitmaps/skybox/front.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/left.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/back.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/right.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/up.jpg'), new BitmapFileMaterial('../../resources/bitmaps/skybox/down.jpg') );
Po wypełnieniu wszystkich argumentów i stworzeniu obiektu dodaliśmy go do zasobów sceny, korzystając z metody addChild() na obiekcie scene. view.scene.addChild(background);
Na końcu konstruktora wywołaliśmy metodę onResize(), aby ustawić scenę względem okna, i dodaliśmy nasłuch na zdarzenia Event.RESIZE i Event.ENTER_FRAME. W metodzie onResize() zapisaliśmy zmianę pozycji widoku, tak aby view zawsze było w samym środku okna aplikacji. view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5;
Zadaniem metody onEnterFrame() jest odświeżanie zawartości widoku przez odwołanie się do jego metody render() oraz obracanie kamerą względem osi Y oraz X. Poniżej w tabeli 3.61 zawarto jedynie konstruktor klasy Skybox.
138
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 3.61. Metody klasy Skybox
Nazwa
Opis
Skybox(front:Material, left:Material, back:Material, right:Material, up:Material, down:Material)
Konstruktor
Skybox6 Skybox6 jest drugą klasą stworzoną do generowania tła i panoram, której zasada działania jest taka sama jak w przypadku zwykłego Skybox. Tutaj również wyświetlony obraz bazuje na formie sześcianu, który pokrywa całą powierzchnię sceny. Jest jednak między tymi klasami pewna różnica, i to nie tylko w nazwie. Otóż Skybox6 jako źródło wyświetlanego obrazu potrzebuje tylko jednego pliku graficznego, w którym trzeba umieścić grafikę dla każdej ze ścian. Na rysunku 3.32 przedstawiono schemat tekstury z podziałem na pozycje poszczególnych fragmentów panoramy. Rysunek 3.32.
Schemat tekstury dla obiektu klasy Skybox6
Tak przygotowany plik może być użyty jako źródło wyświetlanego obrazu, a sam obiekt tworzy się, stosując jeden materiał, a nie sześć, jak miało to miejsce w poprzednio omawianej klasie. Przy dokonywaniu wyboru między Skybox a Skybox6 należy się zastanowić, co będzie dla nas wygodniejsze i czy w trakcie wyświetlania sceny nie będą się musiały zmieniać pojedyncze ściany tła. Aby sprawdzić, jak działa Skybox6, skopiuj kod z przykładu dotyczącego klasy Skybox, w miejscu gdzie importowana jest klasa oraz definiowany obiekt background, zamień nazwę Skybox na Skybox6. Aby stworzyć obiekt background, posłuż się poniższym fragmentem kodu: background = new Skybox6(new BitmapFileMaterial('../../resources/bitmaps/ skybox/skybox.jpg'));
Rozdział 3. Obiekty
139
Ponieważ — tak samo jak w przypadku klasy Skybox — klasa Skybox6 nie ma dodatkowych metod, tabela 3.62 zawiera jedynie konstruktor tej klasy. Tabela 3.62. Metody klasy Skybox6
Nazwa
Opis
Skybox6(material:Material = null)
Konstruktor
Podsumowanie Podstawą wszystkich brył są punkty, które w bibliotece Away3D reprezentują obiekty klasy Vertex. Dwa punkty Vertex połączone linią tworzą obiekt Segment oraz LineSegment. Obiekty klas Segment, Face oraz Sprite3D same w sobie nie są widoczne na scenie, potrzebują obiektu kontenera, który zapewni warstwę wizualną. Klasa Segment ma metody lineTo(), curveTo() oraz moveTo(), które pozwalają na rysowanie linii prostych i krzywych. Obiekt klasy Face jest zbiorem trzech punktów Vertex: v0, v1, v2, które połączone tworzą formę trójkąta o wypełnionej powierzchni. Klasa Segment zawiera metody potrzebne do tworzenia dowolnej liczby linii prostych i krzywych. Obiekt klasy Mesh jest kontenerem dla obiektów typu Segment, Face i Sprite3D. Dzięki metodom — odpowiednio — addFace(), addSegment() oraz addSprite() umożliwia wyświetlenie elementów klas Face, Segment czy też Sprite3D. Klasa Sprite3D tworzy dwuwymiarową figurę w kształcie prostokąta, który jest zawsze zwrócony ku kamerze. Jedynym sposobem oddziaływania Sprite3D na otaczające go środowisko jest zmienienie swojej skali względem odległości od kamery. MovieClipSprite w porównaniu z klasą Sprite3D dodatkowo umożliwia stosowanie obiektów MovieClip jako źródła wyświetlanego obrazu. DirectionalSprite to klasa generująca specjalny obiekt, który zmienia wyświetlany obraz w zależności od kąta nachylenia obiektu względem otoczenia.
140
Flash i ActionScript. Aplikacje 3D od podstaw
Dodając źródło obrazu dla obiektu klasy DirectionalSprite, stosuje się metodę addDirectionalMaterial(), w której pierwsza właściwość jest punktem Vertex określającym kierunek, a druga to źródło obrazu. Żaden z obiektów w pakiecie away3d.primitives nie wymaga dodatkowych obiektów, aby były one widoczne. Każda z klas pakietu away3d.primitives dziedziczy z klasy AbstractPrimitive, która jest klasą macierzystą, chociaż jej obiekt nie generuje jakiejkolwiek bryły. Klasa AbstractPrimitive ma metody i właściwości, które umożliwiają wyświetlanie tworzonych brył. Do zbudowania bryły jakiejkolwiek klasy potrzebna jest metoda buildPrimitive() z klasy AbstractPrimitive. Wewnątrz klasy AbstractPrimitive i jej pochodnych stosowana jest przestrzeń nazw arcane. Klasa Init umieszczona w pakiecie away3d.core.utils generuje obiekt pomocniczy dla ustawiania różnego rodzaju wartości dla właściwości. Klasa LineSegment tworzy linię, która nie potrzebuje dodatkowego kontenera, aby była widoczna na scenie. Klasa Trident tworzy obiekt układu trzech osi: X, Y oraz Z, w którym można regulować ich długość oraz wyświetlić nazwy. Obiekty zawarte w pakiecie away3d.primitives swoje punkty centralne, jak wskazuje nazwa, mają w samym środku zajmowanej przestrzeni lokalnej. Punkt ten jest odniesieniem przy zmianie położenia bądź kąta nachylenia obiektu. RegularPolygon tworzy obiekt, którego forma ma postać wielokąta foremnego, standardowo złożonego z ośmiu krawędzi. Do stworzenia kuli można posłużyć się dwiema klasami: Sphere oraz GeodesicSphere. GeodesicSphere z uwagi na swoją budowę pozwala uzyskać kulisty kształt z mniejszą liczbą trójkątów. Za ich liczbę odpowiada wartość właściwości fractures, który reguluje podział każdej z krawędzi. Obiekty klas Torus oraz TorusKnot stosuje się głównie przy testach wydajnościowych aplikacji.
Rozdział 3. Obiekty
141
Podstawami obiektów klas Cone oraz Cylinder są ośmiokąty foremne, można regulować liczbę tych kątów, jak również to, czy w ogóle mają być one wyświetlane. Odwołując się do elementów tablicy vertices zawartej w obiekcie, można modyfikować jego wygląd. W ten sposób można zamienić stożek prosty w ścięty lub pochyły. Klasa SeaTurtle to w rzeczywistości wyeksportowany do pliku *.as model. W pakiecie away3d.primitives umieszczone są również klasy, których obiekty nie mają powierzchni, a jedynie same krawędzie. Nazwy tych klas zawierają przedrostek Wire. Klasy Skybox oraz Skybox6 zostały specjalnie dodane do tworzenia tła oraz panoram. Klasa Skybox korzysta z sześciu różnych materiałów, a klasa Skybox6 z jednego, w którym każdy z boków ma swoje konkretne miejsce. Panoramy wygenerowane na sześcianie są bardziej wydajne od tych stworzonych na obiekcie Sphere. Przypisanie wartości true właściwości bothsides sprawia, że powierzchnia trójwymiarowego obiektu renderowana jest z obu stron, niezależnie od pozycji kamery.
142
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 4. Materiały Na wstępie warto wyjaśnić, co kryje się za pojęciem materiał. Otóż materiałem będziemy określali obiekty, których rolą jest pokrycie powierzchni trójwymiarowego obiektu w konkretny sposób. Nie należy mylić tego z teksturą, ponieważ jest ona plikiem graficznym, z którego korzysta kilka rodzajów materiałów. W niektórych z omówionych do tej pory przykładach musieliśmy zastosować nieznane nam jeszcze materiały. Ponieważ w kolejnych rozdziałach będziemy zajmowali się bardziej złożonymi pojęciami, jest to dobry moment, aby przyjrzeć się rodzajom materiałów dostępnych w Away3D. Każdy z nich stworzony został do konkretnych zastosowań, przez co różnią się od siebie warunkami użycia, implementacją i sposobem oddziaływania na otoczenie. Mimo że materiały umieszczone są w jednym pakiecie away3d.materials, można podzielić je na kilka umownych kategorii. Najważniejszy podział polega na rozróżnieniu materiałów reagujących na działanie światła od tych, które go nie potrzebują do prawidłowego wyświetlania. Na tym etapie gdybyśmy skorzystali z którejś z klas materiałów reagujących na światło bez jego źródła, otrzymalibyśmy zwykłe czarne pokrycie. Dlatego w tym rozdziale zajmiemy się tą drugą grupą materiałów. Zaczniemy od najprostszych i skończymy na sposobie mieszania kilku naraz. Czytając ten rozdział, dowiesz się: Jakie rodzaje materiałów dostępnych w Away3D nie reagują na światło. W jakich warunkach korzystać z poszczególnych materiałów. Jak tworzyć i dodawać materiały do powierzchni obiektu. Jakie właściwości oraz metody mają klasy reprezentujące poszczególne rodzaje materiałów. Jak dodawać obrazy i inne źródła materiałów do zasobów aplikacji.
144
Flash i ActionScript. Aplikacje 3D od podstaw
Czym jest klasa Cast oraz jak jej używać. Jak korzystać z dodanych do zasobów plików graficznych bądź elementów MovieClip.
Przygotowanie zasobów dla aplikacji Dodawanie plików do zasobów w programie Adobe Flash Są sytuacje, w których liczba plików danego projektu musi być ograniczona do minimum, aby ułatwić przenoszenie aplikacji i nie narażać jej na ewentualne błędy spowodowane brakiem wybranego elementu. W takich sytuacjach można wszelkie pliki medialne umieścić w zasobach wybranej aplikacji bezpośrednio w pliku Flash. Spowoduje to zwiększenie rozmiarów programu, ale przynajmniej wszystkie potrzebne bitmapy, animacje lub dźwięki będą umieszczone w jednym miejscu. Przechowywanie zasobów bezpośrednio w aplikacji powinno być stosowane jedynie wtedy, gdy jesteśmy pewni, że zasoby te nie ulegną zmianie. W przeciwnym razie jakakolwiek podmiana zasobów może być bardzo niewygodna. Aby dodać plik graficzny i wykorzystać go w kodzie źródłowym, należy wykonać następujące kroki: 1. W pierwszej kolejności należy w górnym menu wcisnąć przycisk File. W rozwiniętej liście należy wybrać opcję Import, a następnie jedną z możliwości: Import to Stage… lub Import to Library… W przypadku pierwszej opcji wybrany element pojawi się dodatkowo na stole montażowym. Nas interesuje jednak druga możliwość. Omówiony krok przedstawiono na rysunku 4.1. 2. Po poprawnym zaimportowaniu wybranego pliku do zasobów (w naszym przypadku pliku front.jpg) w panelu Library powinna się pojawić nowa pozycja z jego nazwą. Po kliknięciu lewym przyciskiem myszy na tę nazwę w białym polu nad listą powinna się ukazać zawartość pliku, tak jak to pokazano na rysunku 4.2. 3. Gdy proces importu przebiegł pomyślnie i obrazek jest widoczny, można nadać mu nowe ustawienia. W tym celu należy kliknąć prawym przyciskiem myszy na konkretny element i wybrać opcję Properties…, tak jak to pokazano na rysunku 4.3.
Rozdział 4. Materiały
145
Rysunek 4.1.
Wybór opcji importowania pliku do zasobów
Rysunek 4.2.
Poprawnie zaimportowany plik graficzny
4. Po wykonaniu poprzedniego kroku powinno się pojawić nowe okno zatytułowane Properties. W zależności od rodzaju wybranego elementu zawiera ono różne opcje ustawień. W przypadku plików graficznych jest to między innymi określenie jakości, z jaką będzie wyświetlany obrazek. Każdy z elementów ma jednak jedną wspólną sekcję o nazwie Linkage, w której określa się odwołanie do klasy w kodzie ActionScript 3.0. Wewnątrz tej sekcji znajduje się pole wyboru Export for ActionScript, które domyślnie jest odznaczone i należy je zaznaczyć. Spowoduje to odblokowanie pozostałych pól wewnątrz sekcji, których wypełnienie jest niezbędne do utworzenia odwołania w kodzie ActionScript. Pomińmy pole
146
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 4.3.
Włączenie okna opcji wybranego elementu
wyboru Export in frame 1, które zawsze jest zaznaczone — nas interesują pola Class oraz Base class. W polu Class należy podać nazwę klasy, która domyślnie jest taka sama jak nazwa wgranego elementu, z kolei w polu Base class podana jest ścieżka do klasy, z której nasza będzie dziedziczyła. Zawartość tego pola standardowo uzależniona jest od rodzaju elementu, ale można ją również zmienić. W polu Class podaliśmy nazwę pliku bez jego rozszerzenia, ponieważ nie stosuje się kropek w nazwach klas. Okno Properties z nowymi ustawieniami przedstawiono na rysunku 4.4 Rysunek 4.4.
Okno ustawień odwołania zasobu biblioteki do klasy
Rozdział 4. Materiały
147
5. Po wykonaniu wszystkich tych czynności kliknij przycisk OK w oknie Properties. Jeżeli nasza klasa nie zostanie odnaleziona, wyświetli się stosowny komunikat. Nie należy się tym przejmować, ponieważ jeszcze nie skończyliśmy pracy nad wybranym plikiem. Po pojawieniu się ostrzeżenia należy kliknąć OK. Okno zostanie zamknięte, a w panelu Library obok nazwy pliku pojawi się podana w ustawieniach nazwa klasy. Alternatywnym i szybszym sposobem stworzenia odwołania dla wybranego obiektu jest kliknięcie na szarym polu przy jego nazwie w panelu Library. To spowoduje pojawienie się pola tekstowego, w którym można podać nazwę klasy, tak jak to pokazano na rysunku 4.5. Rysunek 4.5.
Alternatywny sposób wpisania odwołania dla elementu
Stworzone dla zasobu powiązanie do klasy można wykorzystać w kodzie ActionScript na różne sposoby. W następnym podpunkcie tego podrozdziału poznamy dwa z nich.
Odwoływanie się do zasobów w kodzie źródłowym aplikacji Dodane do zasobów aplikacji pliki graficzne bądź inne elementy można w kodzie źródłowym wykorzystać w dwojaki sposób. Pierwszy, bardziej zaawansowany, polega na stworzeniu odpowiedniej klasy dla wybranego elementu, drugi zaś bazuje na specjalnie przygotowanej do tego celu klasie Cast, zaimplementowanej w bibliotece Away3D.
148
Flash i ActionScript. Aplikacje 3D od podstaw
Tworzenie klasy dla wybranego elementu w zasobach aplikacji W poprzednim podrozdziale w ostatnim kroku dodawania zasobów do aplikacji wspomniano o możliwości pojawienia się okna dialogowego z informacją ostrzegającą, że klasa o podanej nazwie nie istnieje. Jeżeli na zawartości zasobu mają być wykonywane jakiekolwiek operacje, można się zastanowić, czy taka klasa w rezultacie nie powinna faktycznie istnieć, zamiast być traktowana jedynie jako łącznik. Weźmy za przykład obiekt klasy MovieClip, który zawiera w sobie przyciski reagujące na kliknięcie myszką — aby zachować porządek i stosować obiektowe podejście do programowania, warto stworzyć dla takiego zasobu odpowiednią klasę. W jej wnętrzu można zaprogramować wszystkie potrzebne zachowania elementów zasobu, z którym jest ona powiązana. Przykładową strukturę klasy stworzonej dla zasobu tekstury przedstawiono w następującym kodzie: package { import flash.display.Sprite; public class Tekstura extends Sprite { // jakieś właściwości public function Tekstura():void { // jakieś operacje } public function get_bitmapData():BitmapData { // jakieś operacje zwracające obiekt klasy BitmapData } public function jakas_metoda ():void { // jakieś operacje } } }
Przyjmując, że klasa Tekstura ma być między innymi źródłem danych dla materiału klasy BitmapMaterial, dodaliśmy metodę, której wywołanie teoretycznie zwraca obiekt typu BitmapData. Poza tym klasa Tekstura ma również pewną metodę wykonującą dodatkowe czynności związane tylko z zawartością obiektu tej klasy. W tym pseudokodzie zawarliśmy dwie ważne kwestie związane z tworzeniem klas dla zasobów aplikacji — współpracy z innymi obiektami oraz wykonywania operacji w swoim obrębie. Przykładowe zastosowanie klasy Tekstura przedstawiono w następującym kodzie źródłowym:
Rozdział 4. Materiały
149
var tekstura:Tekstura = new Tekstura(); var bitmap:BitmapMaterial = new BitmapMaterial(tekstura.get_bitmapData()); tekstura.jakas_metoda();
Stosowanie klasy Cast Biblioteka Away3D ma w swoich źródłach klasę o nazwie Cast, która stworzona została specjalnie do tego, aby w szybki sposób tłumaczyć, przechowywać oraz reprezentować wybrane obiekty z zasobów aplikacji. Jest to klasa statyczna, co oznacza, że nie trzeba tworzyć jej obiektu, aby móc korzystać z metod oraz właściwości, które zawiera. Używanie jej w celu odwołania do konkretnego obiektu ogranicza się do jednej krótkiej linijki kodu, którą przedstawiono poniżej; Cast.bitmap('nazwa_jakiejs_bitmapy_zapisanej_w_zasobach_aplikacji')
W tym przypadku tworzy się odwołanie do konkretnej bitmapy, stosując metodę bitmap() i podając jako argument nazwę fikcyjnej klasy zapisanej w ustawieniach rysunku. Poza metodą bitmap() klasa Cast ma również inne, które opisano w tabeli 4.1. Tabela 4.1. Metody klasy Cast
Nazwa
Opis
bitmap
Zwraca element w postaci obiektu typu BitmapData
bytearray
Zwraca element w postaci obiektu typu ByteArray
color
Zwraca element w postaci wartości typu uint
material
Zwraca element w postaci obiektu typu Material
string
Zwraca element w postaci wartości typu String
tryclass
Zwraca element w postaci obiektu typu Object
trycolor
Zwraca element w postaci wartości typu uint
wirematerial
Zwraca element w postaci obiektu typu Material
xml
Zwraca element w postaci obiektu typu Xml
Osadzanie zasobów poprzez korzystanie ze znacznika [Embed] Zasoby potrzebne do prawidłowego działania aplikacji można osadzić w pliku swf w trakcie jego kompilacji. Operacja ta, potocznie określana jako wstrzykiwanie, polega na zastosowaniu znacznika metadanych [Embed], który dostępny jest w środowisku Flex SDK. W znaczniku [Embed] ścieżkę do zasobu określa się we właściwości source. Jeżeli z wybranego zasobu potrzebujemy konkretnego elementu, można odwołać się do niego poprzez właściwość symbol. Poza tym można również
150
Flash i ActionScript. Aplikacje 3D od podstaw
określić typ MIME osadzanego zasobu we właściwości mimeType. Należy pamiętać o tym, że obiekt typu Class, łączący z wybranym zasobem, musi być zadeklarowany tuż pod znacznikiem [Embed]. Poniżej przedstawiono przykład osadzenia zasobów poprzez zastosowanie znacznika [Embed]: [Embed(source="../../resources/bitmaps/map2.jpg")] private var NormalMap:Class; [Embed(source="../../resources/bitmaps/waterMap.jpg")] private var WaterMap:Class; [Embed(source="../../resources/bitmaps/seaturtle.jpg")] private var SeaturtleBitmap:Class; [Embed(source="../../resources/bitmaps/specular.jpg")] private var Board:Class; [Embed(source="../../resources/swfs/form.swf")] private var movieMC:Class; [Embed(source="../../resources/swfs/water.swf", symbol="Water")] private var Water:Class;
Przykład Zanim zaczniemy omawiać poszczególne rodzaje materiałów, stworzymy klasę o nazwie MaterialsExample, która będzie podstawą do implementacji i sprawdzenia, jak materiały zachowują się na powierzchni obiektu. Do tego celu stworzymy scenę oraz kilka różnego rodzaju obiektów, którym przypiszemy konkretne materiały. Aby w łatwy sposób zmieniać rodzaj wyświetlanego materiału, stworzymy prosty panel użytkownika, w którym umieścimy obiekt typu ComboBox o nazwie materialSelect. W liście wyboru tego obiektu dodamy nazwy wszystkich omawianych w tym rozdziale materiałów. Poza składnikiem ComboBox dodamy również trzy przyciski: togglePauseButton, lessButton i moreButton. Pierwszy z wymienionych przycisków widoczny będzie jedynie wtedy, gdy wyświetlanym materiałem będzie obiekt klasy VideoMaterial. Z kolei pozostałe dwa będą wyświetlane wraz z materiałem TransformBitmapMaterial. O funkcjach, które te przyciski spełniają, wspomnimy w dalszych częściach tego rozdziału, teraz przepisz następujący kod do pliku MaterialsPanel.as, aby w klasie bazowej tego przykładu móc stworzyć obiekt tego panelu. package { import flash.display.Sprite;
Rozdział 4. Materiały import import import import import import import
151
flash.events.Event; flash.events.MouseEvent; fl.core.UIComponent; fl.controls.Label; fl.controls.Button; fl.controls.ComboBox; fl.data.DataProvider;
public class MaterialsPanel extends Sprite { public function MaterialsPanel() { var materialLabel:Label = new Label(); materialLabel.text = 'Materiał:'; materialLabel.x = 15; materialLabel.y = 15; materialLabel.width = 70; addChild(materialLabel); var dp:DataProvider = new DataProvider(); dp.addItem( { label: 'WireframeMaterial', data: 'WireframeMaterial' } ); dp.addItem( { label: 'WireColorMaterial', data: 'WireColorMaterial' } ); dp.addItem( { label: 'ColorMaterial', data: 'ColorMaterial' } dp.addItem( { label: 'EnviroColorMaterial', data: 'EnviroColorMaterial' } ); dp.addItem( { label: 'BitmapMaterial', data: 'BitmapMaterial' dp.addItem( { label: 'BitmapFileMaterial', data: 'BitmapFileMaterial' } ); dp.addItem( { label: 'EnviroBitmapMaterial', data: 'EnviroBitmapMaterial' } ); dp.addItem( { label: 'GlassMaterial', data: 'GlassMaterial' } dp.addItem( { label: 'TransformBitmapMaterial', data: 'TransformBitmapMaterial' } ); dp.addItem( { label: 'MovieMaterial', data: 'MovieMaterial' } dp.addItem( { label: 'AnimatedBitmapMaterial', data: 'AnimatedBitmapMaterial' } ); dp.addItem( { label: 'VideoMaterial', data: 'VideoMaterial' }
); } );
); );
var materialSelect:ComboBox = new ComboBox(); materialSelect.name = 'materialSelect'; materialSelect.dataProvider = dp; materialSelect.width = 150; materialSelect.x = 70; materialSelect.y = 10; addChild(materialSelect); materialSelect.addEventListener(Event.CHANGE, materialChange);
);
var togglePauseButton:Button = new Button(); togglePauseButton.name = "togglePause"; togglePauseButton.label = "Włącz/Wyłącz";
152
Flash i ActionScript. Aplikacje 3D od podstaw togglePauseButton.x = 220; togglePauseButton.y = 10; togglePauseButton.width = 100; togglePauseButton.visible = false; addChild(togglePauseButton); togglePauseButton.addEventListener(MouseEvent.CLICK, buttonClick); var lessSegmentsButton:Button = new Button(); lessSegmentsButton.name = "lessSegments"; lessSegmentsButton.label = "-"; lessSegmentsButton.x = 220; lessSegmentsButton.y = 10; lessSegmentsButton.width = 30; lessSegmentsButton.visible = false; addChild(lessSegmentsButton); lessSegmentsButton.addEventListener(MouseEvent.CLICK, buttonClick); var moreSegmentsButton:Button = new Button(); moreSegmentsButton.name = "moreSegments"; moreSegmentsButton.label = "+"; moreSegmentsButton.x = 270; moreSegmentsButton.y = 10; moreSegmentsButton.width = 30; moreSegmentsButton.visible = false; addChild(moreSegmentsButton); moreSegmentsButton.addEventListener(MouseEvent.CLICK, buttonClick); var lessExponentButton:Button = new Button(); lessExponentButton.name = "lessExponent"; lessExponentButton.label = "-"; lessExponentButton.x = 220; lessExponentButton.y = 10; lessExponentButton.width = 30; lessExponentButton.visible = false; addChild(lessExponentButton); lessExponentButton.addEventListener(MouseEvent.CLICK, buttonClick); var moreExponentButton:Button = new Button(); moreExponentButton.name = "moreExponent"; moreExponentButton.label = "+"; moreExponentButton.x = 270; moreExponentButton.y = 10; moreExponentButton.width = 30; moreExponentButton.visible = false; addChild(moreExponentButton); moreExponentButton.addEventListener(MouseEvent.CLICK, buttonClick); } private function materialChange(e:Event):void { dispatchEvent(new Event(e.target.value + 'Event')); }
Rozdział 4. Materiały
153
private function buttonClick(e:MouseEvent):void { dispatchEvent(new Event(e.target.name + 'Event')); } public function setVisibility(objectName:String,val:Boolean):void { if (getChildByName(objectName) is UIComponent) { (getChildByName(objectName) as UIComponent).visible = val; } } public function setLabel(labelName:String,val:String):void { if (getChildByName(labelName) is Button) { (getChildByName(labelName) as Button).label = val; } } public function draw(_width:Number, _height:Number, _color:uint = 0xF1F1F1, _alpha:int = 1):void { graphics.clear(); graphics.beginFill(_color,_alpha); graphics.drawRect(0, 0, _width, _height); graphics.endFill(); } } }
Gdy masz gotowy do użycia plik MaterialsPanel.as, przepisz poniższy kod źródłowy klasy bazowej naszego przykładu do pliku MaterialsExample.as. package { import flash.display.StageAlign; import flash.display.StageQuality; import flash.display.StageScaleMode; import flash.display.MovieClip; import flash.events.Event; import flash.events.MouseEvent; import away3d.containers.View3D; import away3d.materials.*; import away3d.primitives.*; public class MaterialsExample extends MovieClip { [Embed(source="../../resources/bitmaps/WaterNormalMap.jpg")] private var NormalMap:Class; [Embed(source="../../resources/bitmaps/waterMap.jpg")]
154
Flash i ActionScript. Aplikacje 3D od podstaw private var WaterMap:Class; [Embed(source="../../resources/bitmaps/seaturtle.jpg")] private var SeaturtleBitmap:Class; [Embed(source="../../resources/bitmaps/specular.jpg")] private var Board:Class; [Embed(source="../../resources/swfs/form.swf")] private var movieMC:Class; [Embed(source="../../resources/swfs/water.swf", symbol="Water")] private var Water:Class; private private private private private private private private private private private private private private private
var var var var var var var var var var var var var var var
panel:MaterialsPanel; view:View3D; wireframe:WireframeMaterial; wirecolor:WireColorMaterial; color:ColorMaterial; enviroColor:EnviroColorMaterial; bitmap:BitmapMaterial; bitmapFile:BitmapFileMaterial; glassMaterial:GlassMaterial; enviroBitmap:EnviroBitmapMaterial; transformBitmap:TransformBitmapMaterial; animatedBitmap:AnimatedBitmapMaterial; movie:MovieMaterial; video:VideoMaterial; currentMaterial:*;
private private private private private
var var var var var
sphere:Sphere; cube:Cube; plane:Plane; seaturtle:SeaTurtle; currentObject:*;
public function MaterialsExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; removeEventListener(Event.ADDED_TO_STAGE, init); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame);
Rozdział 4. Materiały
155
view = new View3D(); addChild(view); initMaterials(); initObjects(); addPanel(); onResize(); } private function addPanel():void { panel = new MaterialsPanel(); addChild(panel); panel.addEventListener('togglePauseEvent', onPanelEvent); panel.addEventListener('lessSegmentsEvent', onPanelEvent); panel.addEventListener('moreSegmentsEvent', onPanelEvent); panel.addEventListener('lessExponentEvent', onPanelEvent); panel.addEventListener('moreExponentEvent', onPanelEvent); panel.addEventListener('WireframeMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('WireColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('ColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('EnviroColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('BitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('BitmapFileMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('EnviroBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('GlassMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('TransformBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('DepthBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('MovieMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('AnimatedBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('VideoMaterialEvent', onMaterialChangeEventHandler); } private function onPanelEvent(e:Event):void { switch(e.type) { case 'togglePauseEvent': { (currentMaterial as VideoMaterial).netStream.togglePause(); break;
156
Flash i ActionScript. Aplikacje 3D od podstaw } case 'lessSegmentsEvent': { currentObject.material.scaleX /= .5; currentObject.material.scaleY /= .5; break; } case 'moreSegmentsEvent': { currentObject.material.scaleX *= .5; currentObject.material.scaleY *= .5; break; } case 'lessExponentEvent': { if (currentMaterial.exponent >= 5) currentMaterial.exponent -= 5; break; } case 'moreExponentEvent': { currentMaterial.exponent += 5; break; } } } private function initMaterials():void { var waterMap:Bitmap = new WaterMap(); var seaturtleBitmap:Bitmap = new SeaturtleBitmap(); var board:Bitmap = new Board(); //WireframeMaterial wireframe = new WireframeMaterial(0x000000); //WireColorMaterial wirecolor = new WireColorMaterial(0xFF0000, { wireColor:0x000000} ); //ColorMaterial color = new ColorMaterial(0xFF0000); //EnviroColorMaterial enviroColor = new EnviroColorMaterial (0xFF0000, waterMap.bitmapData); //BitmapMaterial bitmap = new BitmapMaterial(seaturtleBitmap.bitmapData); //BitmapFileMaterial bitmapFile = new BitmapFileMaterial('../../resources/ bitmaps/road.jpg');
Rozdział 4. Materiały
157
//EnviroBitmapMaterial enviroBitmap = new EnviroBitmapMaterial(seaturtleBitmap.bitmapData, waterMap.bitmapData); //TransformBitmapMaterial transformBitmap = new TransformBitmapMaterial(board.bitmapData, { repeat:true, scaleX:1, scaleY:1 } ); //MovieMaterial movie = new MovieMaterial(new movieMC(), { interactive:true } ); //AnimatedBitmapMaterial animatedBitmap = new AnimatedBitmapMaterial(new Water()); currentMaterial = wireframe; } private function initObjects():void { //Sphere sphere = new Sphere( { material:currentMaterial, segmentsW:16, segmentsH:16 } ); //Cube cube = new Cube( { material:currentMaterial, segmentsW:8, segmentsH:8, segmentsD:8 } ); cube.scale(2); //Plane plane = new Plane( { material:currentMaterial, yUp:false, bothsides:true, width:400, height:400 } ); //SeaTurtle seaturtle = new SeaTurtle(); seaturtle.material=currentMaterial; seaturtle.scale(.5); seaturtle.rotationX = -45; currentObject = sphere; view.scene.addChild(currentObject); } private function onMaterialChangeEventHandler(e:Event = null):void { panel.setVisibility('togglePause', false); panel.setVisibility('lessSegments', false); panel.setVisibility('moreSegments', false); panel.setVisibility('lessExponent', false); panel.setVisibility('moreExponent', false); if (String(Object(currentMaterial).constructor) == '[class VideoMaterial]')
158
Flash i ActionScript. Aplikacje 3D od podstaw { (currentMaterial as VideoMaterial).nc.close(); (currentMaterial as VideoMaterial).netStream.close(); (currentMaterial as VideoMaterial).video.clear(); } if (currentObject != null) { view.scene.removeChild(currentObject); currentObject = null; currentMaterial = null; } if (e != null) { switch(e.target.value) { case 'WireframeMaterialEvent': currentMaterial = wireframe; currentObject = sphere; break; case 'WireColorMaterialEvent': currentMaterial = wirecolor; currentObject = cube; break; case 'ColorMaterialEvent': currentMaterial = color; currentObject = sphere; break; case 'EnviroColorMaterialEvent': currentMaterial = enviroColor; currentObject = seaturtle; break; case 'BitmapMaterialEvent': currentMaterial = bitmap; currentObject = seaturtle; break; case 'BitmapFileMaterialEvent': currentMaterial = bitmapFile; currentObject = sphere; break; case 'EnviroBitmapMaterialEvent': currentMaterial = enviroBitmap; currentObject = seaturtle; break; case 'GlassMaterialEvent': var normalMap:Bitmap = new NormalMap(); var waterMap:Bitmap = new WaterMap(); currentMaterial = new GlassMaterial(normalMap.bitmapData, waterMap.bitmapData, currentObject, false, { smooth:true, exponent:0 } ); panel.setVisibility('lessExponent', true);
Rozdział 4. Materiały
159
panel.setVisibility('moreExponent', true); break; case 'TransformBitmapMaterialEvent': panel.setVisibility('lessSegments', true); panel.setVisibility('moreSegments', true); currentMaterial = transformBitmap; currentObject = plane; break; case 'MovieMaterialEvent': currentMaterial = movie; currentObject = plane; break; case 'AnimatedBitmapMaterialEvent': currentMaterial = animatedBitmap; currentObject = plane; break; case 'VideoMaterialEvent': panel.setVisibility('togglePause', true); currentMaterial = new VideoMaterial( { file:'../../resources/ flvs/test.flv' } ); currentObject = plane; break; default:break; } } currentObject.material = currentMaterial; view.scene.addChild(currentObject); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = panel.height + (stage.stageHeight - panel.height) *.5; panel.draw(stage.stageWidth, 50); } private function onEnterFrame(e:Event):void { currentObject.rotationY--; if (currentMaterial == transformBitmap) transformBitmap.offsetX += 10; view.render(); } } }
Zanim zajmiemy się omawianiem konkretnych materiałów, przyjrzyjmy się niektórym fragmentom kodu tego przykładu. W pierwszej kolejności musieliśmy załączyć potrzebne nam klasy z biblioteki Away3D oraz ze standardowych pakietów ActionScript 3.0. Użyliśmy klas nadających odpowiednie ustawienia stołowi montażowemu projektu wraz z klasami zdarzeń Event i MouseEvent.
160
Flash i ActionScript. Aplikacje 3D od podstaw import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.MouseEvent;
Z biblioteki Away3D zaimportowaliśmy klasę View3D potrzebną do stworzenia obiektu widoku oraz wszystkie klasy materiałów i obiektów dostępnych w Away3D. Warto w tym miejscu wspomnieć, że importowanie tylko potrzebnych klas z wybranego pakietu jest zgodne ze sztuką pisania kodu, ale gdy korzystamy z większości klas dostępnych w pakiecie, można pokusić się o drogę na skróty i użyć *. import away3d.materials.*; import away3d.primitives.*;
W klasie MaterialsExample w pierwszej kolejności zdefiniowaliśmy obiekty typu Class, potrzebne do uzyskania dostępu do osadzonych plików graficznych JPG oraz SWF. Taki sposób osadzania zasobów został omówiony w punkcie „Osadzanie zasobów poprzez korzystanie ze znacznika [Embed]”. [Embed(source="../../resources/bitmaps/WaterNormalMap.jpg")] private var NormalMap:Class; [Embed(source="../../resources/bitmaps/waterMap.jpg")] private var WaterMap:Class; [Embed(source="../../resources/bitmaps/seaturtle.jpg")] private var SeaturtleBitmap:Class; [Embed(source="../../resources/bitmaps/specular.jpg")] private var Board:Class; [Embed(source="../../resources/swfs/form.swf")] private var movieMC:Class; [Embed(source="../../resources/swfs/water.swf", symbol="Water")] private var Water:Class;
Następnie zadeklarowaliśmy obiekt napisanej przez nas klasy MaterialsPanel, widoku View3D i każdej z klas materiałów, które zastosowano w przykładzie. Poza samymi materiałami potrzebowaliśmy również trójwymiarowych obiektów. W tym celu skorzystaliśmy z klas: Sphere, Cube, Plane oraz SeaTurtle. W zależności od wybranego materiału na ekranie pojawi się jeden z tych obiektów. Poza standardowymi obiektami i materiałami zdefiniowaliśmy również currentMaterial i currentObject, którym nie przypisaliśmy konkretnego typu, co z kolei pozwoliło na wielokrotne wykorzystanie tych zmiennych w procesie zmiany materiału i obiektu. W konstruktorze klasy MaterialsExample zapisaliśmy instrukcję warunkową if, której celem jest sprawdzenie, czy obiekt stage został utworzony. Jeżeli wynikiem tego warunku jest true, to wywołana zostanie metoda init(), w przeciwnym razie dla obiektu stage utworzony zostanie obiekt nasłuchujący zdarzenia Event.ADDED_TO_STAGE, który uruchomi metodę init() z chwilą utworzenia obiektu klasy Stage w konstruktorze aplikacji.
Rozdział 4. Materiały
161
Wewnątrz metody init() w pierwszych linijkach nadaliśmy odpowiednie ustawienia jakości obrazu, wyrównania oraz skalowania obiektu stage. Dzięki tym ustawieniom obiekty będą wyświetlały się w odpowiedniej skali oraz pozycji. Poza tym usunęliśmy zbędny już obiekt nasłuchujący zdarzenia Event.ADDED_TO_ STAGE i dodaliśmy nowe do odświeżania zawartości listy wyświetlanych obiektów oraz zmiany rozmiaru okna aplikacji. W dalszej części metody init() stworzyliśmy i dodaliśmy do listy wyświetlanych obiektów widok View3D. W kolejnych linijkach kodu wywołaliśmy metody: initMaterials(), initObjects(), addPanel() oraz onResize(). W metodzie addPanel() stworzyliśmy obiekt klasy MaterialsPanel, dodaliśmy go do listy wyświetlanych obiektów i utworzyliśmy szereg obiektów nasłuchujących zdarzeń przypisanych temu panelowi. Dodane detektory wywołują dwie metody. W przypadku przycisków tą metodą jest onPanelEvent(), z kolei przy zmianie opcji w obiekcie typu ComboBox następuje wywołanie metody onMaterialChangeEventHandler(). panel = new MaterialsPanel(); addChild(panel); panel.addEventListener('togglePauseEvent', onPanelEvent); panel.addEventListener('lessSegmentsEvent', onPanelEvent); panel.addEventListener('moreSegmentsEvent', onPanelEvent); panel.addEventListener('lessExponentEvent', onPanelEvent); panel.addEventListener('moreExponentEvent', onPanelEvent); panel.addEventListener('WireframeMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('WireColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('ColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('EnviroColorMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('BitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('BitmapFileMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('EnviroBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('GlassMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('TransformBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('DepthBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('MovieMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('AnimatedBitmapMaterialEvent', onMaterialChangeEventHandler); panel.addEventListener('VideoMaterialEvent', onMaterialChangeEventHandler);
162
Flash i ActionScript. Aplikacje 3D od podstaw
W metodzie onPanelEvent() zapisaliśmy instrukcję warunkową switch(), w której sprawdzany jest typ przechwyconego zdarzenia. Bloki kodu poszczególnych warunków omówione są w dalszych częściach tego rozdziału. Wewnątrz metody initMaterials() zapisaliśmy tworzenie obiektów zdefiniowanych jako materiały. O każdym z nich wspomnimy podczas omawiania konkretnego materiału w kolejnych podrozdziałach. Na razie istotne jest to, że do obiektu currentMaterial przypisaliśmy obiekt wireframe, tym samym ustalając go jako domyślny rodzaj materiału. W metodzie initObjects() również zapisaliśmy tworzenie obiektów, tylko że tym razem są to bryły, na które nałożony zostanie wybrany materiał. W tej metodzie każdemu z obiektów przypisaliśmy jako materiał obiekt currentMaterial. W pierwszej kolejności stworzyliśmy obiekt kuli, przypisując jego właściwościom segmentsW i segmentsH wartość 16. sphere = new Sphere( { material:currentMaterial, segmentsW:16, segmentsH:16 } );
W drugiej kolejności stworzyliśmy obiekt sześcianu z liczbą segmentów równą 8 na każdym z jego boków, a metodą scale() zwiększyliśmy jego wymiary dwukrotnie. cube = new Cube( { material:currentMaterial, segmentsW:8, segmentsH:8, segmentsD:8 } ); cube.scale(2);
Trzecim obiektem, który stworzyliśmy, jest plansza plane o szerokości i wysokości równej 400 pikselom, wyświetlana z obu stron i ustawiona w pozycji pionowej. plane = new Plane( { material:currentMaterial, yUp:false, bothsides:true, width:400, height:400 } );
Aby efekty działania niektórych materiałów były lepiej widoczne, potrzebny jest obiekt o nieregularnych kształtach. Dlatego właśnie jako ostatni dodaliśmy obiekt klasy SeaTurtle. W swoich pierwotnych wymiarach byłby on stanowczo za duży, dlatego stosując metodę scale(), zmniejszyliśmy go o połowę. Dodatkowo obróciliśmy model żółwia o kąt 45 stopni względem osi X. seaturtle = new SeaTurtle(); seaturtle.material = currentMaterial; seaturtle.scale(.5); seaturtle.rotationX = -45;
Na końcu metody initObjects() ustawiliśmy obiekt kuli jako domyślną bryłę currentObject i dodaliśmy ją do sceny widoku, stosując metodę addChild().
Rozdział 4. Materiały
163
W przypadku metody onMaterialChangeEventHandler()na tę chwilę istotne jest tylko to, że odpowiada ona za przypisanie odpowiedniego materiału do bryły. W pierwszej kolejności w tym kodzie chowamy dodatkowe przyciski w panelu użytkownika. panel.setVisibility('togglePause', false); panel.setVisibility('lessSegments', false); panel.setVisibility('moreSegments', false); panel.setVisibility('lessExponent', false); panel.setVisibility('moreExponent', false);
Następnie sprawdzamy, czy aktualnie oglądany materiał nie jest typu wideo. Jeżeli tak, to czyścimy jego źródło. Potem usuwamy ze sceny aktualnie wyświetlaną bryłę, przy okazji sprawdzając przezornie, czy obiekt faktycznie istnieje. if (currentObject != null) { view.scene.removeChild(currentObject); currentObject = null; currentMaterial = null; }
Po usunięciu obiektu currentObject ze sceny zapisaliśmy instrukcję switch, w której wykonywany jest kod w zależności od wybranego materiału. Poszczególne fragmenty tego kodu zostaną później omówione. Na końcu metody onMaterialChangeEventHandler() przypisaliśmy obiektowi current Object nowy materiał i dodaliśmy go do sceny. currentObject.material = currentMaterial; view.scene.addChild(currentObject);
W bloku metody onResize() zapisaliśmy zmianę położenia widoku, tak aby był on zawsze na środku okna, z uwzględnieniem pozycji panelu użytkownika. Z kolei na obiekcie panel wywołaliśmy metodę draw(), nadając jej takie argumenty, aby nowo narysowane tło zajmowało całą szerokość okna i było wysokie na 50 pikseli. view.x = stage.stageWidth * .5; view.y = panel.height + (stage.stageHeight - panel.height) *.5; panel.draw(stage.stageWidth, 50);
W metodzie onEnterFrame() wprawiliśmy w ruch obrotowy obiekt currentObject oraz zapisaliśmy komendę odświeżającą zawartość sceny widoku. Poza tym użyliśmy instrukcji warunkowej, która sprawdza, czy aktualny materiał jest obiektem klasy TransformBitmapMaterial. Jeżeli warunek jest prawdziwy, to dodatkowo na obiekcie materiału zwiększana jest wartość właściwości offsetX. Efekt zmiany tej właściwości omówiony zostanie w podrozdziale poświęconym klasie Transform BitmapMaterial.
164
Flash i ActionScript. Aplikacje 3D od podstaw currentObject.rotationY--; if (currentMaterial == transformBitmap) transformBitmap.offsetX += 10; view.render();
Na chwilę obecną omówiliśmy wszystkie potrzebne metody zapisane w tej klasie. Pozostałymi zajmiemy się w odpowiednim czasie w dalszych częściach tego rozdziału.
Linie WireframeMaterial Materiał WireframeMaterial przedstawia trójwymiarowy obiekt w formie siatki, generowanej poprzez zaznaczenie krawędzi bryły i połączenie jej punktów liniami. Jest to najbardziej podstawowa forma prezentacji obiektu 3D w przestrzeni, co nie oznacza jednak, że jest najbardziej wydajna. Podobnie jak w zwykłych aplikacjach pisanych w ActionScript 3.0, rysowanie dużej liczby linii powoduje zwiększenie zużywanych zasobów. Formę siatki definiują trzy właściwości: wireColor określający kolor linii, wireAlpha ustalający poziom przezroczystości i thickness, który określa grubość linii. Tego typu materiał najlepiej stosować wtedy, kiedy testujemy działanie naszej aplikacji lub ustawiamy dany obiekt w przestrzeni. Efekt zastosowania tego materiału przedstawiono na rysunku 4.6. Rysunek 4.6.
Zastosowanie materiału WireframeMaterial
Rozdział 4. Materiały
165
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie wireframe, ustawiając kolor linii jako biały. wireframe = new WireframeMaterial(0x000000);
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału WireframeMaterial obiekt wireframe przypisujemy do currentMaterial, a sphere do currentObject. case 'WireframeMaterial': currentMaterial = wireframe; currentObject = sphere; break;
W tabelach 4.2 oraz 4.3 wypisane zostały właściwości oraz metody klasy Wireframe Material. Tabela 4.2. Właściwości klasy WireframeMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
wireAlpha
Number
1
Poziom przezroczystości krawędzi ustalany w przedziale od 0 do 1
wireColor
uint
null
Kolor krawędzi ustalany w kodzie szesnastkowym
thickness
Number
1
Grubość krawędzi bryły
Tabela 4.3. Metody klasy WireframeMaterial
Nazwa
Opis
WireframeMaterial(wireColor:* = null, init:Object = null)
Konstruktor
WireColorMaterial WireColorMaterial jest urozmaiconą wersją poprzednio poznanego Wireframe Material, ponieważ obiekt tej klasy umożliwia wypełnienie bryły wybranym kolorem i jednocześnie podkreślenie jej krawędzi. Materiał WireColorMaterial jest
standardowym sposobem pokrywania obiektów w bibliotece Away3D, jeżeli nie zdefiniowano innego. W takiej sytuacji krawędzie bryły wyświetlane są w czarnym kolorze, a jej ściany w wybranym losowo. Za określenie koloru wypełnienia odpowiada właściwość color, a pozostałe — wireColor, wireAlpha oraz thickness — mają to samo zastosowanie jak w przypadku WireframeMaterial. Przykład zastosowania tego materiału przedstawiono na rysunku 4.7.
166
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 4.7.
Zastosowanie materiału WireColorMaterial
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie wirecolor, ustawiając kolor linii jako biały, oraz wypełniliśmy ściany kolorem czerwonym. wirecolor = new WireColorMaterial(0xFF0000, { wireColor:0x000000 } );
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału WireColorMaterial obiekt wirecolor przypisujemy do currentMaterial, a cube do currentObject. case 'WireColorMaterial': currentMaterial = wirecolor; currentObject = cube; break;
W tabelach 4.4 oraz 4.5 wypisane zostały właściwości oraz metody klasy WireColorMaterial. Tabela 4.4. Właściwości klasy WireColorMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
color
uint
alpha
Number
1
Poziom widzialności ścian obiektu
wireAlpha
Number
1
Poziom widzialności krawędzi ustalany w przedziale od 0 do 1
wireColor
uint
null
Kolor krawędzi ustalany w kodzie szesnastkowym
thickness
Number
1
Grubość linii krawędzi bryły
Kolor ścian ustalany w kodzie szesnastkowym
Rozdział 4. Materiały
167
Tabela 4.5. Metody klasy WireColorMaterial
Nazwa
Opis
WireColorMaterial(color:* = null, init:Object = null)
Konstruktor
Kolor ColorMaterial ColorMaterial,
jak nazwa wskazuje, wypełnia ściany obiektu jednolitym kolorem. W porównaniu z poprzednim typem WireColorMaterial ten nie zaznacza w jakikolwiek sposób krawędzi bryły. Co za tym idzie — nie jesteśmy w stanie dostrzec przestrzenności obiektu. Gdy stworzymy obiekt kuli i nadamy mu jednolity kolor, otrzymamy w rzeczywistości okrąg, który równie dobrze mógłby zostać stworzony w zwykły sposób, za pomocą podstawowych narzędzi Flash.
Może się jednak zdarzyć, że będziesz potrzebował takiego rodzaju wypełnienia. Dlatego w tym przykładzie wykorzystamy materiał ColorMaterial i nałożymy go na model. Efekt, który uzyskamy po skompilowaniu kodu, przedstawia poniższa ilustracja. Na rysunku 4.8 przedstawiono efekt zastosowania materiału ColorMaterial. Rysunek 4.8.
Zastosowanie materiału ColorMaterial
168
Flash i ActionScript. Aplikacje 3D od podstaw
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie color, wypełniając wszystkie ściany jednolitym kolorem czerwonym. color = new ColorMaterial(0xFF0000);
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału ColorMaterial obiekt color przypisujemy do currentMaterial, a sphere do currentObject. case 'ColorMaterial': currentMaterial = color; currentObject = sphere; break;
W tabelach 4.6 oraz 4.7 wypisane zostały właściwości oraz metody klasy Color Material. Tabela 4.6. Właściwości klasy ColorMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
color
uint
null
Kolor wypełnienia ustalany w kodzie szesnastkowym
Tabela 4.7. Metody klasy ColorMaterial
Nazwa
Opis
ColorMaterial(color:* = null, init:Object = null)
Konstruktor
EnviroColorMaterial EnviroColorMaterial jest materiałem, którego działanie symuluje odbicie otoczenia na powierzchni wybranego obiektu. W bibliotece Away3D jest jeszcze kilka innych o podobnych właściwościach i zastosowaniach. Jako pierwszy z nich poznamy właśnie EnviroColorMaterial. Generowanie refleksów w czasie rzeczywistym wymaga wielu procesów obliczeniowych i zużywa dużo zasobów komputera, dlatego częściej symuluje się taki efekt, stosując specjalnie przygotowaną bitmapę, która odzwierciedla otoczenie obiektu. W przypadku EnviroColorMaterial przy stosowaniu bitmapy otoczenia należy również pamiętać o ustaleniu jej poziomu przejrzystości, tak aby materiał bazowy nie był zakryty całkowicie. W przypadku EnviroColorMaterial właściwym pokryciem powierzchni jest jednolity kolor, który podaje się jako pierwszy atrybut color w konstruktorze klasy. Drugim atrybutem jest enviroMap, w którym należy podać obiekt klasy BitmapData jako źródło otoczenia. Siłę pseudoodbicia określa właściwość reflectiveness, którego standardowa wartość równa jest 0.5. Efekt zastosowania materiału EnviroColorMaterial na modelu żółwia przedstawiono na rysunku 4.9.
Rozdział 4. Materiały
169
Rysunek 4.9.
Zastosowanie materiału EnviroColorMaterial
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie enviroColor, wypełniając wszystkie ściany jednolitym kolorem czerwonym. enviroColor = new EnviroColorMaterial(0xFFFFFF, waterMap.bitmapData);
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału EnviroColorMaterial obiekt enviroColor przypisujemy do currentMaterial, a seaturtle do currentObject. case 'EnviroColorMaterial': currentMaterial = enviroColor; currentObject = seaturtle; break;
W tabelach 4.8 oraz 4.9 wypisane zostały właściwości oraz metody klasy Enviro ColorMaterial. Tabela 4.8. Właściwości klasy EnviroColorMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
color
uint
null
Kolor wypełnienia ustalany w kodzie szesnastkowym
reflectiveness
Number
0.5
Ustala intensywność wyświetlania odbicia otoczenia
170
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 4.9. Metody klasy EnviroColorMaterial
Nazwa
Opis
EnviroColorMaterial(color:*, enviroMap:BitmapData, init:Object = null)
Konstruktor
Bitmapy BitmapMaterial BitmapMaterial jest zdecydowanie bardziej atrakcyjnym materiałem do pokrywania powierzchni obiektów niż omówione do tej pory. Za pomocą BitmapMaterial możemy zapewnić wybranemu obiektowi teksturę. Wykorzystanie takiego rodzaju pokrycia nadaje odpowiedni charakter obiektowi, na który jest ono nałożone, i sprawia o wiele więcej frajdy podczas jego oglądania. BitmapMaterial korzysta z obiektów klasy BitmapData wcześniej pobranych lub dodanych do zasobów tekstur. Korzystanie z tego rodzaju materiału jest dobre, gdy w projekcie dany obiekt będzie korzystał z niezmiennej tekstury, zawartej w zasobach, które są ładowane automatycznie wraz z uruchomieniem aplikacji.
Stosowanie tego materiału jest bardzo proste, ponieważ w konstruktorze Bitmap Material wystarczy podać odwołanie do obiektu BitmapData, który ma pokryć powierzchnię wybranego obiektu. Uzyskany efekt przedstawiono na rysunku 4.10. Aby pokryć model żółwia odpowiednią teksturą, skorzystaliśmy z obiektu klasy BitmapMaterial. W konstruktorze klasy tego materiału odnieśliśmy się do obiektu typu BitmapData z obiektu seaturtleBitmap. bitmap = new BitmapMaterial(seaturtleBitmap.bitmapData);
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału BitmapMaterial obiekt bitmap przypisujemy do currentMaterial, a seaturtle do currentObject. case 'BitmapMaterial': currentMaterial = bitmap; currentObject = seaturtle; break;
Rozdział 4. Materiały
171
Rysunek 4.10.
Zastosowanie materiału BitmapMaterial
W tabelach 4.10 oraz 4.11 wypisane zostały właściwości oraz metody klasy Bitmap Material. Tabela 4.10. Właściwości klasy BitmapMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
bitmap
BitmapData
null
Obiekt BitmapData stosowany jako źródło wyświetlanej tekstury
alpha
Number
1
Poziom przezroczystości wyświetlanej bitmapy
blendMode
String
„normal”
Określa sposób wyświetlania tekstury
colorTransform ColorTransform
null
Obiekt klasy ColorTransform wyświetlanej bitmapy
height
Number
NaN
Zwraca wysokość tekstury
width
Number
NaN
Zwraca szerokość tekstury
repeat
Boolean
false
Określa, czy tekstura ma być powielana na całej powierzchni obiektu
showNormals
Boolean
false
Przedstawia wektor normalny na każdym z wielokątów obiektu
smooth
Boolean
false
Określa, czy tekstura ma być wygładzona na powierzchni obiektu
172
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 4.11. Metody klasy BitmapMaterial
Nazwa
Opis
BitmapMaterial(bitmap:BitmapData, init:Object = null)
Konstruktor
getPixel32(u:Number, v:Number):uint
Zwraca kolor piksela w podanych współrzędnych uv
BitmapFileMaterial BitmapFileMaterial, w odróżnieniu od wcześniej prezentowanego BitmapMaterial, czerpie zasoby z zewnętrznych plików graficznych, co sprawia, że aplikacja staje się bardziej dynamiczna i łatwa w modyfikowaniu. Największą zaletą korzystania z tego materiału jest możliwość zmiany tekstury w każdej chwili bez konieczności ingerowania w zasoby plików projektu oraz bez konieczności kolejnej kompilacji. Używając BitmapFileMaterial, należy pamiętać jednak o etapie ładowania tekstury. W zależności od rozmiaru pliku graficznego i prędkości łącza proces ładowania jest zróżnicowany. Nie zawsze możemy się spodziewać natychmiastowego pojawienia się lub zmiany tekstury, dlatego powinno się stosować odpowiednie zdarzenia procesu ładowania, ewentualnego błędu spowodowanego brakiem pliku lub zerwaniem połączenia i w końcu prawidłowego pobrania pliku. Mając kontrolę nad tymi czynnościami, można stworzyć stabilną aplikację z uniwersalnymi obiektami. Innym rozwiązaniem jest również wcześniejsze załadowanie potrzebnych tekstur z plików XML i stosowanie zewnętrznych bibliotek ładowania zasobów.
W najbardziej podstawowej formie korzystanie z BitmapFileMaterial ogranicza się do podania ścieżki do tekstury podczas tworzenia obiektu materiału. Tym właśnie sposobem obiekt kuli pokryliśmy bitmapą, czego efekt przedstawiono na rysunku 4.11. Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej stworzyliśmy obiekt o nazwie bitmapFile, ustawiając w jego konstruktorze odwołanie do pliku graficznego umieszczonego w konkretnej lokalizacji. bitmapFile = new BitmapFileMaterial('../../resources/bitmaps/road.jpg');
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału BitmapFileMaterial obiekt bitmapFile przypisujemy do currentMaterial, a plane do currentObject. case 'BitmapFileMaterial': currentMaterial = bitmapFile; currentObject = plane; break;
Rozdział 4. Materiały
173
Rysunek 4.11.
Zastosowanie materiału BitmapFileMaterial
W tabelach 4.12 oraz 4.13 wypisane zostały właściwości oraz metody klasy Bitmap FileMaterial. Tabela 4.12. Właściwości klasy BitmapFileMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
loader
Loader
null
Obiekt klasy Loader przechowujący pobraną zawartość
Tabela 4.13. Metody klasy BitmapFileMaterial
Nazwa
Opis
BitmapFileMaterial(url:String, init:Object = null)
Konstruktor
addOnLoadError(listener:Function):void
Dodawanie rejestratora zdarzenia wystąpienia błędu
addOnLoadProgress(listener:Function):void
Dodawanie rejestratora zdarzenia postępu ładowania bitmapy
addOnLoadSuccess(listener:Function):void
Dodawanie rejestratora zdarzenia poprawnego załadowania bitmapy
removeOnLoadError(listener:Function):void
Usunięcie rejestratora zdarzenia wystąpienia błędu
removeOnLoadProgress(listener:Function):void
Usunięcie rejestratora zdarzenia postępu ładowania bitmapy
removeOnLoadSuccess(listener:Function):void
Usunięcie rejestratora zdarzenia poprawnego załadowania bitmapy
174
Flash i ActionScript. Aplikacje 3D od podstaw
EnviroBitmapMaterial EnviroBitmapMaterial, podobnie jak wcześniej poznany materiał EnviroColorMaterial, służy do tworzenia iluzji odbijania na powierzchni obiektu jego otoczenia. Różnica w tym przypadku polega na tym, że macierzystym materiałem jest tekstura, a nie jednolity kolor.
Do wykorzystania EnviroBitmapMaterial musimy podać dwa obiekty typu BitmapData w konstruktorze klasy. Pierwszy z nich określa teksturę, jaką pokryty będzie obiekt, a drugi określa bitmapę symulującą otoczenie lub specjalne efekty nałożone na obiekt. Dodatkowo można również ustalić intensywność odbicia, stosując właściwość reflectiveness. Podobnie jak w przypadku materiału EnviroColorMaterial, tutaj również dla przykładu zastosowaliśmy obiekt waterMap, który przedstawia taflę wody pokrywającą teksturę żółwia, tak jak to pokazano na rysunku 4.12. Rysunek 4.12.
Zastosowanie materiału EnviroBitmapMaterial
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej stworzyliśmy obiekt o nazwie enviroBitmap, ustawiając w jego konstruktorze w pierwszym argumencie obiekt BitmapData tekstury pokrywającej żółwia, a w drugim obiekt BitmapData, który ma się odbijać na powierzchni modelu. enviroBitmap = new EnviroBitmapMaterial(seaturtleBitmap.bitmapData, waterMap.bitmapData);
Rozdział 4. Materiały
175
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału EnviroBitmapMaterial obiekt enviroBitmap przypisujemy do currentMaterial, a seaturtle do currentObject. case 'EnviroBitmapMaterial': currentMaterial = enviroBitmap; currentObject = seaturtle; break;
W tabelach 4.14 oraz 4.15 wypisane zostały właściwości oraz metody klasy EnviroBitmapMaterial. Tabela 4.14. Właściwości klasy EnviroBitmapMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
enviroMap
BitmapData
null
Zwraca obiekt użyty jako tekstura odbijanego otoczenia
textureMaterial
BitmapMaterial
null
Zwraca obiekt użyty jako tekstura bazowa bryły
reflectiveness
Number
0.5
Ustala intensywność wyświetlania odbicia otoczenia
Tabela 4.15. Metody klasy EnviroBitmapMaterial
Nazwa
Opis
EnviroBitmapMaterial(bitmap:BitmapData, enviroMap:BitmapData, init:Object = null)
Konstruktor
GlassMaterial W bibliotece Away3D dostępne są rodzaje materiałów, które po zastosowaniu odpowiedniego światła umożliwiają symulowanie wypukłości na powierzchni obiektu bez ingerowania w strukturę jego siatki. W technice tej stosuje się specjalnie spreparowane tekstury, które zawierają informacje o ułożeniu wektorów normalnych. Pojęciem tym zajmiemy się dokładniej w rozdziale 5. „Światło”. W tej chwili istotne jest to, że mapa normalnych jest niezbędna również do prawidłowego działania materiału GlassMaterial (materiał ten przedstawiono na rysunku 4.13). GlassMaterial jest jednym z bardziej złożonych materiałów dostępnych w bibliote-
ce Away3D. Jego wyjątkowość polega na tym, że bez stosowania jakichkolwiek źródeł światła symuluje zniekształcenia powierzchni i refleksy. Złożoność refleksów zależy od kąta nachylenia kamery względem obiektu pokrytego materiałem GlassMaterial. Klasa ta ma również właściwość exponent, której wyższe wartości zwiększają intensywność efektu odbijania otoczenia obiektu.
176
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 4.13.
Zastosowanie materiału GlassMaterial
Obiekt klasy GlassMaterial najczęściej stosuje się do symulowania pofalowanej tafli wody, na której część światła jest odbijana, a część załamywana. Takie zjawisko nazywane jest efektem Fresnela i bazuje na równaniach Fresnela, o których więcej informacji uzyskasz na stronie: http://en.wikipedia.org/wiki/Fresnel_equations. Aby stworzyć obiekt klasy GlassMaterial, należy w jej konstruktorze podać w pierwszej kolejności jako argument normalMap obiekt w postaci BitmapData, który będzie odpowiadał za zniekształcenia powierzchni. W drugim argumencie envMap należy odwołać się do bitmapy otoczenia, która ma być teoretycznie odbijana. Źródło powierzchni, czyli obiekt, na którym GlassMaterial ma być wykorzystany, również należy podać w konstruktorze jako trzeci argument targetModel. Na rysunku 4.14 przedstawiono efekt zastosowania klasy GlassMaterial. Ponieważ w tym przypadku wcześniej należy zdefiniować obiekt, na którym będzie zastosowany materiał GlassMaterial, wszystkie operacje wykonujemy bezpośrednio wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler(). W przypadku wybrania materiału GlassMaterial w pierwszej kolejności stworzyliśmy nowe obiekty klasy Bitmap, obiektowi currentObject przypisaliśmy płaszczyznę plane, a dopiero później stworzyliśmy obiekt klasy GlassMaterial. W konstruktorze jako pierwszy argument podaliśmy obiekt BitmapData dla mapy normalnych, jako drugi teksturę pokrywającą powierzchnię, a na końcu obiekt, na którym materiał będzie zastosowany.
Rozdział 4. Materiały
177
Rysunek 4.14.
Zastosowanie materiału GlassMaterial
case 'GlassMaterial': var normalMap:Bitmap = new NormalMap(); var waterMap:Bitmap = new WaterMap(); currentObject = plane; currentMaterial = new GlassMaterial(normalMap.bitmapData, waterMap.bitmapData, currentObject, false, { smooth:true, exponent:0 } ); panel.setVisibility('lessExponent', true); panel.setVisibility('moreExponent', true); break;
Pojawiające się po wybraniu materiału GlassMaterial przyciski + i - służą do kontrolowania intensywności refleksów wyświetlanych na powierzchni obiektu. W metodzie onPanelEvent() uwzględniliśmy warunki wciśnięcia tych przycisków. W sytuacji wystąpienia zdarzenia lessExponentEvent wartość właściwości exponent jest zmniejszana o 10, jeżeli aktualna wartość jest większa lub równa 10. if (currentMaterial.exponent >= 10) currentMaterial.exponent -= 10;
Z kolei zdarzenie moreExponentEvent powoduje zwiększenie wartości właściwości exponent o 10. currentMaterial.exponent += 10;
W tabelach 4.16 oraz 4.17 wypisane zostały właściwości oraz metody klasy Glass Material.
178
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 4.16. Właściwości klasy GlassMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
dispersionR
Number
1
Określa skalę rozproszenia dla kanału czerwonego
dispersionG
Number
0.95
Określa skalę rozproszenia dla kanału zielonego
dispersionB
Number
0.9
Określa skalę rozproszenia dla niebieskiego
envMapAlpha
Number
1
Określa poziom przezroczystości odbijanej tekstury
exponent
Number
5
Określa złożoność wyświetlanych refleksów
glassColor
uint
16777215
Określa kolor odbicia na szkle
innerRefraction
Number
1.33
Współczynnik załamania światła wewnątrz powierzchni materiału
outerRefraction
Number
1.0008
Współczynnik załamania światła na zewnętrznej warstwie materiału, na którą padają promienie
Tabela 4.17. Metody klasy GlassMaterial
Nazwa
Opis
GlassMaterial(normalMap:BitmapData, envMap:BitmapData, targetModel:Mesh, chromaticDispersion:Boolean = false, init:Object = null)
Konstruktor
TransformBitmapMaterial TransformBitmapMaterial służy przede wszystkim do wypełniania dużych powierzchni
powtarzającym się fragmentem tekstury. Dzięki temu nie musimy używać jednego pliku graficznego w wysokiej rozdzielczości, aby pole pokryć trawą lub pustynię piaskiem. Po ustawieniu właściwości repeat na true tekstura będzie się powtarzała, aż wypełni po brzegi cały obiekt. Wielkość tekstury, a zarazem liczbę jej powtórzeń na powierzchni obiektu można regulować właściwościami scaleX oraz scaleY. Wartość 1 dla obu właściwości określa oryginalny rozmiar wgranej tekstury, ustawienie mniejszych wartości spowoduje redukcję jej rozmiarów i zwiększenie liczby jej powtórzeń. Efekt manipulacji rozmiarem tekstury pokazano na rysunku 4.15.
Rozdział 4. Materiały
179
Rysunek 4.15. Zastosowanie materiału TransformBitmapMaterial
Poza samym powielaniem tekstury na wybranej powierzchni istnieje również możliwość jej przesunięcia lub rozciągnięcia w wybranym kierunku. Do tego celu służą właściwości offsetX, offsetY oraz projectionVector. Stosując je w powtarzających się metodach lub pętlach, można uzyskać efekt niekończącej się tekstury, co może być przydatne w wielu projektach. Aby móc sprawdzić działanie materiału TransformBitmapMaterial, musieliśmy w klasie bazowej wewnątrz metody initMaterials() zapisać obiekt o nazwie transform Bitmap, podając w jego konstruktorze jako pierwszą właściwość obiekt BitmapData, który pokryje powierzchnię płaszczyzny. Aby tekstura była powtarzalna, musieliśmy zapisać wartość true dla właściwości repeat. Dodatkowo właściwościom scaleX i scaleY przypisaliśmy wartość 1, co oznacza, że domyślnie tekstura będzie miała swoje standardowe rozmiary. transformBitmap = new TransformBitmapMaterial(board.bitmapData, { repeat:true, scaleX:1, scaleY:1 } );
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału TransformBitmapMaterial obiekt transformBitmap przypisujemy do currentMaterial, a plane do currentObject. case 'TransformBitmapMaterial': panel.setVisibility('lessSegments', true); panel.setVisibility('moreSegments', true); currentMaterial = transformBitmap; currentObject = plane; break;
Do kontrolowania rozmiarów tekstury służą przyciski - oraz + umieszczone w panelu użytkownika, pojawiające się wraz z wybraniem tego materiału. Kliknięcie przycisku + powoduje zajście warunku moreSegmentsEvent, zapisanego w instrukcji switch() wewnątrz metody onPanelEvent(). W kodzie określonym dla tego zdarzenia zapisaliśmy zmniejszenie skali tekstury o połowę swojego aktualnego rozmiaru.
180
Flash i ActionScript. Aplikacje 3D od podstaw currentObject.material.scaleX *= .5; currentObject.material.scaleY *= .5;
Z kolei wciśnięcie przycisku - powoduje wywołanie zdarzenia lessSegmentsEvent, którego celem jest zwiększenie rozmiarów tekstury o połowę swojego aktualnego rozmiaru. currentObject.material.scaleX /= .5; currentObject.material.scaleY /= .5;
Jak zapewne pamiętasz, w metodzie onEnterFrame() zapisaliśmy instrukcję warunkową if, która w konkretnym przypadku wykonywała jakieś zadanie. Otóż w sytuacji wybrania materiału TransformBitmapMaterial zapisaliśmy ciągłą zmianę przesunięcia tekstury na osi X, co wywoła efekt niekończącej się tekstury. Takie działanie można zastosować na przykład do symulacji fal na tafli wody lub niekończącej się drogi w symulatorach jazdy. W naszym przykładzie kafelkowa tekstura przesuwa się na osi X wewnątrz płaszczyzny, a zaprogramowaliśmy to, stosując taki oto zapis w metodzie onEnterFrame(): if (currentMaterial == transformBitmap) transformBitmap.offsetX += 10;
W tabelach 4.18 oraz 4.19 wypisane zostały właściwości oraz metody klasy TransformBitmapMaterial. Tabela 4.18. Właściwości klasy TransformBitmapMaterial
Nazwa
Rodzaj
Wartość Opis domyślna
globalProjection
Boolean
false
Określa, czy wartości właściwości offsetX, offsetY oraz projectionVector odnoszą się do układu współrzędnych sceny
offsetX
Number
0
Określa poziom przesunięcia tekstur względem osi X wewnątrz obiektu
offsetY
Number
0
Określa poziom przesunięcia tekstur względem osi Y wewnątrz obiektu
projectionVector
Vector3D
null
Wektor określający rozciągnięcie/przesunięcie tekstury, ignorujący współrzędne uv obiektu
rotation
Number
0
Obraca teksturę w przestrzeni uv
scaleX
Number
1
Skaluje według osi X w przestrzeni uv
scaleY
Number
1
Skaluje według osi Y w przestrzeni uv
transform
Matrix
Odwołanie do macierzy transformacji tekstury
Tabela 4.19. Metody klasy TransformBitmapMaterial
Nazwa
Opis
TransformBitmapMaterial(bitmap:BitmapData, init:Object = null)
Konstruktor
Rozdział 4. Materiały
181
Animowane MovieMaterial Główną cechą materiału MovieMaterial jest możliwość załadowania elementu typu Sprite lub MovieClip jako źródła pokrycia powierzchni wybranego obiektu. Pozwala to na zastosowanie animacji złożonej z określonej liczby klatek lub umieszczenie elementów, na których użytkownik może wywołać odpowiednie zdarzenia, na przykład poprzez kliknięcie myszką lub najechanie kursorem. Biblioteka Away3D ma w swoich zasobach jeszcze inne klasy materiałów, które umożliwiają wyświetlanie animacji, dlatego MovieMaterial powinien być głównie stosowany do tworzenia interaktywnych formularzy na powierzchni obiektów trójwymiarowych. Korzystanie z MovieMaterial polega na podaniu w konstruktorze obiektu klasy który ma posłużyć jako źródło do wyświetlanego obrazu. Poza tym jeżeli elementy znajdujące się w tym obiekcie korzystają ze zdarzeń klasy MouseEvent lub Event, należy w obiekcie materiału MovieMaterial przypisać właściwości interactive wartość true. Sprite,
Niestety, mimo że MovieMaterial pozwala na stworzenie ciekawych efektów, ma również wadę — zawartość obiektu Sprite po umieszczeniu na powierzchni bryły ulega rasteryzacji, co oznacza, że wszystkie elementy tracą na jakości obrazu. Oczywiście jest możliwość zastosowania właściwości smooth i przypisania mu wartości true, co wygładzi nieco wyświetlany materiał, jednak nie uzyskamy satysfakcjonującego efektu przy stosowaniu elementów wektorowych. Efekt zastosowania tego materiału przedstawiono na rysunku 4.16. Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie movie, w którego konstruktorze stworzyliśmy obiekt klasy movieMC. Obiekt tej kasy reprezentuje element MovieClip wyświetlony na powierzchni obiektu plane. Aby móc wpisywać teksty w pola tekstowe, wciskać przyciski i poruszać czerwonym kółkiem, musieliśmy przypisać właściwości interactive wartość true. Dzięki temu wszystkie elementy wewnątrz wyświetlanego źródła będą reagowały na zdarzenia MouseEvent. movie = new MovieMaterial(new movieMC(), { interactive:true } );
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału MovieMaterial obiekt movie przypisujemy do currentMaterial, a plane do currentObject.
182
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 4.16.
Zastosowanie materiału MovieMaterial
case 'MovieMaterial': currentMaterial = movie; currentObject = plane; break;
W tabelach 4.20 oraz 4.21 wypisane zostały właściwości oraz metody klasy Movie Material. Tabela 4.20. Właściwości klasy MovieMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
autoUpdate
Boolean
true
Ustala, czy tekstura ma być automatycznie odświeżana
movie
Sprite
1
Ustala źródło elementu tekstury
clipRect
Rectangle
1
Ustala powierzchnię, w której tekstura zostanie wyświetlona
transparent
Boolean
true
Ustala, czy tekstura ma być przezroczysta
Tabela 4.21. Metody klasy MovieMaterial
Nazwa
Opis
MovieMaterial(movie:Sprite, init:Object = null)
Konstruktor
update():void
Aktualizuje teksturę aktualnie wyświetlaną klatką obiektu MovieClip
Rozdział 4. Materiały
183
AnimatedBitmapMaterial Patrząc na nazwę tego materiału, możesz pomyśleć, że korzysta on z zewnętrznych plików typu GIF, zawierających określoną liczbę klatek. Otóż tak nie jest. Implementując ten materiał, jako źródło podajemy element typu movieClip, którego wypełnione klatki stanowią animację. Dlaczego więc warto wspomnieć o tym materiale, skoro wcześniej poznaliśmy MovieMaterial? Ponieważ AnimatedBitmapMaterial jest jego uproszczoną wersją, która pozwala na wyświetlenie animowanych tekstur przy małym zużyciu zasobów komputera. Naturalnie w momentach tworzenia lub aktualizowania zawartości materiału występuje obciążenie, ale poza tym nie odczujemy spadku wydajności działania aplikacji. Istotne jest to, że mamy możliwość kontrolowania wyświetlania animacji na obiekcie. Za pomocą metody play() uruchamiamy odtwarzanie sekwencji, a za pomocą stop() zatrzymujemy ją. Dokładnie tak samo, jakbyśmy kontrolowali animację zapisaną w klatkach elementu MovieClip w zwykłych aplikacjach Flash. AnimatedBitmapMaterial należy stosować w sytuacjach, gdy sekwencja animacji jest krótka i nie zawiera elementów, które wymagałyby ingerencji użytkownika. Jednym z praktycznych zastosowań dla tego materiału jest wykorzystanie go do tworzenia efektów płonącej pochodni bądź ogniska. W naszym przypadku na powierzchni kuli zastosowaliśmy animację falowania wody. Uzyskany efekt przedstawia rysunek 4.17. Rysunek 4.17.
Zastosowanie materiału AnimatedBitmapMaterial
184
Flash i ActionScript. Aplikacje 3D od podstaw
Aby uzyskać taki efekt, w metodzie initMaterials() klasy bazowej zapisaliśmy obiekt o nazwie animatedBitmap, tworząc w jego konstruktorze obiekt Water. Obiekt tej klasy jest odwołaniem do obiektu MovieClip zawierającego zapisaną sekwencję animacji, zamieszczonego w zasobach pliku Flash. animatedBitmap = new AnimatedBitmapMaterial(new Water());
Z kolei wewnątrz instrukcji switch w metodzie onMaterialChangeEventHandler() w przypadku wybrania materiału AnimatedBitmapMaterial obiekt animatedBitmap przypisujemy do currentMaterial, a plane do currentObject. case 'AnimatedBitmapMaterial': currentMaterial = animatedBitmap; currentObject = plane; break;
W tabelach 4.22 oraz 4.23 wypisane zostały właściwości oraz metody klasy Anima tedBitmapMaterial. Tabela 4.22. Właściwości klasy AnimatedBitmapMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
autoplay
Boolean
true
Wskazuje, czy animacja ma być uruchomiona wraz z utworzeniem obiektu
index
int
0
Zwraca numer aktualnej klatki animacji
loop
Boolean
true
Wskazuje, czy dana animacja ma być powtarzana
sources
Array
Zwraca w postaci tablicy wszystkie klatki animacji
Tabela 4.23. Metody klasy AnimatedBitmapMaterial
Nazwa
Opis
AnimatedBitmapMaterial(movie:MovieClip, init:Object = null)
Konstruktor
play()
Uruchamia animację
stop()
Zatrzymuje animację
setFrames(sources:Array)
Zastępuje zapamiętane obiekty BitmapData podanymi w tablicy sources
setMovie(movie:MovieClip)
Ustawia nowy element MovieClip do wyświetlenia materiału
clear()
Czyści całą animację
Rozdział 4. Materiały
185
VideoMaterial Ostatni w grupie animowanych materiałów, które omówimy, jest VideoMaterial. Jak sama nazwa wskazuje, używając go na powierzchni obiektu, możemy umieścić sekwencję wideo ładowaną z zewnętrznego pliku. Formatem plików obsługiwanych przez VideoMaterial jest Flash Video o rozszerzeniu *.flv. Udostępniając klasę VideoMaterial, twórcy biblioteki Away3D oszczędzili nam sporo czasu na tworzenie specjalnego odtwarzacza plików flv w obiekcie typu MovieClip i zastosowanie go jako źródła dla materiału MovieMaterial. Stosując klasę VideoMaterial do uruchomienia sekwencji wideo, wystarczy podczas tworzenia materiału podać ścieżkę do pliku w właściwości file. Jeżeli nie użyjemy konkretnych metod, film zostanie automatycznie włączony. W klasie VideoMaterial tworzone są obiekty klas NetStream, NetConnection oraz Video. Dostęp do tych obiektów zapewniają właściwości netStream, nc oraz video. Dzięki temu można swobodnie operować na źródle filmu, tak jak przy pisaniu zwykłych odtwarzaczy do aplikacji dwuwymiarowych. Na rysunku 4.18 przedstawiono efekt zastosowania tego materiału na płaszczyźnie plane. Rysunek 4.18.
Zastosowanie materiału VideoMaterial
Podobnie jak w przypadku materiału GlassMaterial, tutaj również wszystkie operacje zapisaliśmy wewnątrz metody initMaterials() w klasie bazowej. W pierwszej kolejności zapisaliśmy instrukcję if(), która sprawdza, czy aktualnie używany materiał
186
Flash i ActionScript. Aplikacje 3D od podstaw
jest typu MovieMaterial. Jeżeli tak, to należy wyczyścić ślady po odtwarzaniu filmu, stosując obiekty nc, netStream i video. Aby uzyskać do nich dostęp, należało zinterpretować obiekt currentMaterial jako VideoMaterial. if (String(Object(currentMaterial).constructor) == '[class VideoMaterial]') { (currentMaterial as VideoMaterial).nc.close(); (currentMaterial as VideoMaterial).netStream.close(); (currentMaterial as VideoMaterial).video.clear(); }
W metodzie onMaterialChangeEventHandler(), stosując metodę setVisibility(), pokazaliśmy przycisk o nazwie togglePause, przypisując jego właściwości visible wartość true. Później przypisaliśmy zmiennej currentMaterial nowy obiekt klasy VideoMaterial, podając w właściwości file ścieżkę do wybranego pliku FLV. Na końcu obiektowi currentObject przypisaliśmy płaszczyznę plane. case 'VideoMaterial': panel.setVisibility('togglePause', true); currentMaterial = new VideoMaterial( { file:'../../resources/flvs/ test.flv' } ); currentObject = plane; break;
Do włączenia oraz wyłączenia filmu służy przycisk Włącz/Wyłącz, którego wciśnięcie powoduje wywołanie zdarzenia togglePauseEvent. Wewnątrz metody onPanelEvent() dodaliśmy w instrukcji switch warunek, w którym wywołaliśmy na obiekcie netStream metodę togglePause(). W zależności od stanu, w jakim jest film (czy jest włączony, czy też wstrzymany), metoda ta odpowiednio zatrzyma bądź wznowi jego odtwarzanie. (currentMaterial as VideoMaterial).netStream.togglePause();
W tabelach 4.24 oraz 4.25 wypisane zostały właściwości oraz metody klasy Video Material. Tabela 4.24. Właściwości klasy VideoMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
file
String
„”
Wskazuje na lokalizację pliku *.flv, który ma być odtwarzany
loop
Boolean
true
Wskazuje, czy sekwencja ma być powtarzana
nc
NetConnection
Wskazuje na obiekt klasy NetConnection
netStream
NetStream
Wskazuje na obiekt klasy NetStream
volume
Number
0
Określa poziom głośności
Rozdział 4. Materiały
187
Tabela 4.24. Właściwości klasy VideoMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
video
Video
Wskazuje na obiekt Video
time
Number
Zwraca aktualny czas NetStream
pan
Number
Wskazuje na wykorzystany kanał nadawania dźwięku
0
Tabela 4.25. Metody klasy VideoMaterial
Nazwa
Opis
VideoMaterial(init:Object = null)
Konstruktor
play
Włączenie obrazu
stop
Zatrzymanie obrazu
pause
Wstrzymanie obrazu
close
Przerwanie połączenia i wyświetlania obrazu
seek
Przejście do wybranego miejsca/czasu, podanego w sekundach, wyświetlanego obrazu
Mieszanie materiałów Na zakończenie tego rozdziału warto wspomnieć o możliwości mieszania różnych rodzajów materiałów. Możliwe jest to dzięki wykorzystaniu CompositeMaterial. Rodzaj ten przechowuje dodane materiały jako obiekty typu LayerMaterial w tablicy. Przypomina to nieco warstwy w programach służących do obróbki grafiki, takich jak Photoshop czy Corel. Korzystając z CompositeMaterial, możemy również ustawić poziom przezroczystości dodanych tekstur, co wpływa na to, jak będą one wyświetlane na obiekcie. Sposób ich ustawienia zależy tylko od tego, co chcesz osiągnąć. W każdej chwili oprócz dodania materiału można również go usunąć z tablicy bądź zamienić. Kontrolę nad tym sprawuje się poprzez metody addMaterial(), removeMaterial() i clearMaterials(). W tabelach 4.26 oraz 4.27 wypisane zostały właściwości oraz metody klasy Composite Material. Tabela 4.26. Właściwości klasy CompositeMaterial
Nazwa
Rodzaj
materials
Array
Wartość domyślna
Opis Tablica zawierająca materiały
188
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 4.27. Metody klasy CompositeMaterial
Nazwa
Opis
CompositeMaterial(init:Object = null)
Konstruktor
addMaterial
Dodawanie materiału
removeMaterial
Usuwanie materiału
clearMaterials
Czyszczenie tablicy materiałów
Podsumowanie Materiałem nazywamy obiekt pokrywający powierzchnię bryły. Materiały umieszczone są w pakiecie away3d.materials. Materiały dzieli się na korzystające ze źródła światła i te, które do poprawnego wyświetlania go nie potrzebują. Pliki graficzne oraz inne elementy, co do których jesteśmy pewni, że nie ulegną zmianie, można dodać do zasobów aplikacji bezpośrednio w pliku SWF. Aby odwoływać się do tych elementów, należy zdefiniować nazwę klasy, która będzie wskazywała na wybrany obiekt. Klasa Cast jest pomocniczą klasą umieszczoną w bibliotece Away3D, która odwołuje się do wskazanego w zasobach elementu. Najbardziej prymitywną formą pokrycia obiektu jest zastosowanie WireframeMaterial, który generuje siatkę bryły. Standardowym materiałem w Away3D jest WireColorMaterial, który pokrywa obiekt kolorem i podkreśla jego krawędzie. Materiał ColorMaterial pokrywa obiekt jednolitym kolorem, co powoduje spłaszczenie obiektu. Materiały EnviroColorMaterial, EnviroBitmapMaterial, GlassMaterial symulują odbijanie otoczenia na powierzchni obiektu. W rzeczywistości jako drugą warstwę materiału wyświetlają bitmapę, która ma odzwierciedlać otoczenie. Pozwala to uzyskać efekt refleksów bez konieczności wykonywania dużej liczby obliczeń do stworzenia realnego odbicia. Jest to fałszywe, ale zarazem optymalne rozwiązanie.
Rozdział 4. Materiały
189
Tekstura to obiekt bitmapy stosowany do pokrycia obiektu. W Away3D można teksturę wykorzystać między innymi przy materiałach BitmapMaterial, BitmapFileMaterial lub TransformBitmapMaterial. BitmapMaterial oraz BitmapFileMaterial różnią się tym, że ten drugi korzysta z zewnętrznego pliku graficznego. TransformBitmapMaterial pozwala na generowanie niekończących się tekstur, na przykład dla wody lub podłoża. Dodatkowo stosując właściwości scaleX, scaleY oraz repeat, można skalować i powielać wybraną teksturę na powierzchni. Dzięki temu nie trzeba stosować dużych plików graficznych. GlassMaterial służy do generowania sztucznych refleksów przy zastosowaniu map normalnych. Do tworzenia animowanych pokryć dla obiektów służą materiały MovieMaterial, AnimatedBitmapMaterial oraz VideoMaterial. Materiał MovieMaterial umożliwia zastosowanie obiektów klasy MovieClip jako pokrycia dla trójwymiarowych obiektów, co pozwala na osadzanie na ich powierzchni interaktywnych elementów oraz animacji. Aby materiał reagował na zdarzenia MouseEvent, należy właściwości interactive przypisać wartość true. Do tworzenia animacji bardziej wydajnym materiałem jest AnimatedBitmapMaterial niż MovieMaterial. VideoMaterial służy do wyświetlania filmów w formacie FLV, stosuje obiekty klas NetStream, NetConnection oraz Video. Do mieszania wielu materiałów na jednym obiekcie i prezentowania ich w formie warstw służy materiał CompositeMaterial.
190
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 5. Światło Światło to jeden z najważniejszych czynników, dzięki którym możemy rozróżniać kolory w otaczającym nas świecie. Programiści oraz artyści zajmujący się grafiką trójwymiarową nadal badają światło, aby tworzone przez nich efekty były jak najbardziej zbliżone do występujących w świecie rzeczywistym. Zarówno w produkcjach filmowych, jak i aplikacjach 3D światło oraz jego oddziaływanie odgrywają istotną rolę w prezentacji obiektów w przestrzeni trójwymiarowej. Biblioteka Away3D ma w swoich zasobach kilka rodzajów świateł oraz materiałów, które z nimi współgrają. Sceny, które wykorzystywaliśmy do tej pory, w swoich standardowych ustawieniach nie uwzględniały obiektów światła. Zajmiemy się tym dopiero w tym rozdziale i omówimy ich poszczególne rodzaje. Światło w bibliotece Away3D to trójwymiarowe obiekty, których bezpośrednio nie widać na scenie. Zauważalne natomiast są efekty ich działania na powierzchni innych obiektów widzialnych. Z uwagi na takie rozróżnienie obiekt klasy Scene3D ma specjalną metodę addLight(), która dodaje do jej przestrzeni źródła światła. Poza tą krótką informacją na temat dodawania źródeł światła do sceny czytając ten rozdział, dowiesz się: Jakie rodzaje światła dostępne są w bibliotece Away3D. Jak różne światła oddziałują na obiekty umieszczone na scenie. Jakie materiały reagują na różnego rodzaju oświetlenie. Które materiały są bardziej, a które mniej wydajne. Na jakich zasadach opiera się mapowanie normalnych. Jakim narzędziem tworzyć mapy normalnych. Które z materiałów i w jakim stopniu korzystają z mapowania normalnych.
192
Flash i ActionScript. Aplikacje 3D od podstaw
Rodzaje świateł Klasa bazowa dla przykładów Zanim zaczniemy omawianie poszczególnych rodzajów, przepisz poniższy kod źródłowy, który będzie klasą bazową dla wszystkich przykładów. package { import away3d.materials.ShadingColorMaterial; import flash.display.StageAlign; import flash.display.StageQuality; import flash.display.StageScaleMode; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Vector3D; // import away3d.containers.View3D; import away3d.containers.ObjectContainer3D; import away3d.lights.*; import away3d.materials.*; import away3d.primitives.Sphere; public class Light3DExample extends Sprite { private var view:View3D; private var obj:ObjectContainer3D; private var light:*; public function Light3DExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; removeEventListener(Event.ADDED_TO_STAGE, init); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame); initPanelListeners(); view = new View3D(); addChild(view);
Rozdział 5. Światło
193
addLights(); addObjects(); }
onResize(); private function initPanelListeners():void { } private function addLights():void { } private function addObjects():void { } private function updateLight(e:Event):void { } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; }
}
}
private function onEnterFrame(e:Event):void { obj.rotationY--; view.render(); }
W tym kodzie zaimportowaliśmy wszystkie dostępne w bibliotece Away3D światła i materiały, stosując zapis: import away3d.lights.*; import away3d.materials.*;
Do zdefiniowania obiektu light wykorzystaliśmy *, co oznacza, że będziemy mogli stworzyć obiekt z dowolnie wybranej klasy dostępnej w danym pakiecie. Co prawda nie powinno się stosować uogólnień dotyczących pochodzenia obiektu, ponieważ kod staje się przez to mniej elastyczny i podatny na błędy, ale w tym przykładzie zrobimy wyjątek. Dzięki zastosowaniu * nie będziemy modyfikowali całego kodu, tylko samą zawartość metod initPanelListeners(), addLights(), addObjects() oraz updateLight().
194
Flash i ActionScript. Aplikacje 3D od podstaw
Metoda initPanelListeners() będzie zawierała spis detektorów zdarzeń dla pól tekstowych zawartych w panelu użytkownika. Pola te będą odpowiadały wartościom poszczególnych właściwości wybranego rodzaju światła. Z uwagi na ich rozbieżność przy omawianiu każdego światła będziemy definiowali te pola na nowo. W metodzie addLights() będziemy tworzyli obiekt light dla konkretnego rodzaju światła, przypisując jego właściwościom odpowiednie wartości początkowe. Z uwagi na to, że nie wszystkie materiały i światła są kompatybilne, w metodzie addObjects() będziemy tworzyli obiekty akurat omawianego rodzaju światła.
Metoda updateLight() będzie wywoływana z każdą zmianą wartości w polu tekstowym umieszczonym w panelu użytkownika.
AmbientLight3D Pierwszym rodzajem światła, jakiemu się przyjrzymy, jest AmbientLight3D. Służy ono do oświetlania otoczenia, na co wskazuje pierwszy człon nazwy. Wykorzystanie go na scenie spowoduje oświetlenie wszystkich obiektów w ten sam sposób, niezależnie od kąta padania lub położenia bryły względem światła. AmbientLight3D możemy mieszać swobodnie z innymi rodzajami światła. Należy pamiętać tylko, że nie każdy rodzaj materiału z nim współgra. Efekt zastosowania tego światła przedstawiono na rysunku 5.1. Rysunek 5.1.
Zastosowanie światła AmbientLight3D
Rozdział 5. Światło
195
Jeżeli nie korzystasz z plików przygotowanych specjalnie do tej książki, to stwórz obiekt MovieClip o nazwie panel i umieść w nim dynamiczne pole tekstowe o nazwie ambientText. W przedstawionej klasie bazowej uzupełnij zawartość metody initPanelListeners() o następującą linijkę kodu: panel.ambientText.addEventListener(Event.CHANGE, updateLight);
Następnie stwórz obiekt światła, uzupełniając metodę addLights() o: light = new AmbientLight3D(); panel.ambientText.text = light.ambient; view.scene.addLight(light);
Dodaj obiekty do sceny, wpisując następujący kod źródłowy w metodzie add Objects(): obj = new ObjectContainer3D(); var sph1:Sphere = new Sphere( { radius:50, material:new PhongColorMaterial(0xFF0000), x:-150 } ); var sph2:Sphere = new Sphere( { radius:50, material:new PhongColorMaterial(0xFF0000), x:150 } ); obj.addChild(sph1); obj.addChild(sph2); view.scene.addChild(obj);
Na koniec wpisz poniższą linijkę kodu w metodzie updateLight(): light.ambient = Number(panel.ambientText.text);
Po wykonaniu wszystkich czynności możesz skompilować kod i sprawdzić, jak zachowuje się światło AmbientLight3D w przypadku różnych wartości właściwości ambient. W tabelach 5.1 i 5.2 wypisane są wszystkie właściwości oraz metody tej klasy. Tabela 5.1. Właściwości klasy AmbientLight3D
Nazwa
Rodzaj
Wartość domyślna
Opis
color
uint
16777215
Określa kolor światła
ambient
Number
1
Określa współczynnik natężenia światła
196
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 5.2. Metody klasy AmbientLight3D
Nazwa
Opis
AmbientLight3D
Konstruktor
Na koniec spis materiałów, na których widoczne są efekty działania światła Ambient Light3D: PhongMovieMaterial, PhongColorMaterial, PhongBitmapMaterial, Dot3Bitmap Material.
PointLight3D Światło PointLight3D, jak nazwa wskazuje, emitowane jest z wybranego punktu w przestrzeni. Działanie PointLight3D opiera się na tych samych zasadach co emitowanie światła sztucznego w naszym otoczeniu. Za przykład przyjmijmy żarówkę zawieszoną pod sufitem. Każdy element jest oświetlony w sposób zależny od jego pozycji względem źródła. Intensywność działania światła sztucznego maleje odwrotnie proporcjonalnie do odległości. Oznacza to, że obiekty znajdujące się dalej nie będą odbijały promieni z taką samą siłą jak te umieszczone bliżej. Działanie światła PointLight3D przedstawia rysunek 5.2. Rysunek 5.2.
Zastosowanie światła PointLight3D
Zmodyfikujemy kod klasy bazowej, tak aby zastosować światło PointLight3D i uzyskać efekt jak na rysunku 5.3.
Rozdział 5. Światło
197
Rysunek 5.3.
Zastosowanie światła PointLight3D
Zwróć uwagę, że tym razem panel użytkownika jest znacznie bardziej rozbudowany. Aby kod poprawnie działał, musisz stworzyć identyczny schemat, dlatego przyjrzyj się rysunkowi 5.4 i na jego podstawie dodaj obiekt MovieClip z odpowiednimi polami tekstowymi. Rysunek 5.4.
Schemat panelu użytkownika
W przedstawionej klasie bazowej uzupełnij zawartość metody initPanelListeners() o następujące linijki kodu: panel.falloffText.addEventListener(Event.CHANGE, updateLight); panel.brightnessText.addEventListener(Event.CHANGE, updateLight); panel.lightColor.addEventListener(Event.CHANGE, updateLight);
198
Flash i ActionScript. Aplikacje 3D od podstaw panel.specularText.addEventListener(Event.CHANGE, updateLight); panel.diffuseText.addEventListener(Event.CHANGE, updateLight); panel.ambientText.addEventListener(Event.CHANGE, updateLight); panel.xText.addEventListener(Event.CHANGE, updateLight); panel.yText.addEventListener(Event.CHANGE, updateLight); panel.zText.addEventListener(Event.CHANGE, updateLight);
Następnie stwórz obiekt światła, uzupełniając metodę addLights() o: light = new PointLight3D( { color:0xFFFFFF, specular:1, diffuse:.5, ambient:.5 } ); light.position = new Vector3D(0, 200, 0); view.scene.addLight(light);
Dodaj obiekty do sceny, wpisując następujący kod źródłowy w metodzie add Objects(): obj = new ObjectContainer3D(); var sph1:Sphere = new Sphere( { radius:50, material:new ShadingColorMaterial(0xFF0000), x:-150 } ); var sph2:Sphere = new Sphere( { radius:50, material:new ShadingColorMaterial(0xFF0000), x:150 } ); obj.addChild(sph1); obj.addChild(sph2); view.scene.addChild(obj);
Na koniec wpisz poniższe linijki kodu w metodzie updateLight(): light.color = uint('0x' + panel.lightColor.hexValue); light.brightness = Number(panel.brightnessText.text); light.fallOff = Number(panel.falloffText.text); light.specular = Number(panel.specularText.text); light.diffuse = Number(panel.diffuseText.text); light.ambient = Number(panel.ambientText.text); light.x = Number(panel.xText.text); light.y = Number(panel.yText.text); light.z = Number(panel.zText.text);
Po wykonaniu wszystkich czynności możesz skompilować kod i sprawdzić, jak zachowuje się światło PointLight3D w przypadku różnych wartości jego właściwości. W tabelach 5.3 i 5.4 wypisane są wszystkie właściwości oraz metody tej klasy.
Rozdział 5. Światło
199
Tabela 5.3. Właściwości klasy PointLight3D
Nazwa
Rodzaj
Wartość domyślna
Opis
color
uint
16777215
Określa kolor światła
ambient
Number
1
Określa poziom jasności otoczenia
brightness
Number
1
Określa natężenie światła
diffuse
Number
1
Określa poziom rozproszenia światła
fallOf
Number
1000
Określa długość, w której światło obowiązuje
radius
Number
50
Określa promień światła, w którym jest pełna widoczność
specular
Number
1
Określa współczynnik natężenia światła odbijanego zwierciadlanie
Tabela 5.4. Metody klasy PointLight3D
Nazwa
Opis
PointLight3D
Konstruktor
Na koniec spis materiałów, na których widoczne są efekty działania światła Point Light3D: ShadingColorMaterial, WhiteShadingBitmapMaterial, PhongMultiPassMaterial, PhongPBMaterial.
DirectionalLight3D DirectionalLight3D w przeciwieństwie do PointLight3D emituje wiązkę światła nie z jednego, lecz tysięcy punktów skierowanych w tym samym kierunku. Jak to działa? Wyobraź sobie płytę o nieskończonych wymiarach złożoną z tysięcy diod emitujących światło. Tym właśnie jest DirectionalLight3D. Nie ustalamy jego położenia w przestrzeni, tylko kierunek, w który ma kierować promienie świetlne. Wszystkie obiekty znajdujące się na scenie będą odbijały światło w zależności od tego, jaki ustalimy kierunek. Żeby lepiej zrozumieć działanie tego materiału, spójrz na rysunek 5.5.
Widzimy tu płytę z żarówkami pochyloną w kierunku obiektów pod ustalonym kątem. Ta płyta przedstawia obiekt klasy DirectionalLight3D. Strzałki reprezentują promienie świetlne oraz kierunek ich emitowania. Inną ważną cechą tego rodzaju oświetlenia jest to, że niezależnie od swojego położenia obiekty mogą odbijać światło z tą samą intensywnością.
200
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 5.5.
Wizualizacja działania światła DirectionalLight3D
Na rysunku 5.6 przedstawiono efekt działania światła DirectionalLight3D po zmodyfikowaniu panelu użytkownika i kodu źródłowego klasy bazowej. Rysunek 5.6.
Zastosowanie światła DirectionalLight3D
W tym przypadku panel użytkownika również różni się od poprzednich. Dlatego aby kod działał poprawnie, musisz stworzyć identyczny schemat z tym, który przedstawiono na rysunku 5.7.
Rozdział 5. Światło
201
Rysunek 5.7.
Schemat panelu użytkownika
Aby móc wykorzystać DirectionalLight3D, w naszym przykładzie zmień zawartość metody initPanelListeners() na następującą: panel.brightnessText.addEventListener(Event.CHANGE, updateLight); panel.specularText.addEventListener(Event.CHANGE, updateLight); panel.diffuseText.addEventListener(Event.CHANGE, updateLight); panel.ambientText.addEventListener(Event.CHANGE, updateLight); panel.xText.addEventListener(Event.CHANGE, updateLight); panel.yText.addEventListener(Event.CHANGE, updateLight); panel.zText.addEventListener(Event.CHANGE, updateLight);
W metodzie addLights() skorzystaj z następującego kodu i stwórz obiekt światła: light = new DirectionalLight3D({ color:0xFFFFFF, specular:1, diffuse:.5, ambient:.5 }); view.scene.addLight(light);
Dodaj obiekty do sceny, wpisując następujący kod źródłowy w metodzie add Objects(): obj = new ObjectContainer3D(); var sph1:Sphere = new Sphere( { radius:50, material:new ShadingColorMaterial(0xFF0000), x:-150 } ); var sph2:Sphere = new Sphere( { radius:50, material:new ShadingColorMaterial(0xFF0000), x:150 } ); obj.addChild(sph1); obj.addChild(sph2); view.scene.addChild(obj);
Uzupełnij jeszcze zawartość metody updateLight(), która uruchamiana jest przy każdej zmianie wartości dowolnego pola tekstowego w panelu użytkownika:
202
Flash i ActionScript. Aplikacje 3D od podstaw light.brightness = Number(panel.brightnessText.text); light.specular = Number(panel.specularText.text); light.diffuse = Number(panel.diffuseText.text); light.ambient = Number(panel.ambientText.text); light.direction = new Vector3D(Number(panel.xText.text), Number(panel.yText.text), Number(panel.zText.text));
Teraz możesz skompilować kod i sprawdzić, jak działa światło DirectionalLight3D w przypadku różnych wartości jego właściwości. W tabelach 5.5 i 5.6 wypisane są wszystkie właściwości oraz metody tej klasy. Tabela 5.5. Właściwości klasy DirectionalLight3D
Nazwa
Rodzaj
Wartość domyślna
Opis
ambient
Number
1
Określa poziom jasności otoczenia
brightness
Number
1
Określa natężenie światła
diffuse
Number
0.5
Określa poziom rozproszenia światła
direction
Vector3D
Vector3D(0,0,0)
Określa kierunek padania promieni świetlnych
specular
Number
0.5
Określa współczynnik natężenia światła odbijanego zwierciadlanie
Tabela 5.6. Metody klasy DirectionalLight3D
Nazwa
Opis
DirectionalLight3D
Konstruktor
Na koniec spis materiałów, na których widoczne są efekty działania światła DirectionalLight3D: ShadingColorMaterial, WhiteShadingBitmapMaterial, PhongMultiPass Material, PhongMovieMaterial, PhongColorMaterial, PhongBitmapMaterial, Dot3Bitmap Material, Dot3BitmapMaterialF10.
Materiały reagujące na światło Klasa bazowa dla przykładów Podobnie jak w poprzednim podrozdziale, zanim zaczniemy omawiać materiały reagujące na światło, zapiszemy ogólną klasę, w której będziemy zmieniali zawartość metod addLight(), initMaterial() i czasami addObjects(). Przepisz następujący kod i zapisz jako MaterialsExample.as.
Rozdział 5. Światło package { import import import import import import import // import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.MouseEvent; flash.geom.Vector3D; away3d.containers.View3D; away3d.containers.ObjectContainer3D; away3d.lights.*; away3d.materials.*; away3d.primitives.Sphere; away3d.primitives.Cube; away3d.core.utils.Cast;
public class MaterialsExample extends Sprite { private var view:View3D; private var obj:ObjectContainer3D; private var light:*; private var mat:*; public function MaterialsExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; removeEventListener(Event.ADDED_TO_STAGE, init); stage.addEventListener(Event.RESIZE, onResize); addEventListener(Event.ENTER_FRAME, onEnterFrame); view = new View3D(); addChild(view); addLights(); initMaterial(); addObjects(); onResize(); } private function addLights():void {
203
204
Flash i ActionScript. Aplikacje 3D od podstaw } private function initMaterial():void { } private function addObjects():void { obj = new ObjectContainer3D(); var sph1:Sphere = new Sphere( { radius:50, material:mat, x: -150, segmentsW:16, segmentsH:16 } ); var sph2:Sphere = new Sphere( { radius:50, material:mat, x:150, segmentsW:16, segmentsH:16 } ); obj.addChild(sph1); obj.addChild(sph2); view.scene.addChild(obj); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { obj.rotationY--; view.render(); } } }
PhongColorMaterial Pierwszy człon tej nazwy pochodzi od imienia naukowca wietnamskiego pochodzenia, Bui Tuong Phonga. Sformułował on pojęcie dotyczące oświetlenia modeli w przestrzeni. W skrócie idea ta zakłada, że na obiekt o błyszczącej powierzchni dociera światło, które następnie zostaje rozproszone w każdym kierunku i zabarwione na kolor bryły. W praktyce korzystanie z PhongColorMaterial przynosi efekt, który pokazano na rysunku 5.8.
Rozdział 5. Światło
205
Rysunek 5.8.
Zastosowanie materiału PhongColorMaterial
Aby uzyskać taki efekt, w klasie bazowej MaterialsExample uzupełnij zawartość metody addLights() następującym kodem: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
Następnie w metodzie initMaterial() stwórz obiekt materiału klasy PhongColor Material, używając poniższego kodu: mat = new PhongColorMaterial(0x0000FF);
Po uzupełnieniu tych funkcji i skompilowaniu kodu na ekranie powinieneś ujrzeć dwie kule w kolorze niebieskim odbijające światło przy standardowych ustawieniach materiału. Właściwości oraz metody klasy PhongColorMaterial wypisano w tabelach 5.7 i 5.8. Tabela 5.7. Właściwości klasy PhongColorMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
20
Określa poziom odblasku
specular
Number
0.7
Określa współczynnik natężenia światła odbijanego zwierciadlanie
206
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 5.8. Metody klasy PhongColorMaterial
Nazwa
Opis
PhongColorMaterial
Konstruktor
ShadingColorMaterial ShadingColorMaterial działa podobnie jak wcześniej omówiony PhongColorMaterial, czyli możemy pokazać, jak pada światło i tworzy się cień na obiekcie, lecz nie będzie ono wyglądało tak samo jak w poprzednim przykładzie. Nie zabarwimy powierzchni precyzyjnie punkt po punkcie. Zamiast tego każdemu z segmentów, które są w zasięgu działania światła, nadamy odpowiednio jaśniejszy kolor. Kolor segmentów, do których światło nie dochodzi, będzie stopniowo zaciemniany. W ten sposób nie wygładzamy obiektu, co znacznie zwiększa wydajność aplikacji. ShadingColorMaterial staje się bardziej przydatny na etapie tworzenia i ustawiania elementów sceny. To znaczy, że jeżeli planujesz użycie większej liczby elementów i chciałbyś sprawdzić, jak każdy z nich będzie się prezentował w wybranym położeniu. Efekt działania materiału ShadingColorMaterial przedstawiono na rysunku 5.9. Rysunek 5.9.
Zastosowanie materiału ShadingColorMaterial
Rozdział 5. Światło
Taki efekt uzyskamy, tworząc obiekt klasy ShadingColorMaterial Material() klasy bazowej. Powinno to wyglądać następująco:
207
w metodzie init
mat = new ShadingColorMaterial(0x0000FF);
Z kolei w metodzie addLights() należy dodać następujący kod w celu stworzenia odpowiedniego światła: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
Aby lepiej zapamiętać oraz w razie potrzeby użyć ShadingColorMaterial, w tabelach 5.9 i 5.10 zapisano właściwości oraz metody tej klasy. Tabela 5.9. Właściwości klasy ShadingColorMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
color
uint
14473554
Określa kolor materiału
ambient
uint
0
Określa wartość koloru dla oświetlenia otoczenia
cache
Boolean
false
Ustala, czy odcienie koloru powierzchni obiektu mają być zapamiętane
diffuse
uint
0
Określa wartość koloru przy rozproszeniu światła
shininess
Number
20
Określa poziom odblasku
specular
uint
0
Określa wartość natężenia światła odbijanego zwierciadlanie
Tabela 5.10. Metody klasy ShadingColorMaterial
Nazwa
Opis
ShadingColorMaterial
Konstruktor
PhongBitmapMaterial Zasady działania są takie same jak przy stosowaniu materiału PhongColorMaterial, ale tutaj obiekt pokryty jest teksturą. Aby uzyskać efekt odblasku, poza zdefiniowaniem materiału musimy jeszcze dodać źródło światła do sceny. W przeciwnym razie tekstura wyświetlana będzie jako zwykły obiekt klasy BitmapMaterial. Przejdźmy od razu do przykładu, aby zobaczyć, jak praktycznie można wykorzystać PhongBitmapMaterial. Efekt zastosowania tego materiału przedstawiono na rysunku 5.10.
208
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 5.10.
Zastosowanie materiału PhongBitmapMaterial
Taki efekt uzyskamy, tworząc obiekt klasy PhongBitmapMaterial w metodzie init Material() klasy bazowej. Powinno to wyglądać następująco: mat = new PhongBitmapMaterial(Cast.bitmap('metal'), { } );
Jeżeli stworzysz obiekt klasy PhongBitmapMaterial, jako argument podając tylko obiekt klasy BitmapData, możesz ujrzeć komunikat błędu #1009. Aby temu zapobiec, należy dopisać drugi argument w postaci obiektu inicjującego, zawierającego właściwości dostępne w PhongBitmapMaterial. Jeżeli nie chcesz ustawiać jakichkolwiek właściwości, to stwórz pusty obiekt, wpisując sam nawias klamrowy.
Z kolei w metodzie addLights() należy dodać następujący kod w celu stworzenia odpowiedniego światła: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
Po wykonaniu tych czynności na ekranie pojawią się dwie kule pokryte teksturą, która odbija światło. Właściwości oraz metody klasy PhongBitmapMaterial wypisano w tabelach 5.11 i 5.12.
Rozdział 5. Światło
209
Tabela 5.11. Właściwości klasy PhongBitmapMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
20
Określa poziom odblasku
specular
uint
16777215
Określa wartość koloru dla odbicia światła
textureMaterial
BitmapMaterial
Zwraca obiekt BitmapMaterial użytej tekstury
Tabela 5.12. Metody klasy PhongBitmapMaterial
Nazwa
Opis
PhongBitmapMaterial
Konstruktor
WhiteShadingBitmapMaterial Klasa WhiteShadingBitmapMaterial jest rozszerzoną wersją klasy BitmapMaterial, umożliwiającą nadanie odblasku na powierzchni wybranego obiektu. W przeciwieństwie do materiału PhongBitmapMaterial nie generuje ona odbicia w każdym pikselu powierzchni bryły, tylko nadaje odpowiedni odcień jej segmentom. Gdy stosujemy WhiteShadingBitmapMaterial do tworzenia odblasku, podstawowym jego kolorem, niezależnie od ustawień światła na scenie, jest zawsze biały. Efekt zastosowania tego materiału przedstawiono na rysunku 5.11. Rysunek 5.11.
Zastosowanie materiału WhiteShadingBitmap Material
210
Flash i ActionScript. Aplikacje 3D od podstaw
Jak widać na rysunku, efekt stosowania WhiteShadingBitmapMaterial różni się od PhongBitmapMaterial. Ma to jednak swoje dobre strony, ponieważ wygenerowanie takiego obrazu jest znacznie mniej obciążające dla zasobów komputera użytkownika. Aby sprawdzić na przykładzie działanie tego materiału w metodzie initMaterial(), wpisz następującą linijkę kodu: mat = new WhiteShadingBitmapMaterial(Cast.bitmap('metal'));
Następnie stwórz odpowiednie światło, wpisując w metodzie addLights(): light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
Po uzupełnieniu tych metod i skompilowaniu kodu na ekranie powinieneś ujrzeć dwie kule wypełnione teksturą i odbijające światło. Właściwości oraz metody klasy WhiteShadingBitmapMaterial wypisano w tabelach 5.13 i 5.14. Tabela 5.13. Właściwości klasy WhiteShadingBitmapMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
20
Określa poziom odblasku
Tabela 5.14. Metody klasy WhiteShadingBitmapMaterial
Nazwa
Opis
PhongBitmapMaterial
Konstruktor
clearCache():void
Usuwa z pamięci podręcznej zastosowane bitmapy
PhongMovieMaterial PhongMovieMaterial
to połączenie MovieMaterial z oświetleniem Phong. Materiał ten stosujemy tak samo jak PhongColorMaterial czy też PhongBitmapMaterial, tylko że w tym konkretnym przypadku źródłem grafiki jest element MovieClip. Na rysunku 5.12 przedstawiono zastosowanie PhongMovieMaterial. Aby uzyskać taki efekt, w klasie bazowej MaterialsExample w metodzie initMaterial() stwórz obiekt Sprite i odwołaj się do niego w konstruktorze klasy PhongMovie Material, używając poniższego kodu: var mc:textureMC = new textureMC(); mat = new PhongMovieMaterial(mc);
Rozdział 5. Światło
211
Rysunek 5.12.
Zastosowanie materiału PhongMovieMaterial
W naszym przypadku zastosowaliśmy obrazek w formacie gif, który dodaliśmy do zasobów projektu w postaci obiektu o nazwie liquid. Możesz oczywiście użyć dowolnego obiektu MovieClip lub Sprite. Po określeniu materiału uzupełnij zawartość metody addLights() następującym kodem: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
Po zapisaniu tych metod i skompilowaniu kodu na ekranie powinieneś ujrzeć dwie kule z nałożoną animowaną teksturą, która piksel po pikselu odbija światło. Właściwości oraz metody klasy PhongMovieMaterial wypisano w tabelach 5.15 i 5.16. Tabela 5.15. Właściwości klasy PhongMovieMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
20
Określa poziom odblasku
specular
Number
0.7
Określa wartość koloru dla odbicia światła
Tabela 5.16. Metody klasy PhongMovieMaterial
Nazwa
Opis
PhongMovieMaterial
Konstruktor
212
Flash i ActionScript. Aplikacje 3D od podstaw
Dot3BitmapMaterial A teraz nadszedł czas, żeby przedstawić ciekawsze materiały. Jednym z nich jest Dot3BitmapMaterial. Materiał ten korzysta z metody mapowania normalnych. W języku angielskim określany jest jako Normal Mapping, a w bibliotece Away3D — Dot3 Mapping. Metoda ta polega na symulowaniu wypukłości powierzchni bez konieczności dokonywania zmian w budowie obiektu. Zamiast ingerować w geometrię bryły, stosuje się osobną mapę, która zastępuje oryginalne wektory normalnych w różnych punktach na powierzchni obiektu. Taka mapa tworzona jest w kolorach RGB. Każdy z tych kolorów odpowiada współrzędnym składowej normalnej poszczególnego piksela. Aby lepiej zrozumieć zasadę zastępowania wektorów normalnych, przyjrzyj się rysunkowi 5.13.
Rysunek 5.13. Schemat mapowania wypukłości
Po lewej stronie rysunku 5.13 przedstawiono kierunki wektorów w normalnych teksturach. Jak widać, wszystkie są prostopadłe do powierzchni. Z kolei po prawej stronie rysunku 5.13 ich kierunki zmieniają się w zależności od koloru piksela tworzącego wypukłość tekstury. Miej na uwadze to, że zastosowane kolory są tylko przykładowe. Istotną rolę — oprócz zamiany kierunków wektorów normalnych — gra tutaj światło, a dokładniej kąt oraz ilość światła, jaka pada na powierzchnię obiektu. Padający strumień miejscami odbijany jest w naszą stronę intensywniej, a miejscami jest on wręcz pochłaniany, przez co uzyskujemy efekt nierównej powierzchni. Metoda ta z uwagi na swoje możliwości i małe zużycie pamięci jest bardzo często wykorzystywana przy tworzeniu obiektów przeznaczonych do gier. W przykładzie dla tego materiału skorzystamy z tekstur przedstawionych na rysunku 5.14. Pierwsza tekstura reprezentuje tę, którą widzimy bezpośrednio, druga natomiast przyczyni się do stworzenia otworów i nierówności oraz nada lepszy efekt pierwszej.
Rozdział 5. Światło
213
Rysunek 5.14.
Tekstura i jej mapa normalnych
Istnieje ciekawy plugin do programu Adobe Photoshop stworzony przez firmę NVIDIA, który pozwala na wygenerowanie mapy normalnych z otwartego pliku PSD. Wtyczkę tę można pobrać ze strony: http://developer.nvidia.com/nvidia-texture-toolsadobe-photoshop.
Efekt, który uzyskamy dzięki połączeniu obu tych tekstur w materiale Dot3Bitmap Material, przedstawia rysunek 5.15. Rysunek 5.15.
Zastosowanie materiału Dot3BitmapMaterial
Aby uzyskać taki efekt, w klasie bazowej MaterialsExample uzupełnij zawartość metody addLights() następującym kodem: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -100, 300); view.scene.addLight(light);
214
Flash i ActionScript. Aplikacje 3D od podstaw
Następnie w metodzie initMaterial() stwórz obiekt materiału klasy Dot3Bitmap Material, używając poniższego kodu: mat = new Dot3BitmapMaterial(Cast.bitmap('metal2'), Cast.bitmap('normalMap'));
Dodatkowo zmień wyświetlane na scenie dwie kule w jeden sześcian, na którym umieścimy stworzony materiał. Aby tego dokonać, zmień zawartość metody add Objects(), używając poniższego kodu: obj = new ObjectContainer3D(); var cb:Cube = new Cube( { material:mat } ); cb.scale(3); obj.addChild(cb); view.scene.addChild(obj);
Po uzupełnieniu tych metod i skompilowaniu kodu na ekranie powinieneś ujrzeć sześcian złożony z metalowych płyt. Wykorzystanie Dot3BitmapMaterial na pewno uatrakcyjni wygląd obiektów w Twojej aplikacji. Właściwości oraz metody tej klasy wypisano w tabelach 5.17 i 5.18. Tabela 5.17. Właściwości klasy Dot3BitmapMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
20
Określa poziom odblasku
specular
uint
16777215
Określa współczynnik odbicia poziomu światła
textureMaterial
BitmapMaterial
Zwraca obiekt BitmapMaterial użytej tekstury
normalBitmap
BitmapData
Zwraca obiekt BitmapMaterial użytej mapy wektorów normalnych
Tabela 5.18. Metody klasy Dot3BitmapMaterial
Nazwa
Opis
Dot3BitmapMaterial(bitmap:BitmapData, normalBitmap:BitmapData, init:Object = null)
Konstruktor
Dot3BitmapMaterialF10 Klasa Dot3BitmapMaterialF10 jest rozszerzoną wersją klasy Dot3BitmapMaterial. Dopisek F10 oznacza, że z tego materiału można korzystać jedynie na platformach Adobe Flash Player 10 i wyżej. Ma to swoje uzasadnienie. Otóż wraz z wersją dziesiątą wtyczka Adobe Flash Player obsługuje technologię Adobe Pixel Bender, która pozwala w wydajniejszy sposób tworzyć efekty graficzne za pomocą modułów
Rozdział 5. Światło
215
cieniujących. Brzmi to tajemniczo, ale potraktujmy to jak przejście ze zwykłej jakości obrazu do HD (High Definition — wysoka rozdzielczość). Efekt zastosowania tej klasy w przykładzie MaterialsExample przedstawiono na rysunku 5.16. Rysunek 5.16.
Zastosowanie materiału Dot3BitmapMaterialF10
Aby użyć klasy Dot3BitmapMaterialF10, w przykładzie MaterialsExample zamień kod metody addMaterial() na następujący: mat = new Dot3BitmapMaterialF10(Cast.bitmap('metal2'), Cast.bitmap('normalMap'));
Tak samo jak w przykładzie dla Dot3BitmapMaterialF10, w metodzie addObjects() zamień kod na następujący: obj = new ObjectContainer3D(); var cb:Cube = new Cube( { material:mat } ); cb.scale(3); obj.addChild(cb); view.scene.addChild(obj);
W metodzie addLights() stwórz obiekt światła oraz dodaj go do sceny, tak jak to wypisano w poniższych linijkach kodu: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -500, 500); view.scene.addLight(light);
Teraz możesz skompilować kod i sprawdzić w praktyce, jak działa materiał Dot3BitmapMaterialF10. W tabelach 5.19 i 5.20 wypisane są wszystkie właściwości oraz metody tej klasy.
216
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 5.19. Właściwości klasy Dot3BitmapMaterialF10
Nazwa
Rodzaj
Wartość domyślna Opis
shininess
Number
20
Określa poziom odblasku
specular
uint
16777215
Określa współczynnik odbicia poziomu światła
normalMap
BitmapData
Zwraca obiekt BitmapMaterial użytej mapy wektorów normalnych
Tabela 5.20. Metody klasy Dot3BitmapMaterialF10
Nazwa
Opis
Dot3BitmapMaterialF10(bitmap:BitmapData, normalBitmap:BitmapData, init:Object = null)
Konstruktor
Dot3MovieMaterial Używając animowanych tekstur, również możemy się bawić poprawianiem ich wyglądu przez stosowanie map normalnych. Do tego celu wykorzystuje się materiał o nazwie Dot3MovieMaterial. Z punktu widzenia wcześniej omawianego Dot3Bitmap Material nic nie ulega zmianie w działaniu tego materiału. Według zasad podajemy obiekt typu BitmapData jako mapę normalnych oraz element MovieClip jako teksturę bazową. Tak samo jak w przykładzie z Dot3BitmapMaterial i Dot3BitmapMaterialF10, tutaj również stworzymy sześcian, ale użyjemy innej mapy wektorów normalnych oraz obiektu MovieClip z teksturą skały. Całość będzie wyglądała tak, jak to pokazano na rysunku 5.17. Rysunek 5.17.
Zastosowanie materiału Dot3MovieMaterial
Rozdział 5. Światło
217
Aby uzyskać taki efekt, w pierwszej kolejności zmień odwołanie do klasy textureMC z elementu liquid na stone w pliku projektu. Jeżeli nie masz tych plików, po prostu stwórz dowolną teksturę, zapisz ją do postaci MovieClip i powiąż ją z pustą klasą textureMC. W metodzie addObjects() wpisz następujący kod: obj = new ObjectContainer3D(); var cb:Cube = new Cube( { material:mat } ); cb.scale(3); obj.addChild(cb); view.scene.addChild(obj);
W metodzie initMaterial() stwórz nowy obiekt mat, podając w argumentach obiekt klasy textureMC oraz odwołanie do mapy normalMap2. mat = new Dot3MovieMaterial(new textureMC(), Cast.bitmap('normalMap2'));
W metodzie addLights() stwórz światło, wpisując poniższy kod: light = new DirectionalLight3D(); light.direction = new Vector3D(0, -500, 500); view.scene.addLight(light);
Teraz możesz skompilować kod i sprawdzić, jak działa ten materiał. W tabelach 5.21 i 5.22 wypisane są wszystkie właściwości oraz metody klasy Dot3MovieMaterial. Tabela 5.21. Właściwości klasy Dot3MovieMaterial
Nazwa
Rodzaj
Wartość domyślna
Opis
shininess
Number
0.7
Określa poziom odblasku
specular
Number
0.7
Określa współczynnik odbicia poziomu światła
textureMaterial
MovieMaterial
Zwraca obiekt MovieMaterial użyty jako materiał pokrywający obiekt
normalMap
BitmapData
Zwraca obiekt BitmapMaterial użytej mapy wektorów normalnych
Tabela 5.22. Metody klasy Dot3MovieMaterial
Nazwa
Opis
Dot3MovieMaterial(movie:Sprite, normalMap:BitmapData, init:Object = null)
Konstruktor
218
Flash i ActionScript. Aplikacje 3D od podstaw
PhongMultiPassMaterial Klasa PhongMultiPassMaterial działa podobnie jak wcześniej omówiony Dot3Bitmap Material i tak dalej. Różnicą jest to, że w PhongMultiPassMaterial dodatkowo definiuje się argument specularMap, który określa obszary zróżnicowania odblasku. Taka mapa może mieć postać czarno-białej bitmapy, w której jaśniejsze fragmenty wyznaczają obszary o silniejszym odbiciu światła. W naszym przykładzie skorzystamy z tekstur, które przedstawiono na rysunku 5.18.
Rysunek 5.18. Zestaw tekstur i map do przykładu
Teksturę o nazwie Specular zastosujemy jako wartość argumentu specularMap, aby uzyskać efekt przedstawiony na rysunku 5.19. Zwróć uwagę na kształty jaśniejszych powierzchni kuli. Efekt zastosowania tego materiału przedstawiono na rysunku 5.19. Tak samo jak w poprzednim przykładzie, z powodu sposobu tworzenia obiektu materiału PhongMultiPassMaterial musimy upewnić się, że metody addLights(), addObjects() oraz initMaterial() zostały usunięte, po czym w metodzie init() należy umieścić następujący kod: light = new PointLight3D( { color:0xFFFFFF, specular:1, diffuse:.5, ambient:.5 } ); light.position = new Vector3D(-100, 100, -100); view.scene.addLight(light); light2 = new PointLight3D( { color:0xFFFFFF, specular:1, diffuse:.5, ambient:.5 } ); light2.position = new Vector3D(100, 100, -100); view.scene.addLight(light2);
Rozdział 5. Światło
219
Rysunek 5.19.
Zastosowanie materiału PhongMultiPassMaterial
obj = new ObjectContainer3D(); var sph1:Sphere = new Sphere( { segmentsW:16, segmentsH:16 } ); mat = new PhongMultiPassMaterial(Cast.bitmap('stone'), Cast.bitmap('normalMap2'), sph1, Cast.bitmap('specular'), { } ); sph1.material = mat; obj.addChild(sph1); view.scene.addChild(obj);
W pierwszej kolejności stworzyliśmy dwa niezależne obiekty światła PointLight3D o nazwach light i light2. Po umieszczeniu ich na scenie dodaliśmy obiekt kuli oraz kontenera i przypisaliśmy obiektowi sph1 materiał PhongMultiPassMaterial, podając jego argumentom te same wartości co w poprzednim przykładzie. Po wykonaniu tych czynności dodaliśmy wszystkie elementy do sceny. Tabele 5.23 i 5.24 zawierają właściwości i metody klasy PhongMultiPassMaterial. Tabela 5.23. Właściwości klasy PhongMultiPassMaterial
Nazwa
Rodzaj
Wartość domyślna Opis
gloss
Number
10
Określa poziom odblasku
specular
Number
1
Określa współczynnik odbicia poziomu światła
specularMap
BitmapData
Zwraca obiekt BitmapData użyty jako mapa siły odbijania światła
220
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 5.24. Metody klasy PhongMultiPassMaterial
Nazwa
Opis
PhongMultiPassMaterial (bitmap:BitmapData, normalMap:BitmapData, targetModel:Mesh, specularMap:BitmapData, init:Object)
Konstruktor
Podsumowanie Biblioteka Away3D zawiera następujące rodzaje światła: AmbientLight3D, PointLight3D, DirectionalLight3D. Podstawą wszystkich świateł jest klasa AbstractLight. Klasa AmbientLight3D generuje obiekt, który oświetla wszystkie elementy w jednakowy sposób, niezależnie od pozycji i kąta nachylenia obiektu. Obiekt klasy PointLight3D odzwierciedla sztuczne światło, takie jak żarówka, która oświetla elementy znajdujące się w określonej od niej odległości. Stopień oświetlenia maleje wraz z odległością. Klasa DirectionalLight3D generuje światło, któremu nie określa się pozycji, lecz kierunek, w jakim emitowane są promienie. Światło tego rodzaju oświetla wszystkie elementy umieszczone na scenie. Materiały, na których widoczne są efekty działania światła AmbientLight3D, to: PhongMovieMaterial, PhongColorMaterial, PhongBitmapMaterial, Dot3BitmapMaterial. Materiały, na których widoczne są efekty działania światła PointLight3D, to: ShadingColorMaterial, WhiteShadingBitmapMaterial, PhongMultiPassMaterial, PhongPBMaterial. Materiały, na których widoczne są efekty działania światła DirectionalLight3D, to: ShadingColorMaterial, WhiteShadingBitmapMaterial, PhongMultiPassMaterial, PhongMovieMaterial, PhongColorMaterial, PhongBitmapMaterial, Dot3BitmapMaterial, Dot3BitmapMaterialF10. Materiały reagujące na światło w Away3D można podzielić na trzy kategorie: Zwykłe płaskie cieniowanie, które nie rozprasza światła piksel po pikselu, lecz cieniuje segmenty obiektu. Cieniowanie Phong, które dokładnie rozprasza światło po powierzchni, nadając mu szklany efekt. Cieniowanie z wykorzystaniem map rozproszenia wektorów normalnych, które symulują niewielkie zniekształcenia na powierzchni obiektu.
Rozdział 5. Światło
221
Każdy sposób cieniowania uwzględnia zastosowanie koloru, tekstury oraz obiektu typu Sprite jako źródła obrazu na powierzchni pokrytego obiektu. Najbardziej wydajnymi rodzajami materiałów reagujących na światło są ShadingColorMaterial i WhiteShadingBitmapMaterial. Użytkownicy programu Adobe Photoshop do tworzenia map normalnych mogą posłużyć się specjalną wtyczką stworzoną przez firmę NVIDIA. Klasa Dot3BitmapMaterial ma swoją ulepszoną wersję w postaci klasy Dot3BitmapMaterialF10, która wykorzystuje możliwości wtyczki Adobe Flash Player 10.x. Klasa PhongMultiPassMaterial jako jedna z nielicznych reaguje na kilka różnych źródeł światła jednocześnie.
222
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 6. Modele i animacje W bibliotece Away3D istnieje możliwość umieszczania na scenie — poza obiektami omówionymi w rozdziale 3. „Obiekty” — modeli stworzonych w różnych programach do grafiki trójwymiarowej. Dzięki temu w aplikacjach bazujących na bibliotece Away3D mamy możliwość tworzenia ciekawych projektów z użyciem postaci oraz przedmiotów. Istotne jest również to, że nie trzeba ograniczać się do jednego lub dwóch rodzajów formatów — Away3D pozwala na korzystanie z wielu najbardziej znanych typów modeli. Dwa z nich uwzględniają również obsługę animacji, co daje jeszcze większe możliwości tworzenia ciekawych projektów. Znamy już podstawowe obiekty 3D i różnego rodzaju materiały dostępne w bibliotece Away3D, poszerzymy zatem naszą wiedzę o tematy związane ze stosowaniem modeli różnego typu. Czytając ten rozdział, dowiesz się: Z jakich aplikacji możesz korzystać, aby stworzyć modele dla Away3D. Jakiego rodzaju modeli można użyć w Away3D. Czym są modele Low poly oraz High poly. Jak dodawać modele i korzystać z nich w projektach. Jak w poszczególnych programach wyeksportować model w postaci klasy ActionScript 3.0. Jak posługiwać się animacjami wewnątrz modeli. Jak nakładać tekstury na modele. Programy do tworzenia modeli dla Away3D
224
Flash i ActionScript. Aplikacje 3D od podstaw
3ds Max Wcześniej był znany jako 3D Studio Max. 3ds Max to rozbudowany program, który służy do tworzenia trójwymiarowej grafiki i animacji. W swojej pierwszej odsłonie program ten ujrzał światło dzienne dzięki firmie Kinetix w 1990 roku. W kolejnych latach rozwoju tego projektu przejmowały go różne firmy, aż trafił w ręce giganta branży tworzenia tego typu aplikacji — firmy Autodesk Inc. 3ds Max jest szeroko stosowaną i chyba najbardziej rozpoznawalną aplikacją do tworzenia trójwymiarowych modeli, animacji oraz efektów specjalnych. Korzystają z niej artyści zajmujący się produkcją gier oraz filmów. Możliwości tego programu są naprawdę wielkie, o czym można się przekonać, czytając inne książki poświęcone 3ds Max. Program ten w wersji testowej można pobrać ze strony: http://www.autodesk.pl. Na rysunku 6.1 przedstawiono interfejs programu 3ds Max.
Rysunek 6.1. Interfejs programu 3ds Max
Maya Maya to kolejny profesjonalny program do tworzenia trójwymiarowej grafiki i animacji komputerowej. Aplikację tę stworzyły dwie ekipy, które były pionierami produkcji grafiki trójwymiarowej na potrzeby branży filmowej. Łącząc siły w 1995 roku, stworzyły firmę Alias, która w 1998 roku wydała aplikację Maya.
Rozdział 6. Modele i animacje
225
Z uwagi na swoją cenę program ten miał wąskie grono użytkowników. Po obniżeniu ceny z tego pakietu zaczęli korzystać również inni artyści tworzący obiekty do gier i aplikacji 3D. Program ten w wersji testowej można pobrać ze strony: http://www.autodesk.pl. Na rysunku 6.2 przedstawiono interfejs programu Maya.
Rysunek 6.2. Interfejs programu Maya
LightWave LightWave jest pakietem programów do tworzenia grafiki trójwymiarowej, stworzonym przez firmę NewTek. W swoich pierwszych wersjach przeznaczony był dla komputerów Amiga, a dopiero w 1995 roku opracowano wersje na komputery PC i Macintosh. Dzięki temu grono użytkowników z wąskiego otoczenia produkcji filmowej rozrosło się o grupy artystów tworzących obiekty i animacje na potrzeby gier, aplikacji i mniejszych przedsięwzięć filmowych. W tym programie zastosowano ciekawe rozwiązania pozwalające rozdzielić poszczególne elementy sceny 3D na warstwy, co pozwala na szybkie modyfikowanie wybranego elementu bez ingerowania w jego otoczenie. Sam interfejs z początku może sprawiać problemy, ale po przyzwyczajeniu staje się bardzo praktyczny. Istotne w tym programie jest również to, że umożliwia on korzystanie z wielu dodatkowych wtyczek poprawiających jakość tworzonej grafiki trójwymiarowej.
226
Flash i ActionScript. Aplikacje 3D od podstaw
Więcej informacji na temat tego programu oraz możliwość pobrania go w wersji testowej znajdziesz na stronie: https://www.lightwave3d.com. Na rysunku 6.3 przedstawiono interfejs programu LightWave Modeler.
Rysunek 6.3. Interfejs programu Lightwave Modeler
Blender Blender to program do tworzenia grafiki trójwymiarowej stworzony przez firmę NaN. W drugiej połowie 2002 roku firma NaN odsprzedała prawa do programu stowarzyszeniu Blender Foundation, które określoną sumę uzyskało z publicznej zbiórki. Od tego czasu Blender stał się całkowicie wolnym oprogramowaniem. Program Blender spośród innych aplikacji do tworzenia grafiki trójwymiarowej wyróżnia się nietypowym interfejsem, który z początku wydaje się skomplikowany, ale z czasem okazuje się bardzo intuicyjny. Poza tym Blender ma wbudowany silnik do tworzenia interaktywnych aplikacji 3D. Kolejnym atutem tego pakietu jest jego dostępność dla większości znanych systemów operacyjnych.
Rozdział 6. Modele i animacje
227
Więcej na temat programu Blender wraz z linkami do pobrania można znaleźć na stronie: http://www.blender.org. Na rysunku 6.4 przedstawiono interfejs programu Blender.
Rysunek 6.4. Interfejs programu Blender
MilkShape 3D MilkShape 3D jest aplikacją do tworzenia modeli złożonych z małej liczby wielokątów. W pierwotnej wersji program ten przeznaczony był do tworzenia obiektów do gry Half-Life. Z czasem dodano obsługę wielu różnych formatów, umożliwiając tworzenie nowych modeli dla różnych gier i aplikacji 3D. Program ten można pobrać ze strony: http://www.milkshape3d.com/. Na rysunku 6.5 przedstawiono interfejs programu MilkShape 3D.
228
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.5. Interfejs programu MilkShape 3D
CharacterFX CharacterFX jest darmowym narzędziem do tworzenia animacji modeli. Za pomocą specjalnych narzędzi oraz szkieletu animacji w łatwy sposób można nadać ruch postaci i zapisać jej poszczególne sekwencje. Aplikacja ta jest szczególnie dobrym rozwiązaniem dla twórców gier, którzy w tani i prosty sposób chcą stworzyć odpowiednie animacje. Program ten można pobrać ze strony: http://www.insanesoftware.de. Na rysunku 6.6 przedstawiono interfejs programu CharacterFX.
Rozdział 6. Modele i animacje
229
Rysunek 6.6. Interfejs programu CharacterFX
Google SketchUp Google SketchUp jest programem stworzonym przez firmę Google, który służy do tworzenia trójwymiarowych modeli. Aplikacja ta charakteryzuje się prostym interfejsem pozwalającym nawet początkującym użytkownikom kreować różnego rodzaju obiekty 3D. Jednym z głównych zastosowań tego programu jest tworzenie modeli w formatach KML i KMZ do map w programie Google Earth, ale można w nim również wyeksportować stworzony obiekt do innych popularnych formatów, takich jak 3DS, OBJ lub DAE. Program ten można pobrać ze strony: http://sketchup.google.com. Na rysunku 6.7 przedstawiono interfejs programu Google SketchUp.
230
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.7. Interfejs programu Google SketchUp
PreFab3D PreFab3D jest niewielką aplikacją napisaną przez ludzi związanych z projektem Away3D. Stworzono ją, by umożliwić edycję obiektów 3D, optymalizować je, tak aby w jak najlepszy sposób móc z nich korzystać w projektach bazujących na silnikach typu Away3D. Program ten ma kilka narzędzi umożliwiających stworzenie całej scenerii, światła, ścieżki i wielu innych elementów. Jeśli nie masz doświadczenia w projektowaniu w zaawansowanych programach 3D, stosując PreFab3D, w łatwy sposób możesz wykreować różne elementy do swoich aplikacji. Program ten można pobrać ze strony: http://closier.nl/prefab. Na rysunku 6.8 przedstawiono interfejs programu PreFab3D.
Rozdział 6. Modele i animacje
231
Rysunek 6.8. Interfejs programu PreFab3D
Low poly i High poly Terminem Low poly określa się modele, których siatka geometryczna składa się z małej liczby wielokątów. Z tego typu modeli korzysta się przede wszystkim w aplikacjach, które wyświetlają modele w czasie rzeczywistym, na przykład grach komputerowych. Modele o skomplikowanej budowie siatek częściej stosuje się do produkcji filmów. Komputery o potężnej mocy obliczeniowej wcześniej przetwarzają animacje bardzo szczegółowych obiektów i tworzą obrazy zapisywane w postaci plików wideo. Niełatwo dokładnie wskazać, kiedy zastosować model Low poly, a kiedy High poly. Jest to bardzo niekonkretne szczególnie w dzisiejszych czasach z uwagi na rozwój możliwości sprzętu komputerowego. Głównym powodem stosowania modeli Low poly jest optymalizacja działania aplikacji. Liczbę wielokątów wyświetlanych na scenie w jednej klatce ustalają możliwości sprzętu, na którym uruchomiona jest aplikacja, oraz silnik, na którym została stworzona. Dzisiejsze silniki gier komputerowych pozwalają na używanie bardzo szczegółowych trójwymiarowych obiektów, jednak wymaga to również wydajnego sprzętu. Gdy nastawiamy się na szersze grono odbiorców, lepiej zmniejszyć wymagania sprzętowe i stosować mniej szczegółowe modele oraz specjalne zabiegi kosmetyczne na teksturach. W przypadku aplikacji tworzonych do masowego użytku w sieci stosuje się obiekty Low poly. Sprawia to, że produkt jest bardziej dostępny dla różnych użytkowników.
232
Flash i ActionScript. Aplikacje 3D od podstaw
Format plików obsługiwanych w Away3D 3DS To format stworzony przez autorów programu Autodesk 3ds Max. Z uwagi na swój mały rozmiar i szybkość ładowania stał się jednym z najpowszechniejszych formatów. Dane przechowywane są w nim w postaci binarnej, a ich struktura blokowa przypomina tę stosowaną w języku XML. Należy wspomnieć tutaj jeszcze o tym, że 3DS obsługuje jedynie modele statyczne. Więcej informacji na temat tego formatu znajdziesz pod adresem: http://en.wikipedia.org/wiki/.3ds.
ASE Drugi format opracowany przez twórców Autodesk 3ds Max. W tym przypadku zawartość pliku zapisana jest w formie tekstowej. Oznacza to, że — w przeciwieństwie do formatu 3DS — ASE zapisany jest w zrozumiały dla człowieka sposób i można go otworzyć zwykłym edytorem tekstu.
OBJ To kolejny ze znanych formatów plików dla modeli 3D. Stworzony został przez firmę Wavefront Technologies. Format ten jest otwarty i dostępny w większości aplikacji do tworzenia statycznych modeli 3D. Zapisane w nim dane w postaci tekstowej określają geometrię obiektu oraz współrzędne uv dla tekstur. Więcej informacji na temat tego formatu znajdziesz pod adresami: http://www.martinreddy. net/gfx/3d/OBJ.spec lub http://en.wikipedia.org/wiki/Wavefront_.obj_file.
DAE Format COLLADA z rozszerzeniem *.dae został opracowany w firmie Sony Computer Entertainment. Obecnie jest własnością organizacji Khronos Group, do której należy również Sony Computer Entertainment. Pliki COLLADA najczęściej stosowane są do interaktywnych aplikacji 3D, takich jak gry komputerowe. Dane zapisane w tych plikach są w formacie XML. Jako pierwszy z wymienionych na tej liście umożliwia przechowywanie danych animacji obiektu. Więcej informacji na temat tego formatu znajdziesz pod adresem: http://collada.org.
Rozdział 6. Modele i animacje
233
MD2 Format stworzony i używany przez firmę id Software w silniku Tech 2. Najbardziej znaną aplikacją 3D, która korzysta z tego formatu, jest gra Quake 2. Głównym założeniem twórców było stworzenie optymalnego formatu dla animowanych modeli. Animacje obiektu zapisane są w formie poklatkowej. Każda z klatek zawiera odpowiednie współrzędne dla wszystkich punktów obiektu, dzięki temu przejście między animacjami jest bardzo płynne. Więcej informacji na temat tego formatu znajdziesz pod adresem: http://tfc.duke.free.fr/old/models/md2.htm.
AWD Wprowadzony przez twórców biblioteki Away3D format AWD służy do przedstawiania modeli statycznych. Prace nad tym formatem są wciąż prowadzone, dlatego można się spodziewać, że w przyszłości będzie możliwe umieszczanie w nim również animacji. W swoich założeniach AWD ma być formatem wieloplatformowym, obsługiwanym przez wszystkie wersje biblioteki Away3D. Więcej informacji na temat tego formatu znajdziesz pod adresem: http://code.google.com/p/awd.
AS Istnieje możliwość skonwertowania modeli poszczególnych formatów do plików ActionScript. Po wykonaniu takiego eksportu dane modelu zostaną zapisane jako zwykła klasa ActionScript 3.0. To, jak można przekonwertować model do klasy AS, zostanie omówione w dalszych częściach tego rozdziału.
Konwertowanie modelu do klasy ActionScript Klasa AS3Exporter Pierwszy sposób przerabiania plików na kod źródłowy ActionScript 3.0 polega na skorzystaniu z klasy AS3Exporter zlokalizowanej w pakiecie away3d.exporters. Zastosowanie tej klasy jest niezwykle proste, ponieważ wystarczy stworzyć jej obiekt i wywołać metodę export(). W argumentach tej metody podajemy wczytany obiekt jednego z obsługiwanych modeli, nazwę dla klasy i ścieżkę pakietu, w jakim ma się znajdować.
234
Flash i ActionScript. Aplikacje 3D od podstaw
Przy eksportowaniu modelu do klasy z użyciem AS3Exporter należy pamiętać o zastosowaniu detektora zdarzenia ExporterEvent.COMPLETE. Jeżeli nie wywołasz metody przy zajściu tego zdarzenia, pojawi się następujący komunikat: AS3Exporter Error: No ExporterEvent.COMPLETE event set. Use the method addOnExportComplete(myfunction) before use export();
W metodzie export() zapisany jest warunek, który sprawdza, czy został dodany detektor zdarzenia ExporterEvent.COMPLETE. Jeżeli programista w swoim kodzie przed uruchomieniem tej metody nie stworzył oczekiwanego detektora, to nie zostaną podjęte dalsze działania. To przykład dobrej praktyki sprawdzania, czy wykonane zostały wszystkie konieczne działania przed uruchomieniem metody. W tym przypadku twórcy tej klasy zadbali o poprawną kolejność działań, ale warto samemu pilnować sekwencji wykonywanych operacji. Dzięki temu można zaoszczędzić czas na szukaniu przyczyn błędnego działania kodu. Po poprawnie wykonanej operacji do źródła skonwertowanego modelu odwołujemy się przez umieszczoną w klasie AS3Exporter właściwość o nazwie as3File. Aby zobrazować tę metodę konwertowania, napiszemy prosty program, w którym pobrany plik modelu będziemy mogli zapisać w dowolnym miejscu jako plik klasy ActionScript 3.0. Na rysunku 6.9 przedstawiono okno naszego programu. Rysunek 6.9.
Okno programu konwertera modeli do klas ActionScript 3.0
Cały proces przebiega następująco: 1. Klikając na przycisk 1. Wybierz model, wybieramy plik jednego z dostępnych formatów plików. 2. Podajemy nazwę dla klasy oraz pakiet, w jakim będzie się znajdowała klasa z wygenerowanym modelem. 3. Klikając 3. Zapisz model jako klasę ActionScript, wybieramy lokalizację dla pliku. Powinna się ona zgadzać z podaną ścieżką pakietu. Nazwy pliku nie powinno się zmieniać, ponieważ musi się ona pokrywać z nazwą klasy.
Rozdział 6. Modele i animacje
235
W następującym kodzie źródłowym zastosowano instrukcję warunkową switch() wewnątrz metody onModelLoaded(). W każdym z jej warunków zastosowano klasy i metody przetwarzające pobrane dane z modelu. Omawianiem każdej z tych klas zajmiemy się w dalszych częściach tego rozdziału. Teraz przepisz następujący kod i zapisz go w pliku AS3ExporterExample.as. package { import import import import import import import import import
flash.display.Sprite; flash.events.Event; flash.events.IOErrorEvent; flash.events.MouseEvent; flash.net.FileReference flash.net.FileFilter; fl.controls.Label; fl.controls.Button; fl.controls.TextInput;
import away3d.exporters.AS3Exporter; import away3d.loaders.*; import away3d.events.Loader3DEvent; import away3d.events.ExporterEvent; public class AS3ExporterExample extends Sprite { private var selectBtn:Button; private var exportBtn:Button; private var status:Label; private var className:TextInput; private var classPackage:TextInput; private var modelReference:FileReference; private var as30Reference:FileReference; private var model:*; private var as3Export:AS3Exporter; public function AS3ExporterExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); selectBtn = new Button(); selectBtn.label = '1. Wybierz model'; selectBtn.x = 20; selectBtn.y = 20; selectBtn.width = 110; addChild(selectBtn); selectBtn.addEventListener(MouseEvent.CLICK, selectModel);
236
Flash i ActionScript. Aplikacje 3D od podstaw exportBtn = new Button(); exportBtn.label = '3. Zapisz model jako klasę ActionScript'; exportBtn.x = 20; exportBtn.y = 150; exportBtn.width = 214; addChild(exportBtn); exportBtn.addEventListener(MouseEvent.CLICK, exportModel); exportBtn.enabled = false; var step2Label:Label = new Label(); step2Label.text = '2. Wypełnij dane dla klasy'; step2Label.x = 20; step2Label.y = 55; step2Label.width = 330; addChild(step2Label); status = new Label(); status.text = 'Status:'; status.x = 20; status.y = 200; status.width = 330; addChild(status); className = new TextInput(); className.editable = true; className.text = 'Model'; className.x = 95; className.y = 85; className.width = 257; addChild(className); classPackage = new TextInput(); classPackage.editable = true; classPackage.text = 'models.as'; classPackage.x = 95; classPackage.y = 115; classPackage.width = 257; addChild(classPackage); var nameLabel:Label = new Label(); nameLabel.text = 'Nazwa klasy:'; nameLabel.x = 20; nameLabel.y = 87; nameLabel.width = 70; addChild(nameLabel); var packageLabel:Label = new Label(); packageLabel.text = 'Pakiet:'; packageLabel.x = 20; packageLabel.y = 117; packageLabel.width = 70; addChild(packageLabel);
Rozdział 6. Modele i animacje
237
} private function selectModel(e:MouseEvent):void { exportBtn.enabled = false; model = null; modelReference = new FileReference(); modelReference.addEventListener(Event.SELECT, onModelSelected); modelReference.browse( [ new FileFilter('3DS Max (*.3ds)', '*.3ds'), new FileFilter('Wavefront (*.obj)', '*.obj'), new FileFilter('3DS Max ASCII (*.ase)', '*.ase'), new FileFilter('Away3D (*.awd)', '*.awd'), new FileFilter('Collada (*.dae)', '*.dae'), new FileFilter('Quake 2 (*.md2)', '*.md2') ]); } private function onModelSelected(e:Event):void { modelReference.addEventListener(Event.COMPLETE, onModelLoaded); modelReference.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); modelReference.load(); } private function onModelLoaded(e:Event):void { modelReference.removeEventListener(Event.COMPLETE, onModelLoaded); modelReference.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); status.text = 'Status: Model pobrany.'; switch(modelReference.type.toLowerCase()) { case '.3ds': model = Max3DS.parse(modelReference.data); break; case '.obj': model = Obj.parse(modelReference.data); break; case '.ase': model = Ase.parse(modelReference.data); break; case '.awd': model = AWData.parse(modelReference.data); break; case '.dae': model = Collada.parse(modelReference.data); break;
238
Flash i ActionScript. Aplikacje 3D od podstaw case '.md2': model = Md2.parse(modelReference.data); break; } as3Export = new AS3Exporter(); as3Export.addOnExportComplete(as3ExportComplete); as3Export.export(model, className.text, classPackage.text); } private function as3ExportComplete(e:ExporterEvent):void { exportBtn.enabled = true; } private function exportModel(e:MouseEvent):void { as30Reference = new FileReference(); as30Reference.addEventListener(Event.COMPLETE, onModelExported); as30Reference.save(as3Export.as3File, className.text+'.as'); status.text = 'Status:...'; } private function onModelExported(e:Event):void { as30Reference.removeEventListener(Event.COMPLETE, onModelExported); status.text = 'Status: Model zapisany.'; } private function onLoadError(e:IOErrorEvent):void { status.text = 'Status: Wystąpił błąd podczas pobierania pliku.'; } } }
Aby zrealizować ten przykład, skorzystaliśmy z klas FileReference oraz FileFilter obsługujących okna dialogowe zapisu i odczytu plików. Jak pokazano na rysunku 6.9, stworzyliśmy prosty interfejs użytkownika złożony z dwóch przycisków: 1. Wybierz model o nazwie selectBtn oraz 3. Zapisz model jako klasę ActionScript nazwany exportBtn. Dodatkowo umieściliśmy dwa pola tekstowe: Nazwa klasy, do którego odwołujemy się przez obiekt o nazwie className, oraz pole Pakiet nazwane classPackage. Ich zastosowanie omówimy za chwilę. Poza standardowymi klasami ActionScript 3.0 użyliśmy też kilku klas biblioteki Away3D z pakietu away3d.loaders, klasy AS3Exporter oraz zdarzeń ExporterEvent i Loader3DEvent. W konstruktorze klasy AS3ExporterExample wywołujemy metodę init(), w której stworzyliśmy wszystkie obiekty panelu użytkownika przedstawione na rysunku 6.9.
Rozdział 6. Modele i animacje
239
Obiekt exportBtn ustawiliśmy jako niedostępny, dzięki temu nie będzie można wywołać metody exportModel() bez wcześniejszego wykonania pierwszego i drugiego kroku w aplikacji. W kolejnej metodzie selectModel(), wywoływanej po wciśnięciu przycisku selectBtn, stworzyliśmy obiekt klasy FileReference o nazwie modelReference. Służy on do wyświetlania okna dialogowego wyboru modelu i samego pobrania danych. Tworząc kilka klas FileFilter w metodzie browse() obiektu modelReference, ograniczyliśmy wyświetlanie modeli do określonych rodzajów. Dzięki temu unikniemy ewentualnych błędów spowodowanych wybraniem przez użytkownika pliku o nieobsługiwanym formacie. Poza tym dodaliśmy zdarzenie, które wywołuje metodę onModelSelected(), gdy z listy zostanie wybrany jakiś plik. exportBtn.enabled = false; model = null; modelReference = new FileReference(); modelReference.addEventListener(Event.SELECT, onModelSelected); modelReference.browse( [ new FileFilter('3ds Max (*.3ds)', '*.3ds'), new FileFilter('Wavefront (*.obj)', '*.obj'), new FileFilter('3ds Max ASCII (*.ase)', '*.ase'), new FileFilter('Away3D (*.awd)', '*.awd'), new FileFilter('Collada (*.dae)', '*.dae'), new FileFilter('Quake 2 (*.md2)', '*.md2') ]);
W metodzie onModelSelected() dodaliśmy detektory zakończenia pobierania Event.COMPLETE oraz wystąpienia błędu związanego z operacją wejścia lub wyjścia IOErrorEvent.IO_ERROR. Następnie wywołaliśmy proces ładowania zawartości wybranego pliku, stosując metodę load(). modelReference.addEventListener(Event.COMPLETE, onModelLoaded); modelReference.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); modelReference.load();
To, co nas na chwilę obecną interesuje w metodzie onModelLoaded(), to proces tworzenia obiektu klasy AS3Exporter. Po poprawnym załadowaniu modelu i jego przetworzeniu stworzyliśmy obiekt o nazwie as3Export. W argumentach metody export() podaliśmy wcześniej przetworzony obiekt modelu, podaną w polu tekstowym className nazwę dla klasy oraz ścieżkę pakietu zapisaną w polu classPackage. Poza tym dodaliśmy metodę addOnExportComplete(), aby uniknąć wyświetlenia wcześniej wspomnianego komunikatu błędu. as3Export = new AS3Exporter(); as3Export.addOnExportComplete(as3ExportComplete); as3Export.export(model, className.text, classPackage.text);
240
Flash i ActionScript. Aplikacje 3D od podstaw
Po poprawnym skonwertowaniu modelu do klasy ActionScript 3.0 w metodzie as3ExportComplete() odblokowaliśmy przycisk exportBtn, tak aby użytkownik mógł zapisać stworzoną klasę w postaci pliku ActionScript. exportBtn.enabled = true;
W metodzie exportModel(), uruchamianej po wciśnięciu przycisku exportBtn, stworzyliśmy w pierwszej kolejności kolejny obiekt klasy FileReference o nazwie as30Reference, który potrzebny jest do uruchomienia okna dialogowego zapisu pliku. Następnie dodaliśmy do niego detektor zdarzenia Event.COMPLETE, który po prawidłowym zakończeniu operacji zapisu pliku uruchomi metodę onModelExported(). Do zapisania wygenerowanej klasy do postaci pliku zastosowaliśmy metodę save(), podając w jej pierwszym argumencie odwołanie do zawartości obiektu as3Export. W następnym argumencie tej metody ustawiliśmy domyślną nazwę pliku, odpowiadającą nazwie klasy, wraz z rozszerzeniem .as. as30Reference = new FileReference(); as30Reference.addEventListener(Event.COMPLETE, onModelExported); as30Reference.save(as3Export.as3File, className.text+'.as'); status.text = 'Status: ...';
W tabelach 6.1 oraz 6.2 wypisano właściwości oraz metody klasy AS3Exporter. Tabela 6.1. Właściwości klasy AS3Exporter
Nazwa
Rodzaj
as3File
String
Wartość domyślna Opis Zwraca jako obiekt typu String ostatnio wygenerowaną klasę, której kod zawiera dane modelu przekonwertowane na język ActionScript 3.0
Tabela 6.2. Metody klasy AS3Exporter
Nazwa
Opis
AS3Exporter()
Konstruktor
addOnExportComplete(listener:Function):void
Metoda dodająca detektor dla zdarzenia ExporterEvent.COMPLETE
export(object3d:Object3D, classname:String, packagename:String):void
Przetwarza podany obiekt modelu do postaci klasy ActionScript 3.0 o podanej nazwie i pakiecie
removeOnExportComplete(listener:Function):void
Metoda usuwająca detektor zdarzenia ExporterEvent.COMPLETE
Rozdział 6. Modele i animacje
241
Eksport z programu PreFab3D W poprzednim podrozdziale poznaliśmy program PreFab3D i wspomnieliśmy o tym, że ma on wiele przydatnych funkcji, jeśli chodzi o aplikacje korzystające z biblioteki Away3D. Jedną z możliwości, jakie oferuje PreFab3D, jest eksportowanie modelu do klasy ActionScript 3.0 i zapisanie jej w postaci pliku. Aby zmienić wybrany plik modelu na klasę ActionScript, stosując PreFab3D, w pierwszej kolejności należy go zaimportować na scenę programu. W tym celu wybieramy w górnym menu zakładkę File i klikamy na jedną z opcji: Import Recent 3D Model bądź Import 3D model, tak jak to pokazano na rysunku 6.10. Pierwsza opcja odwołuje się do modeli, które wcześniej były już importowane, a druga do nowych. Rysunek 6.10.
Importowanie pliku modelu
Po wyborze i dodaniu pliku na scenie programu PreFab3D pojawi się model pokryty białym kolorem oraz siatką. Aby dodać teksturę do poszczególnych elementów modelu, należy kliknąć na obiekt. Wtedy wokół niego pojawią się linie formujące sześcian oraz strzałki do przesuwania. Po wyborze fragmentu należy kliknąć w pierwszą ikonę znajdującą się po prawej stronie, pod białym kwadratem o nazwie Original. Na rysunku 6.11 zaprezentowano, jak powinno wyglądać wybranie elementu modelu, oraz zaznaczono ikonę dodawania tekstury. Po wybraniu odpowiednich tekstur dla poszczególnych elementów modelu będzie można zobaczyć go na scenie w pełnej okazałości, tak jak to pokazano na rysunku 6.12.
242
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.11. Zaznaczenie fragmentu modelu i ikony wyboru tekstury
Rysunek 6.12. Model z osadzonymi na nim teksturami
Kiedy model jest już gotowy, można przystąpić do jego eksportu. W tym celu należy kliknąć na opcję Export w górnym menu i wybrać opcję Export AS3 class. W nowej liście wyświetlą się możliwe formy zapisu — w zależności od celu i wersji biblioteki Away3D. Między innymi można wyeksportować model dla biblioteki Away3DLite
Rozdział 6. Modele i animacje
243
oraz specjalną wersję klasy dla modelu w formacie MD2. Nas interesuje teraz zwykłe zapisanie modelu jako klasy dla silnika Away3D, dlatego należy wybrać opcję Away3D, tak jak to pokazano na rysunku 6.13. Rysunek 6.13.
Eksportowanie modelu do klasy ActionScript 3.0
Po wybraniu tej opcji pojawi się nowe okno zawierające ustawienia dla tworzonej klasy, w którym należy wybrać wersję biblioteki Away3D. Podobnie jak w naszym programie, trzeba również wybrać nazwę klasy i ścieżkę do jej pakietu. Na końcu trzeba kliknąć przycisk Save file, aby wybrać miejsce docelowe klasy. Na rysunku 6.14 przedstawiono okno ustawień dla eksportowanej w programie PreFab3D klasy ActionScript 3.0. Rysunek 6.14.
Okno ustawień eksportu do klasy ActionScript 3.0
Po wykonaniu wszystkich czynności plik pojawi się w wybranej lokalizacji i będzie gotowy do użycia w kodzie. Przy okazji warto również zapoznać się z możliwościami eksportu modelu do pliku AWD. Jeśli masz wiedzę na temat eksportu do pliku AS, nie powinno to stanowić problemu.
244
Flash i ActionScript. Aplikacje 3D od podstaw
Eksport z programu Blender Jeżeli tworzysz modele, posługując się programem Blender, to dobrą wiadomością dla Ciebie będzie to, że można również, stosując ten program, wyeksportować pliki w postaci klasy ActionScript 3.0. Do tego celu niezbędny jest dodatkowy plugin, który można znaleźć pod tym adresem: http://www.rozengain.com/files/blog/ blender-export/AS3Export.zip. Zawartość paczki wypakuj do katalogu scripts programu Blender, następnie uruchom program. Aby sprawdzić, czy skrypt został dodany poprawnie, kliknij na opcję File w górnym menu i wybierz Export. W nowej liście pomiędzy innymi pluginami powinna się znajdować również opcja ActionScript 3.0 Class (.as)…, tak jak to przedstawiono na rysunku 6.15.
Rysunek 6.15. Metody eksportu w programie Blender wraz z opcją ActionScript 3.0
Jeżeli w swoim programie nie widzisz takiej opcji, sprawdź, czy w dobrym miejscu umieściłeś zawartość pobranego archiwum. Aby zlokalizować katalog scripts w programie Blender, kliknij w górnym menu przycisk Help i wybierz opcję System,
Rozdział 6. Modele i animacje
245
z kolei w nowym panelu menu kliknij przycisk System Infomation…, tak jak to pokazano na rysunku 6.16. Rysunek 6.16.
Lokalizacja opcji otwarcia okna informacji o systemie
Na ekranie wyświetli się napis: Please check the text system-info.txt in the Text Editor Window. Wybierz edytor tekstu, klikając w menu wyboru widoku, jak na rysunku 6.17. Rysunek 6.17.
Menu wyboru widoku w programie Blender
Następnie z menu kontekstowego wybierz plik system-info.txt, jak to pokazano na rysunku 6.18. W wyświetlonym pliku znajdziesz informacje o lokalizacji katalogu scripts w swoim systemie.
246
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.18. Wybór pliku tekstowego
Po poprawnym zaimportowaniu skryptu w celu wyeksportowania wybranego modelu kliknij ponownie przycisk ActionScript 3.0 Class (.as)… na liście Export w panelu opcji File. Pojawi się nowe okno, w którym należy podać nazwę pakietu dla klasy, rodzaj silnika oraz wybrać lokalizację pliku. Po wypełnieniu wszystkich pól należy kliknąć przycisk Export. Jeżeli operacja zakończy się pomyślnie, to pojawi się napis: Export Successful. Na rysunku 6.19 przedstawiono okno pluginu eksportu modeli do plików ActionScript 3.0. Rysunek 6.19.
Okno pluginu do eksportu modelu w postaci ActionScript 3.0
Plugin ten jest uniwersalny i można z niego korzystać również w innych bibliotekach 3D dostępnych na platformę Adobe Flash. W liście wyboru bibliotek wypisane są kompatybilne wersje różnych silników 3D.
Eksport z programu Autodesk 3ds Max Dla użytkowników, którzy mają program z serii 3ds Max firmy Autodesk, również mam dobre wieści. Otóż nie musicie specjalnie korzystać z innych programów, by konwertować swoje modele do postaci kodu klasy języka ActionScript 3.0. Dzięki skryptowi AS3GeomExporter, który dostępny jest na stronie: http://not-so-stupid.
Rozdział 6. Modele i animacje
247
com/open-source/as3-geom-exporter-english, można bez problemu wyeksportować zaznaczone elementy do pliku *.as. Uruchomienie pluginu w 3ds Max jest znacznie prostsze niż w programie Blender, ponieważ wystarczy jedynie przeciągnąć i upuścić wybrany plik z rozszerzeniem *.ms w oknie programu 3ds Max. Można również skorzystać z opcji Run Script… w liście MAXScript znajdującej się w górnym menu programu. Po kliknięciu w przycisk na ekranie pojawi się okno dialogowe, w którym należy wybrać lokalizację skryptu i go otworzyć. Na rysunku 6.20 pokazano drugi sposób włączania pliku AS3GeomExporter.ms. Rysunek 6.20.
Uruchomienie skryptu przez użycie opcji Run Script
Po uruchomieniu skryptu na ekranie pojawi się okno z opcjami do eksportu. W pierwszej kolejności znajdują się tam pola, w których należy podać ścieżkę pakietu oraz nazwę dla klasy. Pod nimi z menu wyboru należy wybrać bibliotekę 3D, do której klasa będzie przeznaczona. Ciekawa jest możliwość ustawienia skali — dzięki temu nie trzeba będzie zmieniać jej w kodzie aplikacji przy odwołaniu do obiektu tworzonej klasy. Plugin AS3GeomExporter ma również dwie opcje optymalizujące eksportowany model. Pierwsza z nich to Swap face normal. Zaznaczenie tej opcji niweluje problem odwróconych trójkątów. Z kolei zaznaczenie opcji Rounded vertex coord powoduje zaokrąglenie współrzędnych wszystkich wierzchołków modelu. Na rysunku 6.21 przedstawiono okno pluginu z przykładowymi ustawieniami.
248
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.21.
Okno pluginu AS3GeomExporter
Stosowanie modeli 3D w Away3D Do tej pory poznaliśmy różnego rodzaju modele obsługiwane w bibliotece Away3D oraz sposoby zamiany ich na kod klasy ActionScript 3.0. Przyszedł czas na omówienie klas służących do ich ładowania i umieszczania na scenie aplikacji. W przykładzie konwertowania modeli wspominałem, żeby nie zwracać uwagi na instrukcję warunkową switch wewnątrz metody onModelLoaded(). W jej wnętrzu użyliśmy dostępnych w Away3D klas do zbadania zawartości pobranych modeli. Każdą z tych klas omówimy w tym podrozdziale, ale zanim do tego dojdziemy, przerobimy przykład konwertowania modeli tak, by mogły wyświetlać na scenie wybrane modele.
Przykładowa aplikacja Celem przykładowej aplikacji będzie umożliwienie użytkownikowi wybierania modelu z dowolnej lokalizacji na jego dysku i wyświetlania go w oknie programu. Aby ustalić poszczególne opcje wyświetlania, stworzymy panel użytkownika z przyciskami pomagającymi kontrolować wygląd modelu. Poza samą możliwością ładowania pliku modelu dodamy opcję umieszczania na jego powierzchni wybranej tekstury. Poza tym dodamy również pole wyboru materiałów Wireframe Material, WireColorMaterial oraz BitmapMaterial, tak aby sprawdzić, jak prezentuje się geometria modelu.
Rozdział 6. Modele i animacje
249
Pamiętając o tym, że każdy z modeli ma różnie zdefiniowane wymiary, dodamy przyciski kontrolujące skalę wgranego modelu. Jak wiemy z poprzednich punktów tego rozdziału, Away3D akceptuje dwa rodzaje modeli zawierających animacje. Z tego powodu do panelu dodamy przycisk, który uruchomi dostępne w wybranym modelu sekwencje ruchu. Jeżeli użytkownik wybierze format modelu statycznego, to przycisk ten będzie wyłączony. Stwórz plik ModelsPanel.as i przepisz następujący kod źródłowy: package { import import import import import import import import
flash.display.Sprite; flash.events.Event; flash.events.MouseEvent; fl.core.UIComponent; fl.controls.Label; fl.controls.Button; fl.controls.ComboBox; fl.data.DataProvider;
public class ModelsPanel extends Sprite { public function ModelsPanel() { var step1Label:Label = new Label(); step1Label.text = '1. Wybierz plik modelu'; step1Label.x = 10; step1Label.y = 10; step1Label.width = 190; addChild(step1Label); var modelSelectButton:Button = new Button(); modelSelectButton.name = "modelSelect"; modelSelectButton.label = "Wybierz model"; modelSelectButton.x = 15; modelSelectButton.y = 27; modelSelectButton.width = 100; addChild(modelSelectButton); modelSelectButton.addEventListener(MouseEvent.CLICK, buttonClick); var step2Label:Label = new Label(); step2Label.text = '2. Ręcznie wybierz teksturę modelu'; step2Label.x = 10; step2Label.y = 56; step2Label.width = 190; addChild(step2Label); var selectTextureButton:Button = new Button(); selectTextureButton.name = "selectTexture"; selectTextureButton.label = "Wybierz teksturę";
250
Flash i ActionScript. Aplikacje 3D od podstaw selectTextureButton.x = 15; selectTextureButton.y = 73; selectTextureButton.width = 100; selectTextureButton.enabled = false; addChild(selectTextureButton); selectTextureButton.addEventListener(MouseEvent.CLICK, buttonClick); var step3Label:Label = new Label(); step3Label.text = '3. Wgraj model'; step3Label.x = 10; step3Label.y = 103; addChild(step3Label); var loadModelButton:Button = new Button(); loadModelButton.name = "loadModel"; loadModelButton.label = "Wczytaj model"; loadModelButton.x = 15; loadModelButton.y = 121; loadModelButton.width = 100; loadModelButton.enabled = false; addChild(loadModelButton); loadModelButton.addEventListener(MouseEvent.CLICK, buttonClick); var dp:DataProvider = new DataProvider(); dp.addItem( { label: 'WireframeMaterial', data: 'WireframeMaterial' } ); dp.addItem( { label: 'WireColorMaterial', data: 'WireColorMaterial' } ); dp.addItem( { label: 'BitmapMaterial', data: 'BitmapMaterial' } ); var materialSelect:ComboBox = new ComboBox(); materialSelect.name = 'materialSelect'; materialSelect.enabled = false; materialSelect.dataProvider = dp; materialSelect.width = 150; materialSelect.x = 15; materialSelect.y = 163; addChild(materialSelect); materialSelect.addEventListener(Event.CHANGE, materialChange); var playAnimationButton:Button = new Button(); playAnimationButton.name = "playAnimation"; playAnimationButton.label = "Włącz animację"; playAnimationButton.x = 15; playAnimationButton.y = 193; playAnimationButton.width = 100; playAnimationButton.enabled = false; addChild(playAnimationButton); playAnimationButton.addEventListener(MouseEvent.CLICK, buttonClick); var scaleLabel:Label = new Label(); scaleLabel.text = 'Skala modelu';
Rozdział 6. Modele i animacje
251
scaleLabel.x = 10; scaleLabel.y = 225; addChild(scaleLabel); var scaleLessButton:Button = new Button(); scaleLessButton.name = "scaleLess"; scaleLessButton.label = "-"; scaleLessButton.x = 15; scaleLessButton.y = 245; scaleLessButton.width = 30; scaleLessButton.enabled = false; addChild(scaleLessButton); scaleLessButton.addEventListener(MouseEvent.CLICK, buttonClick); var scaleMoreButton:Button = new Button(); scaleMoreButton.name = "scaleMore"; scaleMoreButton.label = "+"; scaleMoreButton.x = 55; scaleMoreButton.y = 245; scaleMoreButton.width = 30; scaleMoreButton.enabled = false; addChild(scaleMoreButton); scaleMoreButton.addEventListener(MouseEvent.CLICK, buttonClick); } private function materialChange(e:Event):void { dispatchEvent(new Event(e.target.value + 'Event')); } private function buttonClick(e:MouseEvent):void { dispatchEvent(new Event(e.target.name + 'Event')); } public function setAvailability(objectName:String, enabled:Boolean):void { if (getChildByName(objectName) is UIComponent) { (getChildByName(objectName) as UIComponent).enabled = enabled; } } public function setLabel(labelName:String,val:String):void { if (getChildByName(labelName) is Button) { (getChildByName(labelName) as Button).label = val; } }
252
Flash i ActionScript. Aplikacje 3D od podstaw public function draw(_width:Number, _height:Number, _color:uint = 0xF1F1F1, _alpha:int = 1):void { graphics.clear(); graphics.beginFill(_color,_alpha); graphics.drawRect(0, 0, _width, _height); graphics.endFill(); } } }
Mając gotowy plik ModelsPanel.as, możemy zająć się główną klasą tego przykładu. Przepisz następujący kod źródłowy do pliku ModelsExample.as, a następnie omówimy jego ważniejsze fragmenty. package { import import import import import import import import import import import import import import import import import import import import import import
flash.display.Bitmap; flash.display.Loader; flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.IOErrorEvent; flash.events.MouseEvent; flash.geom.Vector3D; flash.net.FileReference flash.net.FileFilter; away3d.containers.View3D; away3d.containers.ObjectContainer3D; away3d.materials.BitmapMaterial; away3d.materials.WireColorMaterial; away3d.materials.WireframeMaterial; away3d.core.base.Mesh; away3d.core.base.Object3D; away3d.loaders.data.AnimationData; away3d.loaders.*; away3d.events.Loader3DEvent;
public class ModelsExample extends Sprite { private var panel:ModelsPanel; private var view:View3D; private var model:ObjectContainer3D; private var mat:BitmapMaterial; private var modelReference:FileReference; private var textureReference:FileReference; private var autoTexture:Boolean = true; private var animations:AnimationData; public function ModelsExample():void
Rozdział 6. Modele i animacje { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; removeEventListener(Event.ADDED_TO_STAGE, init); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); addPanel(); onResize(); } private function addPanel():void { panel = new ModelsPanel(); addChild(panel); panel.addEventListener('modelSelectEvent', onPanelEvent); panel.addEventListener('selectTextureEvent', onPanelEvent); panel.addEventListener('loadModelEvent', onPanelEvent); panel.addEventListener('playAnimationEvent', onPanelEvent); panel.addEventListener('scaleLessEvent', onPanelEvent); panel.addEventListener('scaleMoreEvent', onPanelEvent); panel.addEventListener('WireframeMaterialEvent', onPanelEvent); panel.addEventListener('WireColorMaterialEvent', onPanelEvent); panel.addEventListener('BitmapMaterialEvent', onPanelEvent); } private function onPanelEvent(e:Event):void { var mesh:Mesh; switch(e.type) { case 'modelSelectEvent': { if (model && model.children.length > 0) { for each (var child:Mesh in model.children) { model.removeChild(child); } } mat = null; panel.setAvailability('selectTexture', false); panel.setAvailability('loadModel', false); panel.setAvailability('materialSelect', false); panel.setAvailability('playAnimation', false); panel.setAvailability('scaleMore', false); panel.setAvailability('scaleLess', false);
253
254
Flash i ActionScript. Aplikacje 3D od podstaw
modelReference = new FileReference(); modelReference.addEventListener(Event.SELECT, onModelSelected); modelReference.browse( [ new FileFilter('3DS Max (*.3ds)', '*.3ds'), new FileFilter('Wavefront (*.obj)', '*.obj'), new FileFilter('3DS Max ASCII (*.ase)', '*.ase'), new FileFilter('Away3D (*.awd)', '*.awd'), new FileFilter('Collada (*.dae)', '*.dae'), new FileFilter('Quake 2 (*.md2)', '*.md2') ]); break; } case 'selectTextureEvent': { textureReference = new FileReference(); textureReference.addEventListener (Event.SELECT, onTextureSelected); textureReference.browse( [ new FileFilter('*.jpg', '*.jpg'), new FileFilter('*.bmp', '*.bmp'), new FileFilter('*.png', '*.png'), new FileFilter('*.tga', '*.tga') ]); break; } case 'loadModelEvent': { modelReference.addEventListener (Event.COMPLETE, onModelLoaded); modelReference.addEventListener (IOErrorEvent.IO_ERROR, onLoadError); modelReference.load(); break; } case 'playAnimationEvent': { if (animations && animations.animator.isPlaying) { panel.setLabel('playAnimation','Włącz animację'); animations.animator.gotoAndStop(0); } else { animations = (modelReference.type.toLowerCase() == '.dae') ? model.animationLibrary.getAnimation('default') : model. children[0].animationLibrary.getAnimation('default'); if (animations) {
Rozdział 6. Modele i animacje panel.setLabel('playAnimation','Wyłącz animację'); animations.animator.play(); } } break; } case 'scaleLessEvent': { if (model.scaleX > 1) { model.scaleX--; model.scaleY--; model.scaleZ--; } break; } case 'scaleMoreEvent': { model.scaleX++; model.scaleY++; model.scaleZ++; break; } case 'WireframeMaterialEvent': { for each(mesh in model.children) { mesh.material = new WireframeMaterial(0x000000); } break; } case 'WireColorMaterialEvent': { for each(mesh in model.children) { mesh.material = new WireColorMaterial(0xFF0000, { wireColor:0x000000 } ); } break; } case 'BitmapMaterialEvent': { for each(mesh in model.children) { mesh.material = mat; } break; } default:break; } } public function onModelSelected(e:Event):void
255
256
Flash i ActionScript. Aplikacje 3D od podstaw { autoTexture = true; panel.setAvailability('selectTexture', true); panel.setAvailability('loadModel', true); } public function onTextureSelected(e:Event):void { autoTexture = false; textureReference.addEventListener(Event.COMPLETE, onTextureLoaded); textureReference.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); textureReference.load(); } private function onLoadError(e:IOErrorEvent):void { trace(e); } private function loadModel(e:MouseEvent):void { modelReference.addEventListener(Event.COMPLETE, onModelLoaded); modelReference.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); modelReference.load(); } private function onModelLoaded(e:Event):void { switch(modelReference.type.toLowerCase()) { case '.3ds': model = Max3DS.parse(modelReference.data, { autoLoadTextures: (autoTexture ? true : false) } ) as ObjectContainer3D; break; case '.obj': model = Obj.parse(modelReference.data, { autoLoadTextures: (autoTexture ? true : false) } ) as ObjectContainer3D; break; case '.ase': var ase:Mesh = Ase.parse(modelReference.data, { scale:.01 } ); model = new ObjectContainer3D(); model.addChild(ase); break; case '.awd': var awd:Mesh = AWData.parse(modelReference.data) as Mesh; model = new ObjectContainer3D(); model.addChild(awd); break; case '.dae':
Rozdział 6. Modele i animacje
257
model = Collada.parse(modelReference.data, { autoLoadTextures: (autoTexture ? true : false) } ); panel.setAvailability('playAnimation', true); break; case '.md2': var md2:Mesh = Md2.parse(modelReference.data, { scale:0.01 } ); model = new ObjectContainer3D(); model.addChild(md2); panel.setAvailability('playAnimation', true); break; } panel.setAvailability('selectTexture', false); panel.setAvailability('loadModel', false); panel.setAvailability('materialSelect', true); panel.setAvailability('scaleMore', true); panel.setAvailability('scaleLess', true); if (!autoTexture) { for each(var mesh:Mesh in model.children) { mesh.material = mat; } } view.scene.addChild(model); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onTextureLoaded(e:Event):void { var imgLoader:Loader = new Loader(); imgLoader.loadBytes(textureReference.data); imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgBytesLoaded); } private function imgBytesLoaded(e:Event):void { mat = new BitmapMaterial(Bitmap(e.target.content).bitmapData); } private function onResize(e:Event = null):void { panel.draw(200, stage.stageHeight); view.x = panel.width + (stage.stageWidth - panel.width) *.5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { if (model) {
258
Flash i ActionScript. Aplikacje 3D od podstaw model.rotationY--; view.render(); } } } }
Ponieważ celem tej aplikacji jest wyświetlenie modelu na scenie, musieliśmy dodać między innymi klasy View3D, Loader3D, Mesh oraz kilka klas materiałów. Modele o rozszerzeniach MD2 oraz DAE uwzględniają również animacje, dlatego do ich odczytania potrzebowaliśmy klasy AnimationData. Ze standardowych klas ActionScript 3.0 zaimportowaliśmy dodatkowo klasy nadające ustawienia obiektowi stage, tak aby wyświetlane elementy znajdowały się w odpowiednim miejscu i płynnie funkcjonowały. W klasie ModelsExample zdefiniowaliśmy kilka obiektów potrzebnych do pobrania i prezentacji modeli. W pierwszej kolejności trzeba wybrany plik załadować, dlatego zdefiniowaliśmy obiekt modelReference, który posłużył do tego celu. Aby dać użytkownikowi możliwość nałożenia na model konkretnej tekstury, dodaliśmy również kolejny obiekt klasy FileReference o nazwie textureReference. Niektóre modele mają wewnątrz swoich danych odwołania do konkretnych plików graficznych, dlatego nie zawsze będzie konieczne wgrywanie dodatkowych. W sytuacji gdy użytkownik zdecyduje się na użycie konkretnej tekstury, stosując zmienną autoTexture, zmienimy wartość właściwości autoLoadTextures na false. W przeciwnym razie — gdy nie wybierze nowej tekstury — wartość ta będzie ustawiona standardowo jako true i wymusi pobieranie ewentualnie zapisanych wewnątrz tekstur. Aby przedstawić model pokryty wybranym przez użytkownika plikiem graficznym, zdefiniowaliśmy obiekt klasy BitmapMaterial o nazwie mat. Jeżeli wybrany model będzie obsługiwał animacje, to do ich włączenia posłuży obiekt animations klasy AnimationData. Sposób przechowywania pobranego modelu jest taki sam jak w poprzednim przykładzie, ale teraz zostanie on dodany do zawartości sceny. Główne ustawienia aplikacji oraz jej początkowy stan zapisaliśmy w metodzie init(). W pierwszej kolejności nadaliśmy nowe ustawienia obiektowi stage, a następnie stworzyliśmy widok Away3D oraz wywołaliśmy metody addPanel() i onResize(). W metodzie addPanel() stworzyliśmy obiekt klasy ModelsPanel i dodaliśmy go do listy wyświetlanych obiektów. panel = new ModelsPanel(); addChild(panel);
Rozdział 6. Modele i animacje
259
W dalszej części tej metody dodaliśmy detektory zdarzenia wciśnięcia przycisków umieszczonych w panelu użytkownika. Niezależnie od tego, które zdarzenie wystąpiło, wywoływana jest jedna metoda onPanelEvent(). panel.addEventListener('modelSelectEvent', onPanelEvent); panel.addEventListener('selectTextureEvent', onPanelEvent); panel.addEventListener('loadModelEvent', onPanelEvent); panel.addEventListener('playAnimationEvent', onPanelEvent); panel.addEventListener('scaleLessEvent', onPanelEvent); panel.addEventListener('scaleMoreEvent', onPanelEvent); panel.addEventListener('WireframeMaterialEvent', onPanelEvent); panel.addEventListener('WireColorMaterialEvent', onPanelEvent); panel.addEventListener('BitmapMaterialEvent', onPanelEvent);
Ponieważ metoda onPanelEvent() uruchamiana jest przez różne zdarzenia nadesłane z panelu użytkownika, musieliśmy zastosować w jej ciele instrukcję warunkową switch(). W przypadku przechwycenia zdarzenia typu modelSelectEvent w pierwszej kolejności usuwamy zawartość kontenera model, ponieważ przy jednym uruchomieniu aplikacji użytkownik może przypadkowo kilkakrotnie załadować modele. if (model && model.children.length > 0) { for each (var child:Mesh in model.children) { model.removeChild(child); } }
Po wyczyszczeniu zawartości kontenera przypisaliśmy wartość null właściwości mat i stosując metodę setAvailability(), wyłączyliśmy niepotrzebne na tym etapie przyciski w panelu użytkownika. mat = null; panel.setAvailability('selectTexture', false); panel.setAvailability('loadModel', false); panel.setAvailability('materialSelect', false); panel.setAvailability('playAnimation', false); panel.setAvailability('scaleMore', false); panel.setAvailability('scaleLess', false);
W dalszej części kodu dla warunku modelSelectEvent stworzyliśmy nowy obiekt modelReference, który służy do uruchomienia okna dialogowego wyboru modelu. W metodzie browse() dodaliśmy kilka obiektów klasy FileFilter, ograniczając rodzaje akceptowanych plików do stosowanych w Away3D typów modeli. Dzięki temu unikniemy problemów wywołanych załadowaniem danych złego formatu. Do obiektu modelReference dodaliśmy detektor zdarzenia Event.SELECT, który po wyborze konkretnego modelu uruchomi metodę onModelSelected().
260
Flash i ActionScript. Aplikacje 3D od podstaw modelReference = new FileReference(); modelReference.addEventListener(Event.SELECT, onModelSelected); modelReference.browse( [ new FileFilter('3DS Max (*.3ds)', '*.3ds'), new FileFilter('Wavefront (*.obj)', '*.obj'), new FileFilter('3DS Max ASCII (*.ase)', '*.ase'), new FileFilter('Away3D (*.awd)', '*.awd'), new FileFilter('Collada (*.dae)', '*.dae'), new FileFilter('Quake 2 (*.md2)', '*.md2') ]);
Celem metody onModelSelected() jest włączenie przycisków do wyboru tekstury oraz uruchomienia procesu ładowania modelu. Dodatkowo jeśli jest to kolejny pobierany model, na którym nie planujemy stosowania ręcznie wybranej tekstury, przypisujemy zmiennej autoTexture wartość true. Dzięki temu wartość właściwości autoLoadTextures będzie równa true i klasa ładująca sama pobierze tekstury zapisane w danych modelu. Kolejnym warunkiem zapisanym w metodzie onPanelEvent() jest selectTextureEvent, w którego bloku kodu stworzyliśmy nowy obiekt klasy FileReference o nazwie textureReference. Działanie tego obiektu jest podobne do modelReference, ale tutaj ładujemy określone w metodzie browse() rodzaje plików graficznych, które posłużą jako tekstura dla wybranego wcześniej modelu. Do obiektu textureReference dodaliśmy detektor zdarzenia Event.SELECT, który po wyborze konkretnego pliku uruchomi metodę onTextureSelected(). textureReference = new FileReference(); textureReference.addEventListener(Event.SELECT, onTextureSelected); textureReference.browse( [ new FileFilter('*.jpg', '*.jpg'), new FileFilter('*.bmp', '*.bmp'), new FileFilter('*.png', '*.png') ]);
Wywołanie metody onTextureSelected() spowoduje uruchomienie metody load() na obiekcie textureReference, która rozpocznie proces pobierania wybranej tekstury. Aby kontrolować efekt jej wywołania, zapisaliśmy również detektory zdarzeń Event.COMPLETE, gdy skończy pobieranie, oraz IOErrorEvent.IO_ERROR w razie wystąpienia błędu. Ponieważ w takim przypadku użytkownik zdecydował się sam wybrać teksturę, zmiennej autoTexture przypisaliśmy wartość false. Po pobraniu danych tekstury wywołana zostaje metoda onTextureLoaded(), w której zawartość textureReference.data jest dalej przetwarzana. Zanim tekstura będzie nadawała się do ułożenia na modelu, musieliśmy stworzyć obiekt klasy Loader,
Rozdział 6. Modele i animacje
261
w którym wywołaliśmy metodę loadBytes(), podając w jej argumencie texture Reference.data. Metoda ta pobiera dane z tablicy ByteArray źródła, potrzebne do stworzenia obiektu BitmapData. var imgLoader:Loader = new Loader(); imgLoader.loadBytes(textureReference.data); imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imgBytesLoaded);
Po wywołaniu metody loadBytes() obiektu imgLoader zapisaliśmy odwołanie do metody imgBytesLoaded(), która uruchamiana jest po poprawnym i kompletnym wczytaniu danych binarnych z obiektu textureReference. Wewnątrz metody imgBytesLoaded() zapisaliśmy tworzenie obiektu o nazwie mat, zawierającego pobraną teksturę. Obiekt ten jest typu BitmapMaterial, więc w argumencie konstruktora musieliśmy podać obiekt klasy BitmapData. Ponieważ zawartość obiektu imgLoader jest typu Bitmap, wystarczyło jedynie użyć właściwości bitmapData, aby uzyskać dane w postaci obiektu klasy BitmapData. mat = new BitmapMaterial(Bitmap(e.target.content).bitmapData);
Po wybraniu modelu oraz ewentualnej tekstury przycisk Wczytaj model staje się dostępny, a jego wciśnięcie wywołuje zdarzenie typu loadModelEvent. W instrukcji warunkowej switch(), zawartej w metodzie onPanelEvent(), w przypadku wystąpienia tego zdarzenia dodaliśmy dwa detektory zdarzeń określające, czy operacja pobierania danych modelu zakończyła się sukcesem, czy też wystąpił błąd typu ioError. Następnie wywołaliśmy metodę load() pobierającą dane modelu. W metodzie onModelLoaded() w zależności od wybranego rodzaju modelu wewnątrz instrukcji switch zastosowaliśmy klasy przetwarzające pobrane dane tak, aby były one użyteczne do stworzenia nowego obiektu i dodania go do sceny. Operacje zawarte w instrukcji warunkowej omówimy w kolejnych podpunktach, ale poza nią w ciele metody onModelLoaded() zapisaliśmy również inne działania. Aby zapobiec ewentualnym błędom, wyłączyliśmy przyciski Wybierz teksturę oraz Wczytaj model, a włączyliśmy te, które odpowiadają za zmianę materiału oraz zwiększenie i zmniejszenie skali modelu. panel.setAvailability('selectTexture', false); panel.setAvailability('loadModel', false); panel.setAvailability('materialSelect', true); panel.setAvailability('scaleMore', true); panel.setAvailability('scaleLess', true);
Dodatkowo w dwóch warunkach instrukcji switch, jeżeli model jest formatu MD2 bądź DAE, włączony zostaje również przycisk Włącz animację dzięki następującej linijce kodu: panel.setAvailability('playAnimation', true);
262
Flash i ActionScript. Aplikacje 3D od podstaw
Jeżeli wartość zmiennej autoTexture jest równa false, to w pętli for() każdemu elementowi pobranego modelu nadajemy wgraną teksturę. if (!autoTexture) { for each(var mesh:Mesh in model.children) { mesh.material = mat; } }
Na końcu tej metody dodaliśmy obiekt model do sceny. view.scene.addChild(model);
Przyciski - oraz + umieszczone w panelu użytkownika odpowiadają za regulowanie skali wgranego modelu. W zależności od pierwotnych wymiarów modelu, posługując się tymi przyciskami, można odpowiednio zmniejszyć lub zwiększyć trójwymiarowy obiekt. Proces zmiany skali wgranego modelu zapisano w metodzie onPanelEvent() jako dwa osobne warunki. Przycisk - wywołuje zdarzenie typu scaleLessEvent. if (model.scaleX > 1) { model.scaleX--; model.scaleY--; model.scaleZ--; }
Przycisk + wywołuje zdarzenie typu scaleMoreEvent. model.scaleX++; model.scaleY++; model.scaleZ++;
Jak wspomnieliśmy, w panelu użytkownika dodaliśmy również możliwość zmiany materiału wgranego modelu. Do dyspozycji mamy następujące rodzaje: Wireframe Material, WireColorMaterial oraz BitmapMaterial. W zależności od wybranej opcji obiektu typu ComboBox w metodzie onPanelEvent() uruchamiany jest odpowiedni blok kodu, w którym zapisaliśmy przypisanie wybranego materiału wszystkim elementom wgranego modelu. case 'WireframeMaterialEvent': { for each(mesh in model.children) { mesh.material = new WireframeMaterial(0x000000); } break; } case 'WireColorMaterialEvent':
Rozdział 6. Modele i animacje
263
{ for each(mesh in model.children) { mesh.material = new WireColorMaterial(0xFF0000, { wireColor: 0x000000 } ); } break; } case 'BitmapMaterialEvent': { for each(mesh in model.children) { mesh.material = mat; } break; }
3DS W przykładowej aplikacji tego podrozdziału skorzystaliśmy z klasy Max3DS, aby wyświetlić pobrany model 3DS. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: model = Max3DS.parse(modelReference.data, { autoLoadTextures:(autoTexture ? true : false) } ) as ObjectContainer3D;
Stosując metodę parse() klasy Max3DS na danych pobranych w obiekcie modelReference, utworzyliśmy obiekt typu ObjectContainer3D o nazwie model. Dodatkowo w drugim argumencie metody parse() sprawdzamy wartość zmiennej autoTexture. W zależności od wyniku właściwości autoLoadTextures przypisujemy wartości true bądź false. W niektórych modelach, nie tylko formatu 3DS, ścieżki do tekstur mogą być nieprecyzyjne. Dzieje się tak, gdy twórca modelu określi lokalizację tekstur bezpośrednio w swoich zasobach, na przykład w folderze, który w naszym systemie nie istnieje. W takich sytuacjach, jeżeli format modelu umożliwia edycję w edytorze tekstu, należy poszukać takiej ścieżki i dostosować ją do naszych ustawień. Sytuacja jest gorsza, jeżeli nie można edytować pliku modelu lub nie jesteśmy w stanie znaleźć odpowiedniego wpisu. Dlatego warto stosować właściwość autoLoadTextures i ręcznie załączać tekstury, aby uniknąć sytuacji, w których pojawią się sześciany z komunikatami o wystąpieniu błędu. Na rysunku 6.22 przedstawiono sytuację, w której ścieżki do modelu nie odpowiadają naszym ustawieniom i nie pobrano ręcznie tekstury.
264
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.22.
Komunikat błędu wgrywania tekstur wybranego modelu
Efekt zastosowania klasy Max3DS w przykładzie ModelsExample przedstawiono na rysunku 6.23. Rysunek 6.23.
Wgrany model w formacie 3DS
Istnieje jeszcze inny sposób ładowania modeli 3DS, polegający na stosowaniu klasy Loader3D oraz metody load() klasy Max3DS. Kod źródłowy dodawania modelu taką metodą wyglądałby następująco:
Rozdział 6. Modele i animacje
265
private function loadModel():void { loader = Max3DS.load('../../resources/models/max3ds/spaceship/ spaceship.3ds'); loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { model = loader.handle as Mesh; model.material = new BitmapFileMaterial('../../resources/models/ max3ds/spaceship/ spaceship.jpg'); view.scene.addChild(model); }
W pierwszej kolejności należy zaimportować odpowiednie klasy, aby powyższe metody działały poprawnie. W tym przypadku dodaliśmy klasy: Loader3D, Loader3D Event, Max3DS, Mesh oraz BitmapFileMaterial. Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy Max3DS, w której jako argument podaliśmy ścieżkę do pliku. Dopisaliśmy również detektor zdarzenia zakończenia pobierania, aby uruchomić metodę addModel(). Wewnątrz metody addModel() pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model. Do stworzonego obiektu dodaliśmy materiał, a następnie umieściliśmy go na scenie. Tabele 6.3 i 6.4 zawierają właściwości oraz metody obiektu klasy Max3DS. Tabela 6.3. Właściwości klasy Max3DS
Nazwa
Rodzaj
Wartość domyślna
Opis
meshDataList
Array
Tablica obiektów typu MeshData przechowujących przetworzone dane siatki modelu 3DS
scaling
Number
Określa skalę obiektu względem wszystkich osi
Tabela 6.4. Metody klasy Max3DS
Nazwa
Opis
Max3DS(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .3ds do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu ObjectContainer3D z danych binarnych pliku .3ds
266
Flash i ActionScript. Aplikacje 3D od podstaw
OBJ Do wyświetlenia modelu OBJ w naszym przykładzie użyliśmy klasy Obj. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: model = Obj.parse(modelReference.data, { autoLoadTextures:(autoTexture ? true : false) } ) as ObjectContainer3D;
Stosując metodę parse() klasy Obj na danych pobranych w obiekcie modelReference, utworzyliśmy obiekt typu ObjectContainer3D o nazwie model. Dodatkowo w drugim argumencie metody parse() sprawdzamy wartość właściwości autoTexture. Jeżeli wartość autoTexture równa jest true, to tekstury będą pobierane automatycznie z danych zawartych bezpośrednio w modelu. Efekt zastosowania klasy Obj w przykładzie ModelsExample przedstawiono na rysunku 6.24. Rysunek 6.24.
Wgrany model w formacie OBJ
Inna metoda pobierania modelu o rozszerzeniu OBJ polega na zastosowaniu klasy Loader3D oraz metody load() klasy Obj. Kod źródłowy w tym przypadku wyglądałby następująco: private function loadModel():void { loader = Obj.load('../../resources/models/mObj/tf/Pyro/pyro.dae');
Rozdział 6. Modele i animacje
267
loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { model = loader.handle as Mesh; model.material = new BitmapFileMaterial('../../resources/models/ mObj/tf/Pyro/pyro.jpg'); view.scene.addChild(model); }
Do poprawnego działania tych metod niezbędne są klasy: Loader3D, Loader3DEvent, Obj, Mesh oraz BitmapFileMaterial. Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy Obj, w której jako argument podaliśmy ścieżkę do pliku modelu. Dopisaliśmy również metodę addOnSuccess(), która wywołuje metodę addModel(), w chwili gdy model zostanie w całości pobrany. Działanie metody addOnSuccess() jest takie samo jak w przypadku zastosowania detektora zdarzenia Loader3DEvent.LOAD_SUCCESS. Wewnątrz metody addModel() pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model. Następnie dodaliśmy pobrany materiał i umieściliśmy obiekt na scenie. Tabele 6.5 i 6.6 zawierają właściwości oraz metody obiektu klasy Obj. Tabela 6.5. Właściwości klasy Obj
Nazwa
Rodzaj
Wartość domyślna Opis
scaling
Number
Określa skalę obiektu względem wszystkich osi
useGroups
Boolean
Definiuje, czy używać tagów grup do hierarchii obiektu
useMtl
Boolean
Definiuje, czy użyć pliku mtl do określenia tekstur obiektu
Tabela 6.6. Metody klasy Obj
Nazwa
Opis
Obj(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .obj do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu Object3D z danych binarnych pliku .obj
268
Flash i ActionScript. Aplikacje 3D od podstaw
ASE W bibliotece Away3D modele w formacie ASE obsługuje się za pomocą klasy Ase. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: var ase:Mesh = Ase.parse(modelReference.data, { scale:.01 } ); model = new ObjectContainer3D(); model.addChild(ase);
Stosując metodę parse() klasy Ase na danych pobranych w obiekcie modelReference, utworzyliśmy obiekt klasy Mesh o nazwie ase. Z uwagi na to, że model może mieć duże wymiary, dodaliśmy właściwość scale z nową wartością równą 0.01. Utworzony obiekt ase dodaliśmy do kontenera ObjectContainer3D o nazwie model. Efekt zastosowania klasy Ase w przykładzie ModelsExample przedstawiono na rysunku 6.25. Rysunek 6.25.
Wgrany model w formacie ASE
Modele ASE można również pobrać, stosując klasę Loader3D oraz metody load() klasy Ase. Dodawanie modelu tym sposobem przedstawiono w następującym kodzie źródłowym: private function loadModel():void {
Rozdział 6. Modele i animacje
269
loader = Ase.load('../../resources/models/ase/tf/spy/spy.ase'); loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { model = loader.handle as Mesh; view.scene.addChild(model); }
W obu tych metodach skorzystaliśmy z obiektów, których klasy należy wcześniej zaimportować, a są nimi: Loader3D, Loader3DEvent, Ase oraz Mesh. Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy Ase, w której jako argument podaliśmy ścieżkę do pliku. Dopisaliśmy również detektor zdarzenia zakończenia pobierania, aby uruchomić metodę addModel(). Wewnątrz tej metody pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model i umieściliśmy go na scenie. Tabele 6.7 i 6.8 zawierają właściwości oraz metody obiektu klasy Ase. Tabela 6.7. Właściwości klasy Ase
Nazwa
Rodzaj
Wartość domyślna
Opis
scaling
Number
1
Określa skalę obiektu względem wszystkich osi
Tabela 6.8. Metody klasy Ase
Nazwa
Opis
Ase(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .ase do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu Mesh z danych pliku .ase
DAE Modele w formacie DAE można zastosować w aplikacji poprzez klasę Collada. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: model = Collada.parse(modelReference.data, { autoLoadTextures:(autoTexture ? true : false) } );
Dane umieszczone w obiekcie modelReference zastosowaliśmy jako pierwszy argument dla metody parse() klasy Collada. Na ich podstawie utworzyliśmy obiekt typu ObjectContainer3D o nazwie model. Dodatkowo w drugim argumencie tej
270
Flash i ActionScript. Aplikacje 3D od podstaw
metody sprawdzamy wartość właściwości autoTexture. W zależności od tego, czy ręcznie dodamy teksturę, czy nie, wartość właściwości autoLoadTextures będzie równa true lub false. Efekt zastosowania klasy Collada w przykładzie ModelsExample przedstawiono na rysunku 6.26. Rysunek 6.26.
Wgrany model w formacie DAE
Drugi sposób pobierania modelu o rozszerzeniu DAE polega na zastosowaniu klasy Loader3D oraz metody load() klasy Collada. Zastosowanie tych klas przedstawiono w następującym kodzie źródłowym: private function loadModel():void { loader = Collada.load('../../resources/models/dae/tf/scout/scout.dae'); loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { model = loader.handle as Mesh; model.material = new BitmapFileMaterial('../../resources/models/dae/tf/scaut/scaut.jpg'); view.scene.addChild(model); }
Do poprawnego działania tych metod niezbędne są klasy: Loader3D, Loader3DEvent, Md2, Mesh oraz BitmapFileMaterial. Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy Collada, w której jako argument
Rozdział 6. Modele i animacje
271
podaliśmy ścieżkę do pliku. Dopisaliśmy również detektor zdarzenia zakończenia pobierania, aby uruchomić metodę addModel(). Wewnątrz metody addModel() pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model. Następnie dodaliśmy pobrany materiał i umieściliśmy obiekt na scenie. Tabele 6.9 i 6.10 zawierają właściwości oraz metody obiektu klasy Collada. Tabela 6.9. Właściwości klasy Collada
Nazwa
Rodzaj
Wartość domyślna
Opis
scaling
Number
1
Określa skalę obiektu względem wszystkich osi
shading
Boolean
false
Definiuje, czy używać materiałów cieniowania, gdy wykorzystane są materiały koloru
Tabela 6.10. Metody klasy Collada
Nazwa
Opis
Collada(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .dae do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu ObjectContainer3D z danych XML pliku .dae
MD2 W przykładowej aplikacji tego podrozdziału skorzystaliśmy z klasy Md2, aby wyświetlić pobrany model MD2. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: var md2:Mesh = Md2.parse(modelReference.data, { scale:0.01 } ); model = new ObjectContainer3D(); model.addChild(md2);
Podobnie jak w przypadku formatu ASE, tutaj również stworzyliśmy dwa obiekty. Pierwszemu o nazwie md2 przypisaliśmy zawartość obiektu modelReference. Do tego celu posłużyliśmy się metodą parse() z klasy Md2. Z uwagi na duże wymiary modeli w tym formacie dodatkowo zmieniliśmy skalę obiektu md2 na 0.01.
272
Flash i ActionScript. Aplikacje 3D od podstaw
Gotowy obiekt klasy Mesh dodaliśmy do zawartości kontenera klasy ObjectCon tainer3D. Efekt zastosowania klasy Md2 w przykładzie ModelsExample przedstawiono na rysunku 6.27. Rysunek 6.27.
Wgrany model w formacie MD2
Drugi sposób pobierania modelu o rozszerzeniu MD2 polega na zastosowaniu metody load() klasy Md2 oraz przypisaniu pobranej zawartości obiektowi klasy Loader3D. Kod źródłowy użycia tego sposobu wyglądałby następująco: private function loadModel():void { loader = Md2.load('../../resources/models/md2/ogro/tris.md2'); loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { model = loader.handle as Mesh; model.material = new BitmapFileMaterial('../../resources/models/md2/ogro/igdosh.jpg'); view.scene.addChild(model); }
Żeby ten kod był użyteczny, należy w pierwszej kolejności zaimportować odpowiednie klasy, w tym przypadku są nimi: Loader3D, Loader3DEvent, Md2, Mesh oraz BitmapFileMaterial.
Rozdział 6. Modele i animacje
273
Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy Md2, w której jako argument podaliśmy ścieżkę do pliku. Dopisaliśmy również detektor zdarzenia zakończenia pobierania, aby uruchomić metodę addModel(). Wewnątrz metody addModel() pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model. Następnie dodaliśmy pobrany materiał i umieściliśmy obiekt na scenie. Tabele 6.11 i 6.12 zawierają właściwości oraz metody obiektu klasy Md2. Tabela 6.11. Właściwości klasy Md2
Nazwa
Rodzaj
Wartość domyślna
Opis
scaling
Number
1
Określa skalę obiektu względem wszystkich osi
fps
Number
false
Definiuje, czy używać materiałów cieniowania, gdy wykorzystane są materiały koloru
pcxConvert
String
"jpg"
Określa rozszerzenie dla plików .pc7
Tabela 6.12. Metody klasy Md2
Nazwa
Opis
Md2(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .md2 do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu Mesh z danych binarnych pliku .md2
AWD Z modeli zapisanych w formacie AWD można skorzystać dzięki klasie AWData. W przypadku użycia modelu tego rodzaju w instrukcji warunkowej switch wewnątrz metody onModelLoaded() zastosowaliśmy następujący kod: var awd:Mesh = AWData.parse(modelReference.data) as Mesh; model = new ObjectContainer3D(); model.addChild(awd);
Stosując metodę parse() klasy AWData, stworzyliśmy obiekt klasy Mesh o nazwie awd, a następnie dodaliśmy go do kontenera ObjectContainer3D o nazwie model. Efekt zastosowania klasy AWData w przykładzie ModelsExample przedstawiono na rysunku 6.28.
274
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 6.28.
Wgrany model w formacie AWD
Alternatywnym sposobem na pobranie modelu typu AWD i korzystanie z niego w aplikacji jest zastosowanie klasy Loader3D oraz metody load() klasy AWData. Kod źródłowy użycia tego sposobu wyglądałby następująco: private function loadModel():void { loader = AWData.load('../../resources/models/awd/tf/heavy/Heavy.awd'); loader.addOnSuccess(addModel); } private function addModel(e:Loader3DEvent):void { var mat:BitmapFileMaterial = new BitmapFileMaterial('../../resources/models/awd/tf/heavy/images/aw_0.jpg'); model = loader.handle as Mesh; model.material = mat; view.scene.addChild(model); }
Żeby ten kod był użyteczny, należy w pierwszej kolejności zaimportować odpowiednie klasy, w tym przypadku są nimi: Loader3D, Loader3DEvent, AWData, Mesh oraz BitmapFileMaterial. Do pobrania modelu zastosowano obiekt klasy Loader3D o nazwie loader oraz metodę load() klasy AWData, w której jako argument podaliśmy ścieżkę do pliku. Dopisaliśmy również detektor zdarzenia zakończenia pobierania, aby uruchomić metodę addModel(). Wewnątrz metody addModel() pobraną zawartość obiektu loader zapisaliśmy jako obiekt klasy Mesh o nazwie model. Następnie dodaliśmy pobrany materiał i umieściliśmy obiekt na scenie. Tabele 6.13 i 6.14 zawierają właściwości oraz metody obiektu klasy AWData.
Rozdział 6. Modele i animacje
275
Tabela 6.13. Właściwości klasy AWData
Nazwa
Rodzaj
Opis
customPath
String
Określa ścieżkę do zasobów nieokreślonych wewnątrz pliku modelu
pathToSources
String
Ustala niestandardową ścieżkę do źródła zasobów. Wartość tej właściwości zastępuje standardowe odwołanie do katalogu images
url
String
Określa ścieżkę do pliku modelu
Tabela 6.14. Metody klasy AWData
Nazwa
Opis
AWData(init:Object = null)
Konstruktor
load(url:String, init:Object = null)
Ładuje i przetwarza plik .awd do obiektu Loader3D
parse(data:*, init:Object = null)
Tworzy obiekt typu Object3D z danych binarnych pliku .awd
Stosowanie animacji modeli W przykładzie pobierania modeli panel użytkownika zawiera przycisk Włącz animacje, który aktywny jest jedynie wtedy, gdy załadowany model jest formatu MD2 lub DAE. Kliknięcie tego przycisku powoduje włączenie dostępnych w modelu animacji. W poprzednim podrozdziale nie omówiliśmy tego, jak animacje są uruchamiane, zrobimy to teraz przy okazji omawiania klas służących do zarządzania animacjami. W bibliotece Away3D klasy: AnimationData, AnimationDataType, AnimationLibrary, Animator oraz AnimatorEvent służą do zarządzania animacjami zapisanymi w modelach typu MD2 oraz DAE. Nie tworzą one wzajemnych alternatyw, tylko w połączeniu pozwalają wywołać konkretną sekwencję ruchu.
AnimationData i AnimationDataType AnimationData Klasa AnimationData jest głównym składnikiem zarządzania animacjami. Zawiera ona w sobie wszystkie potrzebne dane wybranej sekwencji oraz odwołanie do obiektu klasy Animator, który kontroluje cały proces odtwarzania. Właściwości
276
Flash i ActionScript. Aplikacje 3D od podstaw
oraz metody, które są dostępne w klasie AnimationData, zostały opisane w tabelach 6.15 i 6.16. Tabela 6.15. Właściwości klasy AnimationData
Nazwa
Rodzaj
Wartość domyślna Opis
animationType
String
1
animator
Animator
Odwołanie do obiektu klasy Animator tworzonego wraz z obiektem klasy AnimationData
channel
Dictionary
Obiekt typu Dictionary z nazwami kanałów sekwencji w animacji typu skinAnimation
end
Number
frames
Vector.
Obiekt tablicy zawierający klatki animacji typu vertexAnimation
name
String
Nazwa animacji stosowana jako odwołanie
start
Number
vertices
Vector.
Określa rodzaj animacji
0
Odwołanie do czasu, w którym kończy się animacja
Infinity
Odwołanie do czasu, w którym zaczyna się animacja Obiekt Vector z używanymi punktami Vertex animowanego modelu
Tabela 6.16. Metody klasy AnimationData
Nazwa
Opis
clone(object:Object3D):AnimationData
Metoda fabrykująca
AnimationDataType Klasa AnimationDataType w swoim kodzie zawiera jedynie dwie stałe określające rodzaj wywoływanej animacji. Szczegóły tych stałych wypisano w tabeli 6.17.
Rozdział 6. Modele i animacje
277
Tabela 6.17. Stałe klasy AnimationDataType
Nazwa
Rodzaj
Wartość domyślna Opis
SKIN_ANIMATION
String
skinAnimation
VERTEX_ANIMATION
String
vertexAnimation
Taki rodzaj animacji występuje przy stosowaniu modeli składających się z siatki geometrycznej i szkieletów. Do jej kontrolowania stosuje się obiekt klasy BonesAnimator
Animacja sterowana obiektem klasy VertexAnimator bazuje na zmianie położenia każdego z wierzchołków siatki bez stosowania szkieletu
AnimationLibrary AnimationLibrary to klasa zlokalizowana w pakiecie away3d.loaders.utils. Jej
obiekt pełni funkcję biblioteki zawierającej wszystkie animacje poukładane w ustalonej kolejności. W swoich zasobach ma ona dwie metody, obie wypisano w tabeli 6.18.
Tabela 6.18. Metody klasy AnimationLibrary
Nazwa
Opis
addAnimation(name:String):AnimationData
Dodaje animację do biblioteki animacji
getAnimation(name:String):AnimationData
Zwraca wybraną animację zawartą w bibliotece
Modele w formacie MD2 przeważnie mają następujące sekwencje animacji: default, stand, run, attack, pain1, pain2, pain3, jump, flip, salute, taunt, wave, point, crstand, crwalk, crattack, crpain, crdeath, death1, death2, death3.
Animator i AnimatorEvent Animator Klasa Animator zlokalizowana jest w pakiecie away3d.animators, a jej obiekt służy do kontrolowania wybranej sekwencji animacji. Stosując właściwości oraz metody wypisane w tabelach 6.19 i 6.20, można w pełni kontrolować wybraną animację, podobnie jak animację w dwuwymiarowych aplikacjach Flash.
278
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 6.19. Właściwości klasy Animator
Nazwa
Rodzaj
Wartość domyślna Opis
currentFrame
int
1
cycleNumber
int
delay
Number
0
Określa opóźnienie rozpoczęcia animacji
fps
Number
25
Określa prędkość FPS
isPlaying
Boolean
false
Zwraca informację, czy animacja aktualnie jest włączona
length
Number
loop
Boolean
name
String
Nazwa animacji stosowana jako odwołanie
target
Object3D
Określa obiekt, któremu animacja jest przypisana
totalFrames
Number
Zwraca liczbę wszystkich klatek wybranej animacji
Zwraca numer aktualnej klatki Zwraca numer aktualnego obiegu
Określa długość animacji w sekundach true
Określa, czy animacja będzie powtarzalna
Tabela 6.20. Metody klasy Animator
Nazwa
Opis
Animator(target:Object3D = null, init:Object = null)
Konstruktor
addOnCycle(listener:Function):void
Dodaje detektor zdarzenia cycleEvent
addOnEnterKeyFrame(listener:Function):void
Dodaje detektor zdarzenia typu enterKeyFrame
clone(animator:Animator = null):Animator
Kopiuje właściwości animacji do innego obiektu klasy Animator
gotoAndPlay(frame:uint):void
Metoda przenosi do wybranej klatki i uruchamia animację
Tabela 6.20. Metody klasy Animator (ciąg dalszy)
Nazwa
Opis
gotoAndStop(frame:uint):void
Metoda przenosi do wybranej klatki i zatrzymuje animację
play():void
Uruchomienie animacji
removeOnCycle(listener:Function):void
Usuwa detektor zdarzenia cycleEvent
removeOnEnterKeyFrame(listener:Function):void Usuwa detektor zdarzenia enterKeyFrame stop():void
Zatrzymanie animacji
update(time:Number):void
Przejście do ustalonej sekundy animacji
Rozdział 6. Modele i animacje
279
AnimatorEvent Klasa AnimatorEvent, zlokalizowana w away3d.events, reprezentuje zdarzenia użycia obiektu klasy Animator. Warto o niej wspomnieć, ponieważ może się przydać do wywoływania różnych zdarzeń przy uruchomionej animacji. W tabelach 6.21 i 6.22 wypisano metodę oraz — co ważniejsze — stałe określające zdarzenia. Tabela 6.21. Metoda klasy AnimatorEvent
Nazwa
Opis
AnimatorEvent(type:String, animator:Animator)
Konstruktor
Tabela 6.22. Stałe klasy AnimationEvent
Nazwa
Rodzaj
Wartość domyślna Opis
CYCLE
String
cycle
Określa zdarzenie rozpoczęcia nowego cyklu przejścia przez wszystkie sekwencje animacji
ENTER_KEY_FRAME
String
enterKeyFrame
Określa zdarzenie przejścia do nowej klatki sekwencji
SEQUENCE_DONE
String
sequenceDone
Określa zdarzenie zakończenia odtwarzania sekwencji animacji
SEQUENCE_UPDATE
String
sequenceUpdate
Określa zdarzenie przejścia w sekwencji animacji
START
String
start
Określa zdarzenie rozpoczęcia animacji
STOP
String
stop
Określa zdarzenie zatrzymania animacji
Zastosowanie animacji w przykładzie W przykładzie z poprzedniego podrozdziału dla zdarzenia wciśnięcia przycisku Włącz animacje zapisaliśmy wywołanie metody playAnimations(). W jej ciele zastosowaliśmy instrukcję warunkową, w której sprawdzana jest wartość właściwości isPlaying klasy Animator oraz istnienie obiektu animations. Dzięki temu w zależności od wyniku instrukcji kliknięcie przycisku spowoduje włączenie lub wyłączenie animacji. Ponieważ przy pobieraniu modelu w formacie MD2 stosowany jest dodatkowy obiekt klasy ObjectContainer3D wewnątrz obiektu model, proces uruchomienia animacji należało rozpocząć od sprawdzenia, jakiego rodzaju model został wgrany. W tym celu skorzystaliśmy z instrukcji warunkowej if sprawdzającej wartość własności type obiektu modelReference. Krok ten jest niezbędny do poprawnego od-
280
Flash i ActionScript. Aplikacje 3D od podstaw
wołania się do obiektu animationLibrary i zastosowania metody getAnimation(). Po pobraniu danych animacji default użyliśmy kolejnej instrukcji if, aby sprawdzić, czy wybrana sekwencja jest dostępna. Jeżeli stworzony obiekt animations nie jest pusty, można uruchomić animację, odwołując się do obiektu klasy Animator i jej metody play(). Cały proces wygląda następująco: if (animations && animations.animator.isPlaying) { panel.playAnimationsBtn.label = "Włącz animację"; animations.animator.gotoAndStop(0); } else { if(modelReference.type.toLowerCase() == '.dae') { animations = model.animationLibrary.getAnimation('default'); } else { animations = model.children[0].animationLibrary.getAnimation ('default'); } if (animations) { panel.playAnimationsBtn.label = "Wyłącz animację"; animations.animator.play(); } }
Podsumowanie Do tworzenia trójwymiarowych obiektów służą pakiety takie jak: Autodesk 3ds Max, Autodesk Maya, Lightwave, Blender, MilkShape, Google SketchUp, PreFab3D. PreFab3D to nieduży program o sporych możliwościach, napisany przez ludzi związanych z projektem Away3D. Aby tworzyć animacje na modelach, można również wykorzystać darmową aplikację CharacterFX. Termin Low poly określa modele o siatce złożonej z mniejszej liczby wielokątów, z kolei pojęcie High poly oznacza obiekty o skomplikowanej budowie siatki.
Rozdział 6. Modele i animacje
281
Zakres pojęć Low poly i High poly jest bardzo płynny z uwagi na stale rozwijający się sprzęt komputerowy. Wybór modelu zależy od silnika, na którym opiera się aplikacja, oraz sprzętu odbiorcy. W projektach bazujących na silniku podobnym do Away3D korzysta się z modeli Low poly. Formaty modeli stosowane w Away3D to: 3DS, ASE, OBJ, DAE, MD2, AWD, AS. Z tych modeli jedynie DAE oraz MD2 obsługują animację modeli. Stosując klasę AS3Exporter bądź specjalne wtyczki do programów Blender oraz 3ds Max, można wyeksportować obiekt do postaci klasy ActionScript 3.0. Do każdego z rodzajów modeli dopisano klasy odpowiedzialne za ich pobranie i wyświetlanie. Mają one dwie metody: parse() oraz load(), przetwarzające dane tak, by można było je pokazać na scenie w postaci obiektu. Powierzchnię modelu można pokryć dowolnym materiałem dostępnym w pakiecie away3d.materials. W przypadku gdy tekstura załączona do modelu nie pojawia się na jego powierzchni, można pobrać ją osobno, stosując obiekt klasy BitmapFileMaterial, i przypisać do właściwości material dostępnej w obiekcie modelu. Do użycia animacji zapisanych w modelach DAE oraz MD2 służą klasy: AnimationData, AnimationDataType, AnimationLibrary oraz Animator. Główną klasą do zarządzania animacjami jest AnimationData, zawiera ona obiekt klasy Animator oraz wszystkie dane wybranej animacji. Klasa AnimationLibrary służy jako spis dostępnych animacji w wybranym modelu. Klasa Animator uruchamia i kontroluje wybraną animację.
282
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 7. Praca z obiektami Znamy już podstawowe obiekty trójwymiarowe dostępne w bibliotece Away3D oraz rodzaje modeli, z których można w niej korzystać. Przyszedł czas, aby dowiedzieć się, co takiego można z tymi obiektami zrobić. Zawartość tego rozdziału podzielono na dwie zasadnicze części. W pierwszej zaczniemy od najprostszych operacji. Stosując różne metody i właściwości, wprawimy obiekty w ruch, zmienimy ich pozycje oraz kąty nachylenia. Gdy opanujemy podstawy sterowania trójwymiarowymi obiektami, przejdziemy do trudniejszych tematów. W drugiej części tego rozdziału różnymi metodami zmienimy ukształtowanie powierzchni obiektu, a nawet ją zniszczymy, powodując eksplozję. Brzmi ciekawie? Czytając ten rozdział, dowiesz się: Jak można tworzyć animacje. Jakie biblioteki służą do obsługi animacji obiektów. Jak posługiwać się klasą TweenMax z pakietu GreenSock Tweening Platform. Czym jest przemieszczanie, obracanie oraz skalowanie obiektów. Jakich metod i właściwości używa się do przemieszczania, obracania i skalowania. Czym jest macierz transformacji i jak się nią posługiwać. Jak za pomocą HeightMapModifier modyfikować powierzchnię obiektu. Jak zastosować klasy Elevation oraz SkinExtrude do generowania ukształtowania terenu.
284
Flash i ActionScript. Aplikacje 3D od podstaw
Biblioteki do animacji Animacje w programie Flash można wykonać na dwa sposoby. Pierwszy z nich polega na układaniu elementów w odpowiednim miejscu i formie na linii czasowej programu. Druga metoda wykorzystuje język ActionScript 3.0 — tutaj jest więcej możliwości implementacji animacji. Po pierwsze, można skorzystać ze zdarzenia Event.ENTER_FRAME i za pomocą odpowiednich instrukcji warunkowych decydować o kolejności i warunkach wystąpienia konkretnego działania. Kolejna metoda wykorzystuje obiekty klas Timer i ich zdarzenia. Za ich pomocą można wyznaczyć czas wystąpiesnia, opóźnienia animacji oraz długość jej wykonywania. Ostatnią metodą, najbardziej wygodną, jest zastosowanie specjalnych bibliotek, które cały mechanizm optymalnej animacji wykonują za programistę. Do najpopularniejszych należą: GreenSock Tweening Platform, Adobe Tween, Tweener, GTwen, GoASAP, Tweensy. Przykłady, w których wykonywane są animacje, przeważnie wykorzystują biblioteki TweenMax oraz TweenLite.
Pobieranie i instalacja biblioteki GreenSock Tweening Platform W celu pobrania bibliotek firmy GreenSock należy przejść do strony: http://www. greensock.com/. Po jej załadowaniu wykonaj następujące kroki: 1. Przejdź do działu Tweening Platform v11 po lewej stronie. Jest to ostatnia stabilna wersja, w chwili gdy piszę te słowa. 2. Kliknij na napis Tweening Platform v11. 3. Po załadowaniu strony kliknij na przycisk Download AS3 w prawej kolumnie.
Rozdział 7. Praca z obiektami
285
4. Pojawi się okno z informacjami dotyczącymi licencji. Aby ściągnąć bibliotekę, należy zapoznać się z treścią licencji i zaakceptować jej warunki. 5. Można również zaznaczyć pole I’d like to learn how to get bonus plugins, update notifications, SVN access and more. Wtedy po rozpoczęciu pobierania zostaniesz przeniesiony do strony opisującej zalety i warunki członkostwa w klubie GreenSock. Po zakończonym pobieraniu pliku zawartość archiwum należy wypakować do katalogu, w którym mieszczą się pozostałe biblioteki. W naszym przypadku jest to katalog o nazwie lib.
Wybór biblioteki GreenSock Tweening Platform W całym pakiecie GreenSock Tweening Platform jest kilka klas obsługujących animacje w ActionScript 3.0. Nie będziemy zajmowali się szczegółowym omawianiem tych klas. Najważniejsze jest to, że mamy do wyboru następujące opcje: TweenMax, TweenLite, TweenNano, TimelineLite, TimelineMax. Dwie ostatnie klasy stosuje się do łączenia w łańcuch zdarzeń, pozostałe klasy — TweenMax, TweenLite oraz TweenNano — różną się od siebie rozmiarem oraz możliwościami. Jak nazwy wskazują, najbardziej rozbudowaną wersją jest TweenMax. Z niej właśnie będziemy korzystali w przykładach zawartych w tym i innych rozdziałach. Wybranie tej wersji nie miało konkretnych powodów. Równie dobrze można korzystać z pozostałych.
Implementacja TweenMax Aby używać TweenMax, należy zaimportować klasę o tej samej nazwie. Zlokalizowana jest ona w pakiecie com.greensock. import com.greensock.TweenMax;
Klasa TweenMax ma statyczne metody, dlatego odwołujemy się do nich bez konieczności wcześniejszego definiowania obiektu. W zasadzie to ma ona wiele funkcji służących do tworzenia animacji, ale nas na chwilę obecną najbardziej interesuje metoda to().
286
Flash i ActionScript. Aplikacje 3D od podstaw
Poniżej przedstawiono sposób zastosowania tej metody: TweenMax.to(sphere, 2, {scaleX:1.5, scaleY:2, scaleZ:2, onComplete:onResizeComplete}); private function onResizeComplete():void { trace('onResizeComplete - OK'); }
Pierwszym atrybutem, jaki należy podać, stosując TweenMax.to(), jest odwołanie do obiektu, na którym animacja ma zostać wykonana. Druga właściwość oznacza czas trwania animacji określony w sekundach lub liczbie klatek, jeżeli właściwość useFrames jest ustawiona jako true. W trzecim atrybucie między klamrami należy podać właściwości wraz z nowymi wartościami. Klasa po wywołaniu metody to() w ustalonym czasie doprowadzi obiekt do tego określonego tymi właściwościami nowego stanu. Na koniec można podać dodatkową właściwość onComplete. Jest to odwołanie do funkcji, która ma być wykonana po zakończeniu animacji. Można również wywołać funkcję przed animacją. Do tego celu służy właściwość onInit. Poza stosowaniem metod statycznych można również stworzyć obiekt klasy TweenMax z ustalonymi atrybutami określającymi animację. Poniżej przedstawiono przykład zastosowania obiektu klasy TweenMax: var tween:TweenMax = TweenMax(sphere, 2, {scaleX:1.5, scaleY:2, scaleZ:2, onComplete:onResizeComplete}); private function onResizeComplete():void { tween.reverse(); }
W tym przypadku po stworzeniu obiektu i zakończeniu animacji zostanie wywołana funkcja cofająca uzyskany efekt. Poza reverse() TweenMax zawiera również metody pause() i restart(). Więcej informacji na temat tej biblioteki i jej możliwości umieszczono w dokumentacji pod adresem: http://www.greensock.com/as/docs/tween/.
Klasa bazowa Aby pokazać, jak działają poszczególne właściwości i metody, potrzebujemy przykładów. Każdy z omawianych w tym dziale sposobów w rzeczywistości ogranicza się do jednej, góra dwóch linijek kodu. Dlatego w pierwszej kolejności napiszemy klasę, która będzie uniwersalna i zobrazuje pracę z obiektami.
Rozdział 7. Praca z obiektami
287
Przepisz poniższy kod i skompiluj go, a na ekranie pojawi się efekt przedstawiony na rysunku 7.1. Omówieniem poszczególnych fragmentów kodu zajmiemy się później. Rysunek 7.1.
Zrzut ekranu przykładu
package { import import import import import import import import import // import import import import // import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Matrix3D; flash.geom.Vector3D; flash.text.TextField; flash.text.TextFormat; away3d.containers.View3D; away3d.containers.ObjectContainer3D; away3d.primitives.Cube; away3d.primitives.Trident; com.greensock.TimelineMax; com.greensock.TweenMax;
public class MoveRotateScaleExample extends Sprite { private var view:View3D; private var obj:ObjectContainer3D; private var cube:Cube;
288
Flash i ActionScript. Aplikacje 3D od podstaw private private private private
var var var var
trident:Trident; panel:Sprite; label:TextField; anims:TimelineMax;
public function MoveRotateScaleExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); panel = new Sprite(); addChild(panel); label = new TextField(); label.defaultTextFormat = new TextFormat("_sans",10); panel.addChild(label); cube = new Cube( { width:50, height:50, depth:50 } ); trident = new Trident(100,true); obj = new ObjectContainer3D(); obj.addChild(trident); obj.addChild(cube); view.scene.addChild(obj); anims = new TimelineMax( { repeat: -1, repeatDelay:2 } ); anims.append(TweenMax.to(obj, 1, { x: -100, y: 100, z: 200, onStart:function() { label.text = "Użycie właściwości: x, y, z"; } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie właściwości: position"; obj.position = new Vector3D(-300, -100, 300)
Rozdział 7. Praca z obiektami } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: translate()"; }, onUpdate:function() { obj.translate(new Vector3D(1, 1, 0), 10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveTo()"; obj.moveTo(0, 0, 0); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveForward()"; }, onUpdate:function() { obj.moveForward(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveBackward()"; }, onUpdate:function() { obj.moveBackward(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() {
289
290
Flash i ActionScript. Aplikacje 3D od podstaw label.text = "Użycie metody: moveUp()"; }, onUpdate:function() { obj.moveUp(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveDown()"; }, onUpdate:function() { obj.moveDown(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveLeft()"; }, onUpdate:function() { obj.moveLeft(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveRight()"; }, onUpdate:function() { obj.moveRight(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, rotationY:90, onStart:function() { label.text = "Użycie właściwości: pivotPoint"; obj.pivotPoint = new Vector3D(100, 0, 200); }
Rozdział 7. Praca z obiektami })); anims.append(TweenMax.to(obj, 1, { delay:2, rotationX:90, rotationY:90, rotationZ:90, onStart:function() { label.text = "Użycie właściwości: rotationX, rotationY, rotationZ"; obj.pivotPoint = new Vector3D(0, 0, 0); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: movePivot()"; obj. movePivot (200, 200, 0); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: rotateTo()"; obj.rotateTo(0, 0, 0); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: rotate()"; }, onUpdate:function() { obj.rotate(new Vector3D(0, 1, 0),10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: lookAt()"; obj.lookAt(new Vector3D(100, 100, 0)); }
291
292
Flash i ActionScript. Aplikacje 3D od podstaw })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: pitch()"; }, onUpdate:function() { obj.pitch(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: roll()"; }, onUpdate:function() { obj.roll(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: yaw()"; }, onUpdate:function() { obj.yaw(10); } })); anims.append(TweenMax.to(obj, 1, { delay:2, scaleX:3, scaleY:2, scaleZ:4, onStart:function() { label.text = "Użycie właściwości: scaleX, scaleY, scaleZ"; } })); anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function()
Rozdział 7. Praca z obiektami
293
{ label.text = "Użycie metody: scale()"; obj.scale(.5); } })); anims.append(TweenMax.to(obj, 1, { delay:2, rotationX:0, rotationY:0, rotationZ:0, x:0, y:0, z:0, onStart:function() { obj.scale(1); label.text = ""; } })); onResize(); } private function onEnterFrame(e:Event):void { view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; // panel.graphics.beginFill(0xf1f1f1); panel.graphics.drawRect(0, 0, stage.stageWidth, 40); panel.graphics.endFill(); label.width = panel.width; label.height = panel.height; } } }
Po skompilowaniu przedstawionego kodu źródłowego w oknie aplikacji wyświetli się sześcian wraz z układem współrzędnych, który umieściliśmy specjalnie po to, by łatwiej było nam poznać kierunek zwrotu obiektu. Sześcian w kodzie reprezentowany jest jako obiekt klasy Cube, a układ współrzędnych jako obiekt klasy Trident. Oba znajdują się w kontenerze ObjectContainer3D o nazwie obj. Zastosowaliśmy kontener z uwagi na to, że odwołując się do niego w czasie animacji, modyfikujemy całą jego zawartość. Dzięki temu nie musieliśmy pisać w kodzie podwójnych metod osobno dla sześcianu i układu współrzędnych.
294
Flash i ActionScript. Aplikacje 3D od podstaw
Klasy TimelineMax oraz TweenMax posłużyły nam do stworzenia obiektu anims i wypełnienia go szeregiem etapów animacji sześcianu. W sekwencjach, w których zmiany polegają na zastosowaniu metod, a nie właściwości, musieliśmy użyć funkcji onUpdate(), by wewnątrz jej bloku odwołać się do konkretnej funkcji i stworzyć płynne przejścia między stanami sześcianu. W każdym etapie użyliśmy dodatkowo funkcji onStart(), aby z każdą nową animacją zaktualizować zawartość pola tekstowego label umieszczonego w obiekcie klasy Sprite o nazwie panel.
Przemieszczanie W podrozdziale „Położenie obiektów w przestrzeni” rozdziału 2. „Podstawy biblioteki Away3D” przedstawiono dwie metody ustawiania obiektu na scenie. Pierwsza polegała na zastosowaniu właściwości x, y i z, a druga na wykorzystaniu właściwości position. Na chwilę wrócimy do tych właściwości, ponieważ za ich pomocą można również przemieszczać obiekt, a nie jedynie ustawiać go w miejscu.
Właściwości x, y, z Aby wprawić w ruch obiekt przy zastosowaniu tych właściwości, trzeba dodatkowo skorzystać z operatorów przypisania, takich jak: +=, -=, *= czy też /=. Służą one do zwiększania i zmniejszania wartości właściwości. W tych operatorach każdy ze znaków przed = jest istotny, ponieważ decyduje on o kierunku zmiany pozycji. Zastosowanie tych właściwości w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { x: -100, y: 100, z: 200, onStart:function() { label.text = "Użycie właściwości: x, y, z"; } }));
Rozdział 7. Praca z obiektami
295
position Właściwość ta również nadaje się do wykorzystania przy zmianie pozycji, ale w tym przypadku należy podać wartości pozycji na wszystkich osiach. Jeżeli chcemy przesunąć dany obiekt tylko względem na przykład osi Y, to w pozostałych — X i Z — musimy wpisać wartości początkowe. Stosowniej jednak jest korzystać z właściwości x, y i z w takich sytuacjach, a z position, gdy mamy konkretny punkt docelowy. Implementacja tej właściwości w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie właściwości: position"; obj.position = new Vector3D(-300, -100, 300) } }));
Metody translate() Metoda translate służy do przemieszczania obiektu wzdłuż wektora o określonej długości w dowolnym kierunku. Pierwszy z atrybutów, jaki podajemy w tej funkcji, to Vector3D. Określa on kierunek, w którym ma się poruszać obiekt. Kierunek ustalamy przez podanie trzech współrzędnych: x, y oraz z. Nie jest to jednak punkt docelowy przejścia. Punkt docelowy kończy się na długości, jaką podajemy jako drugi argument funkcji translate. Sposób działania tej metody przedstawiono na rysunku 7.2. Obiekt półprzezroczysty oznacza początkowe położenie. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: translate()"; },
296
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 7.2.
Użycie metody translate()
onUpdate:function() { obj.translate(new Vector3D(1, 1, 0), 10); } }));
moveTo() Metoda moveTo() działa tak samo jak właściwość position. Używamy jej przeważnie wtedy, gdy obiekt ma być przeniesiony w konkretny punkt w przestrzeni. Można również zmieniać pozycję wobec jednej lub dwóch osi, ale jest to mało wygodne rozwiązanie. Korzystając z tej metody, należy podać trzy współrzędne: x, y, z, określające docelową pozycję przemieszczanego obiektu. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveTo()"; obj.moveTo(0, 0, 0); } }));
Rozdział 7. Praca z obiektami
297
moveForward() Metoda moveForward() przesuwa obiekt wzdłuż dodatnich wartości jego lokalnej osi Z. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveForward()"; }, onUpdate:function() { obj.moveForward(10); } }));
Rysunek 7.3 przedstawia działanie i kierunek metody moveForward(). Obiekt półprzezroczysty oznacza początkowe położenie. Rysunek 7.3.
Użycie metody moveForward()
moveBackward() Metoda moveBackward() przesuwa obiekt wzdłuż ujemnych wartości jego lokalnej osi Z. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2,
298
Flash i ActionScript. Aplikacje 3D od podstaw onStart:function() { label.text = "Użycie metody: moveBackward()"; }, onUpdate:function() { obj.moveBackward(10); } }));
Z kolei rysunek 7.4 przedstawia działanie i kierunek metody moveBackward(). Obiekt półprzezroczysty oznacza początkowe położenie. Rysunek 7.4.
Użycie metody moveBackward()
moveUp() Metoda moveUp() przesuwa obiekt wzdłuż dodatnich wartości jego lokalnej osi Y. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveUp()"; }, onUpdate:function() { obj.moveUp(10); } }));
Rozdział 7. Praca z obiektami
299
Rysunek 7.5 przedstawia działanie i kierunek metody moveUp(). Obiekt półprzezroczysty oznacza początkowe położenie. Rysunek 7.5.
Użycie metody moveUp()
moveDown() Metoda moveDown() przesuwa obiekt wzdłuż ujemnych wartości jego lokalnej osi Y. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveDown()"; }, onUpdate:function() { obj.moveDown(10); } }));
Rysunek 7.6 przedstawia działanie i kierunek metody moveDown(). Obiekt półprzezroczysty oznacza początkowe położenie.
300
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 7.6.
Użycie metody moveDown()
moveLeft() Metoda moveLeft() przesuwa obiekt wzdłuż ujemnych wartości jego lokalnej osi X. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveLeft()"; }, onUpdate:function() { obj.moveLeft(10); } }));
Rysunek 7.7 przedstawia działanie i kierunek metody moveLeft(). Obiekt półprzezroczysty oznacza początkowe położenie.
moveRight() Metoda moveRight() przesuwa obiekt wzdłuż dodatnich wartości jego lokalnej osi X. Implementacja tej metody w przykładzie wygląda następująco:
Rozdział 7. Praca z obiektami
301
Rysunek 7.7.
Użycie metody moveLeft()
anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: moveRight()"; }, onUpdate:function() { obj.moveRight(10); } }));
Rysunek 7.8 przedstawia działanie i kierunek metody moveRight(). Obiekt półprzezroczysty oznacza początkowe położenie.
Obracanie W poprzednim podrozdziale poznaliśmy właściwości oraz metody służące do określania pozycji obiektu, z kolei w tym podrozdziale zajmiemy się zmianą kąta nachylenia obiektu. Obiekty 3D mają kilka metod oraz właściwości, których użycie wprawia je w obrót wokół własnej osi lub wybranego obiektu i tworzy efekt orbitowania. Można również obrócić obiekt w kierunku konkretnego punktu. Każdy z obiektów 3D ma punkt centralny, który — jak nazwa wskazuje — umieszczony jest w centrum geometrii obiektu. Służy on jako punkt odniesienia podczas obracania obiektu względem własnego układu współrzędnych. Mimo że jest to punkt środkowy, istnieje możliwość zmienienia jego położenia.
302
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 7.8.
Użycie metody moveRight()
Metody oraz właściwości służące do obracania obiektów przedstawiono w poniższych punktach.
Właściwości pivotPoint Obiekty klas dziedziczących po Object3D mają w swoich zasobach właściwość o nazwie pivotPoint. Wartość pivotPoint jest obiektem klasy Vector3D, który określa pozycję punktu środkowego wybranego obiektu. Punkt środkowy często nazywany jest również zerowym, ponieważ domyślnie jego pozycja to 0 na wszystkich osiach lokalnego układu współrzędnych. pivotPoint jest punktem odniesienia dla pozycji geometrii obiektu. Gdy stosujemy różne wartości położenia pivotPoint, zmiana nachylenia całego obiektu o ten sam kąt przyniesie odmienne efekty. Zastosowanie tej właściwości w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, rotationY:90, onStart:function() { label.text = "Użycie właściwości: pivotPoint"; obj.pivotPoint = new Vector3D(100, 0, 200); } }));
Rozdział 7. Praca z obiektami
303
rotationX, rotationY, rotationZ rotationX, rotationY oraz rotationZ to standardowe właściwości znane z programów Adobe Flash. Służą one do obracania obiektu względem wybranej osi. Zwróć uwagę na ostatnie litery nazw tych właściwości. Oznaczają one oś, wokół której wykonywany jest obrót.
W Away3D nie zmieniły one swego zastosowania. Jedyną dodatkową cechą w przypadku obiektów w Away3D jest możliwość podania tych właściwości przy tworzeniu obiektu. Chcąc wykorzystać te właściwości, należy podać jako wartość stopień obrotu w postaci liczby typu Number. W poniższym kodzie przedstawiono zastosowanie właściwości obrotu. Zastosowanie tych właściwości w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, rotationX:90, rotationY:90, rotationZ:90, onStart:function() { label.text = "Użycie właściwości: rotationX, rotationY, rotationZ"; obj.pivotPoint = new Vector3D(0, 0, 0); } }));
Metody movePivot() Metoda movePivot() sama w sobie nie obraca obiektu. Celem jej jest zmienienie pozycji punktu pivotPoint, który służy jako punkt odniesienia do obracania obiektu. movePivot() ma trzy atrybuty: dx, dy i dz. Każdy z tych atrybutów przyjmuje wartość liczbową, która określa pozycję na osi w lokalnym układzie współrzędnych. Przykład zastosowania metody movePivot() w kodzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() {
304
Flash i ActionScript. Aplikacje 3D od podstaw label.text = "Użycie metody: movePivot()"; obj.movePivot(200, 200, 0); } }));
rotateTo() Metoda rotateTo() jako atrybuty przyjmuje trzy wartości liczbowe: ax, ay i az. Każdy z tych atrybutów odpowiada kątowi nachylenia względem osi globalnego układu współrzędnych lub kontenera, w którym się znajduje obiekt. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: rotateTo()"; obj.rotateTo(0, 0, 0); } }));
Na rysunku 7.9 pokazano dwa sześciany w różnych pozycjach. Sześcian zaznaczony jako pozycja początkowa ustawione ma standardowe wartości kąta nachylenia względem wszystkich osi. Z kolei na sześcianie oznaczonym jako pozycja końcowa zastosowano metodę rotateTo(). Rysunek 7.9.
Efekt wywołania metody rotateTo()
Rozdział 7. Praca z obiektami
305
rotate() rotate()
obraca obiekt o ustalony kąt względem wybranych osi. Metoda ta ma dwa atrybuty. Pierwszy atrybut to axis, który przyjmuje jako wartość obiekt Vector3D. Służy on do wyznaczania osi, względem których wykonany ma zostać obrót. Drugi atrybut o nazwie angle wyznacza kąt nachylenia. Cała tajemnica stosowania tej metody polega na wybraniu odpowiedniego kierunku. W obiekcie Vector3D podajemy trzy wartości. Każda z nich odpowiada za jedną oś lokalnego układu współrzędnych. Najczęściej stosowanymi wartościami są: -1, 1 i 0. Oznaczają one obrót lewostronny, prawostronny oraz brak skrętu na wybranej osi. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: rotate()"; }, onUpdate:function() { obj.rotate(new Vector3D(0, 1, 0),10); } }));
lookAt() Działaniem lookAt() jest obrócenie obiektu w stronę wskazanego punktu w przestrzeni. Metoda ta zawiera dwa atrybuty: target i upAxis. Pierwszy atrybut, target, określa cel, w kierunku którego zwrócony ma być obiekt. Jako wartość target przyjmuje obiekt Vector3D. Druga właściwość, upAxis, jest nieobowiązkowa i domyślnie ustawiona na null. Jako wartość również przyjmuje obiekt Vector3D. Ustawienie wartości upAxis określa, która z osi lokalnego układu współrzędnych będzie obrócona ku górze. Najczęściej lookAt() stosuje się w obiektach kamer, gdy mają przedstawić konkretny punkt. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: lookAt()"; obj.lookAt(new Vector3D(100, 100, 0)); } }));
306
Flash i ActionScript. Aplikacje 3D od podstaw
pitch() Metoda pitch() służy do obracania obiektu względem osi X jego lokalnego układu współrzędnych. W metodzie tej podaje się jeden atrybut angle. Jako wartość przyjmuje liczbę typu Number, która określa stopień obrotu obiektu względem osi X. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: pitch()"; }, onUpdate:function() { obj.pitch(10); } }));
Na rysunku 7.10 pokazano dwa sześciany w różnych pozycjach. Sześcian zaznaczony jako pozycja początkowa ustawione ma standardowe wartości kąta nachylenia względem wszystkich osi. Z kolei na sześcianie oznaczonym jako pozycja końcowa zastosowano metodę pitch(). Rysunek 7.10.
Efekt wywołania metody pitch()
Rozdział 7. Praca z obiektami
307
roll() roll() to podobna metoda do pitch(). Również służy do obracania obiektu względem konkretnej osi, w tym przypadku jest to oś Z lokalnego układu współrzędnych. Metoda roll() ma jedną właściwość angle, która jako wartość przyjmuje liczbę typu Number. Określa ona stopień obrotu obiektu względem osi Z.
Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: roll()"; }, onUpdate:function() { obj.roll(10); } }));
Na rysunku 7.11 pokazano dwa sześciany w różnych pozycjach. Sześcian zaznaczony jako pozycja początkowa ustawione ma standardowe wartości kąta nachylenia względem wszystkich osi. Z kolei na sześcianie oznaczonym jako pozycja końcowa zastosowano metodę roll(). Rysunek 7.11.
Efekt wywołania metody roll()
308
Flash i ActionScript. Aplikacje 3D od podstaw
yaw() Celem stosowania yaw() jest obrócenie obiektu względem lokalnej osi Y układu współrzędnych wybranego obiektu. Metoda yaw() ma jeden atrybut angle. Identycznie jak poprzednie metody pitch() i roll(), yaw() przyjmuje wartości w postaci liczby typu Number, która określa stopień obrotu obiektu względem osi Y. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: yaw()"; }, onUpdate:function() { obj.yaw(10); } }));
Na rysunku 7.12 pokazano dwa sześciany w różnych pozycjach. Sześcian zaznaczony jako pozycja początkowa ustawione ma standardowe wartości kąta nachylenia względem wszystkich osi. Z kolei na sześcianie oznaczonym jako pozycja końcowa zastosowano metodę yaw(). Rysunek 7.12.
Efekt wywołania metody yaw()
Rozdział 7. Praca z obiektami
309
Skalowanie Skalowanie obiektów polega na modyfikowaniu ich wymiarów względem wybranych osi. W Away3D obiekty klas dziedziczących po Object3D mają cztery właściwości oraz jedną metodę, dzięki którym można zmienić rozmiary obiektu.
Właściwości scaleX, scaleY, scaleZ Podobnie jak rotationX, rotationY i rotationZ, właściwości scaleX, scaleY oraz scaleZ są standardowymi właściwościami znanymi z programów Adobe Flash. Służą one do zmiany wymiarów obiektu względem wybranej osi. Ostatnie litery nazw tych właściwości oznaczają oś, wokół której wykonywany jest obrót. Właściwości scaleX, scaleY oraz scaleZ, tak samo jak właściwości rotation w Away3D, można użyć przy tworzeniu obiektu. Chcąc wykorzystać którąkolwiek z właściwości, należy podać wartość liczbową, która określa wielokrotność zmiany rozmiaru obiektu. Zastosowanie tych właściwości w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, scaleX:3, scaleY:2, scaleZ:4, onStart:function() { label.text = "Użycie właściwości: scaleX, scaleY, scaleZ"; } }));
Efekt zmiany skali z powyższymi wartościami przedstawiono na rysunku 7.13.
310
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 7.13.
Zmodyfikowany obiekt sześcianu
Metody scale() Metoda scale() ma jeden atrybut o nazwie scale, którego wartość liczbowa odpowiada za zmianę rozmiarów obiektu względem wszystkich osi. Gdy podamy jedną wartość, obiekt powiększy się lub zmniejszy jednakowo z każdej ze stron. Gdy wymagane jest równomierne modyfikowanie wymiarów obiektu, stosowanie tej metody jest szybsze od podawania tych samych wartości każdej z właściwości: scaleX, scaleY i scaleZ. Implementacja tej metody w przykładzie wygląda następująco: anims.append(TweenMax.to(obj, 1, { delay:2, onStart:function() { label.text = "Użycie metody: scale()"; obj.scale(.5); } }));
Rozdział 7. Praca z obiektami
311
Macierz transformacji Czym jest macierz transformacji? Z definicji macierze transformacji to macierze, które opisują zależność między współrzędnymi punktu przed transformacją i po. Pojęcie macierzy transformacji znane jest z dwuwymiarowych aplikacji Flash. W ActionScript 3.0 przy użyciu klas Matrix i Transform można wykonać różne transformacje graficzne na wybranym obiekcie. Do funkcji transformacji należą: translacja, obrót, skalowanie, pochylenie. W Away3D również istnieje możliwość korzystania z macierzy transformacji. Obiekty pochodne od Object3D mają specjalną właściwość transform. Umożliwia ona skorzystanie z obiektów klasy Matrix3D, zlokalizowanej w pakiecie flash.geom. Klasa ta reprezentuje macierz transformacji, która wskazuje położenie i orientację trójwymiarowego obiektu. Za jej pomocą można wykonać wszystkie funkcje transformacji. Na rysunku 7.14 przedstawiono budowę macierzy transformacji. Rysunek 7.14.
Budowa macierzy transformacji
312
Flash i ActionScript. Aplikacje 3D od podstaw
Tworzenie macierzy transformacji Macierz transformacji tworzy się poprzez podanie ciągu 16 liczb w atrybucie typu Vector. Wartości te odpowiadają poszczególnym właściwościom macierzy. Należy podawać je w kolejności od lewej do prawej kolumny i od górnego do dolnego wiersza. W poniższym przykładzie przedstawiono sposób implementacji nowej macierzy transformacji, której wartości dwukrotnie zwiększają skalę obiektu: obj.transform = new Matrix3D(new [2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,1]);
Operacje transformacji na macierzy Matrix3D wykonuje się, stosując jej metody. Tabele 7.1 i 7.2 zawierają najważniejsze z metod i właściwości klasy Marix3D. Tabela 7.1. Właściwości klasy Matrix3D
Nazwa
Rodzaj
Wartość domyślna
Opis
determinant
Number
Określa, czy macierz jest odwracalna
position
Vector3D
Pozycja obiektu w przestrzeni
rawData
Vector.
Ciąg 16 liczb typu Number. Są to wartości macierzy
Tabela 7.2. Metody klasy Matrix3D
Nazwa
Opis
Matrix3D(v:Vector. = null)
Konstruktor
append(lhs:Matrix3D):void
Metoda łączy macierze, mnożąc obie
appendRotation(degrees:Number, axis:Vector3D, Ustala stopień obrotu obiektu, do którego pivotPoint:Vector3D = null):void zastosowano macierz appendScale(xScale:Number, yScale:Number, zScale:Number):void
Ustala poziom skalowania na każdej z osi obiektu, do którego zastosowano macierz
appendTranslation(x:Number, y:Number, z:Number):void
Ustala przesunięcie na każdej z osi obiektu, do którego zastosowano macierz
decompose(orientationStyle:String = "eulerAngles"):Vector.
Zwraca ustawienia translacji, obrotu i skali w postaci obiektu Vector złożonego z trzech obiektów Vector3D
identity():void
Przekształca w macierz jednostkową
interpolateTo(toMat:Matrix3D, percent:Number):void
Interpoluje macierz obiektu, przybliżając ją do macierzy docelowej o ustaloną wartość
invert():Boolean
Metoda odwraca macierz
pointAt(pos:Vector3D, at:Vector3D = null, up:Vector3D = null):void
Metoda obraca obiekt w kierunku wybranego punktu w przestrzeni
transpose():void
Metoda zamienia kolumny na wiersze i wiersze na kolumny
Rozdział 7. Praca z obiektami
313
Modyfikowanie powierzchni obiektu Modyfikowanie powierzchni obiektu polega na zmianie położenia poszczególnych punktów jego siatki. Daje to możliwość tworzenia nierówności terenu, ścieżek, flag, peleryn i innych obiektów, których powierzchnia nie jest płaska. Aby osiągnąć efekt nierówności w Away3D, korzysta się z klas: PathExtrusion, HeightMapModifier, SkinExtrude oraz Elevation. Każda z nich modyfikuje powierzchnię na swój sposób i w konkretnym celu. W poniższych punktach zobaczymy, jak i do czego stosować wymienione klasy. Zacznijmy od PathExtrusion.
PathExtrusion Klasa PathExtrusion znajduje się w pakiecie away3d.extrusions. Za pomocą tej klasy można wykreować wszelkiego rodzaju ścieżki i trasy. Na początku należy stworzyć obiekt Path i ustalić liczbę punktów, z których będzie się składała przyszła ścieżka. Każdy z tych punktów ma postać obiektu Vector3D. Dodawanie punktów ścieżki jest ważne z uwagi na to, że decydują one o jej przyszłym wyglądzie. Na tym etapie należy pamiętać o pewnym schemacie dodawania punktów. Polega on na podzieleniu etapów ścieżki na trzy punkty. Pierwszy z punktów jest początkiem etapu. Kolejny punkt jest kontrolnym, określającym krzywiznę fragmentu ścieżki. Ostatni punkt zamyka dany etap. Rysunek 7.15 przedstawia schemat jednego z fragmentów ścieżki. Rysunek 7.15.
Fragment ścieżki złożony z trzech punktów
Aby zachować ciągłość trasy, należy układać poszczególne jej fragmenty tak, aby ostatni punkt poprzedniego etapu łączył się z pierwszym punktem następnego. Łączenie to polega na przypisywaniu tych samych wartości obu punktom. Jeżeli chcemy stworzyć zamknięty tor, można dodatkowo zastosować właściwość closePath, przypisując jej wartość true. Niezależnie od pozycji pierwszego i ostatniego punktu przerwa między nimi zostanie wypełniona. Im bliżej siebie te punkty się znajdują, tym mniej zniekształcone będzie wypełnienie luki.
314
Flash i ActionScript. Aplikacje 3D od podstaw
Za wygląd ścieżki odpowiada właściwość profile, która zawiera tablicę obiektów Vector3D. Każdy z tych obiektów kształtuje profil trasy. Aby stworzyć płaską nawierzchnię, trzeba podać minimalnie dwa punkty. Jednak im więcej różnych współrzędnych zawiera tablica, tym bardziej skomplikowany kształt będzie miała ścieżka. Rozbieżność między położeniem pierwszego i ostatniego punktu określa szerokość bądź wysokość ścieżki. Rysunek 7.16 przedstawia przykładowe trzy profile, które można zastosować do ścieżki.
Rysunek 7.16. Przykładowe profile ścieżki
Każda ze współrzędnych właściwości profile odnosi się do lokalnego układu obiektu ścieżki. Jako przykład dla PathExtrusion stworzymy mało skomplikowany tor wyścigowy, który przedstawiono na rysunku 7.17. Rysunek 7.17.
Tor stworzony za pomocą PathExtrusion
Jest mało skomplikowany, przydałoby się trochę więcej fragmentów trasy, żeby zakręty były równe i okrągłe. Możesz udoskonalić i urozmaicić ten tor, aby poćwiczyć. Na razie przepisz i skompiluj kod źródłowy dla tego przykładu.
Rozdział 7. Praca z obiektami package { import flash.display.Sprite; import flash.events.Event; import flash.geom.Vector3D; import away3d.containers.View3D; import away3d.core.utils.Cast; import away3d.core.geom.Path; import away3d.extrusions.PathExtrusion; import away3d.materials.BitmapMaterial; public class PathExtrusionExample extends Sprite { private var view:View3D; private var path:Path; private var profile:Array; private var track:PathExtrusion; private var texture:BitmapMaterial; public function PathExtrusionExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); view.camera.y = 1000; view.camera.z = -1500; view.camera.lookAt(new Vector3D(0, 0, 0)); path = new Path( [ new Vector3D(-400, 0, 0), new Vector3D(-400, 0, -700), new Vector3D(0, 0, 0), new Vector3D(0, 0, 0), new Vector3D(400, 0, 400), new Vector3D(400, 0, 0), new Vector3D(400, 0, 0), new Vector3D(200, 0, -700), new Vector3D(0, -50, 0), new Vector3D(0, -50, 0), new Vector3D(-200, 0, 700), new Vector3D(-400, 0, 0) ]); // profile = new Array( new Vector3D(-40, 0, 0),
315
316
Flash i ActionScript. Aplikacje 3D od podstaw new Vector3D(40, 0, 0) ); texture = new BitmapMaterial(Cast.bitmap('road.jpg')); texture.smooth = true; track = new PathExtrusion(); track.path = path; track.profile = profile; track.material = texture; track.bothsides = true; track.subdivision = 20; track.closePath = true; view.scene.addChild(track); } private function onEnterFrame(e:Event):void { track.rotationY++; view.render(); } } }
W tym kodzie źródłowym najważniejszymi klasami, jakie zaimportowaliśmy, są Vector3D, Path oraz PathExtrusion. One odpowiadają za budowę trasy, z kolei do nadania tekstury wykorzystaliśmy klasy Cast oraz BitmapMaterial. W klasie PathExtrusionExample poza standardowym obiektem widoku View3D zdefiniowaliśmy obiekt klasy Path, PathExtrusion, BitmapMaterial oraz tablicę profile. private private private private private
var var var var var
view:View3D; path:Path; profile:Array; track:PathExtrusion; texture:BitmapMaterial;
Standardowo w konstruktorze klasy odwołujemy się do metody init(), gdy obiekt Stage zostanie zainicjowany. W metodzie init() dodaliśmy rejestrator zdarzeń Event.ENTER_FRAME, który uruchamia metodę onEnterFrame(). addEventListener(Event.ENTER_FRAME, onEnterFrame);
Stworzyliśmy widok i ustawiliśmy jego położenie w oknie programu. Poza tym zmieniliśmy pozycję kamery widoku, tak aby obejmowała ona całą trasę. Zmiana położenia względem osi Y wymagała ustawienia na nowo punktu, w kierunku którego kamera ma być zwrócona. view.camera.y = 1000; view.camera.z = -1500; view.camera.lookAt(new Vector3D(0, 0, 0));
Rozdział 7. Praca z obiektami
317
Kolejnym etapem w metodzie init() było stworzenie obiektu klasy Path i przypisanie mu współrzędnych ścieżki. Wymienione punkty w przestrzeni stworzyły tor na wzór liczby 8. path = [ new new new new new new new new new new new new ]);
new Path( Vector3D(-400, 0, 0), Vector3D(-400, 0, -700), Vector3D(0, 0, 0), Vector3D(0, 0, 0), Vector3D(400, 0, 400), Vector3D(400, 0, 0), Vector3D(400, 0, 0), Vector3D(200, 0, -700), Vector3D(0, -50, 0), Vector3D(0, -50, 0), Vector3D(-200, 0, 700), Vector3D(-400, 0, 0)
Aby trasa była płaska i zwrócona ku osi Y, stworzyliśmy obiekt tablicy Array o nazwie profile. Dodaliśmy dwa podstawowe obiekty Vector3D, dzięki którym ustaliliśmy szerokość ścieżki na lokalnej osi X. profile = new Array( new Vector3D(-40, 0, 0), new Vector3D(40, 0, 0) );
Po ustawieniu kształtu trasy, stosując obiekt klasy BitmapMaterial oraz klasę Cast, stworzyliśmy teksturę, którą pokryliśmy tor. Żeby poprawić jakość tekstury, użyliśmy również właściwości smooth. texture = new BitmapMaterial(Cast.bitmap('road.jpg')); texture.smooth = true;
Na końcu metody init() utworzyliśmy obiekt track klasy PathExtrusion. Jego wartościom path, profile oraz material przypisaliśmy wcześniej stworzone obiekty. track = new PathExtrusion(); track.path = path; track.profile = profile; track.material = texture;
Dodatkowo ustawiając właściwość bothsides na true, wypełniliśmy teksturą również spód trasy. Aby zakręty toru były bardziej kształtne, zwiększyliśmy liczbę segmentów w jego odcinkach do 20. track.bothsides = true; track.subdivision = 20;
318
Flash i ActionScript. Aplikacje 3D od podstaw
Przypisując właściwości closePath wartość true, zamknęliśmy obieg i połączyliśmy pierwszy punkt trasy z ostatnim. track.closePath = true; view.scene.addChild(track);
Przy każdym wystąpieniu zdarzenia Event.ENTER_FRAME dodaliśmy wywołanie metody onEnterFrame(). W jej ciele zapisaliśmy zmianę kąta nachylenia obiektu track oraz odświeżenie widoku. track.rotationY++; view.render();
Tabele 7.3 i 7.4 zawierają najważniejsze z metod i właściwości klasy PathExtrusion. Tabela 7.3. Właściwości klasy PathExtrusion
Nazwa
Rodzaj
Wartość domyślna Opis
alignToPath
Boolean
true
Określa, czy tablica punktów powinna być wyrównana do podanej ścieżki typu Path
closePath
Boolean
false
Określa, czy ostatni punkt ścieżki powinien łączyć się z pierwszym, tworząc zamknięty obieg
coverAll
Boolean
true
Określa, czy tekstury powinny się rozciągać tak, aby pokrywały całą powierzchnię
flip
Boolean
false
Określa, czy ściany powinny być odwrócone
materials
Array
Tablica zawierająca materiały, które mają być wykorzystane w każdym z punktów ścieżki
path
Path
Obiekt ścieżki służący do modyfikowania powierzchni
profile
Array
Tablica obiektów Vector3D określająca profil powierzchni
rotations
Array
Tablica zawierająca obiekty Vector3D określające kąty nachylenia punktów ścieżki względem wszystkich osi
scales
Array
Tablica zawierająca obiekty Vector3D określające skale ustawione na punktach ścieżki
subdivision
int
2
Określa podział segmentów w stworzonej powierzchni
Tabela 7.4. Metody klasy PathExtrusion
Nazwa
Opis
PathExtrusion(path:Path, profile:Array, scales:Array, rotations:Array, init:Object)
Konstruktor
Rozdział 7. Praca z obiektami
319
HeightMapModifier Zlokalizowana w pakiecie away3d.modifiers klasa HeightMapModifier umożliwia zmianę współrzędnych poszczególnych punktów siatki wybranego obiektu 3D. Oznacza to, że można za jej pomocą tworzyć nierówności siatek każdego rodzaju obiektów trójwymiarowych. Podstawą tworzenia tych nierówności jest obiekt BitmapData specjalnie spreparowanego pliku graficznego. Przykład takiego pliku pokazano na rysunku 7.18. Rysunek 7.18.
Plik służący jako mapa dla HeightMapModifier
Taki plik nie jest teksturą, lecz służy jako mapa ukształtowania powierzchni. Wymiarami powinien pokryć całą powierzchnię obiektu. Tworząc obiekt klasy HeightMapModifier, podajemy dwa argumenty. Pierwszy z tych argumentów reprezentuje trójwymiarowy obiekt. Drugi natomiast odpowiada obiektowi BitmapData mapy ukształtowania powierzchni. Wewnątrz klasy informacje z obu podanych argumentów są mieszane. Skutkiem tego każdemu z punktów siatki wybranego obiektu przypisywany jest konkretny kolor, który będzie odwzorowany na osi Y wybranego obiektu 3D. Kolory ciemniejsze reprezentują dodatnie wartości y i tworzą tym samym wyżyny. Odwrotnie jest z kolorami jaśniejszymi. Im odcień jest jaśniejszy, tym niższe wartości y przypisywane są punktowi na siatce. Pozycje te można dodatkowo kontrolować właściwościami salce oraz offset klasy HeightMapModifier. Przykładowo jeżeli z jakichś powodów jaśniejsze kolory mapy powinny odzwierciedlać wyższe fragmenty powierzchni, należy właściwości scale przypisać wartość -1. Dzięki temu każdemu punktowi siatki geometrycznej powierzchni zostanie nadana odwrócona wartość właściwości y.
320
Flash i ActionScript. Aplikacje 3D od podstaw
O dokładności odwzorowania powierzchni decyduje liczba segmentów, z których składa się obiekt 3D. Większa liczba segmentów gwarantuje lepsze odwzorowanie, ale również wymaga większego zużycia zasobów komputera. Samo stworzenie obiektu klasy HeightMapModifier nie spowoduje wygenerowania nierówności powierzchni. Po ustaleniu wszystkich właściwości modyfikację powierzchni należy wywołać metodą execute() tej klasy. Jako przykład dla HeightMapModifier stworzymy ukształtowanie terenu bazujące na danych z rysunku 7.18. Efekt skompilowania kodu przedstawiono na rysunku 7.19. Rysunek 7.19.
Ukształtowanie terenu z użyciem HeightMapModifier
Aby uzyskać taki efekt, przepisz i skompiluj następujący kod źródłowy: package { import flash.display.BitmapData; import flash.display.Sprite; import flash.events.Event; import flash.geom.Vector3D; // import away3d.lights.PointLight3D; import away3d.modifiers.HeightMapModifier; import away3d.primitives.Plane; import away3d.containers.View3D; import away3d.core.utils.Cast; import away3d.materials.WhiteShadingBitmapMaterial; public class HeightMapModifierExample extends Sprite {
Rozdział 7. Praca z obiektami
321
private var view:View3D; private var texture:WhiteShadingBitmapMaterial; private var terrainMap:BitmapData; private var plane:Plane; private var modifier:HeightMapModifier; private var light:PointLight3D; private var backward:Boolean = false; public function HeightMapModifierExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); // view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); // view.camera.y = 600; view.camera.lookAt(new Vector3D(0, 0, 0)); // light = new PointLight3D(); light.specular = .05; light.diffuse = 0.75; light.ambient = 0.5; view.scene.addLight(light); // texture = new WhiteShadingBitmapMaterial (Cast.bitmap('terrainTexture')); texture.smooth = true; // plane = new Plane(); plane.material = texture; plane.bothsides = true; plane.width = plane.height = 512; plane.segmentsW = plane.segmentsH = 30; view.scene.addChild(plane); // modifier = new HeightMapModifier(plane, Cast.bitmap('terrainMap')); modifier.scale = .25; modifier.execute(); } private function onEnterFrame(e:Event):void { if (light.x > 400) backward = true; if (light.x < -400) backward = false;
322
Flash i ActionScript. Aplikacje 3D od podstaw // if (backward) light.x--; else light.x++; plane.rotationY--; view.render(); } } }
Po skompilowaniu tego kodu źródłowego na ekranie pojawi się plansza z nałożonym ukształtowaniem terenu. Aby uzyskać wyraźniejszy efekt nierówności, dodaliśmy światło PointLight3D, które przemieszcza się wzdłuż osi X. Najważniejszymi klasami, które zaimportowaliśmy w tym przykładzie, są: tworząca nierówności HeightMapModifier, umożliwiająca odbijanie światła WhiteShadingBitmapMaterial oraz samo źródło światła w postaci PointLight3D. W ciele klasy HeightMapModifierExample przed jej metodami zdefiniowaliśmy potrzebne obiekty oraz jedną zmienną backward, która ustala kierunek ruchu światła. Obiekt plane reprezentuje planszę, do której dodaliśmy nierówności. Do nałożenia ukształtowania terenu posłużyły nam obiekty modifier klasy HeightMapModifier oraz obiekt klasy BitmapData o nazwie terrainMap. Ponieważ do lepszego wyeksponowania ukształtowania terenu wykorzystaliśmy ruchome światło, użyliśmy do tego celu obiektu klasy PointLight3D. Poza samym światłem ważne również było użycie odpowiedniego rodzaju tekstury. Jednym z typów materiałów reagujących na źródło światła jest WhiteShadingBitmapMaterial. W naszym przykładzie obiekt tej klasy nazwaliśmy po prostu texture. W konstruktorze klasy po zainicjowaniu obiektu Stage odwołujemy się do metody init(). W metodzie init() dodaliśmy detektor zdarzenia Event.ENTER_FRAME, który uruchamia metodę onEnterFrame. addEventListener(Event.ENTER_FRAME, onEnterFrame);
Poza tym w metodzie init() stworzyliśmy wszystkie potrzebne obiekty do wygenerowania sceny, zaczynając od najbardziej podstawowego, czyli widoku Away3D. Aby móc zobaczyć całą planszę, przesunęliśmy kamerę wyżej i ustawiliśmy jej kierunek na punkt zerowy sceny. view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); // view.camera.y = 600; view.camera.lookAt(new Vector3D(0, 0, 0));
Rozdział 7. Praca z obiektami
323
Po utworzeniu widoku stworzyliśmy obiekt światła PointLight3D o nazwie light. Przypisując nowe wartości właściwościom specular, diffuse oraz ambient, ustawiliśmy poziom natężenia światła, współczynnik rozproszenia oraz natężenie oświetlenia otoczenia. Po ustawieniu wszystkich właściwości dodaliśmy obiekt light jako źródło światła na scenie, stosując metodę addLight(). light = new PointLight3D(); light.specular = .05; light.diffuse = 0.75; light.ambient = 0.5; view.scene.addLight(light);
W kolejnych dwóch linijkach stworzyliśmy obiekt tekstury, który wypełnia powierzchnię planszy. Obiekt ten nazwaliśmy texture. Jest to materiał rodzaju WhiteShadingBitmapMaterial. Jako źródła użyliśmy obrazka terrainTexture, który wcześniej został dodany do zawartości pliku fla tego przykładu. Dodatkowo przypisując wartość true właściwości smooth, poprawiliśmy jakość wyświetlania tekstury. texture = new WhiteShadingBitmapMaterial(Cast.bitmap('terrainTexture')); texture.smooth = true;
W następnym etapie zajęliśmy się planszą. Utworzyliśmy obiekt klasy Plane o nazwie plane. Ustawiliśmy wymiary obiektu plane na 512 pikseli wysokości i szerokości. Dla lepszego odwzorowania ukształtowania zwiększyliśmy liczbę segmentów w wierszach i kolumnach planszy z 1 do 30. Aby plane był widoczny z każdej ze stron, nadaliśmy jego właściwości bothsides wartość true. Oczywiście przypisaliśmy również planszy wcześniej utworzony materiał do pokrycia powierzchni. Na koniec ustawiony obiekt plane dodaliśmy do zawartości sceny, stosując metodę addChild(). plane = new Plane(); plane.material = texture; plane.bothsides = true; plane.width = plane.height = 512; plane.segmentsW = plane.segmentsH = 30; view.scene.addChild(plane);
Na koniec metody init() stworzyliśmy i dodaliśmy obiekt modyfikujący powierzchnię planszy. Obiekt modifier, bo tak go nazwaliśmy, w swoim konstruktorze przyjmuje dwa argumenty. Pierwszy z nich to obiekt, którego powierzchnia ma być zmieniona; w naszym przykładzie jest to plansza plane. Drugiemu argumentowi przypisaliśmy obiekt mapy ukształtowania terenu, dodanego pod nazwą terrainMap do zasobów pliku fla. Ponieważ przy standardowych ustawieniach różnice w ukształtowaniu będą duże, zmniejszyliśmy ich skalę, przypisując właściwości scale wartość 0.25.
324
Flash i ActionScript. Aplikacje 3D od podstaw
Na koniec wywołaliśmy metodę execute(), która generuje nierówności powierzchni. modifier = new HeightMapModifier(plane, Cast.bitmap('terrainMap')); modifier.scale = .25; modifier.execute();
W metodzie onEnterFrame() w pierwszej kolejności sprawdzamy pozycję światła na osi X globalnego układu współrzędnych. Jeżeli pozycja ta przekracza wartość 400 pikseli, to zmiennej backward przypisujemy wartość true. Z kolei gdy pozycja obiektu light na osi X jest mniejsza od -400 pikseli, to wartość zmiennej backward ustawiamy na false. Po tych instrukcjach if kolejna sprawdza wartość zmiennej backward. Jeżeli jest ona prawdą, to obiekt light zmienia swoją pozycję na osi X, zmniejszając wartość właściwości x. Gdy wartość zmiennej backward jest fałszem, to pozycja światła zmienia się, zwiększając wartość właściwości x. if (light.x > 400) backward = true; if (light.x < -400) backward = false; if (backward) light.x--; else light.x++;
Dodatkowo aby pokazać planszę dookoła, użyliśmy właściwości rotationY, nieustannie zmniejszając jej wartość o 1. Na końcu po każdej operacji następuje odświeżenie sceny. plane.rotationY--; view.render();
Tabele 7.5 i 7.6 zawierają najważniejsze z metod i właściwości klasy HeightMap Modifier. Tabela 7.5. Właściwości klasy HeightMapModifier
Nazwa
Rodzaj
Wartość domyślna
Opis
Channel
uint
1
Kolor reprezentujący wysokie wartości z podanej mapy
heightMap
BitmapData
null
Obiekt BitmapData służący jako mapa do modyfikowania powierzchni
maxLevel
uint
255
Maksymalny poziom modyfikowania powierzchni
mesh
Mesh
null
Obiekt siatki modyfikowanej powierzchni wybranego obiektu
offset
Number
0
Określa przesunięcie względem osi Y dodane do każdego z punktów siatki obiektu
scale
Number
1
Określa skalę obliczonej wartości wysokości punktów
Rozdział 7. Praca z obiektami
325
Tabela 7.6. Metody klasy HeightMapModifier
Nazwa
Opis
HeightMapModifier(mesh:Mesh, heightMap:BitmapDatal, channel:uint, maxLevel:uint, scale:Number, offset:Number)
Konstruktor
execute():void
Uruchamia modyfikacje siatki obiektu
refreshNormals():void
Modyfikuje położenie wierzchołków siatki wzdłuż ich wektorów normalnych
refreshPositions():void
Stosuje aktualną wartość przemieszczenia i ustawia ją jako początkową dla kolejnych zmian wartości
reset():void
Przywraca pierwotne wartości współrzędnych dla punktów obiektu
Elevation i SkinExtrude W poprzednim punkcie aby wygenerować nierówności, skorzystaliśmy z obiektów klas HeightMapModifier oraz Plane. W tym punkcie również stworzymy efekt zniekształcenia powierzchni, jednak stosując dwie inne klasy. Pierwszą z tych klas jest Elevation, a drugą SkinExtrude. Obie te klasy uzupełniają się nawzajem w działaniach. Dlatego omówimy je w tym jednym punkcie. Klasa Elevation zlokalizowana jest w pakiecie away3d.extrusions. Służy ona do ustalenia pozycji wierzchołków na podstawie danych pobranych z obiektu BitmapData. Informacje te generują punkty bazujące na kolorach stosowanej mapy. Tak jak w przypadku HeightMapModifier, współrzędna na osi Y wygenerowanych punktów uzależniona jest od koloru w wybranym miejscu. Klasa Elevation interpretuje kolory zastosowane na mapie, odwrotnie niż HeightMapModifier. W tym przypadku jaśniejsze kolory ustawiają wyższe wartości na osi Y. Samo stworzenie obiektu nie wystarczy do wygenerowania danych ukształtowania powierzchni. Aby otrzymać tablicę punktów, należy skorzystać z metody generate() i uzupełnić jej argumenty. Po pierwsze, podajemy źródło mapy powierzchni, przypisując obiekt BitmapData właściwości sourceBmd, która jest parametrem metody generate(). Następnie podajemy kanał kolorów, z których będziemy pobierali dane. Standardowo jest to kolor czerwony 'r'. Uzupełniając właściwości subdivisionX i subdivisionY, ustalamy liczbę segmentów względem osi X i Y. Dokładniejsze odwzorowanie ukształtowania zależy od większej liczby segmentów. Podając mniejsze liczby we właściwościach subdivisionX i subdivisionY, zwiększamy szczegółowość terenu. Standardowe wartości obu tych właściwości równe są 10.
326
Flash i ActionScript. Aplikacje 3D od podstaw
Ważną właściwością jest elevate. Podając jej wartość numeryczną, określa się skalę zróżnicowania ukształtowania. Klasa SkinExtrude, tak samo jak Elevation, zlokalizowana jest w pakiecie away3d. extrusions. SkinExtrude dziedziczy z klasy Mesh, co oznacza, że jej celem jest stworzenie trójwymiarowego obiektu. Przyjmując w konstruktorze jako atrybut aPoints tablicę wygenerowanych punktów współrzędnych, SkinExtrude tworzy obiekt siatki, który można dowolnie przemieszczać, modyfikować i pokrywać różnego rodzaju materiałami. Poza tym atrybutem ma również drugi o nazwie init. To obiekt typu Object, któremu można przypisać różne właściwości inicjacyjne. Aby pokryć teksturą całą powierzchnię obiektu klasy SkinExtrude, trzeba w drugim argumencie konstruktora przypisać właściwości coverAll wartość true.
Teraz zajmiemy się przerobieniem wcześniejszego przykładu, tak aby zastosować obie klasy Elevation i SkinExtrude. Wcześniej wspominaliśmy, że w klasie Elevation rozłożenie punktów na osi Y działa odwrotnie niż w klasie HeightMapModifier. Dlatego w tym przykładzie użyjemy mapy powierzchni z rysunku 7.18 z odwróconymi kolorami. Nowa mapa ukształtowania terenu wygląda tak jak na rysunku 7.20. Rysunek 7.20.
Plik służący jako mapa dla klasy Elevation
Z kolei efekt skompilowania kodu źródłowego tego punktu pokazano na rysunku 7.21. Różnice między tym przykładem a tym z poprzedniego punktu są znikome. Żeby móc to ocenić, przepisz i skompiluj poniższy kod źródłowy. Następnie omówimy jego poszczególne fragmenty.
Rozdział 7. Praca z obiektami Rysunek 7.21.
Ukształtowanie terenu z użyciem Elevation i SkinExtrude
package { import flash.display.BitmapData; import flash.display.Sprite; import flash.events.Event; import flash.geom.Vector3D; // import away3d.lights.PointLight3D; import away3d.extrusions.Elevation; import away3d.extrusions.SkinExtrude; import away3d.containers.View3D; import away3d.core.utils.Cast; import away3d.materials.WhiteShadingBitmapMaterial; public class ElevationExample extends Sprite { private var view:View3D; private var texture:WhiteShadingBitmapMaterial; private var extrude:SkinExtrude; private var elevation:Elevation; private var light:PointLight3D; private var backward:Boolean = false; private var verts:Array; public function ElevationExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void
327
328
Flash i ActionScript. Aplikacje 3D od podstaw { removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); // view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); // view.camera.y = 600; view.camera.lookAt(new Vector3D(0, 0, 0)); // light = new PointLight3D(); light.specular = .05; light.diffuse = 0.75; light.ambient = 0.5; view.scene.addLight(light); // texture = new WhiteShadingBitmapMaterial(Cast.bitmap ('terrainTexture')); texture.smooth = true; // elevation = new Elevation(); verts = elevation.generate(Cast.bitmap('terrainMap'), 'r', 10, 10, 2, 2, .25); extrude = new SkinExtrude(verts, { coverall:true } ); extrude.material = texture; extrude.rotationX = 90; extrude.position = new Vector3D(0, 0, 0); extrude.pivotPoint = new Vector3D(256, 256, 0); view.scene.addChild(extrude); } private function onEnterFrame(e:Event):void { if (light.x > 400) backward = true; if (light.x < -400) backward = false; // if (backward) light.x--; else light.x++; extrude.rotationY--; view.render(); } } }
Aby wygenerować ukształtowanie terenu w tym kodzie źródłowym, skorzystaliśmy z dwóch klas: SkinExtrude oraz Elevation. Aby tak samo jak w przykładzie HeightMapModifier uwydatnić krzywizny siatki, zastosowaliśmy między innymi źródło światła PointLight3D i teksturę rodzaju WhiteShadingBitmapMaterial.
Rozdział 7. Praca z obiektami
329
W konstruktorze klasy odwołujemy się do metody init(), zaraz gdy zainicjowany zostanie obiekt Stage. W metodzie init() na samym początku stworzyliśmy widok Away3D, ustawiając jego pozycję w centrum okna aplikacji. view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view);
Aby kamerą objąć cały obiekt planszy, musieliśmy umieścić ją wyżej w przestrzeni i skierować ją na środek sceny. view.camera.y = 600; view.camera.lookAt(new Vector3D(0, 0, 0));
Po wykonaniu tych czynności stworzyliśmy nowy obiekt światła. Ustawiliśmy przykładowe wartości dla jego właściwości specular, diffuse oraz ambient, po czym stosując metodę addLight(), dodaliśmy obiekt light jako źródło światła na scenie. light = new PointLight3D(); light.specular = .05; light.diffuse = 0.75; light.ambient = 0.5; view.scene.addLight(light);
Do pokrycia powierzchni terenu zastosowaliśmy materiał WhiteShadingBitmap Tak samo jak w przykładzie dla klasy HeightMapModifier, jako źródło dla tekstury wykorzystaliśmy wcześniej dodany do zasobów fla plik graficzny. Przypisując wartość true właściwości smooth, wygładziliśmy wyświetlaną na powierzchni teksturę.
Material.
texture = new WhiteShadingBitmapMaterial(Cast.bitmap('terrainTexture')); texture.smooth = true;
Następnie zajęliśmy się tworzeniem powierzchni. W pierwszej kolejności potrzebowaliśmy punktów, które określają nierówności terenu. Do tego celu stworzyliśmy obiekt klasy Elevation i wywołaliśmy jego metodę generate(). Jako pierwszy argument podaliśmy instancję pliku graficznego jako źródło do stworzenia punktów. W następnym argumencie, podając jako jego wartość 'r', przypisaliśmy kanał, z którego pobierane będą informacje. Liczbę 10 przypisaliśmy argumentom subdivisionX oraz subdivisionY, pozostawiając tym samym standardową liczbę segmentów w wierszu i kolumnie. Ponieważ wymiary mapy powierzchni są o połowę mniejsze od tekstury, którą jest pokryta, należało dwukrotnie zwiększyć wartości scalingX oraz scalingY.
330
Flash i ActionScript. Aplikacje 3D od podstaw
Aby uzyskać przybliżony efekt do tego z przykładu dla klasy HeightMapModifier, musieliśmy dodatkowo zmniejszyć o połowę wartość właściwości elevate, tak aby zmienić wysokość wygenerowanych zniekształceń. elevation = new Elevation(); verts = elevation.generate(Cast.bitmap('terrainMap'), 'r', 10, 10, 2, 2, .25);
Po wygenerowaniu tablicy punktów verts wykorzystaliśmy te punkty przy tworzeniu obiektu klasy SkinExtrude. Podając je w pierwszym argumencie aPoints, ustawiliśmy wygląd powierzchni. W drugim argumencie właściwości coverall przypisaliśmy wartość true. Dzięki temu sprawiliśmy, że tekstura pokryła w jednolity sposób całą powierzchnię obiektu extrude. extrude = new SkinExtrude(verts, { coverall:true } );
W kolejnych linijkach kodu obiekt extrude pokryliśmy wcześniej stworzoną teksturą oraz zmieniliśmy jego położenie. Z uwagi na to, że standardowo obiekty SkinExtrude ustawione są pionowo, musieliśmy przy użyciu właściwości rotationX obrócić go o 90 stopni. W odróżnieniu od innych trójwymiarowych obiektów, punkt zerowy naszej planszy znajduje się w jej lewym górnym rogu. Ponieważ obracamy planszę względem osi Y, musieliśmy przesunąć punkt zerowy obiektu extrude o połowę wymiarów planszy. Do tego celu właściwości pivotPoint przypisaliśmy nowy obiekt Vector3D. Po zapisaniu wszystkich ustawień dodaliśmy obiekt do sceny. extrude.material = texture; extrude.rotationX = 90; extrude.position = new Vector3D(0, 0, 0); extrude.pivotPoint = new Vector3D(256, 256, 0); view.scene.addChild(extrude);
W metodzie onEnterFrame() wykonujemy te same czynności co w przykładzie dla klasy HeightMapModifier. Po pierwsze, sprawdzamy położenie światła na osi X. Następnie w zależności od tej pozycji przypisujemy inną wartość zmiennej backward i ustalamy kierunek poruszania obiektu light. Obrót planszy uzyskaliśmy przez zmniejszanie wartości rotationY obiektu extrude. Z każdym wywołaniem tej metody po wykonaniu wszystkich operacji odświeżamy obraz widoku. Tabele 7.7 i 7.8 zawierają właściwości oraz metody klasy Elevation.
Rozdział 7. Praca z obiektami
331
Tabela 7.7. Właściwości klasy Elevation
Nazwa
Rodzaj
Wartość domyślna Opis
maxElevation
Number
255
Określa maksymalny poziom generowania zniekształceń
minElevation
Number
0
Określa minimalny poziom generowania zniekształceń
Tabela 7.8. Metody klasy Elevation
Nazwa
Opis
Elevation()
Konstruktor
generate(sourceBmd:BitmapData, channel:String, subdivisionX:int, subdivisionY:int, scalingX:Number, scalingY:Number, elevate:Number):Array
Tworzy obiekt tablicy Array zawierający informacje zmodyfikowanej siatki obiektu
W tabeli 7.9 zamieszczono konstruktor klasy SkinExtrusion. Tabela 7.9. Metody klasy SkinExtrusion
Nazwa
Opis
SkinExtrude(aPoints:Array, init:Object)
Konstruktor
Explode i Merge Biblioteka Away3D ma w swoich zasobach klasy umożliwiające rozłożenie na czynniki pierwsze powierzchni obiektu oraz połączenie różnych elementów w jedną spójną całość. Do tych zadań służą klasy Explode oraz Merge, zlokalizowane w pakiecie away3d.tools. Stworzenie i użycie obiektu klasy Explode powoduje rozdzielenie siatki wybranego obiektu na pojedyncze trójkąty. Każdy z nich ma postać nowego trójwymiarowego elementu, co pozwala na wykonanie na nim dowolnych zmian pozycji bądź skali, bez ingerencji w otoczenie, w jakim się znajduje. Należy zaznaczyć, że klasa Explode sama w sobie nie tworzy animacji wybuchu wybranego obiektu. Aby osiągnąć taki efekt, należy wykonać odpowiednie operacje na każdym z trójkątów. W przykładzie dla tego punktu przedstawimy jeden ze sposobów wykonania eksplozji obiektu. Efekt, który uzyskamy po przepisaniu i skompilowaniu kodu źródłowego, przedstawiono na rysunku 7.22.
332
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 7.22. Zastosowanie klasy Explode na obiekcie klasy Sphere
Sposób wykorzystania i działania obiektu klasy Explode omówimy, gdy przepiszesz i skompilujesz następujący kod źródłowy. Wewnątrz wykorzystano odwołania do tekstury kuli i tła, dlatego do poprawnego działania przykładu będzie potrzebny plik fla zawierający źródła tych bitmap. package { import flash.display.StageAlign; import flash.display.StageQuality; import flash.display.StageScaleMode; import flash.display.Sprite; import flash.events.Event; import flash.geom.Vector3D; // import away3d.containers.View3D; import away3d.containers.ObjectContainer3D; import away3d.core.base.Object3D; import away3d.core.utils.Cast; import away3d.materials.BitmapMaterial; import away3d.primitives.Sphere; import away3d.tools.Explode; // import com.greensock.TweenMax; public class ExplosionExample extends Sprite { private var view:View3D; private var deathstar:Sphere; private var explodedDeathstar:ObjectContainer3D; public function ExplosionExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); }
Rozdział 7. Praca z obiektami
333
private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); deathstar = new Sphere( { segmentsH:16, segmentsW:16, radius:180, material:new BitmapMaterial(Cast.bitmap('deathstar')) } ); var explode:Explode = new Explode(true); explodedDeathstar = explode.apply(deathstar) as ObjectContainer3D; view.scene.addChild(explodedDeathstar); destroyDeathstar(); onResize(); } private function destroyDeathstar():void { for (var i:uint = 0; i < explodedDeathstar.children.length; i++) { TweenMax.to(explodedDeathstar.children[i], 10, { x:randomPosition(1000, 2000), y:randomPosition(1000, 2000), z:randomPosition(1000, 2000), onComplete:removePart, onCompleteParams:[explodedDeathstar.children[i]] }); } } private function removePart(part:Object3D):void { explodedDeathstar.removeChild(part); } private function randomPosition(min:Number, max:Number):Number { return Math.floor(Math.random() * max - min); } private function onEnterFrame(e:Event):void { view.render(); } private function onResize(e:Event = null):void
334
Flash i ActionScript. Aplikacje 3D od podstaw { view.x = universe.x = stage.stageWidth * .5; view.y = universe.y = stage.stageHeight * .5; } } }
Gdy zastanawiałem się nad przykładem obrazującym zastosowanie klasy Exploprzypomniał mi się fragment filmu Gwiezdne wojny: Powrót Jedi, w którym zniszczono Gwiazdę Śmierci. Aby stworzyć podobną stację Imperium Galaktycznego, zastosowaliśmy klasę Sphere oraz materiał BitmapMaterial, odwołując się do źródła obrazu poprzez klasę Cast. Niezbędnym elementem tego przykładu jest oczywiście klasa Explode, która podzieli na fragmenty siatkę kuli. de,
W klasie ExplosionExample zdefiniowaliśmy obiekty, które będą potrzebne w całym jej ciele. Obiekt klasy Sphere nazwaliśmy deathstar, a jej rozdzieloną wersję zapisaliśmy w postaci obiektu klasy ObjectContainer3D o nazwie explodedDeathstar. Przykład zaczęliśmy profilaktycznie od sprawdzenia, czy obiekt stage został utworzony. W metodzie init() na początku zapisaliśmy standardowe ustawienia okna aplikacji oraz dodaliśmy potrzebne detektory zdarzeń Event.ENTER_FRAME i Event.RESIZE. W następnej kolejności dodaliśmy widok ze standardowymi ustawieniami i utworzyliśmy obiekty deathstar oraz explodedDeathstar. Obiektowi kuli przypisaliśmy nową liczbę segmentów w pionie i poziomie, zmieniliśmy długość promienia i dodaliśmy teksturę o nazwie deathstar, umieszczoną w zasobach pliku fla. deathstar = new Sphere( { segmentsH:16, segmentsW:16, radius:180, material:new BitmapMaterial(Cast.bitmap('deathstar')) } );
Po utworzeniu obiektu kuli nie dodaliśmy go do sceny, ponieważ nie jest nam on potrzebny w całej okazałości. To, co chcieliśmy wyświetlić, to jego podzielone fragmenty, a do uzyskania tego efektu stworzyliśmy nowe obiekty. Pierwszy z nich to obiekt klasy Explode o takiej samej nazwie. Wpisując w konstruktorze tej klasy wartość true, określiliśmy, że każdy trójkąt będzie osobnym obiektem klasy Mesh. var explode:Explode = new Explode(true);
Drugi utworzony obiekt to kontener explodedDeathstar, którego zawartość wypełniliśmy, stosując metodę apply() obiektu Explode. Użycie tej metody polegało na podaniu w argumencie elementu, którego siatka miała być podzielona. W naszym przykładzie odwołaliśmy się do wcześniej utworzonego obiektu deathstar. Po tej operacji obiekt explodedDeathstar mogliśmy umieścić na scenie Away3D.
Rozdział 7. Praca z obiektami
335
explodedDeathstar = explode.apply(deathstar) as ObjectContainer3D; view.scene.addChild(explodedDeathstar);
Animację rozproszenia po całej scenie obiektów kontenera explodedDeathstar zapisaliśmy w osobnej metodzie destroyDeathstar(). destroyDeathstar();
W metodzie destroyDeathstar() umieściliśmy pętlę for, która wykonuje swój blok kodu dla każdego z elementów uwzględnionych wewnątrz kontenera explodedDeath star. Zadaniem tej metody jest przesunięcie obiektu za pomocą klasy TweenMax do losowej pozycji z wybranego przedziału liczbowego. Po każdym wykonaniu takiej animacji wywoływana jest metoda removePart(), która usuwa poszczególny element z kontenera. for (var i:uint = 0; i < explodedDeathstar.children.length; i++) { TweenMax.to(explodedDeathstar.children[i], 10, { x:randomPosition(1000, 2000), y:randomPosition(1000, 2000), z:randomPosition(1000, 2000), onComplete:removePart, onCompleteParams:[explodedDeathstar.children[i]] }); }
Określenie nowych pozycji dla trójkątów zapisaliśmy w metodzie randomPosition(), której argumenty min i max określają przedział liczbowy dla wylosowanej wartości. return Math.floor(Math.random() * max - min);
W tabelach 7.10 i 7.11 wypisano metody oraz właściwości klasy Explode. Tabela 7.10. Właściwości klasy Explode
Nazwa
Rodzaj
Wartość
Opis
recenter
Boolean
false
Jeżeli odseparowane części są obiektami klasy Mesh, to właściwość ta określa, czy ich współrzędne mają być na nowo wyśrodkowane
unicmeshes
Boolean
false
Określa, czy odseparowane części mają mieć postać obiektów klasy Mesh
Tabela 7.11. Metody klasy Explode
Nazwa
Opis
Explode(unicmeshes:Boolean, recenter:Boolean)
Konstruktor
apply(object3d:Object3D):Object3D
Wykonuje rozłączenie siatki wybranego obiektu
336
Flash i ActionScript. Aplikacje 3D od podstaw
Mimo że w przykładzie nie skorzystaliśmy z klasy Merge, warto o niej wspomnieć. W przeciwieństwie do Explode zadaniem tej klasy jest połączenie różnych obiektów w spójną całość. Proces ten można wykonać w następujący sposób: var merge:Merge = new Merge(); var mergedObject:Mesh = new Mesh(); for (var i:int = 0; i < explodedObject.children.length; i++) { merge.apply(mergedObject,explodedObject.children[i]); }
W tym kodzie dodaliśmy obiekt klasy Merge, nie zmieniając jego standardowych ustawień, oraz pusty obiekt klasy Mesh. Zakładając, że wcześniej stworzyliśmy kontener o nazwie explodedObject, który zawiera potrzebne kawałki rozbitego elementu, połączyliśmy je, stosując metodę apply() obiektu klasy Merge wewnątrz pętli for. Z każdą jej iteracją do obiektu mergedObject dodaliśmy kolejną część z kontenera explodedObject. Zastosowań dla klasy Merge może być wiele. Jedną z sytuacji, w której powinno się z niej skorzystać, jest konieczność połączenia geometrii poszczególnych elementów. Do tego celu nie posłuży nam zwykły addChild() klasy ObjectContainer3D, dlatego warto wiedzieć o istnieniu takiej klasy i zapoznać się z zawartością tabel 7.12 i 7.13. Tabela 7.12. Właściwości klasy Merge
Nazwa
Rodzaj
keepmaterial
Boolean
keepMaterial
Boolean
Wartość
Opis Określa, czy dołączany obiekt ma zachować pokrywający go materiał
false
Zwraca ustaloną wartość właściwości keepmaterial
objectspace
Boolean
true
Określa, czy dołączany obiekt ma zachować lokalny układ współrzędnych
unicgeometry
Boolean
false
Określa, czy dołączany obiekt ma zachować współrzędne uv oraz wierzchołków
Tabela 7.13. Metody klasy Merge
Nazwa
Opis
Merge(objectspace:Boolean, unicgeometry:Boolean, keepMaterial:Boolean)
Konstruktor
apply(mesh1:Mesh, mesh2:Mesh):void
Przyłączenie obiektu mesh2 do mesh1
Rozdział 7. Praca z obiektami
337
Podsumowanie Każdy z trójwymiarowych obiektów ma metody i właściwości pozwalające na jego przemieszczanie, rotację i skalowanie. Aby zmienić pozycję na konkretnej osi, można się posłużyć właściwościami x, y, z. W przypadku zmiany pozycji na wszystkich osiach można zastosować właściwość position. Metoda translate() służy do przemieszczenia obiektu w dowolnym kierunku wzdłuż wektora o określonej długości. Metodą moveTo() można przenieść obiekt w dowolny punkt w przestrzeni. Do przemieszczania obiektu wzdłuż osi lokalnego układu współrzędnych można wykorzystać metody: moveForward() i moveBackward() — przemieszczające obiekt do przodu i wstecz wzdłuż osi Z, moveUp() i moveDown() — przemieszczające obiekt w górę i w dół wzdłuż osi Y, moveLeft() i moveRight() — przemieszczające obiekt w lewo i w prawo wzdłuż osi X. Właściwość pivotPoint określa punkt środkowy, wokół którego obraca się wybrany obiekt. Do zmiany kąta nachylenia w poszczególnych osiach służą właściwości rotationX, rotationY i rotationZ. Jeżeli zajdzie potrzeba, można przenieść punkt pivotPoint, stosując metodę movePivot(). Jest to pożyteczne w przypadku tworzenia efektu orbitowania. Metodami lookAt() oraz rotateTo() można skierować obiekt w wybrany punkt w przestrzeni. Metodami roll(), pitch() oraz yaw() można obracać obiekt wokół własnej osi. Do zmiany skali obiektu służą właściwości scaleX, scaleY, scaleZ oraz metoda scale(). W Away3D macierzą transformacji jest obiekt klasy Matrix3D o wymiarach 4 na 4. Do generowania ścieżek służą klasy Path oraz PathExtrusion. Ścieżka składa się z dwóch tablic punktów Vector3D, które wyznaczają jej tor oraz definiują profil jej podłoża.
338
Flash i ActionScript. Aplikacje 3D od podstaw
Podstawą do tworzenia zniekształceń powierzchni jest bitmapa z dwoma głównymi kolorami, których różne odcienie określają pozycje punktów na osi Y. Klasami generującymi zniekształcenia powierzchni są HeightMapModifier, Elevation i SkinExtrude. Do rozdzielania trójkątów obiektu trójwymiarowego służy obiekt klasy Explode. Do połączenia siatek różnych obiektów należy zastosować obiekt klasy Merge. Przy stosowaniu obiektu Merge można określić, czy każdy z obiektów powinien zachować swoje właściwości.
Rozdział 8. Interaktywność Jedną z głównych zalet pisania aplikacji na platformę Flash jest to, że można tworzyć ciekawe interaktywne gry i strony internetowe. W tego typu projektach większość operacji wykonywanych jest za pomocą myszki, klawiatury i coraz częściej kamer. Jeżeli masz jakieś doświadczenie w realizacji tego typu projektów, z pewnością łatwo połączysz zdobytą wiedzę z informacjami zawartymi w tym rozdziale. Jeżeli są to Twoje początki z aplikacjami tworzonymi na platformę Adobe Flash Player, nie przejmuj się, na tym etapie wiesz już wystarczająco dużo. Tym, co łączy zwykłe dwuwymiarowe aplikacje z projektami bazującymi na bibliotece Away3D, jest to, że w obu przypadkach można korzystać z urządzeń takich jak mysz czy klawiatura. Stosując szereg standardowych metod, zdarzeń z ActionScript 3.0 oraz Away3D, można zaprogramować odpowiednie operacje, które wywoływane będą przez ingerencję użytkownika. Czytając ten rozdział, dowiesz się: Jak obsługuje się zdarzenia wciśnięcia i zwolnienia klawisza klawiatury. Czym są i jakie role odgrywają klasy KeyboardEvent, Keyboard oraz KeyLocation. Jak sterować trójwymiarowym obiektem za pomocą klawiatury. Czym jest klasa MouseEvent3D. W jakich sytuacjach i jak stosować MouseEvent oraz MouseEvent3D. Jak przemieszczać trójwymiarowe obiekty przy użyciu myszki. Jak malować po powierzchni trójwymiarowych obiektów.
340
Flash i ActionScript. Aplikacje 3D od podstaw
Używanie klawiatury W większości gier i innych aplikacji niekiedy obiekty sterowane są za pomocą klawiatury. W aplikacjach Flash do wykrywania czynności wykonywanych przez użytkownika na klawiaturze stosuje się obiekt zdarzenia KeyboardEvent.
Klasa KeyboardEvent Klasa KeyboardEvent, ponieważ reprezentuje obiekt zdarzenia, zlokalizowana jest w pakiecie flash.events. Ma ona dwie stałe: KeyboardEvent.KEY_DOWN i KeyboardEvent. KEY_UP, które określają przypadki wciśnięcia i zwolnienia klawisza na klawiaturze. Aby móc korzystać z tych zdarzeń, należy metodę addEventListener wywołać na obiekcie stage, tak jak to pokazano poniżej: stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
Samo wywołanie detektora nie wystarczy do podjęcia konkretnych akcji. W ciele metody wywoływanej podczas wystąpienia zdarzenia należy rozpoznać, który klawisz zmienił swój stan. Do tego celu służą dwie właściwości: keyCode i charCode. Pierwsza zwraca wartość numeryczną odpowiadającą numerowi klawisza, a druga określa wartość liczbową kodu znaku wciśniętego klawisza. Istnieje zasadnicza różnica między wartościami tych właściwości. Polega ona na tym, że kod klawisza jest przypisany do konkretnego klawisza fizycznego, natomiast kod znaku jest przypisany do konkretnego znaku. Czyli jeśli na przykład chcemy wykorzystać klawisz 5 na klawiaturze numerycznej i w górnym rzędzie, należy odwołać się do wartości właściwości charCode. Domyślny zestaw kodów znaków UTF-8 zawiera w swoim zbiorze identyfikatory dla każdego ze znaków z osobna, odróżnia tym samym wielkość liter.
Na klawiaturze umieszczone są również klawisze specjalne, takie jak Ctrl, Alt czy Shift. Do określenia, czy któryś z tych klawiszy zmienił swój stan, służą specjalne właściwości: ctrlKey, altKey i shiftKey. Wartości tych właściwości są typu Boolean, gdzie true oznacza, że klawisz jest wciśnięty. Tabele 8.1, 8.2 oraz 8.3 zawierają metody, właściwości i stałe klasy KeyboardEvent.
Rozdział 8. Interaktywność
341
Tabela 8.1. Właściwości klasy KeyboardEvent
Nazwa
Rodzaj
Wartość domyślna Opis
altKey
Boolean
false
Określa, czy klawisz Alt jest wciśnięty
ctrlKey
Boolean
false
Określa, czy klawisz Ctrl jest wciśnięty
controlKey
Boolean
false
Określa, czy klawisz Control jest wciśnięty
commandKey
Boolean
false
Określa, czy klawisz Command jest wciśnięty
shiftKey
Boolean
false
Określa, czy klawisz Shift jest wciśnięty
charCode
uint
Wartość liczbowa kodu znaku wciśniętego klawisza
keyCode
uint
Wartość liczbowa odpowiadająca numerowi klawisza na klawiaturze
keyLocation uint
Położenie klawisza na klawiaturze
Tabela 8.2. Metody klasy KeyboardEvent
Nazwa
Opis
KeyboardEvent(type:String, bubbles:Boolean, Konstruktor cancelable:Boolean, charCodeValue:uint, keyCodeValue:uint, keyLocationValue:uint, ctrlKeyValue:Boolean, altKeyValue:Boolean, shiftKeyValue:Boolean, controlKeyValue:Boolean, commandKeyValue:Boolean) updateAfterEvent
Określa, czy po zakończeniu przetwarzania zdarzenia obraz powinien być zrenderowany, jeżeli lista wyświetlania została zmodyfikowana
Tabela 8.3. Stałe klasy KeyboardEvent
Nazwa
Opis
KEY_DOWN
Definiuje zdarzenie wciśnięcia klawisza
KEY_UP
Definiuje zdarzenie zwolnienia wciśniętego klawisza
Klasa Keyboard Keyboard jest klasą typu final, umieszczoną w pakiecie flash.ui. Ma ona zdefiniowane kody większości znaków w postaci stałych publicznych. Do jej metod oraz właściwości można odwoływać się bez tworzenia obiektu. Dzięki tym cechom Keyboard stanowi swego rodzaju słownik do stosowania w instrukcjach warunkowych takich jak if czy switch.
342
Flash i ActionScript. Aplikacje 3D od podstaw switch(event.charCode) { case Keyboard.UP: trace('Wciśnięto klawisz górnej strzałki'); break; case Keyboard.DOWN: trace('Wciśnięto klawisz dolnej strzałki'); break; case Keyboard.LEFT: trace('Wciśnięto klawisz lewej strzałki'); break; case Keyboard.RIGHT: trace('Wciśnięto klawisz prawej strzałki'); break; }
Tabele 8.4 i 8.5 zawierają metody i właściwości klasy Keyboard. Ważniejsze od nich są jednak stałe umieszczone w tabeli 8.6. Keyboard ma dużą liczbę stałych, dlatego przedstawiono tylko niektóre z nich. Tabela 8.4. Właściwości klasy Keyboard
Nazwa
Rodzaj
Wartość domyślna
Opis
capsLock
Boolean
false
Określa, czy klawisz Caps Lock jest wciśnięty
hasVirtualKeyboard
Boolean
false
Określa, czy udostępniona jest klawiatura wirtualna
numLock
Boolean
false
Określa, czy klawisz Num Lock jest wciśnięty
physicalKeyboardType
String
Określa rodzaj klawiatury
Tabela 8.5. Metody klasy Keyboard
Nazwa
Opis
isAccessible():Boolean
Określa, czy ostatnio wciśnięty klawisz jest dostępny dla innych plików SWF
Tabela 8.6. Stałe klasy Keyboard
Nazwa
Opis
A … Z
Klawisze alfabetyczne
NUMBER_0 … NUMBER_9
Klawisze numeryczne umieszczone w górnym rzędzie
NUMPAD_0 … NUMPAD_9
Klawisze numeryczne umieszczone na klawiaturze numerycznej
F1 … F12
Klawisze funkcyjne
ENTER
Klawisz Enter
ESCAPE
Klawisz Esc
CONTROL
Klawisz Ctrl
BACKSPACE
Klawisz Backspace
SPACE
Klawisz spacji
LEFT
Klawisz lewej strzałki
Rozdział 8. Interaktywność
343
Tabela 8.6. Stałe klasy Keyboard (ciąg dalszy)
Nazwa
Opis
RIGHT
Klawisz prawej strzałki
UP
Klawisz górnej strzałki
DOWN
Klawisz dolnej strzałki
Klasa KeyLocation W tabeli 8.1 punktu „Klasa KeyboardEvent” wspomniano o tym, że właściwość keyLocation określa położenie wciskanego lub zwalnianego klawisza. W sytuacji gdy dany klawisz ma swoje duplikaty w innych miejscach na klawiaturze, zastosowanie tej właściwości pozwala na odróżnienie na przykład lewego i prawego klawisza Ctrl, co umożliwia przypisanie każdemu innych działań. Do określenia wartości właściwości keyLocation klasy KeyboardEvent służą stałe klasy KeyLocation. Zlokalizowana w pakiecie flash.ui klasa KeyLocation — tak samo jak Keyboard — jest klasą z przypisanym atrybutem final. KeyLocation można traktować jako mały słownik, który ma w swoich zasobach określenia dostępnych pozycji na klawiaturze. if(event.charCode == Keyboard.CONTROL && event.keyLocation == KeyLocation.LEFT) { trace('Wciśnięto lewy Ctrl'); } else if(event.charCode == Keyboard.CONTROL && event.keyLocation == KeyLocation.RIGHT) { trace('Wciśnięto prawy Ctrl'); }else { trace('Wciśnięto inny klawisz'); }
Ponieważ klasa KeyLocation nie ma swoich metod i właściwości, tylko stałe określające położenie, w tym punkcie uwzględniono jedną tabelę 8.7. Tabela 8.7. Stałe klasy KeyLocation
Nazwa
Opis
D_PAD
Określa, czy wciśnięty klawisz znajduje się na panelu kierunkowym
LEFT
Określa, czy wciśnięty klawisz znajduje się po lewej stronie klawiatury
RIGHT
Określa, czy wciśnięty klawisz znajduje się po prawej stronie klawiatury
344
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 8.7. Stałe klasy KeyLocation (ciąg dalszy)
Nazwa
Opis
NUM_PAD
Określa, czy wciśnięty klawisz znajduje się na klawiaturze numerycznej lub w jej wirtualnym odpowiedniku
STANDARD
Określa, czy pozycja klawisza nie została określona w konkretnych pozycjach
Sterowanie statkiem kosmicznym w przestrzeni Skoro poznaliśmy klasy obsługujące interakcje użytkownika z użyciem klawiatury, przyszedł czas na zastosowanie zdobytej wiedzy w praktyce. Do tego celu wykorzystamy możliwość sterowania futurystycznym pojazdem w przestrzeni kosmicznej. Aplikacja ta składa się z dwóch warstw. Pierwsza to przestrzeń trójwymiarowa, w której porusza się pojazd, druga warstwa to element interfejsu użytkownika, w którym widoczne są aktualna prędkość pojazdu oraz spis klawiszy i akcji, jakie wywołują. Całość przedstawiono na rysunku 8.1.
Rysunek 8.1. Zrzut ekranu z aplikacji sterowania statkiem kosmicznym
Widoczny statek kosmiczny to jeden z darmowych modeli w formacie 3DS, które są dostępne w sieci. W pozycji początkowej pojazd ten skierowany jest dziobem w stronę osi Z. Gdy wciskamy klawisze lewej i prawej strzałki, obiekt obraca się
Rozdział 8. Interaktywność
345
wokół osi Z. Natomiast klawiszami strzałki górnej i dolnej odpowiednio podnosi się i opuszcza dziób. Wprawienie w ruch statku kosmicznego następuje po wciśnięciu klawisza spacji. Zwroty i skręty uzależnione są od kąta nachylenia pojazdu i prędkości, z jaką się porusza. Standardowo prędkość jest równa 50, lecz można ją zwiększyć, wciskając klawisz A, lub zmniejszyć klawiszem Z. Aby szybko zredukować prędkość do jej standardowej wartości, wystarczy użyć klawisza C. W sytuacji gdy stracimy pojazd z pola widzenia, można przywrócić go do pozycji początkowej, wciskając klawisz X, lub namierzyć kamerą jego pozycję, używając klawisza T. Ponieważ kamery zostaną omówione dopiero w rozdziale 9. „Kamery”, skorzystamy ze standardowej, utworzonej wraz z widokiem. Teraz przepisz i skompiluj kod źródłowy tego przykładu, a następnie zajmiemy się omawianiem jego poszczególnych fragmentów. package { import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.display.MovieClip; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.Vector3D; import flash.ui.Keyboard; // import away3d.containers.ObjectContainer3D; import away3d.containers.View3D; import away3d.loaders.Loader3D; import away3d.loaders.Max3DS; import com.greensock.TweenMax; public class KeyboardEventsExample extends Sprite { private var view:View3D; private var speed:Number = 5; private var shipLoader:Loader3D; private var ship:ObjectContainer3D; private var leftArrowDown:Boolean = false; private var rightArrowDown:Boolean = false; private var upArrowDown:Boolean = false; private var downArrowDown:Boolean = false; private var spaceDown:Boolean = false; private var trackShip:Boolean = false; private var speedMonitor:Monitor; public function KeyboardEventsExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); }
346
Flash i ActionScript. Aplikacje 3D od podstaw private function init(e:Event = null):void { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); view = new View3D(); view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; addChild(view); view.camera.z = -100; speedMonitor = new Monitor(); speedMonitor.txt.text = String(speed * 10); addChild(speedMonitor); loadShipModel(); onResize(); } private function onResize(e:Event = null):void { universe.x = stage.stageWidth * .5; universe.y = stage.stageHeight * .5; view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; speedMonitor.y = stage.stageHeight - 100; speedMonitor.x = 100; } private function loadShipModel():void { ship = new ObjectContainer3D(); shipLoader = Max3DS.load('../../resources/models/max3ds/spaceship/ spaceship.3DS'); shipLoader.rotationY = -90; shipLoader.rotationX = 90; shipLoader.position = new Vector3D(0, 0, 0); ship.addChild(shipLoader); view.scene.addChild(ship); } private function onKeyDown(e:KeyboardEvent):void { if (e.keyCode == Keyboard.C) { speed = 5; speedMonitor.txt.text = String(speed * 10);
Rozdział 8. Interaktywność
347
} if (e.keyCode == Keyboard.A) { speed += .5; speedMonitor.txt.text = String(speed * 10); } if (e.keyCode == Keyboard.Z) { speed -= .5; speedMonitor.txt.text = String(speed * 10); } if (e.keyCode == Keyboard.LEFT) leftArrowDown = true; if (e.keyCode == Keyboard.RIGHT) rightArrowDown = true; if (e.keyCode == Keyboard.UP) upArrowDown = true; if (e.keyCode == Keyboard.DOWN) downArrowDown = true if (e.keyCode == Keyboard.SPACE) spaceDown = true; } private function onKeyUp(e:KeyboardEvent):void { if (e.keyCode == Keyboard.LEFT) leftArrowDown = false; if (e.keyCode == Keyboard.RIGHT) rightArrowDown = false; if (e.keyCode == Keyboard.UP) upArrowDown = false; if (e.keyCode == Keyboard.DOWN) downArrowDown = false if (e.keyCode == Keyboard.SPACE) spaceDown = false; if (e.keyCode == Keyboard.X) { trackShip = false; view.camera.lookAt(new Vector3D(0, 0, 0)); TweenMax.to(ship, 1, { x:0, y:0, z:0, rotationY:0, rotationX:0, rotationZ:0 } ); } if (e.keyCode == Keyboard.T) { trackShip = !trackShip; } } private function onEnterFrame(e:Event):void { if (spaceDown) ship.moveForward(speed); if (leftArrowDown) ship.roll(-5); if (rightArrowDown) ship.roll(5); if (upArrowDown) ship.pitch(5); if (downArrowDown) ship.pitch( -5); if (trackShip) view.camera.lookAt(ship.scenePosition); view.render(); } } }
348
Flash i ActionScript. Aplikacje 3D od podstaw
W tym przykładzie skorzystaliśmy z podstawowych klas języka ActionScript 3.0 między innymi do obsługi zdarzeń użycia klawiatury, słownika kodów klawiszy oraz wyświetlenia interfejsu. import flash.display.MovieClip; import flash.events.KeyboardEvent; import flash.ui.Keyboard;
Z Away3D poza widokiem użyliśmy klas służących do ładowania i umieszczania na scenie zewnętrznych obiektów 3D. W tym konkretnym przypadku modeli w formacie 3DS. import import import import
away3d.containers.ObjectContainer3D; away3d.containers.View3D; away3d.loaders.Loader3D; away3d.loaders.Max3DS;
Dodatkowo do ustawienia statku w pozycji zerowej sceny skorzystaliśmy z biblioteki TweenMax. import com.greensock.TweenMax;
W klasie KeyboardEventsExample na początku zdefiniowaliśmy potrzebne obiekty i zmienne. Jako obiekt reprezentujący model 3D statku kosmicznego wykorzystaliśmy obiekt klasy Loader3D i nazwaliśmy go shipLoader. Wewnętrzne ustawienia pozycji modelu uzależnione są od tego, jak został on wyeksportowany. Dlatego właśnie zawartość obiektu shipLoader umieścimy w kontenerze o nazwie ship, aby w jego wnętrzu dokonywać zmian. Obiekt o nazwie speedMonitor klasy Monitor to reprezentant obiektu interfejsu. Kolejne linijki to zmienne stosowane do obsługi pojazdu. Zmienna speed to stopień zmiany prędkości statku, z kolei trackShip określa, czy kamera ma śledzić obiekt statku, czy nie. Zmienne: leftArrowDown, rightArrowDown, upArrowDown, downArrowDown oraz spaceDown określają, czy wybrany klawisz został wciśnięty. private private private private private private private private private private private
var var var var var var var var var var var
view:View3D; shipLoader:Loader3D; ship:ObjectContainer3D; speedMonitor:Monitor; speed:Number = 5; trackShip:Boolean = false; leftArrowDown:Boolean = false; rightArrowDown:Boolean = false; upArrowDown:Boolean = false; downArrowDown:Boolean = false; spaceDown:Boolean = false;
W konstruktorze uruchamiamy metodę init(), z chwilą gdy obiekt stage zostanie zainicjowany.
Rozdział 8. Interaktywność
349
if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init);
W metodzie init() w pierwszych dwóch linijkach usunęliśmy detektor zdarzenia Event.ADDED_TO_STAGE i utworzyliśmy nowy dla Event.ENTER_FRAME. W kolejnych linijkach ustawiliśmy właściwości dla okna aplikacji i dodaliśmy kolejne rejestratory zdarzeń dla obiektu stage. Pierwszy z nich uruchamia metodę onResize() z każdą zmianą rozmiarów okna. Kolejne dwa słuchają zdarzeń wciśnięcia i zwolnienia klawiszy klawiatury. removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
W dalszej części metody init() stworzyliśmy widok Away3D i zmieniliśmy pozycję kamery na osi Z, tak aby przybliżyć ją do obiektu statku kosmicznego. view = new View3D(); addChild(view); view.camera.z = -100;
Po utworzeniu widoku dodaliśmy obiekt speedMonitor, który przedstawia listę dostępnych klawiszy i prędkość pojazdu. Wyświetloną w panelu użytkownika prędkość początkową uzyskaliśmy z iloczynu wartości zmiennej speed i liczby 10. speedMonitor = new Monitor(); speedMonitor.txt.text = String(speed * 10); addChild(speedMonitor);
Na końcu init() wywołujemy metodę loadShipModel() i onResize(), aby ustawić wszystkie obiekty w odpowiednim miejscu. loadShipModel(); onResize();
W metodzie onResize() centrujemy widok Away3D oraz obiekt typu movieClip o nazwie universe. Obiekt universe to gwieździste tło, które zostało bezpośrednio umieszczone w głównym oknie aplikacji. Z kolei obiekt interfejsu użytkownika umieszczamy w lewym dolnym rogu okna. universe.x = stage.stageWidth * .5; universe.y = stage.stageHeight * .5; view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; speedMonitor.y = stage.stageHeight - 100; speedMonitor.x = 100;
350
Flash i ActionScript. Aplikacje 3D od podstaw
Metoda loadShipModel() służy do pobrania modelu z konkretnej lokalizacji i umieszczenia go na scenie. W pierwszej kolejności stworzyliśmy obiekt klasy ObjectContainer3D o nazwie ship, który będzie służył jako kontener dla pobranego modelu. Następnie pobierany jest model w formacie 3DS za pomocą klasy Max3DS i jej metody load. Po załadowaniu zmieniliśmy kąt nachylenia statku na osi Y oraz X i ustawiliśmy jego pozycję w miejscu zerowym kontenera. Na końcu umieściliśmy obiekt na scenie. ship = new ObjectContainer3D(); shipLoader = Max3DS.load('models/spaceship/spaceship.3ds'); shipLoader.rotationY = -90; shipLoader.rotationX = 90; shipLoader.position = new Vector3D(0, 0, 0); ship.addChild(shipLoader); view.scene.addChild(ship);
W sytuacji gdy model pochodzi z nieznanych źródeł, warto wcześniej sprawdzić w Away3D, jak prezentuje się on na scenie. Może dojść do tego, że jego skala i pozycja nie odpowiadają konkretnym potrzebom. W takiej sytuacji, jeżeli nie mamy możliwości edytowania modelu, można umieścić go w kontenerze ObjectContainer3D i w jego wnętrzu skorygować skalę bądź kąty nachylenia, na końcu ustawiając jego pozycję. Dzięki temu unikniemy konieczności przestawiania całej sceny i kamery, aby uzyskać oczekiwany efekt.
Metoda onKeyDown() w całości składa się z instrukcji warunkowych. W tym miejscu sprawdzane są wartości klawiszy i przypisywane odpowiednie akcje bądź wartości zmiennym określającym stan używanych klawiszy. W sytuacji gdy wartość keyCode zdarzenia KeyboardEvent równa jest Keyboard.C, prędkość redukowana jest do wartości początkowej. if (e.keyCode == Keyboard.C) { speed = 5; speedMonitor.txt.text = String(speed * 10); }
Z kolei gdy wartość keyCode zdarzenia KeyboardEvent równa jest Keyboard.A, prędkość statku zwiększa się o 0.5. if (e.keyCode == Keyboard.A) { speed += .5; speedMonitor.txt.text = String(speed * 10); }
Zmniejszenie prędkości następuje po wciśnięciu klawisza Z, którego wartość keyCode równa jest Keyboard.Z.
Rozdział 8. Interaktywność
351
if (e.keyCode == Keyboard.Z) { speed -= .5; speedMonitor.txt.text = String(speed * 10); }
Aby zachować ciągłość akcji sterowania statkiem, nie modyfikujemy w tym miejscu jego pozycji, lecz przypisujemy wartość typu Boolean właściwościom, które w metodzie onEnterFrame() będą wskazywały na wciskane przez użytkownika klawisze. Zdarzenie KeyboardEvent.KEY_DOWN uruchamiane jest jedynie przy zmianie stanu, w jakim znajduje się klawisz, dlatego zapisanie zmiany pozycji w metodzie onKeyDown() spowodowałoby pojedyncze przejście dla każdego wciśniętego klawisza. if if if if if
(e.keyCode (e.keyCode (e.keyCode (e.keyCode (e.keyCode
== == == == ==
Keyboard.LEFT) Keyboard.RIGHT) Keyboard.UP) Keyboard.DOWN) Keyboard.SPACE)
leftArrowDown = true; rightArrowDown = true; upArrowDown = true; downArrowDown = true spaceDown = true;
W metodzie onKeyUp() również zawarliśmy instrukcje warunkowe if, ale tutaj sprawdzaliśmy stan klawisza i przypisywaliśmy odpowiedniej zmiennej wartość false. Nie mogliśmy wszystkim naraz przypisać wartości false, ponieważ mogłoby to przerwać ciągłość innej akcji. if if if if if
(e.keyCode (e.keyCode (e.keyCode (e.keyCode (e.keyCode
== == == == ==
Keyboard.LEFT) Keyboard.RIGHT) Keyboard.UP) Keyboard.DOWN) Keyboard.SPACE)
leftArrowDown = false; rightArrowDown = false; upArrowDown = false; downArrowDown = false spaceDown = false;
W przypadku wystąpienia warunku, w którym wciśnięty jest klawisz X, zapisaliśmy zmianę wartości zmiennej trackShip na false oraz skierowaliśmy kamerę w stronę środka sceny. Poza tym korzystając z klasy TweenMax, stworzyliśmy animację, w której statek kosmiczny powraca do swojej pierwotnej pozycji. if (e.keyCode == Keyboard.X) { trackShip = false; view.camera.lookAt(new Vector3D(0, 0, 0)); TweenMax.to(ship, 1, { x:0, y:0, z:0, rotationY:0, rotationX:0, rotationZ:0 } ); }
W warunku wciśnięcia klawisza T zapisaliśmy zmianę wartości trackShip na przeciwną do aktualnie ustawionej. if (e.keyCode == Keyboard.T) { trackShip = !trackShip; }
352
Flash i ActionScript. Aplikacje 3D od podstaw
W metodzie onEnterFrame(), która jest stale wywoływana, użyliśmy wszystkich zmiennych, których wartości zmieniają się w metodach onKeyDown() oraz onKeyUp(). W zależności od tego, która ze zmiennych równa jest wartości true, wykonywana jest akcja obrotu bądź przesunięcia. Dodatkowo jeżeli wartość trackShip równa jest true, kamera przy każdym odświeżeniu ustawia się w kierunku pozycji statku. if (spaceDown) ship.moveForward(speed); if (leftArrowDown) ship.roll(-5); if (rightArrowDown) ship.roll(5); if (upArrowDown) ship.pitch(5); if (downArrowDown) ship.pitch(-5); if (trackShip) view.camera.lookAt(ship.scenePosition); view.render();
Używanie myszy W poprzednim podrozdziale pokazaliśmy, jak z użyciem klawiatury manipulować trójwymiarowymi obiektami umieszczonymi na scenie. Poparliśmy to przykładem sterowania pojazdem kosmicznym. Jak jednak wiadomo, w aplikacjach Flash częściej stosuje się mysz komputerową niż klawiaturę. Dlatego w tym podrozdziale przyjrzymy się możliwym sposobom zastosowania tego urządzenia wraz z biblioteką Away3D.
MouseEvent Jak wiemy, w języku ActionScript 3.0 dostępna jest klasa MouseEvent, której obiekt obsługuje zdarzenia związane z myszą. W zwykłych aplikacjach dwuwymiarowych zdarzeń myszy można nasłuchiwać na obiektach potomnych względem klasy InteractiveObject, czyli prawie na każdym widocznym obiekcie. Niestety, na obiektach trójwymiarowych umieszczonych na scenie Away3D nie ma możliwości bezpośredniego stosowania zdarzeń klasy MouseEvent. Mimo to są sytuacje, w których można połączyć stosowanie klasy MouseEvent z obiektami trójwymiarowymi. Pierwszy sposób polega na zastosowaniu klasy MovieClipSprite, którą poznaliśmy w rozdziale 3. „Obiekty”. Jak pamiętamy, wyświetlana w obiekcie klasy MovieClipSprite tekstura ma postać obiektu klasy DisplayObject. Skutkiem tego na powierzchni tej tekstury można korzystać ze zdarzeń MouseEvent. Nie tylko na obiekcie klasy MovieClipSprite można stosować zdarzenia MouseEvent. Otóż w rozdziale 4. „Materiały” poznaliśmy klasy MovieMaterial, PhongMovieMaterial oraz Dot3MovieMaterial, dla których źródłem wyświetlanej grafiki są obiekty klas Sprite oraz MovieClip. Jak wiemy, bezpośrednio na obiekcie zarówno klasy Sprite, jak i MovieClip można wywołać zdarzenia MouseEvent, ale gdy używamy ich wewnątrz
Rozdział 8. Interaktywność
353
któregoś z wyżej wymienionych materiałów, są one niedostępne dla wskaźnika myszy. Jest to normalny stan, który można zmienić, nadając właściwości interactive wybranego materiału wartość true. Inny sposób wykorzystania zdarzeń MouseEvent do modyfikowania obiektów 3D polega na pobieraniu współrzędnych wskaźnika myszy i przekazywaniu ich do sceny Away3D. Wykrywając zmianę pozycji kursora myszki w oknie aplikacji, można na obiekcie umieszczonym w przestrzeni trójwymiarowej wykonać czynności takie jak skalowanie, przesunięcie czy też obrót. W przykładzie umieszczonym w kolejnym punkcie tego podrozdziału odniesiemy się do tego sposobu interakcji z użyciem myszki komputerowej.
Obracanie i skalowanie statku kosmicznego Widocznym elementem w tym przykładzie będzie jedynie model statku kosmicznego zastosowany wcześniej w punkcie „Sterowanie statkiem kosmicznym w przestrzeni”. Pozycją początkową dla pojazdu będzie środek przestrzeni trójwymiarowej. Sam model zostanie skierowany dziobem wzdłuż osi Z. Poruszając kursorem, będzie można doprowadzić model do różnych pozycji i wymiarów. Przykładowe ustawienia zaprezentowano na rysunku 8.2.
Rysunek 8.2. Przykładowe ustawienia pozycji i wymiarów modelu
354
Flash i ActionScript. Aplikacje 3D od podstaw
Aby zmienić pozycję modelu statku kosmicznego, należy wcisnąć lewy przycisk myszy i trzymając go, poruszać kursorem w różne strony okna aplikacji. Wciśnięcie i trzymanie przycisku powoduje zmianę wartości jednej ze zmiennych, która jest warunkiem podejmowania działań obrotu. Bez tego każde poruszenie kursorem, nawet nieplanowe, powodowałoby modyfikacje. Aby dodatkowo przy poruszaniu kursorem kontrolować wymiary modelu, należy użyć klawisza Ctrl. Cały mechanizm wyjaśnimy przy omawianiu kodu źródłowego tego przykładu. Teraz przepisz i skompiluj następujący kod. package { import flash.display.StageAlign; import flash.display.StageQuality; import flash.display.StageScaleMode; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.KeyboardEvent; import flash.ui.Keyboard; import flash.geom.Vector3D; // import away3d.containers.ObjectContainer3D; import away3d.containers.View3D; import away3d.loaders.Loader3D; import away3d.loaders.Max3DS; public class MouseEventExample extends Sprite { private var view:View3D; private var shipLoader:Loader3D; private var ship:ObjectContainer3D; private var mouseDown:Boolean = false; private var ctrlDown:Boolean = false; private var xPos:Number; private var yPos:Number; private var ease:Number = .3; public function MouseEventExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize);
Rozdział 8. Interaktywność
355
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); // view = new View3D(); addChild(view); view.camera.z = -50; // ship = new ObjectContainer3D(); shipLoader = Max3DS.load(' ../../resources/models/max3ds/spaceship/ spaceship.3DS'); shipLoader.rotationY = -90; shipLoader.rotationX = 90; shipLoader.position = new Vector3D(0, 0, 0); ship.addChild(shipLoader); view.scene.addChild(ship); // onResize(); } private function onMouseDown(e:MouseEvent):void { mouseDown = true; } private function onMouseUp(e:MouseEvent):void { mouseDown = false; } private function onKeyDown(e:KeyboardEvent):void { if (e.keyCode == Keyboard.CONTROL) ctrlDown = true; } private function onKeyUp(e:KeyboardEvent):void { if (e.keyCode == Keyboard.CONTROL) ctrlDown = false; } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { xPos = stage.mouseX - stage.stageWidth * .5; yPos = stage.mouseY - stage.stageHeight * .5;
356
Flash i ActionScript. Aplikacje 3D od podstaw if (mouseDown && !ctrlDown) { ship.rotationY += (xPos * .5 - ship.rotationY) * ease; ship.rotationX += (yPos * .5 - ship.rotationX) * ease; } else if (mouseDown && ctrlDown) { ship.scale(Math.sqrt(xPos * xPos + yPos * yPos)*.01); } view.render(); } } }
W tym przykładzie najważniejszą dla nas klasą z pakietu flash jest oczywiście MouseEvent. Z Away3D, tak samo jak w poprzednich przykładach tego rozdziału, skorzystaliśmy z modelu 3DS. Dlatego potrzebowaliśmy klas Loader3D, Max3DS i ObjectContainer3D do umieszczenia modelu statku na scenie. Na początku ciała klasy MouseEventExample zdefiniowaliśmy obiekt widoku Away3D, obiekty służące do pobrania i wyświetlenia modelu 3DS oraz kilka zmiennych. Pierwsza w kolejności mouseDown przyjmuje wartości typu Boolean i określa, czy lewy przycisk myszy jest wciśnięty. Jeżeli jest, to wartość tej właściwości równa się true. Kolejną zmienną, którą dodaliśmy, jest ctrlDown. Jej wartość również jest typu Boolean i określa, czy został wciśnięty klawisz Ctrl. Właściwości xPos oraz yPos to zmienne liczbowe, które określają odległość pozycji kursora od środka okna aplikacji. Ich wartości są modyfikowane przy każdym odświeżeniu stołu montażowego. Ostatnia zmienna ease odpowiada za złagodzenie przejścia między pozycjami bądź wymiarami modelu pojazdu. Jej wartość w trakcie uruchamiania oraz używania programu nie zmienia się, dlatego można zdefiniować ją jako wartość stałą. private private private private private private private private
var var var var var var var var
view:View3D; shipLoader:Loader3D; ship:ObjectContainer3D; mouseDown:Boolean = false; ctrlDown:Boolean = false; xPos:Number; yPos:Number; ease:Number = .3;
Podobnie jak w poprzednich konstruktorach dla klas przykładów, i tutaj, gdy obiekt stage jest gotowy, uruchamiamy metodę init(). if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init);
Rozdział 8. Interaktywność
357
Na początku metody init() ustawiliśmy jakość generowanego obrazu na poziomie LOW. W następnej kolejności wyłączyliśmy skalowanie okna oraz wyrównaliśmy stół montażowy do lewego górnego rogu. Dzięki temu aplikacja będzie działała płynniej i przy powiększeniu okna zwiększymy obszar działania kursora. stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT;
W kolejnych linijkach kodu metody init() usunęliśmy detektor zdarzenia Event. ADDED_TO_STAGE i utworzyliśmy szereg nowych. Kolejność, z jaką je dodaliśmy, jest przypadkowa. Pierwszy detektor odpowiada za nasłuchiwanie zdarzenia Event.ENTER_FRAME i wywołanie metody onEnterFrame(). W drugiej kolejności dodaliśmy detektor dla zmiany rozmiarów okna aplikacji Event.RESIZE. Kolejne dwa detektory nasłuchują zdarzeń MouseEvent.MOUSE_DOWN oraz MouseEvent.MOUSE_UP, które odpowiadają za kliknięcie i puszczenie przycisku myszki. Podobne zadanie przydzieliliśmy detektorom zdarzeń KeyboardEvent.KEY_DOWN i KeyboardEvent.KEY_UP, z tą różnicą, że one wskazują na kliknięcie bądź zwolnienie klawisza na klawiaturze. removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
Po ustaleniu detektorów zdarzeń stworzyliśmy i dodaliśmy obiekt widoku biblioteki Away3D. Dodatkowo przybliżyliśmy kamerę do pozycji -50 na osi Z, tak aby statek w swojej początkowej pozycji był dobrze widoczny. view = new View3D(); addChild(view); view.camera.z = -50;
Gdy widok został dodany, stworzyliśmy obiekt statku kosmicznego, korzystając z ustawień z poprzednich przykładów tego rozdziału. Po pobraniu modelu za pomocą klasy Max3DS obróciliśmy go tak, aby dziobem, zwrócony był w stronę osi Z. Następnie umieściliśmy go w kontenerze o nazwie ship i dodaliśmy go do sceny. ship = new ObjectContainer3D(); shipLoader = Max3DS.load('models/spaceship/spaceship.3ds'); shipLoader.rotationY = -90; shipLoader.rotationX = 90; shipLoader.position = new Vector3D(0, 0, 0); ship.addChild(shipLoader); view.scene.addChild(ship);
Na końcu bloku kodu metody init() odwołaliśmy się do metody onResize().
358
Flash i ActionScript. Aplikacje 3D od podstaw
Kolejnymi metodami, które zapisaliśmy w klasie MouseEventExample, są onMouseDown() oraz onMouseUp(). Gdy wciśnięty jest lewy przycisk myszki, wywoływana jest metoda onMouseDown(), w której właściwości mouseDown przypisywana jest wartość true. Z kolei po puszczeniu przycisku myszy wartość mouseDown() zostaje zmieniona na false w metodzie onMouseUp(). Idąc tym torem, zapisaliśmy metody onKeyDown() oraz onKeyUp(), które uruchamiane są z każdym wciśnięciem i zwolnieniem klawisza na klawiaturze. Jednak nie każde wywołanie tych metod powoduje zmiany. Wewnątrz obu metod zastosowaliśmy instrukcję warunkową if. Obie filtrują kody wciskanych i zwalnianych klawiszy w celu wychwycenia wartości numerycznej odpowiadającej numerowi klawisza Ctrl. Jeżeli klawisz ten zostanie wciśnięty, to w metodzie onKeyDown() zmienna ctrlDown zmieni swoją wartość na true. Z kolei po puszczeniu przycisku Ctrl w metodzie onKeyUp() zmienna ctrlDown zmieni swoją wartość na false. Przy każdej zmianie rozmiaru okna uruchamiana jest metoda onResize(). W jej ciele zapisaliśmy wyśrodkowanie widoku view w oknie programu. view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5;
Na końcu klasy MouseEventExample zapisaliśmy metodę onEnterFrame(), wywoływaną przy każdym wystąpieniu zdarzenia Event.ENTER_FRAME. W pierwszych dwóch linijkach wpisaliśmy formuły wyliczające różnicę między współrzędnymi kursora i współrzędnymi środka okna aplikacji. xPos = stage.mouseX - stage.stageWidth * .5; yPos = stage.mouseY - stage.stageHeight * .5;
Następnie zapisaliśmy instrukcje if, które sprawdzają wartości zmiennych mouseDown oraz ctrlDown. W pierwszym przypadku sprawdzamy, czy wartość mouseDown jest równa true, a wartość ctrlDown równa false. Jeżeli warunek zachodzi, to wcześniej zdefiniowane odległości xPos i yPos zostaną użyte do zmiany kątów nachylenia modelu na osiach X i Y. Parametr ease służy jako mnożnik łagodzący przejście między pozycjami. if (mouseDown && !ctrlDown) { ship.rotationY += (xPos * .5 - ship.rotationY) * ease; ship.rotationX += (yPos * .5 - ship.rotationX) * ease; }
Jeżeli wartości właściwości mouseDown i ctrlDown są równe true, to każda zmiana współrzędnych kursora spowoduje zmianę wymiarów obiektu. Im dalej od środka będzie skierowany kursor, tym większą skalę będzie miał model statku kosmicznego. Aby obliczyć tę odległość, musieliśmy użyć właściwości xPos i yPos. Bezpośrednio
Rozdział 8. Interaktywność
359
w metodzie scale() obiektu ship obydwie te wartości podnieśliśmy do kwadratu i dodaliśmy. Następnie wyliczyliśmy pierwiastek z tej sumy i pomnożyliśmy go przez 0.01. W ten sposób, bazując na współrzędnych pozycji kursora, uzyskamy długość wektora, którą będziemy skalowali obiekt ship. else if (mouseDown && ctrlDown) { ship.scale(Math.sqrt(xPos * xPos + yPos * yPos)*.01); }
Na końcu metody onEnterFrame() odświeżamy widok metodą render().
MouseEvent3D We wcześniejszych przykładach tego podrozdziału pokazaliśmy, że można za pomocą MouseEvent stworzyć interakcje użytkownika ze środowiskiem Away3D. Problem polega na tym, że przedstawione sposoby nie odnosiły się bezpośrednio do obiektów trójwymiarowych, tylko do obiektów klas pochodnych względem InteractiveObject. Na szczęście biblioteka Away3D ma swoją implementację pełnej obsługi zdarzeń myszki w postaci klasy MouseEvent3D. Zlokalizowana w pakiecie away3d.events klasa MouseEvent3D jest odpowiednikiem standardowego MouseEvent, tylko że w tym przypadku ma ona zastosowanie bezpośrednio na obiektach umieszczonych w przestrzeni trójwymiarowej, niezależnie od ich rodzaju i materiału, którym są pokryte. W swoich zasobach klasa MouseEvent3D ma stałe, które definiują większość zdarzeń znanych z MouseEvent, między innymi MouseEvent3D.MOUSE_DOWN, MouseEvent3D.MOUSE_UP czy też MouseEvent3D.MOUSE_MOVE. Każde ze zdarzeń MouseEvent3D wywołuje się bezpośrednio na obiekcie, na którym ma ono być rejestrowane. Działa to na tych samych zasadach jak w przypadku zwykłych obiektów DisplayObject. Obj3d.addEventListener(MouseEvent3D.MOUSE_DOWN, onMouseDown);
Oczywiście zamiast odwoływać się do obiektu zdarzenia, można wpisać samą postać String, którą reprezentuje stała obiektu MouseEvent3D. Powyższy przykład dodania detektora w tym przypadku wyglądałby następująco: Obj3d.addEventListener("mouseDown3d", onMuseDown);
Tabele 8.8 i 8.9 zawierają metody i właściwości klasy MouseEvent3D. W tabeli 8.10 wypisane zostały stałe odpowiadające konkretnym zdarzeniom.
360
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 8.8. Właściwości klasy MouseEvent3D
Nazwa
Rodzaj
Wartość domyślna Opis
ctrlKey
Boolean
false
elementVO
ElementVO
Obiekt typu ElementVO, na którym wywołano zdarzenie
material
Material
Materiał obiektu 3D, na którym wywołano zdarzenie
Object
Object3D
Obiekt 3D, na którym wywołano zdarzenie
sceneX
Number
Współrzędna x na scenie
sceneY
Number
Współrzędna y na scenie
sceneZ
Number
Współrzędna z na scenie
screenX
Number
Współrzędna x w widoku
screenY
Number
Współrzędna y w widoku
screenZ
Number
Współrzędna z w widoku
shiftKey
Number
uv
UV
Współrzędne UV wewnątrz obiektu, na którym wywołano zdarzenie
view
View3D
Obiekt widoku, w którym wywołano zdarzenie
Określa, czy klawisz Ctrl jest wciśnięty
false
Określa, czy klawisz Shift jest wciśnięty
Tabela 8.9. Metody klasy MouseEvent3D
Nazwa
Opis
MouseEvent3D(type:String)
Konstruktor
clone():Event
Tworzy kopię obiektu MouseEvent3D
Tabela 8.10. Stałe klasy MouseEvent3D
Nazwa
Wartość domyślna
Opis
MOUSE_DOWN
‘mouseDown3d’
Zdarzenie wciśnięcia przycisku myszy na trójwymiarowym obiekcie
MOUSE_MOVE
‘mouseMove3d’
Zdarzenie poruszania kursorem na trójwymiarowym obiekcie
MOUSE_OUT
‘mouseOut3d’
Zdarzenie opuszczenia kursora z trójwymiarowego obiektu lub któregokolwiek z obiektów w nim umieszczonych
MOUSE_OVER
‘mouseOver3d’
Zdarzenie najechania kursorem na trójwymiarowy obiekt lub którykolwiek z obiektów w nim umieszczonych
MOUSE_UP
‘mouseUp3d’
Zdarzenie puszczenia przycisku myszy na trójwymiarowym obiekcie
Rozdział 8. Interaktywność
361
Tabela 8.10. Stałe klasy MouseEvent3D (ciąg dalszy)
Nazwa
Wartość domyślna
Opis
ROLL_OVER
‘rollOut3d’
Zdarzenie najechania kursorem na trójwymiarowy obiekt
ROLL_OUT
‘rollOver3d’
Zdarzenie opuszczenia kursora z trójwymiarowego obiektu
Metody dla zdarzeń MouseEvent3D Obiekty klasy Object3D mają własne metody inicjujące detektory zdarzeń dla MouseEvent3D. Każda z tych metod ma inną nazwę, która wskazuje na rodzaj zdarzenia. Jako argument dla takiej metody należy podać jedynie nazwę metody, która ma być wywołana po zajściu zdarzenia. Tabela 8.11 zawiera spis nazw metod oraz zdarzeń, którym one odpowiadają. Tabela 8.11. Metody obsługi zdarzeń MouseEvent3D
Nazwa metody
Zdarzenie
addOnMouseDown
MouseEvent3D.MOUSE_DOWN
addOnMouseMove
MouseEvent3D.MOUSE_MOVE
addOnMouseOut
MouseEvent3D.MOUSE_OUT
addOnMouseOver
MouseEvent3D.MOUSE_OVER
addOnMouseUp
MouseEvent3D.MOUSE_UP
addOnRollOut
MouseEvent3D.ROLL_OUT
addOnRollOver
MouseEvent3D.ROLL_OVER
Malowanie na trójwymiarowych obiektach W tym punkcie wyjaśnimy, jak napisać aplikację umożliwiającą malowanie po powierzchni obiektów 3D. Aby wykonać to zadanie, w pierwszej kolejności stworzymy obiekt ściany i wypełnimy go teksturą z ceglaną powierzchnią. Następnie do tego obiektu dodamy detektory zdarzeń poruszania kursorem, wciśnięcia i puszczenia przycisku myszy. Dodatkową zmienną typu Boolean sprawimy, że malowanie będzie trwało od momentu wciśnięcia do zwolnienia przycisku myszy. Sam proces rysowania umieścimy w metodzie obsługi zdarzenia Event.ENTER_FRAME. Aby na powierzchni ściany były widoczne zmiany, skorzystamy z materiału typu MovieMaterial. Jego pierwszą warstwę wypełnimy wcześniej wspomnianą ceglaną
362
Flash i ActionScript. Aplikacje 3D od podstaw
teksturą. Kolejne rysunki będą skutkiem tworzenia nowych obiektów Sprite, w których metodą drawCircle() będziemy rysowali pojedyncze, półprzezroczyste czerwone koła. Aby ustalić współrzędne kursora na obiekcie, skorzystamy ze wspomnianego w tabeli 8.8 obiektu UV. Efekt, który uzyskamy po napisaniu i skompilowaniu przykładu, ilustruje rysunek 8.3. Rysunek 8.3.
Malowanie graffiti na trójwymiarowej ścianie
Teraz przepisz i skompiluj następujący kod. Następnie zajmiemy się omawianiem jego poszczególnych fragmentów. package { import import import import import import // import import import import import import
flash.geom.Vector3D; flash.events.Event; flash.display.StageQuality; flash.display.StageAlign; flash.display.StageScaleMode; flash.display.Sprite; away3d.containers.View3D; away3d.primitives.Cube; away3d.core.utils.Cast; away3d.materials.MovieMaterial; away3d.primitives.data.CubeMaterialsData; away3d.events.MouseEvent3D;
Rozdział 8. Interaktywność public class GraffitiExample extends Sprite { private var view:View3D; private var wall:Cube; private var cubeMaterials:CubeMaterialsData; private var drawingEnabled:Boolean = false; public function GraffitiExample() { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; removeEventListener(Event.ADDED_TO_STAGE, init); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); // view = new View3D(); addChild(view); // cubeMaterials = new CubeMaterialsData(); cubeMaterials.front = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.back = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.top = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.bottom = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.left = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.right = new MovieMaterial(new brickTexture(), { interactive:true } ); wall = new Cube( { width:1024, height:512, depth:50, cubeMaterials:cubeMaterials } ); wall.addOnMouseDown(onMouseDown); wall.addOnMouseUp(onMouseUp); wall.addEventListener(MouseEvent3D.MOUSE_MOVE, onMouseMove); view.scene.addChild(wall); // onResize(); } private function onMouseDown(e:MouseEvent3D):void { drawingEnabled = true; }
363
364
Flash i ActionScript. Aplikacje 3D od podstaw private function onMouseUp(e:MouseEvent3D):void { drawingEnabled = false; } private function onMouseMove(e:MouseEvent3D):void { if (drawingEnabled) { var graff:Sprite = new Sprite(); graff.graphics.beginFill(0xFF0000, .5); graff.graphics.drawCircle(e.uv.u * (e.material as MovieMaterial). movie.width, (e.material as MovieMaterial).movie.height e.uv.v * (e.material as MovieMaterial).movie.height, 10); graff.graphics.endFill(); (e.material as MovieMaterial).movie.addChild(graff); } } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onEnterFrame(e:Event):void { wall.rotationY -= .1; view.render(); } } }
Na początku bloku klasy GraffitiExample zdefiniowaliśmy obiekt widoku View3D, a następnie obiekt klasy Cube o nazwie wall. Jak wspomnieliśmy na początku tego punktu, materiałem pokrywającym ścianę jest obiekt klasy MovieMaterial. W naszym przykładzie nazwaliśmy go wallTexture. Jako źródło dla tego materiału posłużył obiekt klasy brickTexture. Jest to element MovieClip dodany do biblioteki pliku fla. Po zdefiniowaniu wszystkich potrzebnych obiektów utworzyliśmy jeszcze jedną zmienną o nazwie drawingEnabled, której wartość określa, czy można rysować po powierzchni ściany. private private private private private
var var var var var
view:View3D; wall:Cube; wallTexture:MovieMaterial; textureMC:brickTexture; isDrawing:Boolean = false;
W konstruktorze uruchamiamy metodę init(), z chwilą gdy obiekt Stage zostanie zainicjowany.
Rozdział 8. Interaktywność
365
if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init);
Gdy metoda init() zostanie wywołana, obiekt nasłuchujący zdarzenia Event.ADDED_ TO_STAGE będzie zbędny, dlatego można go usunąć, stosując metodę removeEvent Listener(). Dla szybszego działania aplikacji ustawiliśmy niską jakość wyświetlanego obrazu, stosując w tym celu wartość StageQuality.LOW. W kolejnych linijkach usunęliśmy skalowanie obiektów i wyrównaliśmy stół montażowy do lewego górnego rogu. Ostatnimi operacjami, jakie wykonaliśmy na obiekcie stage, było przypisanie im detektorów zdarzeń reagujących na zmianę rozmiarów okna i odświeżanie wyświetlanego obrazu. removeEventListener(Event.ADDED_TO_STAGE, init); stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
Po ustaleniu wszystkich opcji i dodaniu obiektów nasłuchujących zdarzeń Event. i Event.ENTER_FRAME stworzyliśmy i dodaliśmy widok bez ustalania jakichkolwiek dodatkowych zmian wartości jego właściwości. Położenie zmieniamy w metodzie onResize().
RESIZE
view = new View3D(); addChild(view);
W kolejnych linijkach metody init() stworzyliśmy i uzupełniliśmy zawartość obiektu klasy CubeMaterialsData o nazwie cubeMaterials. Każdemu z boków sześcianu przypisaliśmy nowy materiał typu MovieMaterial. W każdym z tych materiałów źródłem wyświetlanego obrazu jest obiekt klasy brickTexture. Dodatkowo aby umożliwić malowanie na powierzchni tego materiału, ustawiliśmy wartość właściwości interactive jako true. cubeMaterials = new CubeMaterialsData(); cubeMaterials.front = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.back = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.top = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.bottom = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.left = new MovieMaterial(new brickTexture(), { interactive:true } ); cubeMaterials.right = new MovieMaterial(new brickTexture(), { interactive:true } );
366
Flash i ActionScript. Aplikacje 3D od podstaw
Po wykonaniu tych czynności stworzyliśmy obiekt klasy Cube o nazwie wall, w którym właściwości cubeMaterials przypisaliśmy nasz obiekt cubeMaterials. Następnie korzystając z wcześniej poznanych metod addOnMouseDown(), addOnMouseUp() oraz zwykłego addEventListener() dodaliśmy obiekt nasłuchujący zdarzeń MouseEvent3D. wall = new Cube( { width:1024, height:512, depth:50, cubeMaterials:cubeMaterials } ); wall.addOnMouseDown(onMouseDown); wall.addOnMouseUp(onMouseUp); wall.addEventListener(MouseEvent3D.MOUSE_MOVE, onMouseMove);
Na końcu metody init() dodaliśmy obiekt ściany do sceny Away3D i wywołaliśmy metodę onResize() w celu ustawienia widoku na środku okna aplikacji. view.scene.addChild(wall); onResize();
Metody onMouseDown() oraz onMouseUp() wywoływane są w sytuacji zajścia zdarzenia MouseEvent3D. Po wciśnięciu przycisku myszki uruchamia się metoda onMouseDown(), w której właściwości mouseDown przypisywana jest wartość true. Z kolei przy puszczeniu przycisku myszy wywoływana jest metoda onMouseUp(), w której mouseDown zmienia swoją wartość na false. Najważniejszą metodą w klasie GraffitiExample jest onMouseMove(). Kod zapisany w jej ciele odpowiada za rysowanie wzorów na powierzchni tekstury obiektu wall. Aby malowanie było możliwe jedynie przy wciśniętym przycisku myszy, musieliśmy w pierwszej kolejności zastosować instrukcję warunkową if, w której sprawdzana jest wartość właściwości drawingEnabled. Jeżeli wartość tej właściwości równa jest true, to wewnątrz instrukcji tworzony jest nowy obiekt klasy Sprite. var graff:Sprite = new Sprite();
Stosując odwołanie do obiektu graphics wewnątrz obiektu graff, można korzystać z jego metod rysowania. Do narysowania półprzezroczystego czerwonego koła użyliśmy metod beginFill() oraz drawCircle(). W metodzie beginFill() jako argument podaliśmy szesnastkowy kod koloru czerwonego. graff.graphics.beginFill(0xFF0000, .5);
Metoda drawCircle() przyjmuje trzy właściwości: x, y, oraz radius. Jako x podaliśmy wartość właściwości u z obiektu UV i pomnożyliśmy ją przez szerokość tekstury. e.uv.u * (e.material as MovieMaterial).movie.width;
Rozdział 8. Interaktywność
367
Z kolei aby określić wartość argumentu y, musieliśmy od wysokości tekstury odjąć iloczyn tej wysokości i wartości właściwości v z obiektu UV. Dzięki temu współrzędne tekstury będą zgodne z ruchem kursora. (e.material as MovieMaterial).movie.height - e.uv.v * (e.material as MovieMaterial).movie.height;
Etap rysowania zakończyliśmy metodą endFill(), po czym umieściliśmy nowo utworzony obiekt graff w wyświetlanym obiekcie klasy brickTexture. graff.graphics.endFill(); (e.material as MovieMaterial).movie.addChild(graff);
Warto wspomnieć o tym, że zapis (e.material as MovieMaterial).movie odwołuje się do źródła materiału, na którym aktualnie znajduje się kursor.
Rozmieszczanie obiektów na planszy Przyjmijmy, że Twoim zadaniem jest napisanie strategicznej gry czasu rzeczywistego. Jednymi z kluczowych elementów takiej gry są: możliwość zmiany pozycji obiektów strategicznych, tworzenie oraz przemieszczanie jednostek bojowych. Tymi konkretnymi zagadnieniami zajmiemy się w tym punkcie, a efekt, który uzyskamy, kompilując kod źródłowy przykładu, przedstawia rysunek 8.4. Rysunek 8.4.
Rozmieszczanie obiektów na planszy
368
Flash i ActionScript. Aplikacje 3D od podstaw
Na rysunku 8.4 widzimy kawałek planszy pokryty teksturą ze sztucznym ukształtowaniem terenu tworzącym pole podzielone wyschniętym korytem rzeki. Na tej planszy umieszczony jest jeden budynek, który w naszym scenariuszu pełni funkcję fabryki pojazdów latających. Poza nim w powietrzu wiszą cztery wyprodukowane statki kosmiczne. Tworzenie tych latających obiektów następuje po kliknięciu na obiekt fabryki — pojazd pojawia się na jej platformie, po czym przemieszcza na losowo wybraną pozycję. Najechanie kursorem na którykolwiek z modeli powoduje pojawienie się czerwonej poświaty. Oznacza to, że na wybranym elemencie można wykonać akcję zmiany pozycji. Żeby zmienić pozycję wybranego obiektu, należy kliknąć na niego, trzymając wciśnięty klawisz Ctrl. Na podłożu planszy pojawi się niebieski celownik, tak jak to pokazano na rysunku 8.4. Celownik ten, podobnie jak w grach tego typu, wyznacza nowe miejsce docelowe. Aby wybrany obiekt zmienił swoją pozycję na nową, należy kliknąć lewy przycisk myszy, trzymając wciśnięty klawisz Ctrl. W przypadku obiektu fabryki pozycja zmieniona zostanie natychmiastowo, ponieważ — w przeciwieństwie do obiektów statków — nie stworzyliśmy animacji płynnego przejścia wygenerowanej za pomocą bibliotek TweenMax i TimelineMax. Wszystkie procesy, które zachodzą w kodzie źródłowym tego przykładu, omówimy za chwilę. Teraz przepisz ten kod i załącz go jako główną klasę programu. Do poprawnego działania aplikacji niezbędne są zasoby umieszczone w pliku fla. package { import import import import import import import import import // import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.KeyboardEvent; flash.filters.GlowFilter; flash.geom.Vector3D; flash.ui.Keyboard; away3d.core.base.Object3D; away3d.core.utils.Cast; away3d.core.render.Renderer; away3d.primitives.Plane; away3d.materials.MovieMaterial; away3d.materials.BitmapMaterial;
Rozdział 8. Interaktywność import import import import import import // import import
away3d.events.MouseEvent3D; away3d.events.Loader3DEvent; away3d.containers.ObjectContainer3D; away3d.containers.View3D; away3d.loaders.Loader3D; away3d.loaders.Max3DS; com.greensock.TweenMax; com.greensock.TimelineMax;
public class MouseEvent3DExample extends Sprite { private var view:View3D; private var pointer:Plane; private var pointerMC:Crosshair = new Crosshair(); private var board:ObjectContainer3D; private var ground:Plane; private var texture:BitmapMaterial; private var modelLoader:Loader3D; private var landingDock:ObjectContainer3D; private var fighter:ObjectContainer3D; private var animations:TimelineMax; private var leftArrowDown:Boolean = false; private var rightArrowDown:Boolean = false; private var upArrowDown:Boolean = false; private var downArrowDown:Boolean = false; private var selectedBuilding:Object3D; private var selectedVehicle:Object3D; private var ctrlDown:Boolean = false; private var zoomInDown:Boolean = false; private var zoomOutDown:Boolean = false; private var fighters:Vector. = new Vector.; public function MouseEvent3DExample():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); view = new View3D(); view.renderer = Renderer.BASIC; view.x = stage.stageWidth * .5;
369
370
Flash i ActionScript. Aplikacje 3D od podstaw view.y = stage.stageHeight * .5; addChild(view); view.camera.zoom = 15; view.camera.y = 800; view.camera.lookAt(new Vector3D(0, 0, 0)); view.scene.addOnMouseMove(overScene); pointer = new Plane( { width:34, height:34, material:new MovieMaterial(pointerMC),pushfront:true } ); initBoard(); initBuildings(); onResize(); } private function initBoard():void { board = new ObjectContainer3D(); texture = new BitmapMaterial(Cast.bitmap('terrain')); ground = new Plane(); ground.width = 512; ground.height = 512; ground.pushback = true; ground.material = texture; ground.addOnMouseDown(onGroundClick); board.addChild(ground); view.scene.addChild(board); } private function overScene(e:MouseEvent3D):void { pointer.x = e.sceneX; pointer.z = e.sceneZ; } private function onGroundClick(e:MouseEvent3D):void { trace(e.sceneX, e.sceneZ); if (ctrlDown && selectedBuilding) { selectedBuilding.alpha = 1; selectedBuilding.x = pointer.x; selectedBuilding.z = pointer.z; selectedBuilding = null; view.scene.removeChild(pointer); } else if (ctrlDown && selectedVehicle) { view.scene.removeChild(pointer); TweenMax.to(selectedVehicle, 1, { x:pointer.x, z:pointer.z, onComplete:function() { selectedBuilding = null; } } ); } else{}
Rozdział 8. Interaktywność
371
} private function initBuildings():void { modelLoader = Max3DS.load('../../resources/models/max3ds/ landingDock/LandingDock.3DS'); modelLoader.addOnSuccess(onLandingDockSuccess); } private function onLandingDockSuccess(e:Loader3DEvent):void { landingDock = new ObjectContainer3D(); landingDock.ownCanvas = true; landingDock.name = 'RED_landingDock'; landingDock.addChild(modelLoader.handle); landingDock.scale(.2); landingDock.x = 71; landingDock.z = 196; landingDock.y = 15; landingDock.rotationX = 90; landingDock.filters = [ new GlowFilter(0xFF0000, 0, 7, 7, 1, 1)]; view.scene.addChild(landingDock); landingDock.addOnMouseDown(landingDockOnMouseDown); landingDock.addOnRollOver(landingDockOnMouseRollOver); landingDock.addOnRollOut(landingDockOnMouseRollOut); } private function landingDockOnMouseDown(e:MouseEvent3D):void { trace('landingDock:CLICK'); if (ctrlDown) { selectedBuilding = landingDock; selectedBuilding.alpha = .5; view.scene.addChild(pointer); pointerMC.gotoAndStop('move'); }else { modelLoader = Max3DS.load('../../resources/models/max3ds/ spaceFighter01/spaceFighter01.3ds'); modelLoader.addOnSuccess(onModelLoaderSuccess); } } private function landingDockOnMouseRollOver(e:MouseEvent3D):void { trace('landingDock:RollOver'); TweenMax.to(landingDock.filters[0], .5, { alpha:1 } ); } private function landingDockOnMouseRollOut(e:MouseEvent3D):void { trace('landingDock:RollOut');
372
Flash i ActionScript. Aplikacje 3D od podstaw TweenMax.to(landingDock.filters[0], .5, { alpha:0 } ); } private function randomPosition(min:Number, max:Number):Number { return Math.floor(Math.random() * (1 + max - min)) + min; } private function onModelLoaderSuccess(e:Loader3DEvent):void { var randomX:Number = landingDock.x - randomPosition(-150, 150); var randomZ:Number = landingDock.z - randomPosition(-150, 150); fighter = new ObjectContainer3D(); fighter.ownCanvas = true; fighter.addOnMouseDown(fighterOnMouseDown); fighter.addOnRollOver(fighterOnMouseRollOver); fighter.addOnRollOut(fighterOnMouseRollOut); fighter.name = fighter + fighters.length; fighter.filters = [new GlowFilter(0xFF0000, 0, 7, 7, 1, 1)]; fighter.addChild(modelLoader.handle); fighter.scale(.001); fighter.rotationX = 90; fighter.x = landingDock.x; fighter.y = 20; fighter.z = landingDock.z; view.scene.addChild(fighter); fighters.push(fighter); animations = new TimelineMax( { onComplete:onFighterReady } ); animations.append(TweenMax.to(fighter, 1, { scaleX:.1, scaleY:.1, scaleZ:.1 } )); animations.append(TweenMax.to (fighter, 2, { x:randomX, y: 60, z:randomZ } )); animations.play(); } private function onFighterReady():void { trace('Fighter ready!'); } private function fighterOnMouseDown(e:MouseEvent3D):void { trace('Fighter:CLICK'); if (ctrlDown) { selectedVehicle = e.target as ObjectContainer3D; view.scene.addChild(pointer); pointerMC.gotoAndStop('move'); } } private function fighterOnMouseRollOver(e:MouseEvent3D):void
Rozdział 8. Interaktywność { trace('Fighter:RollOver'); TweenMax.to(e.currentTarget.filters[0], .5, { alpha:1 } ); } private function fighterOnMouseRollOut(e:MouseEvent3D):void { trace('Fighter:RollOut'); TweenMax.to(e.currentTarget.filters[0], .5, { alpha:0 } ); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } private function onKeyDown(e:KeyboardEvent):void { if (e.keyCode == Keyboard.LEFT) leftArrowDown = true; if (e.keyCode == Keyboard.RIGHT) rightArrowDown = true; if (e.keyCode == Keyboard.UP) upArrowDown = true; if (e.keyCode == Keyboard.DOWN) downArrowDown = true; if (e.keyCode == Keyboard.CONTROL) ctrlDown = true; if (e.keyCode == 187) zoomInDown = true; if (e.keyCode == 189) zoomOutDown = true; } private function onKeyUp(e:KeyboardEvent):void { if (e.keyCode == 187) zoomInDown = false; if (e.keyCode == 189) zoomOutDown = false; if (e.keyCode == Keyboard.LEFT) leftArrowDown = false; if (e.keyCode == Keyboard.RIGHT) rightArrowDown = false; if (e.keyCode == Keyboard.UP) upArrowDown = false; if (e.keyCode == Keyboard.DOWN) downArrowDown = false; if (e.keyCode == Keyboard.CONTROL) { ctrlDown = false; if (selectedBuilding) { selectedBuilding.alpha = 1; selectedBuilding = null; view.scene.removeChild(pointer); } } } private function onEnterFrame(e:Event):void { if (zoomInDown) { if (ctrlDown) view.camera.y += 5;
373
374
Flash i ActionScript. Aplikacje 3D od podstaw else view.camera.zoom++; } if (zoomOutDown) { if (ctrlDown) view.camera.y -= 5; else view.camera.zoom--; } if (leftArrowDown) { if (ctrlDown) view.camera.rotationY++; else view.camera.x -= 10; } if (rightArrowDown) { if (ctrlDown) view.camera.rotationY--; else view.camera.x += 10; } if (upArrowDown) { if (ctrlDown) view.camera.rotationX++; else view.camera.z += 10; } if (downArrowDown) { if (ctrlDown) view.camera.rotationX--; else view.camera.z -= 10; } view.render(); } } }
Jak w większości kodów źródłowych zawartych w tej książce, ustawiliśmy poziom jakości wyświetlania elementów, brak skalowania oraz wyrównanie stołu montażowego. Użyliśmy do tego celu klas StageAlign, StageQuality i StageScaleMode. Jednym z istotnych elementów tego przykładu jest możliwość zmiany położenia obiektów znajdujących się na planszy. Aby móc wyznaczyć nową pozycję na trzech osiach równocześnie, musieliśmy skorzystać z klasy Vector3D. Do samego przemieszczenia użyliśmy zdarzeń MouseEvent3D i animacji generowanych klasami TweenMax i TimelineMax. Ponieważ do zrealizowania tego przykładu skorzystaliśmy z modeli zapisanych w formacie 3DS, musieliśmy użyć kilku klas, aby wyświetlić je na scenie. W pierwszej kolejności użyliśmy ogólnej klasy Loader3D, przechowującej model. Aby pobrać obiekt w formacie 3DS, skorzystaliśmy z klasy Max3DS. Skala oraz położenie zawartości modelu uzależnione są od zastosowanych podczas eksportu ustawień. Pobrany z określonej lokalizacji obiekt modelu umieściliśmy w kontenerze Object Container3D, aby w jego wnętrzu dostosować obiekt modelu do ustawień sceny.
Rozdział 8. Interaktywność
375
Wspomniane otoczenie stworzyliśmy za pomocą klasy Plane i nadaliśmy odpowiedni charakter powierzchni, stosując klasę Cast oraz BitmapMaterial. Przy wybieraniu nowej pozycji dla obiektów za śladem kursora podąża niebieski celownik. Podobnie jak w grach strategicznych, pokazuje on nowe docelowe położenie na planszy. Do jego wykonania zastosowaliśmy obiekt klasy Plane oraz teksturę MovieMaterial. Źródłem dla tego materiału jest osadzony w zasobach pliku fla element MovieClip. Wewnątrz klasy MouseEvent3DExample zadeklarowaliśmy kilkanaście obiektów i zmiennych potrzebnych do prawidłowego funkcjonowania przykładu. Jak zwykle zaczęliśmy od zdefiniowania widoku Away3D. Wskaźnikiem nowych pozycji jest obiekt klasy Plane o nazwie pointer. Dla źródła materiału wykorzystaliśmy obiekt klasy Crosshair. Crosshair nie zawiera w sobie żadnych dodatkowych metod poza konstruktorem. Służy jedynie jako łącznik z elementem MovieClip z biblioteki pliku fla. Tego typu połączenia mogą wydawać się zbędne. Jednak gdyby projekt w swoich założeniach wymagał dodatkowych operacji wykonywanych na wskaźniku, dobrze byłoby umieścić je w osobnej klasie, takiej jak Crosshair, i odwoływać się do nich poprzez obiekt tej klasy. W następnej kolejności zadeklarowaliśmy obiekty potrzebne do wyświetlenia planszy. Do stworzenia powierzchni zastosowaliśmy obiekt klasy Plane o nazwie ground, a do pokrycia jej teksturą użyliśmy obiektu texture klasy BitmapMaterial. Przyjmując, że do samego otoczenia moglibyśmy dodać jeszcze inne elementy, wygenerowaną powierzchnię umieściliśmy w kontenerze board. W razie konieczności modyfikowania środowiska wystarczyłoby odwołanie się do tego obiektu. W tym przykładzie za pobieranie modeli odpowiada obiekt o nazwie modelLoader klasy Loader3D. W przypadku budynku fabryki pobrana zawartość umieszczana jest w kontenerze o nazwie landingDock. Z kolei każdy utworzony statek kosmiczny przypisywany jest do obiektu fighter i dodawany do wektora Vector. o nazwie fighters. Aby wyselekcjonować konkretny element z całej grupy dodanych modeli, zdefiniowaliśmy specjalne obiekty klasy Object3D o nazwach selectedBuilding i selected Vehicle. Do opanowania animacji złożonych z więcej niż jednej sekwencji posłużyliśmy się obiektem animations klasy TimelineMax. Poza samymi obiektami zadeklarowaliśmy również szereg zmiennych potrzebnych do wykonywania operacji w konkretnych przypadkach. Wszystkie te właściwości
376
Flash i ActionScript. Aplikacje 3D od podstaw
przyjmują wartości typu Boolean i odpowiadają za kliknięcie poszczególnych klawiszy klawiatury. Do określania stanu klawiszy strzałek zastosowaliśmy zmienne: leftArrowDown, rightArrowDown, upArrowDown oraz downArrowDown, których wartości domyślnie równe są false. W zależności od tego, który klawisz został wciśnięty, odpowiadającej zmiennej przypisujemy nową wartość równą true. Za zmianę wartości właściwości ctrlDown odpowiada wciśnięcie lub zwolnienie klawisza Ctrl. Z kolei klawisze + i – umieszczone w bloku klawiszy alfanumerycznych regulują wartości właściwości zoomInDown oraz zoomOutDown. W konstruktorze uruchamiamy metody init(), z chwilą gdy obiekt Stage zostanie zainicjowany. if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init);
W metodzie init() w pierwszej kolejności wykonaliśmy wszystkie potrzebne operacje na obiekcie stołu montażowego. Ustawiliśmy jakość generowanego obrazu, wyrównanie oraz wyłączyliśmy skalowanie. Następnie dodaliśmy detektory dla zdarzeń zmiany rozmiaru, odświeżenia obrazu i użycia klawiatury. stage.quality = StageQuality.LOW; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; removeEventListener(Event.ADDED_TO_STAGE, init); addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
W dalszej części metody init() stworzyliśmy widok Away3D i zmieniliśmy ustawienia kamery tak, aby obejmowała całą powierzchnię planszy. Do renderowania widoku skorzystaliśmy z klasy BasicRenderer, której obiekt utworzyliśmy, stosując odwołanie Renderer.BASIC. Przed umieszczeniem widoku na liście wyświetlanych obiektów wywołaliśmy metodę addOnMouseMove(), która przy zdarzeniu poruszania kursorem ma wywołać metodę overScene(). view = new View3D(); view.renderer = Renderer.BASIC; view.scene.addOnMouseMove(overScene); addChild(view); view.camera.zoom = 15; view.camera.y = 800; view.camera.lookAt(new Vector3D(0, 0, 0));
Rozdział 8. Interaktywność
377
Po dodaniu widoku w celach testowych umieściliśmy obiekt klasy Trident. Długość jego ramion ustawiliśmy na poziomie 100 pikseli i zmieniliśmy wartość właściwości showLetters na true. Linijkę kodu dodającą oś Trident do sceny Away3D umieściliśmy w komentarzu tak, aby nie wyświetlać obiektu trident bez potrzeby. trident = new Trident(100, true); //view.scene.addChild(trident);
Dalej w metodzie init() stworzyliśmy wskaźnik pozycji, który wcześniej zdefiniowaliśmy jako obiekt pointer. Jego wysokość oraz szerokość ustawiliśmy tak, aby zgadzały się z wymiarami źródła grafiki pointerMC. Ze względu na swoją rolę wskaźnik jest zawsze widoczny na powierzchni planszy. Aby uzyskać taki efekt, nie podnieśliśmy obiektu na osi Y, tylko zastosowaliśmy właściwość pushfront. Przypisanie jej wartości true powoduje wyświetlenie siatki wybranego obiektu nad pozostałymi znajdującymi się w tej samej pozycji Y. pointer = new Plane( { width:34, height:34, material:new MovieMaterial(pointerMC),pushfront:true } );
Na końcu metody init() odwołaliśmy się do dwóch metod tworzących potrzebne modele oraz metody ustawiającej pozycję widoku. initBoard(); initBuildings(); onResize();
W metodzie initBoard() poza stworzeniem planszy i nadaniem jej odpowiednich ustawień ważne było zastosowanie metody uruchamiającej metodę onGroundClick() przy każdym kliknięciu na powierzchni planszy. ground.addOnMouseDown(onGroundClick);
Metoda overScene() wywoływana jest w sytuacji poruszania kursorem w przestrzeni sceny. W tym przypadku interesowały nas jedynie współrzędne osi X oraz Z, które przypisaliśmy do pozycji obiektu pointer. pointer.x = e.sceneX; pointer.z = e.sceneZ;
W metodzie onGroundClick() zapisaliśmy proces przemieszczania wybranego obiektu. Wewnątrz ciała metody w pierwszej kolejności znajdują się instrukcje warunkowe, które sprawdzają, czy został wciśnięty klawisz Ctrl oraz jakiego rodzaju jest kliknięty obiekt. Zdefiniowaliśmy dwie możliwe sytuacje. Pierwsza dotyczy kliknięcia obiektu landingDock reprezentującego budynek fabryki. W tym przypadku przejście między pozycjami jest natychmiastowe, ponieważ nie zastosowaliśmy tutaj klasy TweenMax, tylko od razu przypisaliśmy właściwościom x i z nowe
378
Flash i ActionScript. Aplikacje 3D od podstaw
wartości pozycji wskaźnika pointer. Poza tym przywróciliśmy pierwotny stan przezroczystości obiektu landingDock, wyczyściliśmy zawartość obiektu selected Building i usunęliśmy ze sceny wskaźnik. if (ctrlDown && selectedBuilding) { selectedBuilding.alpha = 1; selectedBuilding.x = pointer.x; selectedBuilding.z = pointer.z; selectedBuilding = null; view.scene.removeChild(pointer); }
Druga sytuacja określona w metodzie onGroundClick() dotyczy pojazdów latających, czyli obiektów fighter. W ich przypadku aby zmienić pozycję, stosuje się klasę TweenMax, w której właściwościom x i z przypisaliśmy nowe wartości położenia wskaźnika. Dodatkowo wewnątrz metody to() klasy TweenMax zapisaliśmy metodę, która zostanie wywołana na koniec animacji. Celem tej metody jest usunięcie ze sceny obiektu pointer oraz wyczyszczenie odwołania do klikniętego pojazdu w obiekcie selectedVehicle. else if (ctrlDown && selectedVehicle) { TweenMax.to(selectedVehicle, 1, { x:pointer.x, z:pointer.z, onComplete:function() { view.scene.removeChild(pointer); selectedVehicle = null; } } ); }
Aby pobrać z wybranej lokalizacji model fabryki w metodzie initBuildings(), skorzystaliśmy z obiektu modelLoader oraz klasy Max3DS. Dodatkowo użyliśmy detektora zdarzeń Loader3DEvent, aby przy zakończeniu pobierania wywołać metodę onLandingDockSuccess(). modelLoader = Max3DS.load('../../resources/models/max3ds/landingDock/LandingDock.3DS'); modelLoader.addOnSuccess(onLandingDockSuccess);
Dopiero w metodzie onLandingDockSuccess() zapisaliśmy proces dodawania modelu do sceny Away3D. W pierwszej kolejności nadaliśmy mu nazwę oraz umieściliśmy zawartość obiektu modelLoader w kontenerze landingDock. Ponieważ w swoich pierwotnych ustawieniach wymiary modelu są znacznie większe od wymiarów dodanej planszy, musieliśmy zastosować metodę scale() do zmniejszenia skali obiektu landingDock. landingDock = new ObjectContainer3D(); landingDock.name = 'RED_landingDock';
Rozdział 8. Interaktywność
379
landingDock.addChild(modelLoader.handle); landingDock.scale(.2);
W kolejnych linijkach ustawiliśmy model w wybranym miejscu i przypisaliśmy mu filtr czerwonej poświaty, który będzie się pojawiał po każdym najechaniu kursora na obiekt. landingDock.x = 71; landingDock.z = 196; landingDock.y = 15; landingDock.filters = [ new GlowFilter(0xFF0000, 0, 7, 7, 1, 1)];
Aby poświata była widoczna na obiekcie 3D, należy przypisać jego właściwości ownCanvas wartość true.
Do rozpoznawania akcji myszy na powierzchni obiektu landingDock zastosowaliśmy trzy metody zdarzeń MouseEvent3D. landingDock.addOnMouseDown(landingDockOnMouseDown); landingDock.addOnRollOver(landingDockOnMouseRollOver); landingDock.addOnRollOut(landingDockOnMouseRollOut);
Kliknięcie na obiekt landingDock powoduje uruchomienie metody landingDockOn MouseDown(). W jej ciele sprawdzamy, czy wartość właściwości ctrlDown jest równa true. Jeżeli warunek jest spełniony, to metoda ta spowoduje zmianę pozycji obiektu fabryki i wyświetlenie wskaźnika. if (ctrlDown) { selectedBuilding = landingDock; selectedBuilding.alpha = .5; view.scene.addChild(pointer); pointerMC.gotoAndStop('move'); }
W sytuacji gdy wartość właściwości ctrlDown jest równa false, celem metody landingDockOnMouseDown() jest stworzenie nowego obiektu statku kosmicznego. Proces ten zaczyna się podobnie jak tworzenie modelu fabryki. W pierwszej kolejności za pomocą klasy Max3DS pobierany jest z określonej lokalizacji model SpaceFighter01.3DS. Następnie z chwilą zakończenia pobierania wywołujemy metodę onModelLoaderSuccess(). else { modelLoader = Max3DS.load('./models/sf/spaceships/SpaceFighter01.3DS'); modelLoader.addOnSuccess(onModelLoaderSuccess); }
380
Flash i ActionScript. Aplikacje 3D od podstaw
W metodzie onModelLoaderSuccess() zapisaliśmy cały proces tworzenia i pokazania modelu statku kosmicznego. Według scenariusza obiekt ma się pojawić na powierzchni budynku, zwiększyć swoją pozycję na osi Y i przenieść w przypadkowe miejsce wokół fabryki. Żeby uzyskać taki efekt, w pierwszej kolejności zdefiniowaliśmy dwie zmienne numeryczne: randomX i randomY, których wartości są różnicą pozycji modelu fabryki i losowej liczby z przedziału. Do wyznaczenia przypadkowej liczby napisaliśmy osobną metodę randomPosition(), którą omówimy później. var randomX:Number = landingDock.x - randomPosition(-150, 150); var randomZ:Number = landingDock.z - randomPosition(-150, 150);
W dalszej części metody onModelLoaderSuccess() stworzyliśmy obiekt fighter, który przechowuje pobraną zawartość obiektu modelLoader. fighter = new ObjectContainer3D(); fighter.addChild(modelLoader.handle);
Kolejność definiowania właściwości nie ma tutaj większego znaczenia, dlatego zaczęliśmy od dodania detektorów zdarzeń MouseEvent3D. Tak samo jak w przypadku obiektu landingDock, na każdy ze statków kosmicznych będzie można kliknąć, najechać i opuścić kursorem powierzchnię modelu. fighter.addOnMouseDown(fighterOnMouseDown); fighter.addOnRollOver(fighterOnMouseRollOver); fighter.addOnRollOut(fighterOnMouseRollOut);
Następnie dodaliśmy filtr czerwonej poświaty, który będzie się pojawiał, gdy kursor znajdzie się na powierzchni modelu. fighter.filters = [new GlowFilter(0xFF0000, 0, 7, 7, 1, 1)];
Ponieważ pojazd ma się wyłaniać z hangaru, dla animacji ustawiliśmy jego pozycję zgodną z położeniem fabryki oraz skalę na poziomie jednej tysięcznej. fighter.scale(.001); fighter.x = landingDock.x; fighter.y = 20; fighter.z = landingDock.z;
Po utworzeniu statku i przypisaniu jego właściwościom nowych współrzędnych dodaliśmy go do sceny oraz obiektu fighters. view.scene.addChild(fighter); fighters.push(fighter);
Na końcu metody onModelLoaderSuccess() stworzyliśmy animację złożoną z dwóch etapów. W pierwszym powiększyliśmy skalę obiektu, co ma symbolizować jego powstawanie, w drugiej zaś przenieśliśmy go do losowych pozycji x i z na wysokości 100 pikseli.
Rozdział 8. Interaktywność
381
animations = new TimelineMax( { onComplete:onFighterReady } ); animations.append(TweenMax.to(fighter, 1, { scaleX:.4, scaleY:.4, scaleZ:.4 } )); animations.append(TweenMax.to(fighter, 2, { x:randomX, y: 100, z:randomZ } )); animations.play();
Do tworzenia animacji złożonej z kilku etapów najlepiej stosować klasę TimelineMax. Poszczególne fazy animacji dodaje się metodą append(), w której argumentem jest odwołanie do klasy TweenMax. Gotową sekwencję animacji można uruchomić, stosując metody play() lub restart(). Szczegóły dotyczące klasy TweenMax znajdują się na stronie: http://www.greensock.com/timelinemax/.
Metoda randomPosition(), jak wspomnieliśmy wcześniej, służy do wygenerowania losowej liczby. Do wyliczenia tej wartości posłużyliśmy się metodami klasy Math oraz przedziału liczbowego podanego w argumentach min i max. return Math.floor(Math.random() * (1 + max - min)) + min;
W metodach landingDockOnMouseRollOver() i landingDockOnMouseRollOut() zapisaliśmy animację pokazywania i chowania poświaty. Aby płynnie przejść od jednego stanu do drugiego, skorzystaliśmy z klasy TweenMax, w której odwołujemy się do pierwszej pozycji z tablicy filters obiektu landingDock i zmieniamy wartość właściwości alpha. //landingDockOnMouseRollOver TweenMax.to(landingDock.filters[0], .5, { alpha:1 } ); //landingDockOnMouseRollOut TweenMax.to(landingDock.filters[0], .5, { alpha:0 } );
Na tej samej zasadzie zakodowaliśmy pojawianie się poświaty wokół obiektów fighter w metodach fighterOnMouseRollOver() i fighterOnMouseRollOut(). Jedyną różnicą jest odwołanie do wybranego obiektu. Ponieważ w tym przypadku liczba pojazdów latających może być większa niż jeden, aby animacja dotyczyła konkretnego obiektu, musieliśmy w klasie TweenMax użyć zapisu e.currentTarget.filters[0]. Metoda fighterOnMouseDown() odpowiada za pojawienie się wskaźnika i rozpoczęcie procesu przemieszczania obiektu latającego. Żeby sprawdzić, czy użytkownik, klikając na obiekt, chce go przenieść w inne miejsce, zastosowaliśmy instrukcję if sprawdzającą, czy zmienna ctrlDown jest równa true. Jeżeli ma ona wartość true, na scenie w pozycji kursora pojawia się wskaźnik. Żeby przejście dotyczyło konkretnego pojazdu, przypisaliśmy obiektowi selectedVehicle odwołanie do klikniętego pojazdu. if (ctrlDown) {
382
Flash i ActionScript. Aplikacje 3D od podstaw selectedVehicle = e.target as ObjectContainer3D; view.scene.addChild(pointer); pointerMC.gotoAndStop('move'); }
Przy każdej zmianie rozmiaru okna uruchamiana jest metoda onResize(). W jej ciele zapisaliśmy wyśrodkowanie widoku view w oknie programu. view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5;
Znane nam z wcześniejszych przykładów metody onKeyDown() oraz onKeyUp() służą do sprawdzania, czy zaszły warunki zapisane w instrukcjach if, i przypisania odpowiedniej wartości poszczególnym właściwościom. W obu metodach tego przykładu sprawdzaliśmy akcje wciśnięcia i zwolnienia klawiszy strzałek, Ctrl oraz znaków plusa i minusa znajdujących się w górnym rzędzie klawiatury. W metodzie onKeyUp() przy zwolnieniu klawisza Ctrl dodatkowo sprawdziliśmy, czy kursor znajdował się na obiekcie landingDock. Jeżeli tak, to musieliśmy zapisać powrót do pierwotnego stanu poziomu jego przezroczystości, wyczyścić zawartość obiektu selectedBuilding i usunąć wskaźnik ze sceny. if (e.keyCode == Keyboard.CONTROL) { ctrlDown = false; if (selectedBuilding) { selectedBuilding.alpha = 1; selectedBuilding = null; view.scene.removeChild(pointer); } }
Na końcu zapisaliśmy metodę onEnterFrame(), która wywoływana jest przy każdym wystąpieniu zdarzenia Event.ENTER_FRAME. Zawiera ona w sobie kilka instrukcji warunkowych. W każdej z nich dodatkowo sprawdzamy, czy został wciśnięty klawisz Ctrl. Zastosowanie kombinacji klawiszy pozwoliło nam uniknąć korzystania z większej ich liczby. Pierwsza instrukcja if sprawdza, czy wartość zmiennej zoomInDown równa jest true. Jeżeli warunek jest spełniony, to w zależności od stanu zmiennej ctrlDown wykonywana jest odpowiednia operacja. Gdy klawisz Ctrl jest wciśnięty, to kamera zmienia swoją pozycję na osi Y. W przeciwnym razie zwiększana jest wartość właściwości zoom. if (zoomInDown) { if (ctrlDown) view.camera.y += 5;
Rozdział 8. Interaktywność
383
else view.camera.zoom++; }
W przypadku drugiej instrukcji if sprawdzana jest wartość zmiennej zoomOutDown. Gdy wartość tej właściwości jest równa true, to kamera może zmieniać swoją pozycję na osi Y lub zmniejszać wartość właściwości zoom. if (zoomOutDown) { if (ctrlDown) view.camera.y -= 5; else view.camera.zoom--; }
Trzecia instrukcja if dotyczy zdarzenia wciśnięcia klawisza lewej strzałki. Jeżeli warunek jest spełniony, to kamera może wykonywać obrót w lewą stronę lub zmniejszyć pozycję na osi X. if (leftArrowDown) { if (ctrlDown) view.camera.rotationY--; else view.camera.x -= 10; }
Działania czwartej instrukcji warunkowej if są odwrotne do poprzedniej. Dotyczy ona bowiem wciśnięcia klawisza prawej strzałki. Gdy wartość właściwości right ArrowDown jest równa true, to kamera może obracać się w prawą stronę bądź zwiększać pozycję na osi X. if (rightArrowDown) { if (ctrlDown) view.camera.rotationY++; else view.camera.x += 10; }
W piątej instrukcji if sprawdzana jest wartość zmiennej upArrowDown. Jeżeli warunek jest spełniony, to kamera może wykonywać obrót w górę lub zwiększyć pozycję na osi Z. if (upArrowDown) { if (ctrlDown) view.camera.rotationX++; else view.camera.z += 10; }
W ostatniej instrukcji if warunkiem jest wciśnięcie klawisza dolnej strzałki. Gdy wartość właściwości downArrowDown jest równa true, to kamera może wykonać obrót w dół bądź zmniejszyć pozycję na osi Z. if (downArrowDown) {
384
Flash i ActionScript. Aplikacje 3D od podstaw if (ctrlDown) view.camera.rotationX--; else view.camera.z -= 10; }
Na końcu metody onEnterFrame() wywołaliśmy odświeżenie wygenerowanego obrazu metodą render().
Podsumowanie Do kontrolowania obiektów można korzystać zarówno z myszki, jak i klawiatury. Klasa KeyboardEvent reprezentuje zdarzenia związane z używaniem klawiatury. Rozpoznaje procesy wciśnięcia i zwolnienia klawisza. Aby wykrywać akcje klawiatury, należy dodać detektor bezpośrednio do obiektu klasy Stage. Do rozpoznawania wciśniętego klawisza służą właściwości keyCode i charCode. Pierwsza właściwość zwraca wartość numeryczną położenia klawisza na klawiaturze, a druga określa kod znaku wciśniętego klawisza. Przy kontrolowaniu zdarzeń użycia klawiatury przydatna jest klasa Keyboard, która między innymi ma zdefiniowane stałe określające kody standardowych znaków klawiszy. Klasa KeyLocation służy do określenia lokalizacji klawisza, ma stałe określające strefy standardowej klawiatury. W metodach wywołanych zdarzeniami klawiatury warto stosować dodatkowe zmienne o wartościach typu Boolean, określające status poszczególnego klawisza. Jeśli stosujemy te zmienne w metodach wywołujących animację, będzie możliwe tworzenie konkretnych efektów dla różnych kombinacji wciśniętych klawiszy. Zdarzenia MouseEvent nie działają bezpośrednio na obiekcie trójwymiarowym. Na materiałach interaktywnych można stosować zdarzenia klasy MouseEvent. Właściwość interactive w materiałach służy do określenia, czy mają być stosowane zdarzenia myszki. Zdarzenia MouseEvent wywoływane na obiekcie klasy Stage można wykorzystać do zmiany kątów nachylenia poszczególnych obiektów na scenie.
Rozdział 8. Interaktywność
385
Biblioteka Away3D ma własną klasę do obsługi zdarzeń myszy o nazwie MouseEvent3D. Stosując współrzędne UV, można umieszczać elementy w konkretnym miejscu tekstury typu MovieMaterial. Aby filtry typu Blur lub Glow były widoczne na elementach, trzeba zastosować właściwość ownCanvas na wybranym obiekcie i przypisać mu wartość true.
386
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 9. Kamery Z rozdziału 2. „Podstawy biblioteki Away3D” wiemy, że kamera z uwagi na swoją rolę jest jednym z podstawowych i najważniejszych komponentów biblioteki Away3D. Dzięki niej w oknie aplikacji jesteśmy w stanie zobaczyć wszystkie te obiekty, które znajdują się w jej polu widzenia. Wiemy również, że kamera jest niewidocznym, pozbawionym powłoki obiektem, któremu można określać kąt nachylenia i pozycję w przestrzeni. Nic nie stoi na przeszkodzie, aby do jednej sceny dodać kilka kamer i na przemian rejestrować obraz z różnych punktów w przestrzeni. Biblioteka Away3D umożliwia programiście umieszczenie dowolnej liczby kamer, ale widok w danej chwili może korzystać tylko z jednej. Biorąc pod uwagę zróżnicowanie projektów 3D tworzonych w technologii Flash, ekipa Away3D przygotowała następujące rodzaje kamer: Camera3D, TargetCamera3D, HoverCamera3D oraz SpringCam. W tym rozdziale poznasz każdą z nich i nauczysz się je wykorzystywać w konkretnych przypadkach, poza tym czytając ten rozdział, dowiesz się: Na jakich zasadach działają kamery w Away3D. Jakie są podstawowe elementy i właściwości kamer. Czym są rzutnia, focus, zoom oraz co kryje się za pojęciem Field of View. Czym różnią się od siebie zoom i focus. Czym jest efekt głębi ostrości i jak go uzyskać. Jakiego rodzaju soczewki są dostępne w Away3D, jak one działają oraz do jakich celów służą. Jakich kamer można używać w Away3D oraz jak je implementować.
388
Flash i ActionScript. Aplikacje 3D od podstaw
Podstawy działania kamer w Away3D Kamery w Away3D jako obiekty trójwymiarowe mają podstawowe metody oraz właściwości pozwalające modyfikować ich położenie w przestrzeni sceny. Stosując poznane w rozdziale 7. „Praca z obiektami” sposoby przenoszenia i obracania obiektów, można z łatwością zmienić pozycję oraz kąty nachylenia kamer umieszczonych na scenie. Poza standardowymi właściwościami obiektów trójwymiarowych kamery mają również kilka takich, które odwzorowują kamery i aparaty fotograficzne stosowane w świecie rzeczywistym. Głównymi składowymi kamer w Away3D, jako obiektów prezentujących scenę i jej zawartość, są właściwości: fov, zoom, focus i lens. Aby dobrze zrozumieć ich znaczenie i zastosowanie, musimy zacząć od wyjaśnienia zasad rejestrowania i wyświetlania zawartości przestrzeni trójwymiarowej.
Rejestrowanie sceny w Away3D W bibliotekach takich jak Away3D i programach do tworzenia grafiki trójwymiarowej wyświetlanie zawartości przestrzeni 3D opiera się na regułach rzutowania na płaszczyznę. To zagadnienie stosowane jest w geometrii wykreślnej — każdemu punktowi w przestrzeni przypisuje się jego odpowiednik w miejscu przecięcia się prostej, która przechodzi przez dany punkt, z płaszczyzną zwaną rzutnią. Każda kamera w Away3D ma przed sobą taką płaszczyznę. Fizycznie nie jest ona widoczna na scenie, podobnie jak sama kamera, ale to, co widzi użytkownik w widoku, jest w rzeczywistości zarejestrowanym przez nią obrazem z rzutni. Schemat wyświetlania obrazu wygenerowanego na rzutni przedstawia rysunek 9.1. W grafice komputerowej i fotografii rozróżniamy kilka rodzajów rzutowania, o których wspomnimy w dalszej części tego rozdziału. Teraz zajmiemy się omawianiem właściwości i pojęć związanych z obiektem kamery w Away3D.
Rozdział 9. Kamery
389
Rysunek 9.1.
Schemat wyświetlania obrazu wygenerowanego na rzutni
Podstawowe pojęcia i właściwości kamer w Away3D Pole widzenia W pierwszej kolejności poznamy właściwość o nazwie fov (ang. Field of View — pole widzenia). Według definicji właściwość ta określa pole widzenia od dolnej do górnej krawędzi rzutni wyrażone w stopniach. Wartość tej właściwości wylicza się z pozycji kamery, odległości kamery od rzutni i jej wymiarów. Standardowo rzutnia przyjmuje wymiary okna aplikacji Flash, co sprawia, że całe okno wypełnione jest sceną Away3D. Na rysunku 9.2 zilustrowano pojęcie pola widzenia. Rysunek 9.2.
Pole widzenia między dolną a górną krawędzią rzutni
390
Flash i ActionScript. Aplikacje 3D od podstaw
Zoom i focus Osobom zainteresowanym fotografią pojęcia zoom i focus na pewno nie są obce. W silniku Away3D kamery również mają właściwości zoom i focus, ale nie w każdym przypadku odzwierciedlają one działanie znane z aparatów fotograficznych. Zacznijmy od pojęcia zoom w świecie rzeczywistym. W aparatach fotograficznych i kamerach termin ten rozbijany jest na dwie kategorie: zoom optyczny i zoom cyfrowy. Zoom optyczny polega na zmianie długości ogniskowej w obiektywie. Z kolei zoom cyfrowy polega na rozciągnięciu wycinka obrazu. W obu przypadkach efektem jest przybliżenie bądź oddalenie fotografowanego obiektu. Działanie parametru zoom w kamerach biblioteki Away3D polega na zwiększaniu i pomniejszaniu powierzchni rzutni. Ustawianie większych wartości rzutni powoduje zmniejszenie jej skali, co symuluje efekt przybliżenia wyświetlanych elementów. Zmniejszanie wartości właściwości zoom działa odwrotnie — pole płaszczyzny zwiększa się, powodując złudzenie oddalenia obserwatora od obiektów. Po dokonaniu jakiejkolwiek zmiany płaszczyzna rzutni musi się pokryć z wymiarami kontenera, w którym osadzony jest widok Away3D. Dlatego musi zostać odpowiednio rozciągnięta bądź zwężona. Na rysunku 9.3 przedstawiono obie sytuacje. Z lewej strony wartość właściwości zoom równa jest 10, a z prawej powiększamy ją na przykład do 15. Zwróć uwagę na to,
że nie rozmiar obiektu ulega zmianie, lecz wymiary rzutni. Rysunek 9.3.
Schemat wyświetlania obrazu wygenerowanego na rzutni
W przypadku właściwości focus na próżno porównywać jej działanie z funkcjonowaniem w aparatach fotograficznych i kamerach. W przypadku właściwości focus w kamerach biblioteki Away3D określa ona odległość kamery od rzutni. Zwiększenie tego dystansu powoduje zmniejszenie pola widzenia, z kolei przybliżenie kamery do rzutni powoduje wzrost wartości właściwości fov. Jest to bardziej naturalny sposób kontroli przybliżenia i oddalenia kamery od obiektów.
Rozdział 9. Kamery
391
Rysunek 9.4 przedstawia proporcje pola widzenia w zależności od wartości właściwości focus. Z lewej strony przy większej odległości kamery od rzutni widać, że kąt pola widzenia jest mniejszy. Z kolei w sytuacji po prawej stronie, przy mniejszym dystansie między kamerą a płaszczyzną, kąt pola widzenia jest znacznie większy. Rysunek 9.4.
Schemat wyświetlania obrazu wygenerowanego na rzutni
Porównanie działania właściwości zoom i focus Po przedstawieniu tych właściwości wykorzystanie ich w aplikacjach oraz sam sposób interpretacji nadal mogą wywoływać wątpliwości. Szczerze mówiąc, sam długo się zastanawiałem, w jakich przypadkach korzystać z właściwości focus. Przeważnie do przybliżeń stosowana jest właściwość zoom. Aby porównać zasady funkcjonowania obu właściwości, napiszemy prostą aplikację, w której za pomocą klawiszy będziemy poruszali kamerą w przestrzeni i zmieniali wartości właściwości focus oraz zoom. Przestrzeń sceny wypełniliśmy obiektami klas Plane oraz Cube, tworząc pewnego rodzaju korytarz. Dzięki tym obiektom będziemy w stanie zaobserwować zmiany przy projekcji obrazu wyświetlanego na rzutni. Wszystkie opcje sterowania kamerą oraz kontrolowania wartości zoom i focus wypisane zostały w panelu pomocniczym w lewym górnym rogu okna aplikacji. Na rysunku 9.5 przedstawiono dwie sytuacje. Po lewej stronie zmieniono wartość właściwości focus, a z prawej w tym samym stopniu zmieniono wartość właściwości zoom. Zwróć uwagę na różnicę w położeniu krawędzi dolnego obiektu Plane.
392
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 9.5. Dwa zrzuty ekranów do porównania efektów użycia właściwości focus i zoom
Do omówienia wniosków i ewentualnych możliwości zastosowań przejdziemy później, teraz zajmiemy się przykładem przedstawionym na rysunku 9.5. W pierwszej kolejności stworzymy panel zawierający przyciski wywołujące animacje i zmiany wartości właściwości zoom i focus stosowanej kamery. Stwórz plik CameraPropertiesPanel.as i przepisz następujący kod źródłowy: package { import import import import import
flash.display.Sprite; flash.events.Event; flash.events.MouseEvent; fl.controls.Label; fl.controls.Button;
public class CameraPropertiesPanel extends Sprite { private var _output:Label; public function CameraPropertiesPanel() { var animationsLabel:Label = new Label(); animationsLabel.text = 'Animacje'; animationsLabel.x = 120; animationsLabel.y = 0; addChild(animationsLabel); var focusAnimationButton:Button = new Button(); focusAnimationButton.name = "focusAnimation"; focusAnimationButton.label = "Focus"; focusAnimationButton.x = 20; focusAnimationButton.y = 20; focusAnimationButton.width = 60; addChild(focusAnimationButton); focusAnimationButton.addEventListener(MouseEvent.CLICK, buttonClick);
Rozdział 9. Kamery
393
var zoomAnimationButton:Button = new Button(); zoomAnimationButton.name = "zoomAnimation"; zoomAnimationButton.label = "Zoom"; zoomAnimationButton.x = 90; zoomAnimationButton.y = 20; zoomAnimationButton.width = 60; addChild(zoomAnimationButton); zoomAnimationButton.addEventListener(MouseEvent.CLICK, buttonClick); var startPositionButton:Button = new Button(); startPositionButton.name = "startPosition"; startPositionButton.label = "Pozycja początkowa"; startPositionButton.x = 160; startPositionButton.y = 20; startPositionButton.width = 120; addChild(startPositionButton); startPositionButton.addEventListener(MouseEvent.CLICK, buttonClick); var zoomLabel:Label = new Label(); zoomLabel.text = 'Zoom'; zoomLabel.x = 320; zoomLabel.y = 0; addChild(zoomLabel); var zoomLessButton:Button = new Button(); zoomLessButton.name = "zoomLess"; zoomLessButton.label = "-"; zoomLessButton.x = 300; zoomLessButton.y = 20; zoomLessButton.width = 30; addChild(zoomLessButton); zoomLessButton.addEventListener(MouseEvent.CLICK, buttonClick); var zoomMoreButton:Button = new Button(); zoomMoreButton.name = "zoomMore"; zoomMoreButton.label = "+"; zoomMoreButton.x = 340; zoomMoreButton.y = 20; zoomMoreButton.width = 30; addChild(zoomMoreButton); zoomMoreButton.addEventListener(MouseEvent.CLICK, buttonClick); var focusLabel:Label = new Label(); focusLabel.text = 'Focus'; focusLabel.x = 407; focusLabel.y = 0; addChild(focusLabel); var focusLessButton:Button = new Button(); focusLessButton.name = "focusLess"; focusLessButton.label = "-"; focusLessButton.x = 390;
394
Flash i ActionScript. Aplikacje 3D od podstaw focusLessButton.y = 20; focusLessButton.width = 30; addChild(focusLessButton); focusLessButton.addEventListener(MouseEvent.CLICK, buttonClick); var focusMoreButton:Button = new Button(); focusMoreButton.name = "focusMore"; focusMoreButton.label = "+"; focusMoreButton.x = 430; focusMoreButton.y = 20; focusMoreButton.width = 30; addChild(focusMoreButton); focusMoreButton.addEventListener(MouseEvent.CLICK, buttonClick); output = new Label(); output.x = 20; output.y = 50; output.text = 'wyniki > '; addChild(_output); } public function updateOutput(zoom:Number, focus:Number, fov:Number):void { output.text = 'wyniki > zoom: ' + zoom + ' focus: ' + focus + ' fov: ' + fov; } private function buttonClick(e:MouseEvent):void { dispatchEvent(new Event(e.target.name + 'Event')); } public function draw(_width:Number, _height:Number, _color:uint = 0xF1F1F1, _alpha:int = 1):void { graphics.clear(); graphics.beginFill(_color,_alpha); graphics.drawRect(0, 0, _width, _height); graphics.endFill(); output.width = _width - 40; } } }
Gdy mamy gotowy plik CameraPropertiesPanel.as, możemy się zająć główną klasą tego przykładu. Przepisz następujący kod źródłowy do pliku CameraPropertiesExample.as, a następnie omówimy jego ważniejsze fragmenty. package { import flash.display.StageAlign; import flash.display.StageQuality;
Rozdział 9. Kamery
395
import flash.display.StageScaleMode; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.geom.Vector3D; import flash.ui.Keyboard; import away3d.containers.View3D; import away3d.containers.ObjectContainer3D; import away3d.core.clip.FrustumClipping; import away3d.primitives.Cube; import away3d.primitives.Plane; import com.greensock.TimelineMax; import com.greensock.TweenMax; import com.greensock.easing.*; public class CameraPropertiesExample extends Sprite { private var panel:CameraPropertiesPanel; private var view:View3D; private var ctrlDown:Boolean = false; private var upArrowDown:Boolean = false; private var downArrowDown:Boolean = false; private var leftArrowDown:Boolean = false; private var rightArrowDown:Boolean = false; public function CameraPropertiesExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); view = new View3D(); view.clipping = new FrustumClipping(); addChild(view); view.camera.z = 0; addGroup(0); addGroup(1); addGroup(2); addPanel(); onResize(); } private function addGroup(pos:Number):void { var group:ObjectContainer3D = new ObjectContainer3D(); group.addChild(new Plane( { y:-100, bothsides:true, height:1124, width:1124, segmentsH:10, segmentsW:10 } )); group.addChild(new Plane( { y:100, bothsides:true, height:1124, width:1124, segmentsH:10, segmentsW:10 } ));
396
Flash i ActionScript. Aplikacje 3D od podstaw // group.addChild(new Cube( { y:-50, x:-512, z:512 } )); group.addChild(new Cube( { y:-50, x:512, z:512 } )); group.addChild(new Cube( { y:-50, x:-512, z:-512 } )); group.addChild(new Cube( { y:-50, x:512, z:-512 } )); group.addChild(new Cube( { y:50, x:-256, z:256 } )); group.addChild(new Cube( { y:50, x:256, z:256 } )); group.addChild(new Cube( { y:50, x:-256, z:-256 } )); group.addChild(new Cube( { y:50, x:256, z:-256 } )); group.z = pos * 1124; view.scene.addChild(group); } private function addPanel():void { panel = new CameraPropertiesPanel(); addChild(panel); panel.addEventListener('focusAnimationEvent', onPanelEvent); panel.addEventListener('zoomAnimationEvent', onPanelEvent); panel.addEventListener('startPositionEvent', onPanelEvent); panel.addEventListener('focusLessEvent', onPanelEvent); panel.addEventListener('focusMoreEvent', onPanelEvent); panel.addEventListener('zoomLessEvent', onPanelEvent); panel.addEventListener('zoomMoreEvent',onPanelEvent); } private function onPanelEvent(e:Event):void { var anim:TimelineMax; switch(e.type) { case 'focusAnimationEvent': { view.camera.zoom = 10; view.camera.focus = 100; anim = new TimelineMax(); anim.append(TweenMax.to(view.camera, anim.append(TweenMax.to(view.camera, ease:Elastic.easeOut } )); anim.play(); break; } case 'zoomAnimationEvent': { view.camera.zoom = 10; view.camera.focus = 100; anim = new TimelineMax(); anim.append(TweenMax.to(view.camera, anim.append(TweenMax.to(view.camera, ease:Elastic.easeOut } )); anim.play(); break;
3, { focus:10 } )); 2, { focus:100,
3, { zoom:1 } )); 2, { zoom:10,
Rozdział 9. Kamery
397
} case 'startPositionEvent': { TweenMax.to(view.camera, .5, { rotationX:0, rotationY:0, rotationZ:0, x:0, y:0, z:0, zoom:10, focus:100, onComplete: function() { panel.updateOutput(10, 100, Math.floor(view.camera.fov)); } } ); break; } case 'focusLessEvent': { view.camera.focus--; break; } case 'focusMoreEvent': { view.camera.focus++; break; } case 'zoomLessEvent': { view.camera.zoom--; break; } case 'zoomMoreEvent': { view.camera.zoom++; break; } } panel.updateOutput(view.camera.zoom, view.camera.focus, Math.floor(view.camera.fov)); } private function onKeyDown(e:KeyboardEvent):void { if if if if
(e.keyCode (e.keyCode (e.keyCode (e.keyCode
== == == ==
Keyboard.DOWN) downArrowDown = true; Keyboard.LEFT) leftArrowDown = true; Keyboard.RIGHT) rightArrowDown = true; Keyboard.CONTROL) ctrlDown = true;
} private function { if (e.keyCode if (e.keyCode if (e.keyCode if (e.keyCode
onKeyUp(e:KeyboardEvent):void == == == ==
Keyboard.UP) upArrowDown = false; Keyboard.DOWN) downArrowDown = false; Keyboard.LEFT) leftArrowDown = false; Keyboard.RIGHT) rightArrowDown = false;
398
Flash i ActionScript. Aplikacje 3D od podstaw if (e.keyCode == Keyboard.CONTROL) ctrlDown = false; } private function onEnterFrame(e:Event):void { if (upArrowDown) { if (ctrlDown) view.camera.rotationX++; else view.camera.moveForward(10); } if (downArrowDown) { if (ctrlDown) view.camera.rotationX--; else view.camera.moveBackward(10); } if (rightArrowDown) { if (ctrlDown) view.camera.rotationY++; else view.camera.moveRight(10); } if (leftArrowDown) { if (ctrlDown) view.camera.rotationY--; else view.camera.moveLeft(10); } view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; panel.draw(stage.stageWidth, 70); } } }
Do aranżacji sceny i ustawienia punktów odniesienia dla kamery zastosowaliśmy kilkakrotnie obiekty klas Plane oraz Cube. Żeby nie wyliczać na nowo pozycji każdego z elementów, ustalony układ obiektów umieściliśmy w kontenerze, tak aby modyfikować jedynie jego pozycję. Przemieszczanie i zmiana kąta nachylenia kamery względem wszystkich osi mogą powodować znikanie poszczególnych segmentów umieszczonych na scenie obiektów. Aby temu zapobiec, zastosowaliśmy klasę FrustumClipping. Istotnymi opcjami są również animacje, które można uruchomić odpowiednimi klawiszami. Aby wykonać proste i kilkuetapowe sekwencje animacji, skorzystaliśmy z klas TweenMax, TimelineMax oraz klas z pakietu com.greensock.easing.*, nadających rozmaite efekty przejścia między stanami animowanego obiektu.
Rozdział 9. Kamery
399
Na początku klasy CameraPropertiesExample zdefiniowaliśmy obiekt widoku o nazwie view oraz obiekt napisanej przez nas klasy CameraPropertiesPanel. Poza nimi dodaliśmy również kilka zmiennych odpowiadających za wciśnięcie poszczególnych klawiszy. W konstruktorze klasy CameraPropertiesExample w pierwszej kolejności zmieniliśmy ustawienia dotyczące stołu montażowego. Następnie dodaliśmy do obiektu stage potrzebne do prawidłowego funkcjonowania detektory zdarzeń: Event.ENTER_ FRAME, Event.RESIZE, KeyboardEvent.KEY_DOWN i KeyboardEvent.KEY_UP. Po wykonaniu tej czynności stworzyliśmy widok i od razu przypisaliśmy jego parametrowi clipping nowy sposób wyświetlania siatek obiektów znajdujących się na scenie. Stosując FrustumClipping, pozbyliśmy się problemu znikających trójkątów ułożonych blisko kamery oraz zwiększyliśmy wydajność tego przykładu. Dodatkowo umieściliśmy kamerę w centrum sceny, tak aby znajdowała się na środku wygenerowanego otoczenia. view = new View3D(); view.clipping = new FrustumClipping(); addChild(view); view.camera.z = 0;
W kolejnych linijkach konstruktora trzykrotnie wywołaliśmy metodę addGroup() tworzącą kontener z obiektami. W każdym z przypadków użyliśmy jako argumentu innej wartości liczbowej. Sens metody addGroup() omówimy za chwilę. addGroup(0); addGroup(1); addGroup(2);
Na końcu konstruktora wywołaliśmy metodę addPanel(), a następnie onResize(), w której zmieniane są wymiary panelu użytkownika i pozycja widoku. addPanel(); onResize(); właściwości
Wewnątrz metody addPanel() stworzyliśmy obiekt klasy CameraPropertiesPanel i dodaliśmy go do listy wyświetlanych obiektów. panel = new CameraPropertiesPanel(); addChild(panel);
W dalszej części tej metody utworzyliśmy szereg obiektów nasłuchujących zdarzeń wciśnięcia poszczególnych przycisków panelu użytkownika. Po wychwyceniu któregokolwiek ze zdarzeń wywoływana jest metoda onPanelEvent(). panel.addEventListener('focusAnimationEvent', onPanelEvent); panel.addEventListener('zoomAnimationEvent', onPanelEvent);
400
Flash i ActionScript. Aplikacje 3D od podstaw panel.addEventListener('startPositionEvent', onPanelEvent); panel.addEventListener('focusLessEvent', onPanelEvent); panel.addEventListener('focusMoreEvent', onPanelEvent); panel.addEventListener('zoomLessEvent', onPanelEvent); panel.addEventListener('zoomMoreEvent',onPanelEvent);
W metodzie onPanelEvent() zapisaliśmy instrukcję warunkową switch(), w której sprawdzany jest typ przechwyconego zdarzenia. W zależności od tego, jakie zdarzenie wywołało metodę onPanelEvent(), uruchamiany jest odpowiedni kod. Na przykład wciśnięcie przycisku Focus wywołuje zdarzenie focusAnimationEvent, które z kolei uruchamia następujący kod: case 'focusAnimationEvent': { view.camera.zoom = 10; view.camera.focus = 100; anim = new TimelineMax(); anim.append(TweenMax.to(view.camera, 3, { focus:10 } )); anim.append(TweenMax.to(view.camera, 2, { focus:100, ease:Elastic.easeOut } )); anim.play(); break; }
Zastosowanie instrukcji warunkowej switch(), sprawdzającej wartość właściwości type obiektu e, zaoszczędziło nam pisania osobnej metody dla każdego z nasłuchiwanych zdarzeń. Na końcu metody onPanelEvent() wywołaliśmy na obiekcie panel metodę updateOut put(), podając jako argumenty wartości właściwości zoom, focus oraz fov. panel.updateOutput(view.camera.zoom, view.camera.focus, Math.floor(view.camera.fov));
Metoda addGroup(), jak wskazuje nazwa, dodaje grupę, której elementami są obiekty wyświetlane na scenie Away3D. Grupa ta w rzeczywistości jest obiektem kontenera o nazwie group, do którego dodaliśmy dwa obiekty typu Plane służące za podłogę i sufit oraz osiem obiektów klasy Cube. Cztery z nich zawiesiliśmy pod sufitem, a pozostałe umieściliśmy na podłodze. Wybrana konstrukcja jest przypadkowa, głównym celem wyświetlania obiektów jest zilustrowanie projekcji obiektów na rzutni przy różnych wartościach i zachowaniach kamery. var group:ObjectContainer3D = new ObjectContainer3D(); group.addChild(new Plane( {y:-100, bothsides:true, height:1124, width:1124, segmentsH:10, segmentsW:10 } )); group.addChild(new Plane( { y:100, bothsides:true, height:1124, width:1124, segmentsH:10, segmentsW:10 } )); group.addChild(new Cube( { y:-50, x:-512, z:512 } )); group.addChild(new Cube( { y:-50, x:512, z:512 } ));
Rozdział 9. Kamery group.addChild(new group.addChild(new group.addChild(new group.addChild(new group.addChild(new group.addChild(new
Cube( Cube( Cube( Cube( Cube( Cube(
{ { { { { {
401
y:-50, x:-512, z:-512 } )); y:-50, x:512, z:-512 } )); y:50, x:-256, z:256 } )); y:50, x:256, z:256 } )); y:50, x:-256, z:-256 } )); y:50, x:256, z:-256 } ));
Metoda addGroup() jako argument przyjmuje wartość liczbową pos, która odpowiada za położenie na globalnej osi Z. Podana liczba mnożona jest przez głębię powierzchni kontenera, dzięki czemu każda kolejno dodana grupa w tym przykładzie tworzy korytarz. group.z = pos * 1124; view.scene.addChild(group);
W metodzie onKeyDown() uruchamianej przy zajściu zdarzenia KeyboardEvent.KEY_ DOWN zapisaliśmy instrukcje warunkowe if(), w których sprawdzamy, czy wciśnięte zostały klawisze strzałek oraz Ctrl, które służą do przemieszczania i obracania kamery. if if if if if
(e.keyCode (e.keyCode (e.keyCode (e.keyCode (e.keyCode
== == == == ==
Keyboard.UP) upArrowDown = true; Keyboard.DOWN) downArrowDown = true; Keyboard.LEFT) leftArrowDown = true; Keyboard.RIGHT) rightArrowDown = true; Keyboard.CONTROL) ctrlDown = true;
W metodzie onKeyUp() zapisaliśmy instrukcje if sprawdzające, czy zwalniany klawisz jest jednym z używanych do przemieszczania i obracania kamery. Jeżeli warunek jest spełniony, to wartość wybranej zmiennej zmieniona zostaje na false. W metodzie onEnterFrame() umieściliśmy cztery instrukcje warunkowe, które sprawdzają, czy wartości zmiennych odpowiadających za stan poszczególnych klawiszy są równe true lub false. W przypadku gdy któryś z nich okaże się prawdziwy i odpowiada któremuś z klawiszy strzałek, to uruchomiona zostanie kolejna instrukcja if sprawdzająca stan, w jakim jest klawisz Ctrl. W zależności od wyniku tego warunku kamera zmienia swoje położenie bądź kąt nachylenia względem konkretnej osi. Na końcu tej metody zapisaliśmy odwołanie do metody render(), odświeżającej wyświetlaną zawartość widoku. Ostatnia zapisana metoda onResize() wywoływana jest w konstruktorze oraz przy każdej zmianie wymiarów okna. Zadaniem tej metody jest wyśrodkowanie pozycji widoku względem okna uruchomionej aplikacji i przerysowanie tła panelu użytkownika tak, by zajmował on całą szerokość okna i wysokość 70 pikseli. view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; panel.draw(stage.stageWidth, 70[MM4]);
402
Flash i ActionScript. Aplikacje 3D od podstaw
Głębia ostrości W dziedzinach związanych z optyką pojęcie głębi ostrości oznacza odległość, w której rejestrowane obiekty mają wyraźne, ostre kontury. Pozostałe w różnym stopniu są zamazywane, tak aby odseparować je od głównego obiektu. Głębi ostrości najczęściej używa się w sytuacjach, gdy spośród całej grupy elementów trzeba wyodrębnić jeden lub kilka, na których ma być skupiona uwaga obserwatora. Kamery w bibliotece Away3D również mają opcję głębi ostrości, którą można w łatwy sposób uruchomić, wykonując kilka kroków. W pierwszej kolejności należy utworzyć obiekty klasy DepthOfFieldSprite, które napisano specjalnie do tego celu. DepthOfFieldSprite jest potomną klasy Sprite3D, dlatego jej obiekty zawsze skierowane są w stronę kamery, a używane materiały określają kształty i charakter obiektu.
Z obiektu kamery należy metodą enableDof() włączyć opcję głębi ostrości i ewentualnie zmienić wartości właściwości związanych z tym pojęciem. Aby określić poziomy zróżnicowania rozmycia, należy podać wartość liczbową parametrowi doflevels. Maksymalne rozmycie, jakie może zostać nałożone na obiekt znajdujący się poza zasięgiem, wyznacza właściwość maxblur. Z kolei modyfikowanie wartości właściwości aperture odpowiada regulacji rozmiarów przesłony. Im mniejszy rozmiar przesłony zostanie ustawiony, tym większą głębię uzyskamy. Jeżeli z jakichś powodów trzeba będzie wyłączyć efekt głębi ostrości, należy skorzystać z metody disbleDof(). Gdy masz już podstawowe informacje na temat głębi ostrości, pora na stworzenie przykładu ilustrującego jej działanie w Away3D. Napiszemy prostą i krótką aplikację, w której dodamy określoną liczbę obiektów klasy DepthOfFieldSprite, prezentujących głowę potwora[MM5]. Wszystkie te elementy umieścimy w grupie ObjectContainer3D i wprawimy w powolny obrót względem osi X. Poszczególnymi klawiszami na klawiaturze będziemy kontrolowali właściwości kamery, zmieniając tym samym działanie efektu głębi ostrości. Efekt, jaki uzyskamy, kompilując kod źródłowy tego przykładu, przedstawiono na rysunku 9.6.
Rozdział 9. Kamery
403
Rysunek 9.6.
Zrzut ekranu aplikacji prezentującej efekt głębi ostrości
Na pierwszy rzut oka może się wydawać, że przykład ten został napisany za pomocą standardowych klas języka ActionScript 3.0 bez użycia Away3D. Tak jednak nie jest — możesz to sprawdzić, przepisując i kompilując następujący kod: package { import import import import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D; away3d.containers.View3D; away3d.containers.ObjectContainer3D; away3d.materials.MovieMaterial; away3d.sprites.DepthOfFieldSprite;
public class DoFExample extends Sprite { [Embed(source="monster.swf", symbol="Head")] private var MonsterHead:Class; private var view:View3D; private var heads:ObjectContainer3D = new ObjectContainer3D(); public function DoFExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE;
404
Flash i ActionScript. Aplikacje 3D od podstaw stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); view.camera.enableDof(); view.camera.doflevels = 10; view.camera.aperture = 550; view.camera.maxblur = 5; view.camera.zoom = 2; addChild(view); view.scene.addChild(heads); // var mat:MovieMaterial = new MovieMaterial(new MonsterHead()); for (var i:uint=0; i < 100; i++) { var sp:DepthOfFieldSprite = new DepthOfFieldSprite(mat, 100, 100); sp.x = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.y = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.z = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.scaling = Math.floor(Math.random() * 10) * .1; heads.addSprite(sp); } onResize(); } private function onEnterFrame(e:Event):void { heads.rotationX--; view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
W klasie DoFExample zdefiniowaliśmy obiekt typu Class, w którym osadzony będzie obiekt Head z pliku monster.swf. Poza tym zdefiniowaliśmy obiekt widoku i utworzyliśmy kontener dla obiektów klasy DepthOfFieldSprite. W konstruktorze standardowo przypisaliśmy nowe ustawienia dla stołu montażowego i dodaliśmy detektory zdarzeń. Po wykonaniu tych czynności stworzyliśmy widok w postaci obiektu view. W kolejnych krokach na obiekcie kamery uruchomiliśmy opcję głębi ostrości, wywołując metodę enableDof(), i ustawiliśmy przykładowe wartości dla parametrów doflevels, aperture i maxblur.
Rozdział 9. Kamery
405
view.camera.enableDof(); view.camera.doflevels=10; view.camera.aperture=550; view.camera.maxblur = 5; view.camera.zoom = 2;
W konstruktorze, korzystając z pętli for, utworzyliśmy obiekty klasy DepthOfField Sprite, reprezentujące pęcherzyki powietrza. Każdemu obiektowi sp ustawiliśmy wymiary i przypisaliśmy wcześniej utworzony materiał klasy MovieMaterial o nazwie mat. Źródłem wyświetlanego obrazu na materiale jest obiekt klasy Monster Head, który połączony jest z elementem MovieClip umieszczonym w pliku monster.swf. W kolejnych linijkach ustawiliśmy losowe wartości dla właściwości x, y, z oraz scaling. Dzięki temu zgrupowane elementy będą się różniły wymiarami i pozycją w kontenerze heads. var mat:MovieMaterial = new MovieMaterial(new MonsterHead()); for (var i:uint=0; i < 100; i++) { var sp:DepthOfFieldSprite = new DepthOfFieldSprite(mat, 100, 100); sp.x = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.y = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.z = Math.floor(Math.random() * (1 + 900 + 600)) -600; sp.scaling = Math.floor(Math.random() * (1 + 10)) * .05; heads.addSprite(sp); }
Na końcu konstruktora wywołaliśmy metodę onResize(), w której widok umieszczany jest na środku okna aplikacji. Metoda onEnterFrame() zawiera kod, który powoduje odświeżanie wyświetlanej zawartości sceny i obraca kontener heads względem osi X. Do implementacji efektu głębi ostrości niekoniecznie trzeba angażować kamery. Inną możliwością jest zastosowanie klasy DofCache, której właściwości są takie same jak w przypadku klasy Camera3D i jej potomnych. Aby włączyć efekt głębi ostrości, używając DofCache, należy właściwości statycznej usedof tej klasy przypisać wartość true. Stosując efekt głębi ostrości, korzysta się ze standardowych filtrów Blur, co jest mocno obciążające dla procesora. Przy większej liczbie obiektów DepthOfFieldSprite aplikacja może uruchamiać się znacznie dłużej niż inne bądź funkcjonować z dużym opóźnieniem. Dlatego powinno się dokładnie przemyśleć sytuacje, w jakich Depth of Field miałby być użyty.
406
Flash i ActionScript. Aplikacje 3D od podstaw
Lens Ostatnią z właściwości wymienionych na początku tego podrozdziału jest lens (soczewka). Służy ona do implementacji wirtualnego obiektywu, który modyfikuje wyświetlanie sceny, podobnie jak soczewki używane w aparatach fotograficznych. Dostępnym obiektywom w bibliotece Away3D przyjrzymy się w kolejnym podrozdziale.
Rodzaje soczewek W tym podrozdziale zajmiemy się omawianiem dostępnych w bibliotece Away3D klas reprezentujących różnego rodzaju soczewki. W kolejnych podpunktach przedstawimy poszczególne z nich. Zaczniemy od powiedzenia kilku słów na temat klasy, a skończymy na przykładzie zastosowania jej obiektu. Aby nie tworzyć osobnych aplikacji dla każdej z klas soczewek, napiszemy jedną ogólną, zawierającą przykłady zastosowań wszystkich.
Przykład Aby dobrze zobrazować działanie obiektów soczewek, musimy stworzyć otoczenie złożone z dużej liczby obiektów trójwymiarowych. Korzystając między innymi z płaszczyzny oraz sześcianów, wygenerujemy planszę z określoną liczbą bloków pokrytych różnymi kolorami i mających losowo wyliczoną wysokość. Uzyskany efekt będzie przypominał miasto zbudowane z kolorowych drapaczy chmur, między którymi będziemy mogli się poruszać, stosując klawisze: W, S, A, D, a także strzałki. Poza obsługą klawiszy dodamy również panel użytkownika zawierający przyciski do zmiany soczewki oraz wartości właściwości zoom i focus. Całość będzie wyglądała tak, jak to pokazano na rysunku 9.7. Jak widać na rysunku 9.7, w górnej części okna umieszczony jest panel użytkownika z przyciskami uruchamiającymi konkretny rodzaj soczewki oraz zmieniającymi wartości właściwości zoom i focus. Zanim przejdziemy do klasy bazowej przykładu zastosowania soczewek, stworzymy wspomniany panel użytkownika. Przepisz poniższy kod do pliku LensPanel.as.
Rozdział 9. Kamery
407
Rysunek 9.7.
Zrzut ekranu z przykładu LensExample
package { import import import import import
flash.display.Sprite; flash.events.Event; flash.events.MouseEvent; fl.controls.Label; fl.controls.Button;
public class LensPanel extends Sprite { public function LensPanel() { var animationsLabel:Label = new Label(); animationsLabel.text = 'Rodzaje soczewek'; animationsLabel.x = 190; animationsLabel.y = 0; addChild(animationsLabel); var zoomFocusLensButton:Button = new Button(); zoomFocusLensButton.name = "ZoomFocusLens"; zoomFocusLensButton.label = "ZoomFocusLens"; zoomFocusLensButton.x = 20; zoomFocusLensButton.y = 20; zoomFocusLensButton.width = 100; addChild(zoomFocusLensButton); zoomFocusLensButton.addEventListener(MouseEvent.CLICK, buttonClick); var perspectiveLensButton:Button = new Button(); perspectiveLensButton.name = "PerspectiveLens"; perspectiveLensButton.label = "PerspectiveLens";
408
Flash i ActionScript. Aplikacje 3D od podstaw perspectiveLensButton.x = 130; perspectiveLensButton.y = 20; perspectiveLensButton.width = 100; addChild(perspectiveLensButton); perspectiveLensButton.addEventListener (MouseEvent.CLICK, buttonClick); var sphericalLensButton:Button = new Button(); sphericalLensButton.name = "SphericalLens"; sphericalLensButton.label = "SphericalLens"; sphericalLensButton.x = 240; sphericalLensButton.y = 20; sphericalLensButton.width = 100; addChild(sphericalLensButton); sphericalLensButton.addEventListener(MouseEvent.CLICK, buttonClick); var orthogonalLensButton:Button = new Button(); orthogonalLensButton.name = "OrthogonalLens"; orthogonalLensButton.label = "OrthogonalLens"; orthogonalLensButton.x = 350; orthogonalLensButton.y = 20; orthogonalLensButton.width = 100; addChild(orthogonalLensButton); orthogonalLensButton.addEventListener(MouseEvent.CLICK, buttonClick); var zoomLabel:Label = new Label(); zoomLabel.text = 'Zoom'; zoomLabel.x = 487; zoomLabel.y = 0; addChild(zoomLabel); var zoomLessButton:Button = new Button(); zoomLessButton.name = "zoomLess"; zoomLessButton.label = "-"; zoomLessButton.x = 470; zoomLessButton.y = 20; zoomLessButton.width = 30; addChild(zoomLessButton); zoomLessButton.addEventListener(MouseEvent.CLICK, buttonClick); var zoomMoreButton:Button = new Button(); zoomMoreButton.name = "zoomMore"; zoomMoreButton.label = "+"; zoomMoreButton.x = 510; zoomMoreButton.y = 20; zoomMoreButton.width = 30; addChild(zoomMoreButton); zoomMoreButton.addEventListener(MouseEvent.CLICK, buttonClick); var focusLabel:Label = new Label(); focusLabel.text = 'Focus'; focusLabel.x = 577;
Rozdział 9. Kamery
409
focusLabel.y = 0; addChild(focusLabel); var focusLessButton:Button = new Button(); focusLessButton.name = "focusLess"; focusLessButton.label = "-"; focusLessButton.x = 560; focusLessButton.y = 20; focusLessButton.width = 30; addChild(focusLessButton); focusLessButton.addEventListener(MouseEvent.CLICK, buttonClick); var focusMoreButton:Button = new Button(); focusMoreButton.name = "focusMore"; focusMoreButton.label = "+"; focusMoreButton.x = 600; focusMoreButton.y = 20; focusMoreButton.width = 30; addChild(focusMoreButton); focusMoreButton.addEventListener(MouseEvent.CLICK, buttonClick); } private function buttonClick(e:MouseEvent):void { dispatchEvent(new Event(e.target.name + 'Event')); } public function draw(_width:Number, _height:Number, _color:uint = 0xF1F1F1, _alpha:int = 1):void { graphics.clear(); graphics.beginFill(_color,_alpha); graphics.drawRect(0, 0, _width, _height); graphics.endFill(); } } }
Teraz przepisz kod źródłowy klasy bazowej przykładu do pliku LensExample.as, a następnie omówimy jego ważniejsze fragmenty. package { import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.KeyboardEvent;
import flash.geom.Vector3D;
410
Flash i ActionScript. Aplikacje 3D od podstaw import flash.ui.Keyboard; // import away3d.cameras.lenses.*; import away3d.containers.View3D; import away3d.containers.ObjectContainer3D; import away3d.primitives.Cube; import away3d.primitives.Plane; import away3d.lights.DirectionalLight3D; import away3d.materials.ShadingColorMaterial; import away3d.materials.ColorMaterial; public class LensExample extends Sprite { private var panel:LensPanel; private var view:View3D; private var upArrowDown:Boolean = false; private var downArrowDown:Boolean = false; private var leftArrowDown:Boolean = false; private var rightArrowDown:Boolean = false; private var wDown:Boolean = false; private var sDown:Boolean = false; private var aDown:Boolean = false; private var dDown:Boolean = false; private var ctrlDown:Boolean = false; private var spaceDown:Boolean = false; public function LensExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); // view = new View3D(); view.camera.position = new Vector3D(0, 512, -512); view.camera.lookAt(new Vector3D(0, 0, 0)); addChild(view); var light:DirectionalLight3D = new DirectionalLight3D(); light.direction = new Vector3D(500, -300, 200); view.scene.addLight(light); view.scene.addChild(new Plane( { material:new ColorMaterial(0x999999), y: -25, width:2000, height:1000, segmentsW:10, segmentsH:10, pushback:true } )); for (var i:Number = -10; i < 10; i++) { addBlocks(i); } addPanel(); onResize(); }
Rozdział 9. Kamery private function addPanel():void { panel = new LensPanel();[MM6] addChild(panel); panel.addEventListener('ZoomFocusLensEvent', onPanelEvent); panel.addEventListener('PerspectiveLensEvent', onPanelEvent); panel.addEventListener('SphericalLensEvent', onPanelEvent); panel.addEventListener('OrthogonalLensEvent', onPanelEvent); panel.addEventListener('focusLessEvent', onPanelEvent); panel.addEventListener('focusMoreEvent', onPanelEvent); panel.addEventListener('zoomLessEvent', onPanelEvent); panel.addEventListener('zoomMoreEvent',onPanelEvent); } private function onPanelEvent(e:Event):void { switch(e.type) { case 'ZoomFocusLensEvent': { view.camera.lens = new ZoomFocusLens(); break; } case 'PerspectiveLensEvent': { view.camera.lens = new PerspectiveLens(); break; } case 'SphericalLensEvent': { view.camera.lens = new SphericalLens(); break; } case 'OrthogonalLensEvent': { view.camera.lens = new OrthogonalLens(); break; } case 'focusLessEvent': { view.camera.focus-=10; break; } case 'focusMoreEvent': { view.camera.focus+=10; break; } case 'zoomLessEvent': { view.camera.zoom--; break;
411
412
Flash i ActionScript. Aplikacje 3D od podstaw } case 'zoomMoreEvent': { view.camera.zoom++; break; } } } private function addBlocks(pos:Number):void { var group:ObjectContainer3D = new ObjectContainer3D(); group.x = pos * 100; for (var i:Number = -8; i < 8; i++) { var h = Math.floor(Math.random() * (1 + 300 - 25)) + 25; group.addChild(new Cube( { material:new ShadingColorMaterial (Math.random() * 0xFFFFFF), width:25, depth:25, height:h, y:h * .5, z:50 * i } )); } view.scene.addChild(group); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case case case case case case case case case case
Keyboard.UP: upArrowDown = true; break; Keyboard.DOWN: downArrowDown = true; break; Keyboard.LEFT: leftArrowDown = true; break; Keyboard.RIGHT: rightArrowDown = true; break; Keyboard.W: wDown = true; break; Keyboard.S: sDown = true; break; Keyboard.A: aDown = true; break; Keyboard.D: dDown = true; break; Keyboard.SPACE: spaceDown = true; break; Keyboard.CONTROL: ctrlDown = true; break;
} } private function onKeyUp(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.UP: upArrowDown = false; break; case Keyboard.DOWN: downArrowDown = false; break; case Keyboard.LEFT: leftArrowDown = false; break; case Keyboard.RIGHT: rightArrowDown = false; break; case Keyboard.W: wDown = false; break; case Keyboard.S: sDown = false; break; case Keyboard.A: aDown = false; break;
Rozdział 9. Kamery
413
case Keyboard.D: dDown = false; break; case Keyboard.SPACE: spaceDown = false; break; case Keyboard.CONTROL: ctrlDown = false; break; } } private function onEnterFrame(e:Event):void { if (spaceDown) view.camera.y++; if (ctrlDown) view.camera.y--; if (wDown) view.camera.moveForward(10); if (upArrowDown) view.camera.rotationX++; if (sDown) view.camera.moveBackward(10); if (downArrowDown) view.camera.rotationX--; if (aDown) view.camera.moveLeft(10); if (leftArrowDown) view.camera.rotationY--; if (dDown) view.camera.moveRight(10); if (rightArrowDown) view.camera.rotationY++; view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; panel.draw(stage.stageWidth, 55); } } }
Budowa oraz zasady działania tego kodu źródłowego są podobne do przykładu z podpunktu „Porównanie działania właściwości zoom i focus”, dlatego przyjrzymy się tylko kilku fragmentom klasy LensExample. Do wygenerowania miasta użyliśmy obiektów klas Plane oraz Cube, stosując przy tym oświetlenie typu DirectionalLight3D i materiał ShadingColorMaterial. Dzięki temu różnice w wyświetlaniu obiektów będą wyraźniejsze. W konstruktorze klasy stworzyliśmy widok, przypisując kamerze nowe położenie i punkt skierowania, oraz ustawiliśmy światło w dowolnie wybranej pozycji. var light:DirectionalLight3D = new DirectionalLight3D(); light.direction = new Vector3D(500, -300, 200); view.scene.addLight(light);
W dalszej części konstruktora dodaliśmy podłoże i za pomocą pętli for odwołaliśmy się do metody tworzącej bloki. Kolejno generowaną z wybranego przedziału wartość zmiennej i przypisywaliśmy jako argument metody addBlocks(). view.scene.addChild(new Plane({material:new ColorMaterial(0x999999), y:-25,width:2000, height:1000,segmentsW:10,segmentsH:10,pushback:true}));
414
Flash i ActionScript. Aplikacje 3D od podstaw for (var i:Number = -10; i < 10; i++) { addBlocks(i); }
Wewnątrz metody addBlocks() zdefiniowaliśmy i dodaliśmy obiekt kontenera, który będzie zawierał wygenerowane sześciany. Tworzenie kolumny budynków również wykorzystuje pętlę for z przypadkowo wybranym przedziałem. Zastosowanie jej zredukowało liczbę linijek kodu źródłowego. W każdej iteracji for tworzony jest sześcian, którego kolor i wysokość generowane są losowo, a pozycje na osi Z odpowiadają iloczynowi liczby 50 i wartości zmiennej i. Dodatkowo aby blok postawiony był na podłożu plane, wartość pozycji na osi Y równa się połowie wartości zmiennej h. Na końcu utworzony kontener group z kolumną sześcianów wewnątrz umieszczamy na scenie Away3D. var group:ObjectContainer3D = new ObjectContainer3D(); group.x = pos * 100; for (var i:Number = -8; i < 8; i++) { var h = Math.floor(Math.random() * (1 + 300 - 25)) + 25; group.addChild(new Cube( { material:new ShadingColorMaterial(Math.random() * 0xFFFFFF), width:25, depth:25, height:h, y:h * .5, z:50 * i } )); } view.scene.addChild(group);
Zmiany pozycji kamery oraz rodzaju stosowanego obiektywu wprowadza się, używając klawiatury. Dlatego zapisaliśmy w konstruktorze detektory zdarzeń wciśnięcia i zwolnienia klawisza. Metody uruchamiane po zajściu któregoś z tych przypadków odpowiednio zmieniają wartości właściwości kamery bądź wartości zmiennych stosowanych w metodzie onEnterFrame(). Celem metody wywoływanej przy wystąpieniu zdarzenia Event.ENTER_FRAME jest odświeżenie wyświetlanej zawartości oraz płynne przemieszczanie kamery.
AbstractLens Klasa AbstractLens jest podstawą wszystkich pozostałych klas znajdujących się w pakiecie away3d.cameras.lenses. Ma szereg chronionych właściwości, które wykorzystują dziedziczące po niej: ZoomFocusLens, PerspectiveLens, SphericalLens, OrthogonalLens. Aby generować obraz, nie korzysta się bezpośrednio z klasy AbstractLens. Jednak świadomość tego, że istnieje i jest klasą macierzystą dla pozostałych obiektywów, przyda się przy tworzeniu własnych rodzajów obiektywów.
Rozdział 9. Kamery
415
Proponuję, byś za jakiś czas, po utrwaleniu zdobytej wiedzy, spróbował sam stworzyć nowy rodzaj obiektywu; na razie wystarczą nam informacje zawarte w poniższych tabelach 9.1 i 9.2. Tabela 9.1. Metody klasy AbstractLens
Nazwa
Opis
getPerspective(screenZ:Number):Number
Zwraca wartość liczbową skalowania perspektywy dla podanej odległości od kamery
Tabela 9.2. Stałe klasy AbstractLens
Nazwa
Rodzaj
Wartość
Opis
toDEGREES
Number
57.29577951308232
Przelicznik na stopnie
toRADIANS
Number
0.017453292519943295
Przelicznik na radiany
ZoomFocusLens i PerspectiveLens Działanie obiektywów typu ZoomFocusLens oraz PerspectiveLens jest bardzo podobne, dlatego przedstawimy je razem w jednym podpunkcie. Oba przedstawiają wygenerowaną scenę podobnie do tego, jak ludzkie oko rejestruje otoczenie. Różnica między nimi w uzyskanym efekcie jest niewielka. Gdy stosujemy obiektyw Zoom FocusLens, pole widzenia jest większe, jakby za sprawą zmiany wartości właściwości focus kamera była oddalona od rzutni. Ogólnie ustalono, że wyświetlany w ten sposób obraz jest mniej rzeczywisty niż przy zastosowaniu obiektywu PerspectiveLens. Dlatego nieformalnie ten drugi rodzaj jest uważany za standardowy. Jednak z uwagi na to, że wcześniejsze wersje biblioteki Away3D stosowały ZoomFocusLens, jeszcze w wersji 3.6 oficjalnie domyślnym obiektywem jest ZoomFocusLens. Na rysunku 9.8 przedstawiono zastosowanie obiektywów ZoomFocusLens i Per spectiveLens. W obu przypadkach wartości właściwości kamery nie były modyfikowane.
SphericalLens Obiekt klasy SphericalLens służy do prezentacji sceny z beczkowatymi zniekształceniami obrazu, zwanymi również efektem rybiego oka.
416
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 9.8. Obrazy wygenerowane z użyciem ZoomFocusLens i PerspectiveLens
Zastosowanie tego obiektywu służy do prezentacji sceny z szerokim kątem widzenia. Aby uniknąć efektów rozciągnięcia powierzchni obiektów podczas modyfikowania wartości właściwości focus, można zastosować SphericalLens i uzyskać bardziej kulisty kształt. Efekt zastosowania tego rodzaju obiektywu przedstawia rysunek 9.9. Rysunek 9.9.
Scena wyświetlona z użyciem obiektywu SphericalLens
Rozdział 9. Kamery
417
OrthogonalLens Czasami projekt wymaga innego spojrzenia na wirtualną rzeczywistość, niż to pokazano w poprzednich trzech rodzajach obiektywów. Niektóre aplikacje, jak na przykład gry, wymagają izometrycznego ujęcia sceny. Do takich właśnie celów służy obiektyw klasy OrthogonalLens. Gdy używamy tego obiektywu, wyświetlane elementy zachowują proporcje swoich wymiarów, niezależnie od położenia na scenie i odległości od kamery. Zwróć uwagę w przykładzie dla tego podrozdziału, że zwiększanie wartości właściwości focus — w przeciwieństwie do innych obiektywów — powoduje efekt oddalania kamery. Istotną kwestią jest również to, że na wyświetlanie obiektów bezpośredni wpływ ma kolejność ich stworzenia i umieszczenia na scenie. Kolejno dodawane obiekty w porównaniu z poprzednimi umieszczane są jakby na nowych warstwach sceny. To oznacza, że każdy nowo dodany do sceny obiekt, niezależnie od swojej pozycji, będzie zakrywał obiekty o indeksach niższych niż jego. Gdy korzystamy z soczewki OrthogonalLens, położenie obiektu w przestrzeni jest mniej istotne niż jego pozycja na liście wyświetlanych obiektów. Na rysunku 9.10 przedstawiono scenę z dwóch różnych stron, wyrenderowaną z użyciem soczewki OrthogonalLens. W naszym przykładzie obiekty typu Cube umieszczono na scenie w kierunku dodatnich wartości osi X i Z globalnego układu współrzędnych. W lewym oknie kamera skierowana jest w przeciwnym kierunku, dzięki czemu wydaje się, że sześciany są wyświetlane w odpowiedniej kolejności. W prawym oknie kamera została skierowana w tym samym kierunku, w którym umieszczono sześciany na scenie. Widzimy, że kolejno dodawane rzędy obiektów zakrywają poprzednie, tak jakby były one osadzone na osobnych warstwach.
Rysunek 9.10. Scena wyświetlona z użyciem obiektywu OrthogonalLens
418
Flash i ActionScript. Aplikacje 3D od podstaw
Stosowanie Renderer.CORRECT_Z_ORDER z obiektywem typu OrthogonalLens może powodować przekłamania na siatce obiektów umieszczonych na scenie.
Rodzaje kamer Camera3D W bibliotece Away3D podstawowym rodzajem kamery jest obiekt klasy Camera3D, z której korzystaliśmy we wszystkich dotychczasowych przykładach. Przy każdym wygenerowaniu widoku automatycznie tworzony jest również obiekt klasy Camera3D. W pewnym sensie jest to ułatwienie, ponieważ przy standardowych założeniach nie trzeba samemu tworzyć nowej kamery, wystarczy jedynie odwołać się do właściwości camera zawartej w widoku i dokonać odpowiednich czynności. Jak wiemy, każdy z dodawanych do sceny obiektów swoją pozycję domyślną ma w punkcie centralnym Vector3D(0, 0, 0). W przypadku kamery klasy Camera3D jest inaczej, ponieważ jej położenie na osi Z równe jest -1000. Nie przypadkowo jest to wartość ujemna[MM7], ponieważ jeśli chcemy widzieć elementy znajdujące się blisko centrum sceny, należy kamerę odpowiednio przesunąć. W przypadku Away3D kamera spogląda na obiekty od tyłu, jak w grach z widokiem z trzeciej osoby. Nie będziemy w tym miejscu tworzyli specjalnego przykładu prezentującego zachowania tej kamery, jedynie w krótkim listingu zobaczymy sposób jej tworzenia z przykładowymi ustawieniami. var camera1:Camera3D = new Camera3D(); camera1.position = new Vector3D(500, 500, -500); camera1.lookAt(new Vector3D(100, 100, 100)); view.camera = camera1;
W taki sposób można stworzyć wiele kamer i w odpowiednim czasie przełączać się między nimi, przypisując parametrowi camera konkretny obiekt. W tabelach 9.3 i 9.4 wypisane zostały właściwości oraz metody zawarte w tej klasie, dodatkowo w tabeli 9.5 znajdują się dwie stałe przydatne w przeliczeniach kątów nachylenia.
Rozdział 9. Kamery
419
Tabela 9.3. Właściwości klasy Camera3D
Nazwa
Rodzaj
Wartość
Opis
focus
Number
100
Określa odległość kamery od rzutni
fov
Number
0
Określa pole widzenia; wartość ta jest wyliczana z właściwości focus, zoom oraz pozycji
lens
AbstractLens
ZoomFocusLens
Określa rodzaj stosowanego obiektywu
zoom
Number
10
Określa skalę rzutni
aperture
Number
22
Odzwierciedla regulowanie rozmiaru przesłony stosowane do uzyskania efektu głębi ostrości
doflevels
Number
16
Określa poziomy zróżnicowania rozmycia stosowane do uzyskania efektu głębi ostrości
maxblur
Number
150
Określa maksymalny poziom rozmycia stosowany do uzyskania efektu głębi ostrości
Tabela 9.4. Metody klasy Camera3D
Nazwa
Opis
Camera3D(init:Object = null)
Konstruktor
addOnCameraUpdate(listener:Function):void
Metoda dodająca detektor zdarzenia CameraUpdate
removeOnCameraUpdate(listener:Function):void
Metoda usuwająca detektor zdarzenia CameraUpdate
pan(angle:Number):void
Metoda obracająca kamerę w płaszczyźnie poziomej
tilt(angle:Number):void
Metoda obracająca kamerę w płaszczyźnie pionowej
update():void
Metoda aktualizująca ustawienia rejestrowania sceny
enableDof():void
Metoda uruchamia efekt głębi ostrości
disableDof():void
Metoda wyłącza efekt głębi ostrości
Tabela 9.5. Stałe klasy Camera3D
Nazwa
Rodzaj
Wartość
Opis
toDEGREES
Number
57.29577951308232
Stała stosowana w przeliczeniach wartości na stopnie, stosująca wzór 180/Math.PI
toRADIANS
Number
0.017453292519943295
Stała stosowana w przeliczeniach na radiany, stosująca wzór Math.PI/180
420
Flash i ActionScript. Aplikacje 3D od podstaw
TargetCamera3D Klasa TargetCamera3D jest rozbudowaną wersją poprzednio poznanej klasy Camera3D, posiadającą dodatkowe metody oraz właściwości. Podstawowym celem jej stworzenia było ułatwienie programistom namierzania i śledzenia wybranego obiektu w przestrzeni. Wcześniej poznana metoda lookAt() nie rozwiązuje tego problemu do końca, ponieważ w jej parametrze należy podać konkretną pozycję określoną obiektem klasy Vector3D. Co za tym idzie — przy każdej zmianie położenia docelowego obiektu należy pobierać jego pozycję na nowo i przekazywać ją do metody lookAt(), tak aby kamera mogła śledzić wybrany cel. Znacznie wygodniej jest zastosować kamerę klasy TargetCamera3D, w której wystarczy parametrowi target przypisać konkretny obiekt, który mamy zamiar śledzić, bez konieczności ciągłego sprawdzania jego pozycji. Domyślnym punktem obserwacji dla tej kamery jest środek sceny, istotne jest również to, że w przypadku kamery TargetCamera3D wszystkie metody odpowiedzialne za zmianę pozycji i kąta nachylenia powodują ruch po orbicie wybranego celu, a nie wokół własnej osi. Implementacja kamery TargetCamera3D w najprostszej formie może wyglądać następująco: var camera2:TargetCamera3D = new TargetCamera3D(); camera2.target = ball; //jakiś obiekt klasy Object3D lub potomnej view.camera = camera2;
Dodając metodę przemieszczania obiektu w miejscu, gdzie odświeżany jest widok, można zaobserwować, jak kamera śledzi wybrany cel, nie zmieniając swojej pozycji. Aby wprawić w ruch samą kamerę i okrążać obiekt w wybranym przez nas kierunku, można zastosować detektory zdarzeń KeyboardEvent i po wciśnięciu odpowiedniego klawisza przesunąć kamerę metodami: moveLeft(), moveRight(), moveUp() czy też moveDown(). Z uwagi na swoją prostotę w stosowaniu klasy TargetCamera3D tabele 9.6 i 9.7 zawierają po jednej metodzie i parametrze. Pozostałe właściwości i metody dostępne w TargetCamera3D dziedziczone są po klasie Camera3D. Tabela 9.6. Właściwości klasy TargetCamera3D
Nazwa
Rodzaj
target
Object3D
Wartość
Opis Określa cel do śledzenia
Tabela 9.7. Metody klasy TargetCamera3D
Nazwa
Opis
TargetCamera3D(init:Object = null)
Konstruktor
Rozdział 9. Kamery
421
HoverCamera3D Kolejną dostępną w bibliotece Away3D kamerą jest HoverCamera3D. Ta pochodna klasy TargetCamera3D umożliwia swobodne obracanie kamery wokół wybranego obiektu bez konieczności stosowania dodatkowych funkcji trygonometrycznych. Wystarczy jedynie przydzielić jej cel i odległość, w jakiej ma się znajdować od wyznaczonego punktu. Tak samo jak przypadku klasy TargetCamera3D, do określenia celu obserwacji stosuje się właściwość target, z kolei dystans między kamerą a tym obiektem wyznacza wartość właściwości distance. Poruszanie wokół obiektu na płaszczyźnie poziomej odbywa się przez zmianę wartości właściwości panAngle, natomiast w przypadku płaszczyzny pionowej pozycję kontroluje się właściwością tiltAngle. Wartości obu właściwości można modyfikować jednocześnie w różnym stopniu, dzięki temu kamera może orbitować pod dowolnie ustalonym kątem względem wybranego celu. Istnieje możliwość ograniczenia kąta nachylenia na obu płaszczyznach przez podanie konkretnych wartości dla właściwości: maxPanAngle, minPanAngle, maxTiltAngle oraz minTiltAngle. Przejście między pozycjami jest animacją, której płynność określa właściwość steps. Zwiększenie jej wartości spowoduje dłuższe i bardziej precyzyjne przemieszczenie kamery. Jeżeli zostanie podana wartość 0 jako liczba kroków, to kamera zmieni swoje położenie natychmiast bez widocznych przejść, tak więc poza panAngle i tiltAngle również steps może posłużyć do kontroli prędkości animacji. Poza samym ustawieniem odpowiednich wartości wyżej wspomnianym parametrom należy jeszcze wykonać jedną czynność, aby kamera ruszyła się z miejsca. W metodzie, w której na obiekcie widoku wywoływana jest metoda render(), należy umieścić jeszcze obiekt kamery i wywołać na nim metodę hover(), której zadaniem jest zaktualizowanie położenia. W argumencie tej metody można podać wartość typu Boolean, która określa, czy pozycja ma być zmieniona natychmiast. Domyślna wartość tego argumentu równa jest false. Podstawowy szkielet implementacji kamery HoverCamera3D przedstawia następujący kod źródłowy (bardziej rozbudowany przykład zostanie omówiony w kolejnym podrozdziale): var camera3: HoverCamera3D = new HoverCamera3D(); camera3.panAngle = 0; camera3.tiltAngle = 45; camera3.distance = 800; camera3.target = ball; //jakiś obiekt klasy Object3D lub potomnej camera3.hover(true); view.camera = Camera3; … private function onEnterFrame(e:Event):void {
422
Flash i ActionScript. Aplikacje 3D od podstaw camera3.hover(); view.render(); }
W tabelach 9.8 i 9.9 wypisane zostały podstawowe właściwości i metody klasy HoverCamera3D. Tabela 9.8. Właściwości klasy HoverCamera3D
Nazwa
Rodzaj
Wartość
Opis
distance
Number
800
Odległość między kamerą a obserwowanym obiektem
maxPanAngle
Number
Infinity
Maksymalny kąt nachylenia dla właściwości PanAngle
maxTiltAngle
Number
90
minPanAngle
Number
-Infinity
Maksymalny kąt nachylenia dla właściwości TiltAngle
Minimalny kąt nachylenia dla właściwości PanAngle
minTiltAngle
Number
-90
panAngle
Number
0
Obrót na płaszczyźnie poziomej
steps
Number
8
Liczba kroków określająca płynność animacji
tiltAngle
Number
90
Obrót na płaszczyźnie poziomej
wrapPanAngle
Boolean
false
Określa, czy po przekroczeniu wartości poniżej 0 lub powyżej 360, kąt nachylenia ma być zresetowany, a animacja powtórzona
Minimalny kąt nachylenia dla właściwości TiltAngle
Tabela 9.9. Metody klasy HoverCamera3D
Nazwa
Opis
HoverCamera3D(init:Object = null)
Konstruktor
hover(jumpTo:Boolean = false):Boolean
Modyfikuje aktualne wartości właściwości przejścia kamery
SpringCam Klasa SpringCam została stworzona do konkretnego celu, jakim jest podążanie za wybranym obiektem w ustalonej od niego odległości. W zależności od dystansu, jaki zostanie nadany, kamera ta może być przydatnym narzędziem w grach typu TPP (Third Person Perspective — perspektywa trzeciej osoby) oraz FPP (First Person Perspective — perspektywa pierwszej osoby). Aby przekonać się, że jest możliwe stworzenie takich aplikacji w Away3D 3.6, spójrz na rysunki 9.11 oraz 9.12, na których przedstawiono zrzut z gry BattleCell oraz ministrony Coca-Coli.
Rozdział 9. Kamery
423
Rysunek 9.11.
Zrzut ekranu z gry BattleCell
Rysunek 9.12.
Zrzut ekranu z ministrony Coca-Coli
Aby móc zastosować kamerę SpringCam, należy ustalić jej pozycję oraz obiekt, który ma być śledzony. Do określenia położenia kamery względem obiektu stosuje się właściwość positionOffset, w której należy podać wartość w postaci obiektu klasy Vector3D. Podobnie jak w kamerach TargetCamera3D i HoverCamera3D, tutaj również do wyznaczenia celu stosuje się właściwość o nazwie target. W zasadzie ustalenie tych dwóch właściwości wystarczy do uzyskania efektu kamery z perspektywy pierwszej bądź trzeciej osoby, ale klasa SpringCam ma również kilka właściwości, które określają zachowanie tej kamery w konkretnych warunkach jej stosowania. Mam na myśli reakcję na zmianę położenia namierzanego obiektu, jego kątów
424
Flash i ActionScript. Aplikacje 3D od podstaw
nachylenia czy też czasu, w jakim występuje przemieszczenie. Dla lepszego zrozumienia działania podstawowych właściwości tej klasy w kolejnym podrozdziale przytoczymy przykład, dzięki któremu będzie można zaobserwować różnicę w działaniu kamery przy zastosowaniu poszczególnych kombinacji dla właściwości damping, stiffness oraz mass. Wszystkie przyjmują wartości liczbowe, z czego dla damping określa ona stopień, w jakim kamera tłumi efekt odbijania się od celu; im wyższa wartość, tym ruchy kamery są spokojniejsze. Parametr stiffness określa sztywność sprężyny, na której teoretycznie umieszczona jest kamera. Jeżeli chcemy, aby kamera trzymała się jak najbliżej wybranego obiektu, należy stosować wyższe wartości. Parametr mass określa wagę, która ma wpływ na czas reakcji kamery na zachowania zapisane w poprzednich parametrach. Im cięższa kamera, tym wolniejsze ruchy wykonuje. Podstawowa forma użycia tego rodzaju kamery może wyglądać następująco: var camera4: SpringCam = new SpringCam(); camera4.positionOffset = new Vector3D(0, 100, -300); camera4.target = ball; //jakiś obiekt klasy Object3D lub potomnej view.camera = camera4; … private function onEnterFrame(e:Event):void { camera4.view.render(); }
Aby wprawić kamerę w ruch, należy metodę render() wywołać z właściwości view dostępnej w klasie SpringCam.
W tabelach 9.10 i 9.11 wypisane zostały podstawowe właściwości i metody klasy SpringCam. Tabela 9.10. Właściwości klasy SpringCam
Nazwa
Rodzaj
Wartość
Opis
damping
Number
4
Stopień tłumienia efektu odbijania kamery
lookOffset
Vector3D
mass
Number
positionOffset
Vector3D
stiffness
Number
target
Object3D
Określa obiekt, za którym podąża kamera
view
View3D
Określa obiekt stosowanego widoku
zrot
Number
Określa kąt nachylenia przy zwróceniu się kamery przodem do namierzanego obiektu
Określa kierunek, w którym zwrócona jest kamera 40
Określa wagę kamery, która ma wpływ na szybkość i płynność wykonywanych ruchów Określa pozycję kamery względem wybranego obiektu
1
Określa sztywność sprężyny, na której teoretycznie umieszczona jest kamera
Rozdział 9. Kamery
425
Tabela 9.11. Metody klasy SpringCam
Nazwa
Opis
SpringCam(init:Object = null)
Konstruktor
Przykładowe zastosowania kamer Kamera z perspektywy pierwszej osoby Wspomnieliśmy wcześniej, że do tego typu sytuacji idealnie pasuje kamera rodzaju SpringCam, jednak w tym przypadku stworzymy zupełnie nową klasę, bazując na zwykłej Camera3D. Dlaczego tak? Otóż budując kamerę prawie od podstaw, możemy nadać jej specyficzne właściwości, których nie mają inne kamery dostępne w Away3D. Dodatkowo jest to dobre ćwiczenie na pracę z komponentami biblioteki i poszerzanie ich zastosowań. Przed rozpoczęciem pisania kodu takiej klasy w pierwszej kolejności należy wypisać założenia, jakie powinna ona spełniać: Praca w trybie wolnej kamery i przypisanej do konkretnego obiektu. Swobodne i skalowane obracanie kamery w dowolnych wymiarach okna aplikacji. Swobodne obracanie kamery dzięki wykrywaniu ruchu kursora lub wciśnięciu klawisza myszki i przeciąganiu kursora. Możliwość odwrócenia kierunku obrotu na obu osiach. Możliwość ustawienia granic obracania kamery, gdy ma ona imitować ruchy głowy postaci. Przypisanie podstawowych zdarzeń poruszania kamerą poprzez wciskanie następujących klawiszy: W, S, A, D, Shift oraz spacji. Sytuacje oraz sposoby spełnienia tych założeń zostaną omówione wraz z kodem źródłowym naszej klasy FPP, natomiast ogólny efekt, jaki chcemy uzyskać, przedstawiono na rysunku 9.13.
426
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 9.13.
Przykład zastosowania naszej kamery FPP
Klasa FPP Poniższy listing zawiera cały kod źródłowy klasy FPP. Przepisz go, a następnie zajmiemy się omawianiem jego ważniejszych fragmentów. package { import import import import import import import import import
away3d.core.utils.Init; away3d.cameras.Camera3D; away3d.cameras.lenses.*; flash.display.Stage; flash.events.Event; flash.events.MouseEvent; flash.events.KeyboardEvent; flash.geom.Vector3D; flash.ui.Keyboard;
public class FPP extends Camera3D { private var _stage:Stage; private var _freeCamera:Boolean = true; private var _invertX:Boolean = false; private var _invertY:Boolean = false; private var _directionX:Number = 1; private var _directionY:Number = 1; private var _rotationSensitivity:Number = .5; private var _rotateOnDrag:Boolean = false; private var _height:Number;
Rozdział 9. Kamery private private private private private private private
var var var var var var var
_cameraDirectionPoint:Vector3D = new Vector3D(); _moveCamera:Boolean = false; _shiftDown:Boolean = false; _leftDown:Boolean = false; _rightDown:Boolean = false; _forwardDown:Boolean = false; _backwardDown:Boolean = false;
public var pitchLimit:Number = 60; public var yawLimit:Number = Number.MAX_VALUE; private static const WALK_SPEED:Number = 10; private static const RUN_SPEED:Number = 40; public function FPP(stage:Stage,init:Object = null):void { super(init); _stage = stage; _freeCamera = ini.getBoolean("freeCamera", _freeCamera); _invertX = ini.getBoolean("invertX", _invertX); _invertY = ini.getBoolean("invertY", _invertY); _rotationSensitivity = ini.getNumber("rotationSensitivity", _rotationSensitivity); _rotateOnDrag = ini.getBoolean("rotateOnDrag", _rotateOnDrag); if (_rotateOnDrag) addMouseEventListeners(); if (_invertX) _directionX = -1; if (_invertY) _directionY = -1; addKeyboardEventListeners(); } public function updateCamera():void { _cameraDirectionPoint.x = Math.sin( rotationY * toRADIANS ) * (_shiftDown ? RUN_SPEED : WALK_SPEED); _cameraDirectionPoint.z = Math.cos( rotationY * toRADIANS ) * (_shiftDown ? RUN_SPEED : WALK_SPEED); _cameraDirectionPoint.y = y; if (_freeCamera) { if (_forwardDown) { x += _cameraDirectionPoint.x; z += _cameraDirectionPoint.z; } if (_backwardDown) { x -= _cameraDirectionPoint.x; z -= _cameraDirectionPoint.z; } if (_leftDown) {
427
428
Flash i ActionScript. Aplikacje 3D od podstaw x -= _cameraDirectionPoint.z; z += _cameraDirectionPoint.x; } if (_rightDown) { x += _cameraDirectionPoint.z; z -= _cameraDirectionPoint.x; } } if ((_rotateOnDrag && _moveCamera) || !_rotateOnDrag) { rotationY = (_stage.mouseX - _stage.stageWidth * .5) * _directionX * _rotationSensitivity; rotationX = (_stage.stageHeight * .5 - _stage.mouseY) * _directionY * _rotationSensitivity; if (rotationY > yawLimit) rotationY = yawLimit; else if (rotationY < -yawLimit) rotationY = -yawLimit; [MM8]
if (rotationX > pitchLimit) rotationX = pitchLimit; else if (rotationX < -pitchLimit) rotationX = -pitchLimit; [MM9]
} } public function get toRADS():Number { return toRADIANS; } public function get toDEGS():Number { return toDEGREES; } public function get cameraDirectionPoint():Vector3D { return _cameraDirectionPoint; } public function set freeCamera(val:Boolean):void { _freeCamera = val; } public function get freeCamera():Boolean { return _freeCamera; } public function set invertX(val:Boolean):void
Rozdział 9. Kamery { _invertX = val; _directionX = (val) ? -1 : 1; } public function get invertX():Boolean { return _invertX; } public function set invertY(val:Boolean):void { _invertY = val; _directionY = (val) ? -1 : 1; } public function get invertY():Boolean { return _invertY; } public function set rotateOnDrag(val:Boolean):void { _rotateOnDrag = val; if(val) addMouseEventListeners(); else removeMouseEventListeners(); } public function get rotateOnDrag():Boolean { return _rotateOnDrag; } private function addMouseEventListeners():void { _stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); _stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); } private function removeMouseEventListeners():void { _stage.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); _stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); } private function addKeyboardEventListeners():void { _stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); _stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); } private function removeKeyboardEventListeners():void
429
430
Flash i ActionScript. Aplikacje 3D od podstaw { _stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); _stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.SHIFT: _shiftDown = true; break; case Keyboard.A: _leftDown = true; break; case Keyboard.D: _rightDown = true; break; case Keyboard.W: _forwardDown = true; break; case Keyboard.S: _backwardDown = true; break; case Keyboard.SPACE: break; } } private function onKeyUp(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.SHIFT: _shiftDown = false; break; case Keyboard.A: _leftDown = false; break; case Keyboard.D: _rightDown = false; break; case Keyboard.W: _forwardDown = false; break; case Keyboard.S: _backwardDown = false; break; case Keyboard.SPACE: break; default: break;
Rozdział 9. Kamery
431
} } private function onMouseDown(e:MouseEvent):void { _moveCamera = true; } private function onMouseUp(e:MouseEvent):void { _moveCamera = false; } } }
Zwróć uwagę, że nie przypisaliśmy tej klasy do żadnego konkretnego pakietu. Jeżeli uznasz, że ta klasa będzie przydatna w innych projektach korzystających z Away3D w tej wersji, możesz dodać FPP do pakietu away3d.cameras, pamiętając o zmianie ścieżki w samym pliku klasy. Nasza klasa FPP do poprawnego funkcjonowania przede wszystkim potrzebowała klasy Camera3D, z której dziedziczy właściwości i metody. Dodatkowo załączyliśmy pakiet soczewek, ponieważ w tym przypadku można skorzystać z obiektywu PerspectiveLens i poprawić sposób wyświetlania elementów znajdujących się tuż przed kamerą. Podobnie jak w innych klasach pakietu Away3D, nasza kamera ma kilka właściwości, których wartości są niezbędne do określenia sposobu jej działania. Aby kontrolować poprawność podanych im wartości, zastosowaliśmy klasę Init. Poza tym potrzebowaliśmy również klas do obsługi zdarzeń, klawiatury oraz do wyznaczania pozycji w oknie aplikacji i samej przestrzeni trójwymiarowej. W samej klasie jeszcze przed konstruktorem zdefiniowaliśmy szereg zmiennych i stałych. Niektóre z nich są ogólnie dostępne, inne z kolei działają jedynie w ciele klasy FPP. W pierwszej kolejności określimy obiekt dla klasy Stage pod nazwą _stage, następna zmienna _freeCamera typu Boolean określa, czy obiekt tej klasy ma pracować w trybie wolnej kamery, co oznacza, że kamera nie jest związana z jakimkolwiek innym obiektem umieszczonym na scenie. W niektórych grach istnieje możliwość zmiany kierunku działania osi. Z myślą o tym do klasy FPP dodaliśmy zmienne: _invertX, _invertY, _directionX i _directionY. Dwie pierwsze przyjmują wartości typu Boolean, z kolei następne dwie posłużyły do odwrócenia wyników wyliczeń aktualnych kątów nachylenia. W różnych projektach może być inne zapotrzebowanie na czułość wykonywanych obrotów kamery. Przewidując taką możliwość, dodaliśmy zmienną _rotationSensitivity, której zadaniem jest ograniczenie wyliczonej pozycji kursora na powierzchni okna aplikacji. W wielu przykładach zastosowania FPP w bibliotece Away3D obracanie kamery następuje po wciśnięciu klawisza myszki bądź klawiatury i przeciągnięciu
432
Flash i ActionScript. Aplikacje 3D od podstaw
kursora w wybranym kierunku. W niektórych aplikacjach takie rozwiązanie może bardzo przeszkadzać, dlatego w naszej klasie zastosowaliśmy obsługę obu przypadków zarówno z użyciem wciśniętego przycisku, jak i bez niego. Do wyznaczenia tego trybu służy _rotateOnDrag, gdzie wartość false pozwala na swobodne podążanie za kursorem. Pozostałe prywatne stałe i zmienne służą do wyznaczania pozycji kamery w przestrzeni oraz samego wprawienia jej w ruch, a pitchLimit oraz yawLimit określają granice obrotu kamery. W konstruktorze klasy FPP przypisaliśmy poszczególnym zmiennym wartości pobrane z argumentów stage oraz init. W przypadku braku którejś z wartości w obiekcie init zastępujemy ją domyślną przypisaną konkretnemu parametrowi. W następnych linijkach sprawdziliśmy, czy kamera ma poruszać się w przeciwnych kierunkach do pozycji kursora oraz czy do wykonania obrotu będzie potrzebny wciśnięty przycisk myszy. Cały mechanizm poruszania i zmiany kątów nachylenia mieści się w metodzie co czyni ją najważniejszą metodą w naszej klasie. Wewnątrz jej bloku w pierwszej kolejności wyznaczane są nowe wartości właściwości x, y oraz z w obiekcie _cameraDirectionPoint. W przypadku pozycji na osiach X i Z skorzystaliśmy z metod Math.sin() oraz Math.cos(), które wyliczają nam kierunek[MM10] zależny od kąta nachylenia względem osi Y. Celem zastosowania tych wzorów jest przemieszczanie się do przodu względem obranego kierunku, a nie ogólnego układu osi sceny. updateCamera(),
Przeważnie w grach z kamerą FPP istnieje możliwość zmiany prędkości przemieszczania poprzez wciśnięcie klawisza, na przykład Shift, dlatego przy wyliczeniach dodatkowo zastosowaliśmy skróconą instrukcję warunkową, aby sprawdzić, czy faktycznie został wciśnięty klawisz. _cameraDirectionPoint.x = Math.sin( rotationY * toRADIANS ) * (_shiftDown ? RUN_SPEED : WALK_SPEED); _cameraDirectionPoint.z = Math.cos( rotationY * toRADIANS ) * (_shiftDown ? RUN_SPEED : WALK_SPEED); _cameraDirectionPoint.y = y;
Po wyliczeniu pozycji _cameraDirectionPoint zastosowaliśmy instrukcję warunkową, która sprawdza, czy według ustawień kamera jest w trybie wolnym. Jeżeli wartość zmiennej _freeCamera równa się true, to sprawdzane są kolejno warunki wciśnięcia klawiszy: W, S, A, D. W każdym z przypadków zmiany pozycji kamery posłużyliśmy się wcześniej wyliczonym obiektem _cameraDirectionPoint, a dokładniej jego właściwościami x i z. Wewnątrz pierwszych dwóch instrukcji zmiana ta polegała jedynie na zwiększeniu i zmniejszeniu wartości położenia kamery na osiach X i Z.
Rozdział 9. Kamery
433
if (_forwardDown) { x += _cameraDirectionPoint.x; z += _cameraDirectionPoint.z; } if (_backwardDown) { x -= _cameraDirectionPoint.x; z -= _cameraDirectionPoint.z; }
Z kolei w następnych, mając na uwadze obrót kamery, musieliśmy ustawić wartości naprzemiennie. if (_leftDown) { x -= _cameraDirectionPoint.z; z += _cameraDirectionPoint.x; } if (_rightDown) { x += _cameraDirectionPoint.z; z -= _cameraDirectionPoint.x; }
Podstawą do wyznaczenia kierunku zwrotu kamery na osiach X i Y jest położenie kursora w oknie aplikacji. Do wyliczenia wartości rotationX oraz rotationY zastosowaliśmy różnice aktualnej pozycji kursora i wymiarów okna aplikacji, uzyskując tym samym współrzędne odpowiadające standardowej osi dwuwymiarowej złożonej z czterech ćwiartek. Dodatkowo pomnożyliśmy te wyliczenia przez wartości właściwości _directionX, _directionY oraz _rotationSensitivity. Z wymienionych zmiennych pierwsze dwie w zależności od wybranej osi ustalają kierunek obrotu, który może być przeciwny, jeżeli wymaga tego logika aplikacji. Z kolei _rotationSensitivity redukuje wyliczoną wartość, aby kontrolować czułość obrotu kamery. rotationY = (_stage.mouseX - _stage.stageWidth * .5) * _directionX * _rotationSensitivity; rotationX = (_stage.stageHeight * .5 - _stage.mouseY) * _directionY * _rotationSensitivity;
Może się zdarzyć, że nasza kamera będzie musiała imitować ruch głowy, a jak wiadomo z anatomicznego punktu widzenia, nie można wykonać pełnego obrotu głową. Dlatego właśnie w takich sytuacjach należy zastosować wartości określające limit kąta nachylenia i z każdą klatką animacji sprawdzać, czy obrót mieści się w ustalonych granicach. Do tego celu służą następujące instrukcje warunkowe umieszczone na końcu metody updateCamera().
434
Flash i ActionScript. Aplikacje 3D od podstaw if (rotationY > yawLimit) rotationY = yawLimit; else if (rotationY < -yawLimit) rotationY = -yawLimit; else {/**/} if (rotationX > pitchLimit) rotationX = pitchLimit; else if (rotationX < -pitchLimit) rotationX = -pitchLimit; else {/**/}
Wewnątrz klasy FPP umieściliśmy również kilka metod typu get i set dla poszczególnych właściwości zdefiniowanych jako prywatne. Można było po prostu posłużyć się słowem kluczowym public zamiast private, lecz takie podejście nie zawsze jest dobre, szczególnie jeżeli przy okazji zmiany wartości jednej z właściwości trzeba wykonać dodatkowo inne operacje. Metody toRADS() oraz toDEGS() zwracają wartości właściwości toRADIANS oraz toDEGREES odziedziczone po klasie Camera3D. Niestety, nie są one dostępne bezpośrednio z klasy FPP, więc należało dopisać własne metody. Obie te właściwości są bardzo przydatne przy wyliczeniach w funkcjach trygonometrycznych. Metody zmieniające wartości właściwości invertX oraz invertY w rzeczywistości są tylko pomocniczymi dla programisty korzystającego z klasy FPP. Posłużyliśmy się tymi nazwami w zasadzie tylko dlatego, że są one prostsze do zrozumienia i nie wymagają pamiętania o tym, że _directionX lub _directionY o wartości –1 odwraca kierunek obrotu kamery. Podobną zasadę obraliśmy przy ustawianiu wartości dla właściwości rotateOnDrag. Poza samą jej zmianą dodaliśmy warunek, który dodaje bądź usuwa detektory zdarzeń MouseEvent w zależności od tego, czy kamera ma być obracana dopiero po wciśnięciu lewego przycisku myszki. W metodach onKeyDown() oraz onKeyUp() zapisaliśmy instrukcje warunkowe switch, wewnątrz których sprawdzany jest kod aktualnie wciskanego klawisza. Jeżeli odpowiada on jednemu z przypadków uwzględnionych w instrukcji, przypisywana jest wartość true bądź false konkretnej zmiennej. Oddzielenie sprawdzania stanu poszczególnych klawiszy od wykonywania animacji pozwala na płynne współdziałanie kilku zdarzeń w tym samym czasie. Na końcu naszej klasy dodaliśmy dwie metody onMouseDown() oraz onMouseUp(), których zadaniem jest przypisanie wartości true bądź false parametrowi _moveCamera. Zastosowanie ich jest konieczne, gdy wartość właściwości _rotateOnDrag równa jest true i trzeba wykryć moment, w którym przycisk myszy jest wciśnięty lub puszczony.
Rozdział 9. Kamery
435
Podstawowa implementacja kamery FPP Aby używać kamery w trybie wolnym, czyli bez śledzenia konkretnego obiektu, wystarczy wykonać trzy kroki. Po pierwsze, w kodzie aplikacji trzeba stworzyć obiekt klasy FPP, który będzie ogólnie dostępny, następnie przypisać kamerę do widoku, a wewnątrz metody odświeżającej wyświetlaną scenę trzeba umieścić odwołanie do metody updateCamera(). Przykładowa implementacja może wyglądać tak: private var camera:FPP; ... private function initCamera():void { camera = new FPP(stage); view.camera = camera; } ... private function onEnterFrame(e:Event):void { camera.updateCamera(); view.render(); }
Klasa Player Obiekt klasy Player to w rzeczywistości kontener bazujący na klasie ObjectCon tainer3D, wewnątrz którego umieścimy model broni oraz dodamy naszą kamerę FPP. Z chwilą gdy kamera zostanie przywiązana do obiektu player, będziemy musieli w jej ustawieniach zaznaczyć właściwość freeCamera jako false, ponieważ od tej pory zmieniać pozycję będziemy całemu obiektowi, a nie samej kamerze, ale o tym wspomnę przy implementacji. Na razie przepisz następujący kod źródłowy: package { import import import import import import import import import import import
away3d.containers.ObjectContainer3D; away3d.cameras.lenses.*; away3d.core.utils.Init; away3d.loaders.Loader3D; away3d.loaders.Collada; flash.display.Stage; flash.geom.Vector3D; flash.events.Event; flash.events.MouseEvent; flash.events.KeyboardEvent; flash.ui.Keyboard;
public class Player extends ObjectContainer3D { private var _height:Number;
436
Flash i ActionScript. Aplikacje 3D od podstaw private var _stage:Stage; private var _fpp:FPP; private var _cameraTargetPoint:Vector3D = new Vector3D(); private var modelsContainerDistance:Number = 0; private var modelLoader:Loader3D; private var modelsContainer:ObjectContainer3D = new ObjectContainer3D(); private private private private private
var var var var var
_shiftDown:Boolean = false; _leftDown:Boolean = false; _rightDown:Boolean = false; _forwardDown:Boolean = false; _backwardDown:Boolean = false;
private static const WALK_SPEED:Number = 10; private static const RUN_SPEED:Number = 40; public function Player(stage:Stage, camera:FPP, init:Object = null):void { _stage = stage; _fpp = camera; _fpp.lens = new PerspectiveLens(); _fpp.fov = 55; _height = ini.getNumber("height", 160, { min:100 } ); _fpp.y = _height; addChild(_fpp); /* * Model */ modelLoader = Collada.load('../../resources/models/dae/ fpp/fpp_shotgun.DAE'); modelLoader.addOnSuccess(modelLoaded); addChild(modelsContainer); /* * */ initListeners(); } private function modelLoaded(e:Event):void { modelLoader.handle.name = "gun"; modelsContainer.addChild(modelLoader.handle); modelsContainer.getChildByName("gun").y = _height - 25; } public function update():void { if (_fpp != null && !_fpp.freeCamera) { _fpp.updateCamera(); if (modelsContainer.getChildByName("gun"))
Rozdział 9. Kamery
437
{ _cameraTargetPoint.x = modelsContainerDistance * Math.sin (_fpp.rotationY * _fpp.toRADS) * Math.cos(_fpp.rotationX * _fpp.toRADS) + _fpp.x; _cameraTargetPoint.z = modelsContainerDistance * Math.cos ( _fpp.rotationY * _fpp.toRADS) * Math.cos(_fpp.rotationX * _fpp.toRADS) + _fpp.z; _cameraTargetPoint.y = modelsContainerDistance * Math.sin(_fpp.rotationX * _fpp.toRADS) + _fpp.y; modelsContainer.getChildByName("gun").position = _cameraTargetPoint; modelsContainer.getChildByName("gun").rotationY = _fpp.rotationY; modelsContainer.getChildByName("gun").rotationX = _fpp.rotationX; } if (_forwardDown) { x += _fpp.cameraDirectionPoint.x; z += _fpp.cameraDirectionPoint.z; } if (_backwardDown) { x -= _fpp.cameraDirectionPoint.x; z -= _fpp.cameraDirectionPoint.z; } if (_leftDown) { x -= _fpp.cameraDirectionPoint.z; z += _fpp.cameraDirectionPoint.x; } if (_rightDown) { x += _fpp.cameraDirectionPoint.z; z -= _fpp.cameraDirectionPoint.x; } } } public function get height():Number { return _height; } public function get fpp():FPP { return _fpp; } private function initListeners():void
438
Flash i ActionScript. Aplikacje 3D od podstaw { _stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); _stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.SHIFT: _shiftDown = true; break; case Keyboard.A: _leftDown = true; break; case Keyboard.D: _rightDown = true; break; case Keyboard.W: _forwardDown = true; break; case Keyboard.S: _backwardDown = true; break; case Keyboard.SPACE: break; default: break; } } private function onKeyUp(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.SHIFT: _shiftDown = false; break; case Keyboard.A: _leftDown = false; break; case Keyboard.D: _rightDown = false; break; case Keyboard.W: _forwardDown = false; break; case Keyboard.S: _backwardDown = false; break; case Keyboard.SPACE: break;
Rozdział 9. Kamery
439
default: break; } } } }
W niektórych fragmentach kodu widać podobieństwo do klasy FPP. Tutaj również skorzystaliśmy z klas potrzebnych do obsługi standardowych zdarzeń klawiatury i myszki, wyznaczenia pozycji kursora czy też określenia wartości poszczególnych właściwości. Poza tym chcąc umieścić model formatu DAE wewnątrz kontenera, musieliśmy zaimportować klasy Loader3D oraz Collada z pakietu away3d.loaders. Zupełnie opcjonalnie dodaliśmy również wszystkie klasy z pakietu away3d.cameras. lenses, dzięki czemu będziemy mieli możliwość zmiany sposobu rejestrowania obiektów. Wewnątrz klasy Player standardowo zaczęliśmy od zdefiniowania potrzebnych zmiennych oraz obiektów. W pierwszej kolejności dodaliśmy zmienną _height, która określa pozycję y obiektu naszej klasy FPP o nazwie _fpp. Do ładowania modeli zdefiniowaliśmy obiekt modelLoader, z kolei do jego osadzenia użyliśmy następnego kontenera ObjectContainer3D o nazwie modelsContainer. Zrobiliśmy tak, ponieważ można przyjąć, że cały obiekt klasy Player będzie miał w swoich zasobach więcej niż jeden model broni, a wtedy warto trzymać ją w jednym miejscu. Dodaliśmy również jedną pomocniczą zmienną modelsContainer Distance, która określa odległość kontenera z modelami od kamery. Wartość tej zmiennej można w trakcie używania obiektu regulować, tak aby dystans był odpowiedni w zależności od budowy wybranego modelu. Pozostałe zmienne oraz stałe znamy już z klasy FPP i w przypadku klasy Player mają one takie same role. W konstruktorze klasy Player w pierwszej kolejności nadaliśmy odpowiednie wartości poszczególnym zmiennym i obiektom. W przypadku kamery dodatkowo wybraliśmy nowy rodzaj soczewki rodzaju PerspectiveLens oraz nową wartość dla właściwości fov. Ten krok jest opcjonalny i można śmiało testować zachowanie kamery z innymi ustawieniami. _fpp = camera; _fpp.lens = new PerspectiveLens(); _fpp.fov = 55; _height = ini.getNumber("height", 160, { min:100 } ); _fpp.y = _height; addChild(_fpp);
440
Flash i ActionScript. Aplikacje 3D od podstaw
Warto zwrócić w tym miejscu uwagę na różnicę w wyświetlaniu obiektów przy różnych rozdzielczościach aplikacji. Najlepiej byłoby dopisać specjalną metodę, która regulowałaby pole widzenia kamery w zależności od wymiarów okna, jeżeli zastosowano zapis stage.scaleMode = StageScaleMode.NO_SCALE;.
Po ustawieniu wszystkich właściwości kamery i dodaniu jej do kontenera stworzyliśmy obiekt modelLoader i pobraliśmy poprzez klasę Collada odpowiedni model w formacie DAE. Aby zachować porządek, dodaliśmy również metodę modelLoaded(), która uruchomiona zostanie z chwilą pobrania modelu. modelLoader = Collada.load('../../resources/models/dae/ fpp/fpp_shotgun.DAE'); modelLoader.addOnSuccess(modelLoaded); addChild(modelsContainer);
Na końcu konstruktora wywołaliśmy metodę initListeners(), wewnątrz której zapisaliśmy dodawanie rejestratorów zdarzeń wciśnięcia i zwolnienia klawiszy klawiatury. W metodzie modelLoaded() pobraną zawartość modelLoader nazwaliśmy gun i dodaliśmy do kontenera modelsContainer. Dodatkowo uregulowaliśmy pozycję na osi Y wgranego modelu. modelLoader.handle.name = "gun"; modelsContainer.addChild(modelLoader.handle); modelsContainer.getChildByName("gun").y = _height - 25;
Najważniejsze operacje związane ze zmianą pozycji i kąta nachylenia zapisaliśmy w metodzie update(). W pierwszej kolejności dodaliśmy instrukcję warunkową, aby sprawdzić, czy kamera istnieje i nie jest czasem w trybie wolnym. Jeżeli oba warunki są spełnione, to można wykonywać pozostałe operacje. Na samym początku wywołaliśmy metodę updateCamera() na obiekcie _fpp, ponieważ jego aktualne wartości są niezbędne do poprawnych obliczeń pozycji całego obiektu klasy Player. Później sprawdziliśmy, czy istnieje model o nazwie gun. Jeżeli tak, to można wyliczać pozycję obiektu _cameraTargetPoint. Zastosowane wzory za pomocą metod Math.sin() oraz Math.cos() i kątów nachylenia kamery na osiach X i Y wyliczają nową pozycję w przestrzeni. Zmienna modelsContainer Distance wyznacza odległość kontenera z modelami od kamery. _cameraTargetPoint.x = modelsContainerDistance * Math.sin(_fpp.rotationY * _fpp.toRADS) * Math.cos(_fpp.rotationX * _fpp.toRADS) + _fpp.x; _cameraTargetPoint.z = modelsContainerDistance * Math.cos( _fpp.rotationY * _fpp.toRADS) * Math.cos(_fpp.rotationX * _fpp.toRADS) + _fpp.z; _cameraTargetPoint.y = modelsContainerDistance * Math.sin(_fpp.rotationX * _fpp.toRADS) + _fpp.y;
Rozdział 9. Kamery
441
Po wyliczeniu pozycji należało ją przypisać do właściwości position wybranego modelu. Dodatkowo obróciliśmy model tak samo jak kamerę. modelsContainer.getChildByName("gun").position = _cameraTargetPoint; modelsContainer.getChildByName("gun").rotationY = _fpp.rotationY; modelsContainer.getChildByName("gun").rotationX = _fpp.rotationX;
W dalszej części metody update() zapisaliśmy kolejne instrukcje warunkowe sprawdzające, czy któryś z klawiszy W, S, A, D został wciśnięty. W przypadku wystąpienia któregoś ze zdarzeń wykonywany jest odpowiedni kod przemieszczania obiektu klasy Player. Schemat ten został już wcześniej przedstawiony w metodzie updateCamera() w klasie FPP. I to w zasadzie cała mechanika działania tej klasy. Pozostałe metody, takie jak reakcje na zdarzenia typu KeyboardEvent czy też metody udostępniania wartości właściwości, omówiliśmy w poprzednim przykładzie.
Implementacja kamery FPP wraz z klasą Player Teraz stworzymy prosty przykład zastosowania klas FPP i Player razem w jednej aplikacji. Efekt, który uzyskamy po skompilowaniu kodu, został przedstawiony wcześniej na rysunku 9.13. Przepisz poniższy kod, ustawiając odpowiednie zasoby potrzebne do jego poprawnego funkcjonowania. package { import import import import import import import import import import import import import
away3d.containers.View3D; away3d.loaders.Loader3D; away3d.events.Loader3DEvent; away3d.primitives.Plane; away3d.materials.BitmapFileMaterial; away3d.core.base.Mesh; away3d.loaders.data.AnimationData; away3d.loaders.Md2; flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event;
public class fppExample extends Sprite { private var view:View3D; private var pln:Plane; private var camera:FPP; private var player:Player; private var enemyAnim:AnimationData; private var modelLoader:Loader3D;
442
Flash i ActionScript. Aplikacje 3D od podstaw public function fppExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); // view = new View3D(); addChild(view); pln = new Plane(); pln.material = new BitmapFileMaterial('../../resources/bitmaps/ skybox/down.jpg'); pln.y = -100; pln.segmentsH = pln.segmentsW = 10; pln.height = 1000; pln.width = 1000; view.scene.addChild(pln); /* * FPP i Player */ camera = new FPP(stage, { freeCamera:false,height:120 } ); player = new Player(stage, camera); view.camera = camera; view.scene.addChild(player); /* * Enemy */ modelLoader = Md2.load('../../resources/models/md2/ hueteotl/TRIS.MD2'); modelLoader.addOnSuccess(addEnemy); onResize(); } private function addEnemy(e:Loader3DEvent = null):void { var mat:BitmapFileMaterial = new BitmapFileMaterial ('../../resources/models/md2/hueteotl/hueteotl.jpg'); var enemy:Mesh = modelLoader.handle as Mesh; enemy.scale(.08); enemy.material = mat; view.scene.addChild(enemy); enemyAnim = enemy.animationLibrary.getAnimation('stand'); enemyAnim.animator.play(); } private function onEnterFrame(e:Event):void { player.update(); view.render(); }
Rozdział 9. Kamery
443
private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
Zastosowanie klas FPP i Player w tym przykładzie zaczęliśmy od zdefiniowania dwóch obiektów: camera oraz player. Następnym krokiem było stworzenie tych obiektów w konstruktorze. Jako argumenty dla obiektu camera podaliśmy stage oraz ustawiliśmy właściwość freeCamera jako false, ponieważ nie chcieliśmy, aby kamera zmieniała swoją pozycję niezależnie od obiektu player. Dodatkowo ustawiliśmy jej wysokość na 120, tak aby widok z kamery odpowiadał wysokości wgranego modelu w obiekcie modelLoader. Po wykonaniu wszystkich ustawień przypisaliśmy obiekt camera jako podstawową kamerę widoku, tak samo jak w poprzednim przykładzie jej implementacji. camera = new FPP(stage, { freeCamera:false,height:120 } ); view.camera = camera;
W przypadku obiektu player w argumentach konstruktora podaliśmy stage oraz wcześniej utworzony obiekt kamery, po czym dodaliśmy go do sceny. player = new Player(stage, camera); view.scene.addChild(player);
W poprzedniej implementacji poza utworzeniem kamery musieliśmy odwołać się do metody updateCamera() w metodzie odświeżającej widok aplikacji. Ponieważ we wnętrzu klasy Player wywołaliśmy już tę metodę samą kamerą, nie musimy się tym przejmować. W tym przypadku musieliśmy odwołać się do metody update() na obiekcie player, aby aktualizować jego stan. private function onEnterFrame(e:Event):void { player.update(); view.render(); }
Kamera z perspektywy osoby trzeciej Pojęcie kamery z perspektywy osoby trzeciej bardziej kojarzy się z aplikacjami, w których osadzona jest ona za plecami sterowanej postaci, jednak korzysta się z niej również w symulatorach jazdy, lotu lub jakichkolwiek innych. W tym podrozdziale naszym celem będzie napisanie tego typu symulatora z zastosowaniem kamery SpringCam i sprawdzenie jej zachowania przy zmianie położenia wybranego obiektu.
444
Flash i ActionScript. Aplikacje 3D od podstaw
Efekt, jaki chcemy uzyskać, przedstawiono na rysunku 9.14. Rysunek 9.14.
Przykład zastosowania kamery SpringCam
Klasa Car W pierwszej kolejności zajmiemy się klasą reprezentującą nasz pojazd oraz jego zachowania. Przepisz poniższy kod źródłowy, a później omówimy ważniejsze jego fragmenty. package { import import import import import import import import
away3d.containers.ObjectContainer3D; away3d.core.base.Mesh; away3d.materials.BitmapFileMaterial; away3d.loaders.*; flash.display.Stage; flash.events.Event; flash.events.KeyboardEvent; flash.ui.Keyboard;
public class Car extends ObjectContainer3D { private var _stage:Stage; private var _modelLoader:Loader3D; private var _model:Mesh; private var _maxSpeed:Number = 0; private var _topSteer:Number = 0; private var _speed:Number = 0;
Rozdział 9. Kamery
445
private var _steer:Number = 0; private var _scale:Number = 10; private private private private
var var var var
_leftDown:Boolean = false; _rightDown:Boolean = false; _forwardDown:Boolean = false; _backwardDown:Boolean = false;
public static var MAX_SPEED:Number = 24; public static var MIN_SPEED:Number = -12; public function Car(stage:Stage) { _stage = stage; _modelLoader = Obj.load('../../resources/models/mObj/car/ car_riviera.obj', { autoLoadTextures:false } ); _modelLoader.addOnSuccess(modelLoaded); _stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); _stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); } private function modelLoaded(e:Event):void { _model = _modelLoader.handle as Mesh; _model.y = -10; _model.movePivot(0, 0, 10); _model.material = new BitmapFileMaterial('../../resources/models/ mObj/car/car_riviera.jpg'); _model.scale(10); _scale = _model.scaleZ; addChild(_model); } public function update():void { if (_forwardDown) _maxSpeed = MAX_SPEED; else if (_backwardDown) _maxSpeed = MIN_SPEED; else _maxSpeed = 0; _speed -= (_speed - _maxSpeed) * 0.1; // if( _rightDown ) { if( _topSteer < 30 ) { _topSteer += 5; } } else if( _leftDown ) { if( _topSteer > -30 ) { _topSteer -= 5;
446
Flash i ActionScript. Aplikacje 3D od podstaw } } else _topSteer -= _topSteer * .04; _steer -= ( _steer - _topSteer ) * .5; // yaw((_scale * .1 * _speed * _steer) / 360); moveForward(_scale * .1 * _speed); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: _leftDown = true; break; case Keyboard.RIGHT: _rightDown = true; break; case Keyboard.UP: _forwardDown = true; break; case Keyboard.DOWN: _backwardDown = true; break; case Keyboard.SPACE: break; default: break; } } private function onKeyUp(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: _leftDown = false; break; case Keyboard.RIGHT: _rightDown = false; break; case Keyboard.UP: _forwardDown = false; break; case Keyboard.DOWN: _backwardDown = false; break; case Keyboard.SPACE: break; default: break;
Rozdział 9. Kamery
447
} } } }
Napisana w poprzednim przykładzie klasa Player w swoich założeniach przypomina klasę Car. Z tego powodu tutaj skorzystaliśmy z jej niektórych zaimportowanych komponentów, zastosowaliśmy podobne zmienne, obiekty pomocnicze oraz metody. Z dodatkowych klas użyliśmy Mesh, Loader3D oraz Obj potrzebnych do pobrania modelu w formacie obj. Do nałożenia tekstury zastosowaliśmy klasę BitmapFileMaterial, a do obsługi zdarzeń i sterowania obiektem klas Event, Keyboard Event i Keyboard. W następnej kolejności zdefiniowaliśmy kilka zmiennych potrzebnych do wyliczenia nowej pozycji pojazdu. I tak _speed określa prędkość, z jaką porusza się obiekt, a _maxSpeed wyznacza jej granice, z kolei _steer oraz _topSteer mają podobne zastosowania, tylko że w tym przypadku dotyczą ustalenia kąta, z jakim pojazd skręca. Dodatkowo dopisaliśmy dwie pomocnicze zmienne statyczne: MAX_SPEED i MIN_SPEED, wyznaczające granice prędkości. Ich wartości zostały przypisane zmiennej _maxSpeed w konkretnych warunkach, o których za chwilę powiemy. Pozostałe zadeklarowane zmienne i obiekty są nam już znane z poprzednich przykładów i posłużyły do obsługi klawiatury oraz pobranego modelu. W ciele konstruktora przypisaliśmy podany w argumencie obiekt Stage, zainicjowaliśmy proces pobierania wybranego modelu oraz dodaliśmy detektory zdarzeń KeyboardEvent.KEY_DOWN i KeyboardEvent.KEY_UP. Podobnie jak miało to miejsce w klasie Player, tak i tutaj konfiguracja oraz samo dodanie modelu i tekstury odbywa się w metodzie wywoływanej po zakończeniu jego pobierania. W metodzie modelLoaded() odpowiednio ustawiliśmy pozycję pobranego obiektu, zmieniliśmy położenie punktu centralnego i zmniejszyliśmy jego rozmiary, tak aby pasował on do naszej sceny. Poza tym przypisaliśmy zmiennej _scale wartość skali modelu względem osi Z. Zmienna ta jako składowa wyliczeń pozycji musi określać aktualny rozmiar pojazdu. _model.y = -10; _model.movePivot(0, 0, 10); _model.scale(10); _scale = _model.scaleZ;
Wszystkie operacje związane ze zmianą położenia obiektu klasy Car wykonywane są w metodzie update(), która wywoływana jest w głównej klasie przy każdym wystąpieniu zdarzenia Event.ENTER_FRAME. Sama zmiana pozycji obiektu klasy Car polega na zastosowaniu yaw() oraz moveForward(), ale aby ustalić wartości podane jako argumenty tych metod, musieliśmy wykonać kilka wyliczeń i użyć instrukcji warunkowych.
448
Flash i ActionScript. Aplikacje 3D od podstaw
W pierwszej kolejności sprawdziliśmy, czy został wciśnięty klawisz górnej lub dolnej strzałki. W zależności od sytuacji zmiennej _maxSpeed przypisana została nowa wartość: MAX_SPEED, MIN_SPEED lub 0, gdy nie wciśnięto żadnego. if (_forwardDown) _maxSpeed = MAX_SPEED; else if (_backwardDown) _maxSpeed = MIN_SPEED; else _maxSpeed = 0;
Zmienna _maxSpeed wyznacza granicę prędkości, a _speed poprzednio zapisaną jej wartość. Obie te zmienne były nam potrzebne do ustalenia aktualnej prędkości wyliczanej z wzoru: _speed -= (_speed - _maxSpeed) * 0.1;
W kolejnych instrukcjach warunkowych sprawdziliśmy, czy wciśnięto klawisze lewej lub prawej strzałki. W obu przypadkach zastosowaliśmy również instrukcje warunkowe if() w celu sprawdzenia, czy wartość zmiennej _topSteer nie przekracza ustalonych granic. W przypadku wciśnięcia klawisza prawej strzałki wartość zmiennej _topSteer zwiększana jest o liczbę 5, jeżeli nie przekroczyła ona wartości równej liczbie 30. if( _rightDown ) { if( _topSteer < 30 ) { _topSteer += 5; } }
Z kolei wciskając klawisz lewej strzałki, zmniejszamy o liczbę 5 wartość zmiennej _topSteer, jeżeli nie jest ona mniejsza od liczby -30. else if( _leftDown ) { if( _topSteer > -30 ) { _topSteer -= 5; } }
W przypadku gdy nie zachodzi żaden z wcześniej wymienionych warunków, wartość zmiennej _topSteer jest stopniowo zmniejszana do momentu, gdy będzie równa 0. Taki zabieg ma na celu symulowanie płynnego wyrównania kierunku jazdy. else _topSteer -= _topSteer
* .04;
Aktualna wartość skrętu uzyskiwana jest ze wzoru podobnego do wyliczania prędkości: _steer -= ( _steer - _topSteer ) * .5;
Rozdział 9. Kamery
449
Po ustaleniu wartości właściwości _speed oraz _steer można było je zastosować w yaw() oraz moveForward(). W obu metodach wzięliśmy pod uwagę dodatkowe czynniki, na przykład poziom skrętu zależy również od aktualnej prędkości i skali modelu. yaw((_scale * .1 * _speed * _steer) / 360); moveForward(_scale * .1 * _speed);
Implementacja kamery SpringCam i klasy Car Mając już klasę potrzebną do wyświetlania i sterowania pojazdem, musimy stworzyć odpowiednie środowisko do przetestowania kamery i jej reakcji. Zastosowane wartości w parametrach trzymają kamerę w ryzach, tak aby efekt sprężyny i odchylenia nie był zbyt duży. Oczywiście nic nie stoi na przeszkodzie, aby je zmodyfikować i sprawdzić, jak obiekt klasy SpringCam zachowuje się w różnych warunkach. Przepisz następujący kod źródłowy, przygotuj wszystkie potrzebne zasoby i zobacz, jak kamera podąża za wybranym obiektem. package { import import import import import import // import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D; away3d.containers.View3D; away3d.primitives.Plane; away3d.materials.BitmapFileMaterial; away3d.cameras.SpringCam;
public class tppExample extends Sprite { private var view:View3D; private var camera:SpringCam; private var ground:Plane; private var car:Car; public function tppExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); // view = new View3D(); addChild(view);
450
Flash i ActionScript. Aplikacje 3D od podstaw ground = new Plane(); ground.y = -50; ground.pushback = true; ground.material = new BitmapFileMaterial('../../resources/ bitmaps/skybox/down.jpg'); ground.segmentsH = ground.segmentsW = 10; ground.height = 1000; ground.width = 1000; view.scene.addChild(ground); /* */ initPlayer(); initCamera(); onResize(); } private function initPlayer():void { car = new Car(stage); car.pushfront = true; view.scene.addChild(car); } private function initCamera():void { camera = new SpringCam(); camera.stiffness = 6; camera.damping = 10; camera.mass = 10; camera.positionOffset = new Vector3D(0, 50, -200); camera.target = car; view.camera = camera; } private function onEnterFrame(e:Event):void { car.update(); camera.view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
W tym przykładzie niezbędnymi składnikami klasy bazowej poza widokiem View3D i standardowymi klasami stołu montażowego były oczywiście klasa Car oraz kamera SpringCam. Dodatkowo w celach praktycznych i estetycznych umieściliśmy
Rozdział 9. Kamery
451
proste otoczenie w postaci planszy klasy Plane, na którą z kolei nałożyliśmy teksturę pobraną poprzez klasę BitmapFileMaterial. W przypadku konstruktora klasy tppExample istotne dla nas jest to, że dodaliśmy powierzchnię o nazwie ground, dzięki której będziemy mogli zaobserwować zmiany położenia naszego pojazdu. W kolejnych linijkach konstruktora wykonaliśmy odwołania do trzech metod: initPlayer(), initCamera() oraz onResize(), których kod tworzy pojazd, kamerę i ustawia pozycję widoku w oknie aplikacji. Wewnątrz metody initPlayer() stworzyliśmy obiekt pojazdu i dodaliśmy go do sceny aplikacji. Dodatkowo przypisaliśmy jego parametrowi pushfront wartość true, dzięki czemu unikniemy wzajemnego przenikania wielokątów podłoża i modelu przy wyświetlaniu sceny. car = new Car(stage); car.pushfront = true; view.scene.addChild(car);
W metodzie initCamera() dodaliśmy obiekt klasy SpringCam oraz ustawiliśmy dla jej parametrów: stiffness, damping, mass oraz positionOffset takie wartości, aby nasza kamera trzymała się w miarę sztywno i blisko wyznaczonego obiektu car. Poza tym standardowo przy zmianie rodzaju kamery musieliśmy ją przypisać widokowi. camera = new SpringCam(); camera.stiffness = 6; camera.damping = 10; camera.mass = 10; camera.positionOffset = new Vector3D(0, 50, -200); camera.target = car; view.camera = camera;
W metodzie onEnterFrame() wywołaliśmy metodę render() poprzez właściwość view na obiekcie camera, poza tym zapisaliśmy również aktualizację pozycji pojazdu. car.update(); camera.view.render();
Kamera stosowana w grach typu action RPG A gdyby tak zrobić grę w stylu action RPG z użyciem biblioteki Away3D? Takie zadanie zdecydowanie wymagałoby dużo pracy, ale nie jest niemożliwe do wykonania. W tym podrozdziale zrobimy jeden mały krok, tworząc i ustawiając kamerę typu HoverCamera3D tak, aby śledziła obiekt i umożliwiała pełny obrót wokół niego. Do realizacji tego zadania stworzymy dwie klasy: podstawową aplikacji rpgExample oraz Skeleton, której obiekt będzie celem naszej kamery. W grach typu
452
Flash i ActionScript. Aplikacje 3D od podstaw
action RPG gracz, klikając w wybrane miejsce w otaczającym go środowisku, może wywołać reakcje postaci, takie jak przemieszczenie, podniesienie przedmiotu, rozmowę lub atak. W naszym przykładzie zajmiemy się pierwszą możliwością, czyli zmianą położenia postaci. Po kliknięciu w wybrany punkt na płaszczyźnie obiekt klasy Skeleton obróci się w tym kierunku i zacznie przemieszczać. Dla zwiększenia realizmu przy każdej zmianie pozycji dodamy standardowo dostępną w modelach MD2 animację biegu. Na rysunku 9.15 przedstawiono wygląd aplikacji po skompilowaniu kodu. Rysunek 9.15.
Zrzut ekranu z przykładu rpgExample
Klasa Skeleton W pierwszej kolejności zajmijmy się klasą Skeleton. Podobnie jak w poprzednich przykładach, jest ona kontenerem dla pobieranego modelu oraz zawiera w sobie potrzebne mechanizmy do przemieszczania obiektu w konkretnym kierunku. Przepisz poniższy kod źródłowy, a następnie omówimy jego istotne fragmenty. package { import import import import import import
away3d.materials.BitmapFileMaterial; away3d.core.base.Mesh; away3d.loaders.data.AnimationData; away3d.containers.ObjectContainer3D; away3d.loaders.Loader3D; away3d.loaders.Md2;
Rozdział 9. Kamery import flash.geom.Point; import flash.geom.Vector3D; import flash.events.Event; public class Skeleton extends ObjectContainer3D { private var modelLoader:Loader3D; private var animationData:AnimationData; private var _destination:Point; private var _speed:Number = 20; private var _bounds:Number = 10; private var model:Mesh; public var isMoving:Boolean = false; public function Skeleton():void { modelLoader = Md2.load('../../resources/models/ md2/hueteotl/TRIS.MD2'); modelLoader.addOnSuccess(modelLoaded); } private function modelLoaded(e:Event):void { var mat:BitmapFileMaterial = new BitmapFileMaterial('../../ resources/models/md2/hueteotl/hueteotl.jpg'); model = modelLoader.handle as Mesh; model.material = mat; model.rotationY = 90; model.y = 60; model.scale(.025); animationData = model.animationLibrary.getAnimation('stand'); animationData.animator.play(); addChild(model); } public function set destination(val:Point):void { _destination = val; lookAt(new Vector3D(_destination.x, y,_destination.y)); if (!isMoving) { isMoving = true; animationData = model.animationLibrary.getAnimation('run'); animationData.animator.play(); } } public function update():void { if (isMoving) { var xdiff = _destination.x - x;
453
454
Flash i ActionScript. Aplikacje 3D od podstaw var zdiff = _destination.y - z; var diff = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2)); var fraction = _speed/diff; x += fraction * xdiff; z += fraction * zdiff; if ((x > (_destination.x - _bounds) && x < (_destination.x + _bounds)) && (z > (_destination.y - _bounds) && z < (_destination.y + _bounds))) { isMoving = false; animationData = model.animationLibrary.getAnimation('stand'); animationData.animator.play(); } } } } }
Klasa Skeleton, podobnie jak klasy śledzonych obiektów w poprzednich przykładach, również bazuje na ObjectContainer3D. W jej wnętrzu umieściliśmy model typu MD2 wraz z teksturą i animacjami, dlatego musieliśmy zaimportować potrzebne składniki do ich poprawnej obsługi: Loader3D, Md2, Mesh, AnimationData oraz BitmapFileMaterial. Mechanizm przemieszczania postaci przelicza wartości otrzymane w jednej z właściwości w postaci punktu klasy Point. Ponieważ w tym przypadku nie uwzględniliśmy zmiany pozycji na osi Y, na przykład wspinaczki lub podbiegania pod górę, zastosowanie klasy Point wystarczy, chociaż jej właściwości mogą być nieco mylące. Wewnątrz klasy Skeleton jeszcze przed konstruktorem zdefiniowaliśmy kilka potrzebnych obiektów oraz zmiennych. W pierwszej kolejności są to obiekty potrzebne do obsługi modelu, obiekt klasy Loader3D nazwaliśmy modelLoader, a z kolei za uruchamianie animacji odpowiada AnimationData. Poza nimi uwzględniliśmy również obiekt klasy Mesh o nazwie model. Był on potrzebny do nakładania tekstur. Do zmiany położenia potrzebowaliśmy kilku zmiennych, takich jak _destination, która określa punkt docelowy w przestrzeni bez uwzględnienia osi Y. Prędkość przemieszczania wyznaczyliśmy w zmiennej o nazwie _speed, która domyślnie równa jest 20, ale im większą wartość ustawimy, tym szybciej postać będzie się poruszała. Zmienna _bounds wyznacza granice akceptacji zmiany położenia obiektu, z kolei isMoving określa, czy obiekt klasy Skeleton jest w trakcie przemieszczania.
Rozdział 9. Kamery
455
W konstruktorze klasy Skeleton stworzyliśmy obiekt modelLoader oraz wywołaliśmy pobieranie wybranego obiektu z zasobów modelu. Dodanie modelu oraz nadanie mu odpowiednich właściwości zapisane zostały w metodzie modelLoaded(), która wykonywana jest z chwilą pobrania modelu. W metodzie modelLoaded() zapisany jest cały mechanizm dodawania pobranego modelu do kontenera klasy Skeleton. Ponieważ jest problem z obsługą tekstur w formacie TGA, musieliśmy załadować własną teksturę przygotowaną w formacie JPG, stosując obiekt klasy BitmapFileMaterial. var mat:BitmapFileMaterial = new BitmapFileMaterial('../../resources/models/md2/hueteotl/hueteotl.jpg');
Nie można nałożyć jej bezpośrednio na pobrany model, dlatego zawartość obiektu modelLoader musieliśmy zapisać jako obiekt klasy Mesh, który zawiera w swoich ustawieniach właściwość material. model = modelLoader.handle as Mesh; model.material = mat;
Po nałożeniu tekstury musieliśmy przestawić model, tak aby pasował on do pozycji i kąta nachylenia całego obiektu klasy Skeleton, oraz odpowiednio go przeskalować, by zajmował mniejszą powierzchnię na płaszczyźnie. model.rotationY = 90; model.y = 60; model.scale(.025);
Po ustawieniu modelu w odpowiedniej pozycji, stosując obiekt animationData, uruchomiliśmy jego standardową animację stand i dodaliśmy do kontenera. animationData = model.animationLibrary.getAnimation('stand'); animationData.animator.play(); addChild(model);
Kolejna metoda w klasie Skeleton odpowiada za ustawienie wartości właściwości _destination, ale nie tylko — przy okazji obraca obiekt w stronę wskazanego punku, korzystając ze standardowej metody lookAt(). Stosując właściwości z obiektu Point w obiekcie Vector3D, musieliśmy dodatkowo podać wartość dla pozycji na osi Y. Ustawiliśmy ją jako pozycję y całego obiektu, dzięki czemu nie obróci się w sposób niepożądany w górę lub w dół. _destination = val; lookAt(new Vector3D(_destination.x, y,_destination.y));
Następnie dodaliśmy instrukcję warunkową sprawdzającą, czy obiekt jest już w trakcie zmiany swojej pozycji. Trzeba przewidzieć, czy użytkownik będzie klikał kilkakrotnie w otoczeniu postaci. Jeżeli poprzednie przejście zostało zakończone
456
Flash i ActionScript. Aplikacje 3D od podstaw
lub w ogóle jest to pierwsze podejście, to należy uruchomić animację run oraz oznaczyć nowy stan obiektu. W przeciwnym razie nie trzeba ponownie wykonywać tych czynności. if (!isMoving) { isMoving = true; animationData = model.animationLibrary.getAnimation('run'); animationData.animator.play(); }
Cały mechanizm zmiany pozycji postaci został umieszczony w metodzie update(), co czyni ją najważniejszą w całej klasie. W pierwszej kolejności sprawdziliśmy, czy ustalono jakąkolwiek wartość dla zmiennej isMoving. Jeżeli jest ona równa true, to można wykonywać pozostałe operacje. Przy zmianie położenia musieliśmy określić różnicę między pozycją domyślną a aktualną. W tym celu zapisaliśmy zmienne xdiff oraz zdiff, które następnie wykorzystaliśmy we wzorze z twierdzenia Pitagorasa wyliczającym długość trasy do przebycia. var xdiff = _destination.x - x; var zdiff = _destination.y - z; var diff = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));
W kolejnych linijkach stworzyliśmy zmienną fraction, która określa przesunięcie postaci z użyciem prędkości i wyliczonej wcześniej długości trasy. Wartość tej zmiennej posłużyła nam do zaktualizowania pozycji postaci na osiach X i Z. var fraction = _speed/diff; x += fraction * xdiff; z += fraction * zdiff;
Na końcu metody update() dodaliśmy instrukcję warunkową, której celem jest sprawdzenie, czy postać dotarła do wyznaczonego punktu. Biorąc pod uwagę sytuacje, w których do konkretnej pozycji postaci może zabraknąć kilku setnych, dodaliśmy granice _bounds wyznaczające powierzchnię tolerancji dla zakończenia akcji. Jest to swego rodzaju test kolizji na wybranym obszarze. Jeżeli postać znajdzie się w jego obrębie, to można potraktować to jako dotarcie do celu, domyślnie _bounds równe jest 10. Gdy warunek ten zostanie spełniony, wartość zmiennej isMoving będzie równa false, a animacja run zostanie zastąpiona standardowym stand. if ((x > (_destination.x - _bounds) && x < (_destination.x + _bounds)) && (z > (_destination.y - _bounds) && z < (_destination.y + _bounds))) { isMoving = false; animationData = model.animationLibrary.getAnimation('stand'); animationData.animator.play(); }
Rozdział 9. Kamery
457
Implementacja kamery HoverCamera3D i klasy Skeleton Mając zapisaną i omówioną klasę Skeleton, stworzymy przykład jej zastosowania i połączenia wraz z kamerą HoverCamera3D. Ponieważ jest to standardowa kamera, nie będziemy musieli tworzyć osobnej klasy dla niej, jej obsługę zapiszemy w rpgExample wraz z ustawieniami środowiska. Efekt, jaki uzyskamy po skompilowaniu kodu, został wcześniej przedstawiony na rysunku 9.15. package { import import import import import import import import import import import import import
away3d.materials.BitmapFileMaterial; away3d.containers.View3D; away3d.primitives.Plane; away3d.events.MouseEvent3D; away3d.cameras.HoverCamera3D; flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.events.KeyboardEvent; flash.ui.Keyboard; flash.geom.Point;
public class rpgExample extends Sprite { private var view:View3D; private var ground:Plane; private var player:Skeleton; private var camera:HoverCamera3D; private private private private
var var var var
_leftDown:Boolean = false; _rightDown:Boolean = false; _forwardDown:Boolean = false; _backwardDown:Boolean = false;
public function rpgExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp); // view = new View3D(); addChild(view); ground = new Plane(); ground.material = new BitmapFileMaterial('../../resources/ bitmaps/skybox/down.jpg');
458
Flash i ActionScript. Aplikacje 3D od podstaw ground.segmentsH = ground.segmentsW = 10; ground.height = 1000; ground.width = 1000; ground.addOnMouseUp(onGroundClick); view.scene.addChild(ground); initPlayer(); initCamera(); onResize(); } private function initCamera():void { camera = new HoverCamera3D(); camera.target = player; camera.panAngle = -45; camera.tiltAngle = 20; camera.minTiltAngle = 0; camera.hover(true); view.camera = camera; } private function initPlayer():void { player = new Skeleton(); player.x = player.z = 0; view.scene.addChild(player); } private function onGroundClick(e:MouseEvent3D):void { player.destination = new Point(e.sceneX, e.sceneZ); } private function onEnterFrame(e:Event):void { player.update(); if (_forwardDown) { camera.tiltAngle += 2; } if (_backwardDown) { camera.tiltAngle -= 2; } if (_leftDown) { camera.panAngle += 2; } if (_rightDown) { camera.panAngle -= 2;
Rozdział 9. Kamery } camera.hover(); view.render(); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: _leftDown = true; break; case Keyboard.RIGHT: _rightDown = true; break; case Keyboard.UP: _forwardDown = true; break; case Keyboard.DOWN: _backwardDown = true; break; default:break; } } private function onKeyUp(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.LEFT: _leftDown = false; break; case Keyboard.RIGHT: _rightDown = false; break; case Keyboard.UP: _forwardDown = false; break; case Keyboard.DOWN: _backwardDown = false; break; default:break; } } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
459
460
Flash i ActionScript. Aplikacje 3D od podstaw
Na samym początku bloku kodu klasy rpgExample zdefiniowaliśmy cztery obiekty tworzące całe środowisko oraz cztery zmienne potrzebne do poruszania kamery wokół postaci. Z obiektów poza standardowym widokiem załączyliśmy obiekt klasy Plane o nazwie ground, postać Skeleton o nazwie player oraz oczywiście obiekt camera — kamerę typu HoverCamera3D. Dodane zmienne często wykorzystywaliśmy w poprzednich przykładach i w tym przypadku spełniają tę samą funkcję, czyli określają, czy dany klawisz został wciśnięty, czy nie. W konstruktorze klasy dołączyliśmy standardowe ustawienia stołu montażowego oraz detektory zdarzeń Event.ENTER_FRAME i Event.RESIZE. Dodatkowo umieściliśmy też detektory dla zdarzeń wciśnięcia i zwolnienia klawiszy klawiatury, które przy wystąpieniu danego warunku wywołują odpowiednio metody onKeyDown() lub onKeyUp(). Poza tym stworzyliśmy powierzchnię, po której porusza się postać, i przypisaliśmy jej detektor zdarzenia MouseEvent3D.MOUSE_UP, który po zwolnieniu przycisku myszki uruchamia metodę onGroundClick(). W kolejnych linijkach konstruktora wywołaliśmy dwie interesujące nas metody: initPlayer() i initCamera(). W metodzie initPlayer() zapisaliśmy proces tworzenia obiektu klasy Skeleton. Są to jedynie trzy linijki kodu, ponieważ wszystkie ważniejsze działania związane z tym obiektem zapisaliśmy we wnętrzu jego klasy. player = new Skeleton(); player.x = player.z = 0; view.scene.addChild(player);
Z kolei w metodzie initCamera() utworzyliśmy obiekt camera i nadaliśmy jego parametrom wartości początkowe. Najważniejszą z nich było wyznaczenie celu, który kamera miała śledzić. camera = new HoverCamera3D(); camera.target = player; camera.panAngle = -45; camera.tiltAngle = 20; camera.minTiltAngle = 0; camera.hover(true); view.camera = camera;
W metodzie onGroundClick(), wywoływanej przy puszczeniu przycisku myszy na powierzchni obiektu ground, zapisaliśmy przypisanie parametrowi destination nowego punktu z wartościami e.sceneX oraz e.sceneZ; wskazują one na punkt, w którym wykryto to zdarzenie. player.destination = new Point(e.sceneX, e.sceneZ);
Rozdział 9. Kamery
461
Mechanizm poruszania kamerą oraz wywoływania zmian pozycji obiektu player umieściliśmy w metodzie uruchamianej przez zdarzenie Event.ENTER_FRAME. W pierwszej kolejności odwołaliśmy się do metody update() na obiekcie player, po czym zapisaliśmy kilka instrukcji warunkowych sprawdzających, czy jest wciśnięty któryś z klawiszy strzałek. Jeżeli któryś z warunków jest spełniony, wykonywana jest odpowiednia zmiana kąta nachylenia poprzez zmianę wartości właściwości tiltAngle i panAngle. Na końcu tej metody musieliśmy oczywiście odwołać się do metody hover() i render(), aby zmiany były widoczne.
Podsumowanie Kamery to obiekty trójwymiarowe, mają one podstawowe metody oraz właściwości ustalające ich położenie w przestrzeni sceny. Wyświetlanie obiektów 3D bazuje na regułach rzutowania na płaszczyznę. Płaszczyzna, na której wyświetlane są obiekty trójwymiarowe, nazywa się rzutnią. Podstawowymi pojęciami związanymi z kamerami w Away3D są: Field of View (fov), zoom, focus, Depth of Field oraz lens. Field of View to pole widzenia od dolnej do górnej krawędzi rzutni, w obrębie którego wyświetlane są obiekty. Wartość Field of View wyliczana jest z wymiarów rzutni oraz jej odległości od kamery. Standardowo rzutnia przyjmuje wymiary okna aplikacji Flash. Parametr zoom skaluje powierzchnię rzutni, co powoduje efekt przybliżenia lub oddalenia. Parametr focus określa odległość kamery od rzutni. Depth of Field to głębia ostrości, czyli odległość, w której rejestrowane obiekty mają wyraźne, ostre kontury. Stosowanie Depth of Field ma na celu odseparowanie obiektów od tego, na którym skupiona jest kamera. Głębię ostrości można stosować na obiektach DepthOfFieldSprite. Aby uruchomić efekt głębi ostrości, należy zastosować klasę DofCashe lub wywołać na obiekcie kamery metodę enableDof().
462
Flash i ActionScript. Aplikacje 3D od podstaw
Podstawowe właściwości stosowane przy regulacji Depth of Field to: doflevels, maxblur oraz aperture. Klasą bazową dla wszystkich soczewek jest klasa AbstractLens. Parametr lens w obiekcie kamery określa zastosowaną soczewkę. Wyróżniamy następujące rodzaje soczewek: ZoomFocusLens, PerspectiveLens, SphericalLens oraz OrthogonalLens. Standardowym rodzajem soczewki jest ZoomFocusLens, a podstawową klasą wszystkich dostępnych w Away3D soczewek jest AbstractLens. ZoomFocusLens i PerspectiveLens działają i przedstawiają scenę podobnie jak ludzkie oko. Soczewka SphericalLens służy do prezentacji zniekształconego obrazu zwanego efektem rybiego oka. Do ujęć izometrycznych stosuje się klasę OrthogonalLens. W tym przypadku ważniejsza od pozycji na scenie jest kolejność, w której obiekt został do niej dodany. W Away3D rozróżniamy następujące rodzaje kamer: Camera3D, TargetCamera3D, HoverCamera3D i SpringCam. Camera3D jest domyślnym rodzajem, do którego można się odwołać z obiektu widoku poprzez właściwość camera. Początkowa pozycja kamery jest w punkcie Vector3D(0, 0, -1000), z kolei standardowym punktem obserwacji jest środek sceny. Kamery TargetCamera3D oraz HoverCamera3D są rozbudowanymi wersjami Camera3D i ułatwiają namierzanie wybranego obiektu oraz krążenie wokół niego. W kamerach TargetCamera3D oraz HoverCamera3D do ustalenia obserwowanego obiektu służy właściwość target. W przypadku kamery TargetCamera3D metody takie jak: moveLeft(), moveRight(), moveUp() czy też moveDown() nie przesuwają jej w standardowy sposób, tylko zmieniają pozycję na orbicie. Do zmiany pozycji HoverCamera3D służą właściwości: panAngle, tiltAngle, steps oraz distance. Granice wyznaczają właściwości: maxPanAngle, minPanAngle, maxTiltAngle oraz minTiltAngle. Stosowanie HoverCamera3D wymaga wywołania metody hover() w miejscu, gdzie odświeżana jest zawartość widoku.
Rozdział 9. Kamery
463
Celem kamery SpringCam jest podążanie za wyznaczonym obiektem. W zależności od pozycji kamery względem śledzonego obiektu może ona być zastosowana na przykład w grach FPP i TPP. Reakcje kamery SpringCam reguluje się poprzez właściwości damping, stiffness oraz mass. Pozycję kamery od wybranego celu wyznacza wartość właściwości positionOffset.
464
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 10. Dźwięk Do tej pory poznaliśmy elementy, które są widoczne na scenie bezpośrednio bądź swoim działaniem ingerują w sposób wyświetlania innych obiektów w otoczeniu. Wszystko to działało na nasz zmysł wzroku. Teraz przyszedł czas na poznanie obiektów działających na zmysł słuchu. W bibliotece Away3D dostępne są dwie istotne klasy pozwalające dodać obiekt dźwięku do przestrzeni trójwymiarowej w dowolnym miejscu. Taki obiekt nie jest słyszany równomiernie, jakby był odtwarzany jako tło. Głośność dźwięku oraz kanał, z którego się wydobywa, uzależnione są od pozycji słuchacza w przestrzeni. Klasa ta ma kilka mankamentów, które omówimy i znajdziemy na nie sposoby. Poza tym czytając ten rozdział, dowiesz się: Czym są klasy Sound3D i SimplePanVolumeDriver. Jak należy z nich korzystać w przestrzeni trójwymiarowej. Na jakich zasadach działa zróżnicowanie dźwięku w zależności od pozycji słuchacza. Czym jest wspomniany słuchacz i co zrobić, aby dźwięk reagował na zmiany jego pozycji. Jak kontrolować obiekty 3D za pomocą dźwięku oraz jakie efekty można uzyskać.
Klasy obsługujące dźwięk Sound3D Klasa Sound3D zlokalizowana jest w pakiecie away3d.audio. Obiekt tej klasy jest źródłem emitowania dźwięku w konkretnym położeniu w przestrzeni trójwymiarowej. Posługiwanie się tym narzędziem jest podobne do stosowania świateł i kamer, których również nie widać bezpośrednio na scenie, widać tylko efekty
466
Flash i ActionScript. Aplikacje 3D od podstaw
ich funkcjonowania. Sposób działania dźwięku w przestrzeni 3D zależy od położenia słuchacza, którym przeważnie jest obiekt kamery. W zależności od pozycji i określonego dystansu obiekt klasy Sound3D kontroluje właściwość volume. Im bardziej słuchacz jest oddalony od źródła dźwięku, tym mniejszy jest poziom głośności volume, i odwrotnie — im źródło dźwięku jest bliżej, tym większa jest wartość właściwości volume. Dla zwiększenia realności pozycja obiektu klasy Sound3D decyduje o tym, z którego głośnika wydobywa się dźwięk. Na rysunku 10.1 przedstawiono kilka wariantów umiejscowienia źródła dźwięku i relacji ze słuchaczem. Rysunek 10.1.
Oddziaływanie pozycji obiektu Sound3D na słyszalność dźwięku
Stosowanie klasy Sound3D jest bardzo proste. W pierwszej kolejności należy stworzyć obiekt tej klasy, podając w argumentach konstruktora potrzebne informacje. Jako pierwszy argument należy podać źródło dźwięku w postaci obiektu klasy Sound. Drugi argument, reference, oznacza obiekt, względem którego będzie wyliczana odległość od źródła dźwięku potrzebna do regulacji głośności. Przeważnie obiektem tym jest zastosowana w projekcie kamera. Jako ostatni argument można ręcznie podać obiekt sterownika do kontroli dźwięku, o którym powiemy w następnym punkcie. Po utworzeniu obiektu należy dodać go do sceny jak zwykły element 3D i uruchomić dźwięk metodą play(). Wszystkie właściwości oraz metody klasy Sound3D wypisano w tabelach 10.1 i 10.2.
Rozdział 10. Dźwięk
467
Tabela 10.1. Właściwości klasy Sound3D
Nazwa
Rodzaj
Wartość domyślna Opis
paused
Boolean
false
Zwraca wartość określającą, czy dźwięk jest wstrzymany, czy nie
playing
Boolean
false
Zwraca wartość określającą, czy dźwięk jest włączony, czy nie
scaleDistance
Number
1
Określa skalę dystansu dla symulacji zmiany poziomu głośności dźwięku w zależności od odległości od słuchacza
volume
Number
1
Określa ogólny poziom głośności będący podstawą niezależnie od odległości
Tabela 10.2. Metody klasy Sound3D
Nazwa
Opis
Sound3D(sound:Sound, reference:Object3D, driver:ISound3DDriver, init:Object)
Konstruktor
pause():void
Wstrzymuje odtwarzanie dźwięku
play():void
Uruchamia bądź wznawia odtwarzanie dźwięku
stop():void
Zatrzymuje odtwarzanie dźwięku
togglePlayPause():void
Przełączanie pomiędzy wstrzymywaniem i wznawianiem odtwarzania wybranego dźwięku
SimplePanVolumeDriver Mimo że klasa Sound3D ma właściwości i metody kontrolujące dźwięk, w rzeczywistości wszystkie operacje wykonywane są na specjalnym sterowniku, który jest automatycznie tworzony wraz z obiektem klasy Sound3D lub osobno przez programistę. Podobna sytuacja występuje w przypadku klasy Sound, której operacje zmiany głośności i kierunku odtwarzania generalnie wykonuje obiekt klasy SoundTransform. W Away3D standardowym i w zasadzie jedynym sterownikiem emitowanego dźwięku jest klasa SimplePanVolumeDriver, dziedzicząca po AbstractSound3DDriver. Obie zlokalizowane są w pakiecie away3d.audio.drivers. Obiekt tej klasy jest dobrym narzędziem, lecz ma kilka niedociągnięć, między innymi nie reaguje bezpośrednio na zmianę pozycji słuchacza, tylko źródła dźwięku. Odczuwalne jest to przy porównaniu zachowań dźwięku podczas poruszania samym obiektem klasy Sound3D i osobno kamery. Gdy ustawimy nową pozycję źródła dźwięku, efekty zmiany głośności będą słyszalne, natomiast gdy obiekt pozostaje w miejscu, a pozycję zmienia kamera, nie słychać jakichkolwiek różnic. Trzeba liczyć, że z czasem zostanie to zaktualizowane, bądź samemu próbować napisać nowe metody.
468
Flash i ActionScript. Aplikacje 3D od podstaw
Ponieważ SimplePanVolumeDriver nie ma właściwości publicznych, w tabeli 10.3 umieszczono te, które dziedziczy po klasie AbstractSound3DDriver. Z kolei w tabeli 10.4 wypisano metody klasy SimplePanVolumeDriver. Tabela 10.3. Właściwości klasy SimplePanVolumeDriver
Nazwa
Rodzaj
Wartość domyślna Opis
mute
Boolean
false
Określa, czy dźwięk jest całkowicie wyciszony, czy nie
scale
Number
1
Określa wartość skali dystansu
sourceSound
Sound
volume
Number
Określa źródło dźwięku w postaci obiektu klasy Sound 1
Określa poziom dźwięku
Tabela 10.4. Metody klasy SimplePanVolumeDriver
Nazwa
Opis
SimplePanVolumeDriver()
Konstruktor
pause():void
Wstrzymuje odtwarzanie dźwięku
play():void
Uruchamia bądź wznawia odtwarzanie dźwięku
stop():void
Zatrzymuje odtwarzanie dźwięku
Przykłady zastosowań Znamy już klasy, które odpowiedzialne są za umieszczanie i kontrolowanie dźwięku w przestrzeni trójwymiarowej. W tym podrozdziale przedstawimy dwa scenariusze używania dźwięku w projektach korzystających z biblioteki Away3D. W pierwszej kolejności zastosujemy klasy Sound3D oraz SimplePanVolumeDriver, aby umieścić w przestrzeni obiekt dźwięku. W drugim przykładzie wykorzystamy zwykłe komponenty ActionScript 3.0, dzięki którym będziemy mogli kontrolować zachowanie obiektów trójwymiarowych.
Dźwięk 3D Głównym celem przykładu w tym punkcie jest pokazanie, jak dźwięk może nadać odpowiedni klimat naszym aplikacjom. Często zdarza się, że całe napięcie w horrorach bądź thrillerach wywołuje ścieżka dźwiękowa, a nie prezentowane sceny. Słuchanie muzyki zdecydowanie bardziej pobudza wyobraźnię.
Rozdział 10. Dźwięk
469
W naszym przykładzie stworzymy mroczny klimat. Na wszystkich obiektach użyjemy odpowiednich tekstur załączonych do pliku projektu. Pierwszy obiekt klasy Sound3D o nazwie snd umieścimy tuż przed drzwiami, za którymi jak widać na rysunku 10.2, znajduje się coś złego. Rysunek 10.2.
Zrzut ekranu z aplikacji Sound3DExample
Zbliżając się do tych drzwi, użytkownik wraz ze zmniejszeniem odległości do obiektu snd będzie coraz wyraźniej słyszał charakterystyczne tło dźwiękowe. Gdy będzie się cofał, dźwięk ten będzie zanikał. Poza tym dodamy również kolejny obiekt klasy Sound3D o nazwie snd2, który umieścimy na początku korytarza. Użytkownik, włączając aplikację, od razu usłyszy dźwięk maniakalnego śmiechu dochodzącego zza jego pleców. Gdy będzie zbliżał się do drzwi, dźwięk ten będzie milknął, ale z kolei włączy się tło dźwiękowe dobiegające zza drzwi. Niezależnie od pozycji w korytarzu któryś z tych dźwięków będzie towarzyszył użytkownikowi. Do całego przykładu zbudujemy dwie klasy, aby oddzielić ważniejsze fragmenty dla tego rozdziału od kodu budującego otoczenie. Omawianie zaczniemy od głównej klasy aplikacji. Aby dźwięk w obiekcie Sound3D powtarzał się, należy zmodyfikować kod klasy SimplePanVolumeDriver, dodając detektor zdarzenia Event.SOUND_COMPLETE do obiektu klasy SoundChannel. Jeżeli korzystasz ze źródeł innych niż przeznaczone specjalnie do tej książki, musisz w klasie SimplePanVolumeDriver wewnątrz metody play() dodać detektor, który będzie uruchamiał na nowo źródło dźwięku.
470
Flash i ActionScript. Aplikacje 3D od podstaw
Klasa Sound3DExample W głównej klasie przykładu Sound3DExample zapiszemy wszystkie potrzebne operacje do stworzenia dźwięków w postaci obiektów klas Sound3D. Istotne fragmenty omówimy zaraz po przepisaniu kodu źródłowego tej klasy: package { import import import import import import import import // import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D; flash.media.Sound; flash.net.URLRequest; away3d.audio.drivers.SimplePanVolumeDriver; away3d.core.clip.FrustumClipping; away3d.events.Object3DEvent; away3d.containers.View3D; away3d.audio.Sound3D;
public class Sound3DExample extends Sprite { private private private private private private private
var var var var var var var
ch:crosshair; view:View3D; fpp:FPP; snd:Sound3D; snd2:Sound3D; spvd:SimplePanVolumeDriver; spvd2:SimplePanVolumeDriver;
public function Sound3DExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); // view = new View3D(); view.clipping = new FrustumClipping( { minZ:10 } ); addChild(view); // ch = new crosshair(); addChild(ch); fpp = new FPP(stage, { freeCamera:true,height: 160 }); onResize();
Rozdział 10. Dźwięk
471
var building:Building = new Building(view.scene); building.buildCorridor(); addSounds(); } private function addSounds():void { /* * Doors To Hell */ spvd = new SimplePanVolumeDriver(); snd = new Sound3D(new Sound(new URLRequest('../../resources/sounds/ scary_background_music.mp3')), view.camera, spvd); snd.position = new Vector3D(0, 26, 1810); view.scene.addChild(snd); spvd.volume = .75; snd.play(); /* * Begin */ spvd2 = new SimplePanVolumeDriver(); snd2 = new Sound3D(new Sound(new URLRequest('../../resources/ sounds/maniac_laugh.mp3')), view.camera, spvd2); snd2.position = new Vector3D(0, 26, -1810); spvd2.volume = .75; view.scene.addChild(snd2); snd2.play(); // view.camera.addOnPositionChange(tracePosition); } private function tracePosition(e:Object3DEvent):void { snd._onSceneTransformChanged(); snd2._onSceneTransformChanged(); } private function onEnterFrame(e:Event):void { fpp.updateCamera(); view.render(); } private function onResize(e:Event = null):void { view.x = ch.x = stage.stageWidth * .5; view.y = ch.y = stage.stageHeight * .5; } } }
472
Flash i ActionScript. Aplikacje 3D od podstaw
Najważniejszymi klasami, których obiekty zastosowaliśmy w klasie Sound3DExample, są: Sound, Sound3D, SimplePanVolumeDriver oraz URLRequest. Wspominaliśmy, że dźwięk z obiektów Sound3D nie reaguje na zmianę pozycji słuchacza, w naszym przypadku kamery, dlatego musieliśmy zapożyczyć sobie jedną z metod klasy Sound3D i odrobinę ją przerobić. W ciele klasy Sound3DExample poza standardowym obiektem widoku zdefiniowaliśmy obiekty snd oraz snd2, które korzystają z osobnych sterowników dźwięku klasy SimplePanVolumeDriver o nazwach spvd i spvd2. Aby umożliwić użytkownikowi zmianę pozycji na scenie, dodaliśmy obiekt wcześniej przygotowanej klasy FPP. W konstruktorze klasy Sound3DExample w pierwszej kolejności ustawiliśmy odpowiednie właściwości dla obiektu klasy Stage. Ponieważ na scenie będzie się znajdowało sporo obiektów, dla szybszej pracy aplikacji ustaliliśmy jakość wyświetlanej zawartości na StageQuality.LOW, poza tym dodaliśmy detektory zdarzeń Event.ENTER_FRAME i Event.RESIZE. Następnie stworzyliśmy widok z jedną dodatkową opcją. Otóż aby zapobiec znikaniu ścian, dodaliśmy nową formę wyświetlania zawartości sceny w postaci obiektu klasy FrustumClipping. Sens oraz sposób działania tej klasy omówimy w rozdziale 12. „Optymalizacja”, poświęconym różnym praktykom poprawiania jakości aplikacji. view = new View3D(); view.clipping = new FrustumClipping( { minZ:10 } ); addChild(view);
Po stworzeniu widoku dodaliśmy celownik ch oraz obiekt klasy FPP, podając w jej konstruktorze obiekt stage oraz wysokość, na jakiej będzie zawieszona kamera. Wszystko po to, aby pozwolić użytkownikowi na przemieszczanie się jak w grach typu FPP. ch = new crosshair(); addChild(ch); fpp = new FPP(stage, { freeCamera:true,height: 160 });
Jak wspomnieliśmy, aby oddzielić część tworzenia korytarza od głównego punktu przykładu, podzieliliśmy kod na dwie klasy: Sound3DExample i Building. W dalszej części konstruktora klasy Sound3DExample stworzyliśmy obiekt building i wywołaliśmy jego metodę addCorridor() dodającą korytarz. var building:Building = new Building(view.scene); building.buildCorridor();
Na końcu konstruktora odwołaliśmy się do metody addSounds(), w której wnętrzu w pierwszej kolejności stworzyliśmy obiekty potrzebne do klimatycznego tła dźwiękowego dochodzącego zza drzwi. Najpierw dodaliśmy obiekt sterownika spvd i nadaliśmy jego właściwości volume nową wartość 0.75, wyciszając tym
Rozdział 10. Dźwięk
473
samym poziom standardowej głośności. Następnie stworzyliśmy obiekt snd klasy Sound3D, przypisując w jego konstruktorze argumentowi sound nowy obiekt klasy Sound ze ścieżką do pliku w formacie MP3. Następnie w drugim argumencie odwołaliśmy się do kamery jako obiektu, względem którego wyliczane będą odległości. Ostatniemu argumentowi driver przypisaliśmy stworzony wcześniej sterownik spvd. Po utworzeniu obiektu snd ustawiliśmy jego pozycję, dodaliśmy go do sceny i włączyliśmy dźwięk metodą play(). spvd = new SimplePanVolumeDriver(); spvd.volume = .75; snd = new Sound3D(new Sound(new URLRequest('../../resources/sounds/ scary_background_music.mp3')), view.camera, spvd); snd.position = new Vector3D(0, 26, 1810); view.scene.addChild(snd); snd.play();
Takie same czynności wykonaliśmy względem drugiego źródła dźwięku w postaci obiektów snd2 i spvd2 umieszczonych na początku korytarza. W metodzie addSounds() poza samym faktem dodania źródeł dźwięku istotne jest również dodanie detektora zdarzenia Object3DEvent.POSITION_CHANGED dla obiektu kamery. view.camera.addOnPositionChange(tracePosition);
W bloku kodu metody tracePosition(), wywoływanej przy każdej zmianie pozycji kamery, odwołaliśmy się w dwóch obiektach klasy Sound3D do dziwnie nazwanej metody _onSceneTransformChanged(). Dziwnie nazwanej, ponieważ pierwotnie była to prywatna metoda uruchamiana przy zmianie pozycji samego obiektu źródła dźwięku. snd._onSceneTransformChanged(); snd2._onSceneTransformChanged();
Jeżeli korzystasz ze źródeł innych niż przeznaczone specjalnie do tej książki, musisz w klasie Sound3D zmienić następującą linijkę: private function _onSceneTransformChanged(ev : Object3DEvent) : void
na public function _onSceneTransformChanged(ev : Object3DEvent = null) : void
Klasa Building W tej klasie na dobrą sprawę nie wykorzystaliśmy rzeczy, których wcześniej byśmy nie omówili. Dlatego przedstawimy ogólnie cały schemat planszy korytarza na rysunku 10.3 i tylko w kilku zdaniach opiszemy, co takiego w niej umieściliśmy.
474
Flash i ActionScript. Aplikacje 3D od podstaw
Najpierw jednak przepisz kod do osobnego pliku o nazwie Building.as i zaimportuj go do projektu przykładu. Rysunek 10.3.
Schemat korytarza w przykładzie Sound3DExample
package { import flash.display.Sprite; import flash.geom.Vector3D; import away3d.core.utils.Cast; import away3d.containers.Scene3D; import away3d.containers.ObjectContainer3D; import away3d.primitives.Plane; import away3d.primitives.data.CubeMaterialsData; import away3d.materials.BitmapMaterial; import away3d.materials.TransformBitmapMaterial; import away3d.primitives.Cube; public class Building extends Sprite { private var scene:Scene3D; public var corridor_segments:Number = 8; private var wall_height:int = 256; private var wall_width:int = 508; private var wall_depth:int = 25; public function Building(scn:Scene3D) { scene = scn; } public function buildCorridor():void {
Rozdział 10. Dźwięk
475
var length:Number = corridor_segments * wall_width; var corridor:ObjectContainer3D = new ObjectContainer3D(); var floor:Plane = new Plane( { width:wall_width, height:length, segmentsW:corridor_segments, segmentsH:corridor_segments * 2, material:new TransformBitmapMaterial(Cast.bitmap ('corridorFloor'), { repeat:true, scaleX:.5, scaleY:254 / length } ), pushback:true } ); var ceiling:Plane = new Plane( { width:wall_width, height:length, y:wall_height, segmentsW:corridor_segments, segmentsH:corridor_segments * 2, bothsides:true, back:new TransformBitmapMaterial(Cast.bitmap('ceiling'), { repeat:true, scaleX:.5, scaleY:254 / length } ), pushback:true } ); var longWallL:Cube = new Cube(); longWallL.depth = length - wall_width; longWallL.width = wall_width; longWallL.height = wall_height; longWallL.position = new Vector3D( -266.5, wall_height * .5, 0); // var longWallR:Cube = new Cube(); longWallR.depth = length - wall_width; longWallR.width = wall_width; longWallR.height = wall_height; longWallR.position = new Vector3D(266.5, wall_height * .5, 0); // var longWallMaterials:CubeMaterialsData = new CubeMaterialsData(); longWallMaterials.front = new BitmapMaterial (Cast.bitmap('corridorWall2')); longWallMaterials.back = new BitmapMaterial (Cast.bitmap('corridorWall2')); longWallMaterials.left = new TransformBitmapMaterial(Cast.bitmap ('corridorWall'), { repeat:true, scaleX:1/corridor_segments } ); longWallMaterials.right = new TransformBitmapMaterial(Cast.bitmap ('corridorWall'), { repeat:true, scaleX:1/corridor_segments} ); // longWallL.cubeMaterials = longWallMaterials; longWallR.cubeMaterials = longWallMaterials; // var doorsToHellMaterials:CubeMaterialsData = new CubeMaterialsData(); doorsToHellMaterials.front = new BitmapMaterial (Cast.bitmap('doorsToHell')); doorsToHellMaterials.back = new BitmapMaterial (Cast.bitmap('doorsToHell'));
476
Flash i ActionScript. Aplikacje 3D od podstaw doorsToHellMaterials.left = new BitmapMaterial (Cast.bitmap('corridorWall2')); doorsToHellMaterials.right = new BitmapMaterial (Cast.bitmap('corridorWall2')); var doorsToHell:Cube = new Cube( { cubeMaterials: doorsToHellMaterials } ); doorsToHell.position = new Vector3D( 0, wall_height * .5, length * .5 + wall_depth * .5); doorsToHell.depth = wall_width; doorsToHell.width = wall_width; doorsToHell.height = wall_height; // var backWallMaterials:CubeMaterialsData = new CubeMaterialsData(); backWallMaterials.front = new BitmapMaterial (Cast.bitmap('corridorWall')); backWallMaterials.back = new BitmapMaterial (Cast.bitmap('corridorWall')); backWallMaterials.left = new BitmapMaterial (Cast.bitmap('corridorWall2')); backWallMaterials.right = new BitmapMaterial (Cast.bitmap('corridorWall2')); var backWall:Cube = new Cube( { cubeMaterials:backWallMaterials } ); backWall.position = new Vector3D( 0, wall_height * .5, -length * .5 - wall_depth * .5); backWall.depth = wall_width; backWall.width = wall_width; backWall.height = wall_height; // corridor.addChild(floor); corridor.addChild(ceiling); corridor.addChild(backWall); corridor.addChild(doorsToHell); corridor.addChild(longWallL); corridor.addChild(longWallR); scene.addChild(corridor); } }
}
W klasie Building umieściliśmy metody odpowiedzialne za budowanie pomieszczeń w naszym przykładzie. Do poprawnego wyświetlenia poszczególnych elementów musieliśmy zaimportować klasy Cube oraz Plane generujące elementy ścian, podłóg i sufitów, a do ich wypełnienia dodaliśmy klasy BitmapMaterial oraz TransformBitmapMaterial. Dodatkowo aby każdemu z boków sześcianów nadać odpowiednią teksturę, posłużyliśmy się CubeMaterialsData, a do samego pobierania źródeł obrazów skorzystaliśmy z klasy Cast.
Rozdział 10. Dźwięk
477
Wewnątrz klasy Building zdefiniowaliśmy jeden obiekt scene, który zastosujemy jako odwołanie do głównej sceny stworzonej w klasie bazowej. Poza tym dodaliśmy zmienną corridor_segments, określającą liczbę segmentów korytarza, oraz zmienne wall_height_wall_width i wall_depth, których wartości określają wymiary korytarza. W metodzie buildCorridor() zapisaliśmy procesy tworzenia korytarza, zaczynając od ustalenia jego długości w postaci zmiennej length, której wartość jest iloczynem wall_width i corridor_segments. var length:Number = corridor_segments * wall_width;
Po ustaleniu długości stworzyliśmy kontener o nazwie corridor, w którym umieszczone zostaną wszystkie elementy składowe. var corridor:ObjectContainer3D = new ObjectContainer3D();
Jako pierwszy element dodaliśmy podłogę w postaci obiektu floor klasy Plane. W jego ustawieniach określiliśmy wymiary, liczbę segmentów oraz pokrycie materiałem typu TransformBitmapMaterial. Dzięki temu materiałowi jednym fragmentem tekstury mogliśmy uzupełnić znacznie większą powierzchnię, nie powodując rozciągnięć. W następnej kolejności dodaliśmy sufit w postaci obiektu o nazwie ceiling. Ustawienia w nim zastosowane są bardzo podobne do tych, których użyliśmy wcześniej przy tworzeniu podłogi. Różnica widoczna jest w pozycji względem osi Y oraz w zastosowanym źródle tekstury. Po utworzeniu podłogi i sufitu zapisaliśmy szereg linijek kodu tworzącego poszczególne ściany korytarza. Do ich prezentacji skorzystaliśmy z obiektów klasy Cube oraz materiałów TransformBitmapMaterial i BitmapMaterial. Żeby nadać poszczególnym ścianom odpowiednią teksturę, skorzystaliśmy z obiektu klasy CubeMaterialsData i jego właściwości: left, right, top, bottom, front i back. Na końcu metody buildCorridor()wszystkie utworzone elementy dodaliśmy do wcześniej stworzonego kontenera o nazwie corridor i całość umieściliśmy na scenie, stosując odwołanie do obiektu scene.
Kontrolowanie obiektów dźwiękiem W aplikacjach 3D dźwięk nie musi być tylko emitowany w konkretnym miejscu w przestrzeni, równie dobrze można się nim posłużyć jako formą sprawującą kontrolę nad obiektami umieszczonymi na scenie. Posługując się pobranymi z dźwięku wartościami, można tworzyć wiele różnych animacji, w których obiekty zmieniają swoje rozmiary, położenie, kolory pokrywających je materiałów, a nawet
478
Flash i ActionScript. Aplikacje 3D od podstaw
modyfikują całą swoją budowę i formę. W aplikacjach dwuwymiarowych pisanych w języku ActionScript 3.0 można spotkać wiele różnych wizualizacji dźwięku, których animacje generują mniej lub bardziej skomplikowane formy. To, jak dźwięk zostanie przedstawiony na ekranie, zależy od pomysłowości programisty. Podstawą oczywiście jest znajomość następujących klas: Sound, SoundTransform, SoundChannel oraz SoundMixer, dzięki którym można odtworzyć dźwięk i pobierać jego wartości do konstruowania wizualizacji. Do kontrolowania obiektu w przestrzeni wykorzystamy dwie z wcześniej wymienionych klas: Sound oraz SoundChannel, a efekt, jaki mamy zamiar uzyskać, przedstawia rysunek 10.4. Rysunek 10.4.
Zrzut ekranu przykładu SoundControlExample
Ta dziwna bryła przedstawiona na rysunku 10.4 to w rzeczywistości obiekt kuli, której struktura została zmodyfikowana przez odpowiednio spreparowane wartości właściwości leftPeak oraz rightPeak, pochodzące z obiektu klasy SoundChannel. Zanim jednak zajmiemy się szczegółami, przepisz następujący kod klasy Sound ControlExample: package { import import import import import import import
away3d.primitives.Sphere; away3d.cameras.HoverCamera3D; away3d.containers.View3D; away3d.materials.BitmapFileMaterial; flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode;
Rozdział 10. Dźwięk
479
import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.net.URLRequest; import flash.ui.Keyboard; import flash.geom.Vector3D; public class SoundControlExample extends Sprite { private var view:View3D; private var music:Sound; private var musicChannel:SoundChannel; private var musicOn:Boolean = true; private var move:Boolean = false; private var camera:HoverCamera3D; private var lpa:Number; private var lta:Number; private var lmx:Number; private var lmy:Number; private var sph:Sphere; private var sphHelper:Sphere; private var len:int; private var power:Number = 10; private var ang:Number = 0; private var mod:Number = .05; private var _v3d:Vector3D = new Vector3D(); private var v3d:Vector3D = new Vector3D(); public function SoundControlExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); // view = new View3D(); addChild(view); sph = new Sphere(); //sph.material = new BitmapFileMaterial('../../resources/bitmaps/ specular.jpg'); sph.segmentsH = 28; sph.segmentsW = 28; sphHelper = new Sphere(); sphHelper.segmentsH = 28; sphHelper.segmentsW = 28; sphHelper.radius = 150;
480
Flash i ActionScript. Aplikacje 3D od podstaw view.scene.addChild(sph); len = sphHelper.geometry.faces.length; /* * Camera */ camera = new HoverCamera3D(); camera.panAngle = 0; camera.tiltAngle = 0; view.camera = camera; /* * Sound */ music = new Sound(new URLRequest ("http://scfire-ntc-aa03.stream.aol.com:80/stream/1025" )); musicChannel = music.play(); // onResize(); } private function onKeyDown(e:KeyboardEvent):void { switch(e.keyCode) { case Keyboard.SPACE: if (musicOn) { musicChannel.stop(); musicOn = false; ang = mod = 0; }else { musicChannel = music.play(); musicOn = true; mod = .05; } break; } } private function onMouseDown(e:MouseEvent):void { lpa = camera.panAngle; lta = camera.tiltAngle; lmx = stage.mouseX; lmy = stage.mouseY; move = true; } private function onMouseUp(e:MouseEvent):void { move = false; }
Rozdział 10. Dźwięk
481
private function onEnterFrame(e:Event):void { ang += mod; var ampL:Number = Math.sin(ang) * musicChannel.leftPeak * 4; var ampR:Number = Math.cos(ang) * musicChannel.rightPeak * 4;
}
}
}
for ( var i:int = 0; i < len; ++i ) { for ( var j:int = 0; j < 3; j++ ) { v3d.x = sphHelper.geometry.faces[i].vertices[j].x; v3d.y = sphHelper.geometry.faces[i].vertices[j].y; v3d.z = sphHelper.geometry.faces[i].vertices[j].z; _v3d.x = v3d.x; _v3d.y = v3d.y; _v3d.z = v3d.z; _v3d.normalize(); _v3d.x *= Math.sin( v3d.x * 0.5 ) * ampL * power; _v3d.y *= Math.sin( v3d.x * 0.5 ) * ampL * power; _v3d.z *= Math.sin( v3d.x * 0.5 ) * ampL * power; v3d.x += _v3d.x; v3d.y += _v3d.y; v3d.z += _v3d.z; // _v3d.x = v3d.x; _v3d.y = v3d.y; _v3d.z = v3d.z; _v3d.normalize(); _v3d.x *= Math.cos( v3d.y * 0.5 ) * ampR * power; _v3d.y *= Math.cos( v3d.y * 0.5 ) * ampR * power; _v3d.z *= Math.cos( v3d.y * 0.5 ) * ampR * power; v3d.x += _v3d.x; v3d.y += _v3d.y; v3d.z += _v3d.z; sph.geometry.faces[i].vertices[j].setValue (v3d.x, v3d.y, v3d.z); } } if(move) { camera.panAngle = .5 * (stage.mouseX - lmx) + lpa; camera.tiltAngle = .5 * (stage.mouseY - lmy) + lta; } camera.hover(); view.render();
private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; }
482
Flash i ActionScript. Aplikacje 3D od podstaw
Podobnie jak w kilku wcześniejszych przykładach, do zwiększenia wydajności aplikacji oraz wyśrodkowania wyświetlanej zawartości użyliśmy obiektów klas StageAlign, StageScaleMode oraz StageQuality. Z biblioteki Away3D poza widokiem View3D zaimportowaliśmy również kamerę typu HoverCamera3D, materiał BitmapFileMaterial oraz klasę Sphere, której obiekt będzie modyfikowany przez odtwarzaną ścieżkę dźwiękową. Jak wcześniej wspomniałem, do obsługi dźwięku użyliśmy klasy Sound oraz SoundChannel. Jako że źródłem dźwięku jest strumień radia internetowego, dodaliśmy również klasę URLRequest niezbędną do uzyskania połączenia HTTP. Poza tym musieliśmy zadbać jeszcze o obsługę zdarzeń wywoływanych przez użytkownika lub przez procesy zachodzące wewnątrz aplikacji. Do tego celu zaimportowaliśmy klasy: Event, MouseEvent, KeyboardEvent oraz Keyboard. Następnym krokiem w tworzeniu tego przykładu było zdefiniowanie potrzebnych obiektów i zmiennych wewnątrz klasy SoundControlExample. Zaczynając od związanych z biblioteką Away3D, określiliśmy widok, kamerę oraz dwie kule o nazwach sph i sphHelper. Kula sph reprezentuje obiekt, którego powierzchnia jest modyfikowana, sphHelper zaś jest obiektem pomocniczym, zawierającym wszystkie potrzebne informacje na temat pierwotnej struktury siatki kul. Aby sphHelper spełnił swoją funkcję, musieliśmy przypisać jego właściwościom wartości z obiektu sph. Zmienna len posłużyła nam do przechowywania wartości liczby obiektów klasy Face wewnątrz kuli sphHelper. Zdefiniowanie tej zmiennej nie jest niezbędne, jedynie odciąży to aplikację od niepotrzebnie powielających się wyliczeń wartości, które nie zmieniają swego stanu. Obiekty v3d oraz _v3d klasy Vector3D, podobnie jak ang, mod oraz power, posłużyły do modyfikowania pozycji każdego z punktów Vertex wewnątrz kuli sph. Do obsługi dźwięku zdefiniowaliśmy obiekt o nazwie music, który jest reprezentantem klasy Sound, oraz obiekt klasy SoundChannel o podobnej nazwie musicChannel. Ponieważ w kodzie zapisana została możliwość włączenia i wyłączenia muzyki, wartość zmiennej musicOn określa, czy radiostacja jest odtwarzana, czy też nie. Pozostałe zmienne — move, lpa, lta, lmx oraz lmy — posłużyły nam do określenia pozycji kamery oraz kursora myszki po zmianie wykonanej przez użytkownika. Rolę każdej z tych zmiennych poznamy dokładniej za chwilę. W konstruktorze tej klasy w pierwszej kolejności nadaliśmy odpowiednie ustawienia obiektowi stage, tak aby wyświetlana grafika była najniższej jakości, wyrównana do lewego górnego rogu i nieskalowana podczas zmiany rozdzielczości. Później dodaliśmy szereg rejestratorów dla zdarzeń: Event.ENTER_FRAME, Event.RESIZE, KeyboardEvent.KEY_DOWN, MouseEvent.MOUSE_DOWN oraz MouseEvent.MOUSE_UP. Zdarzenia te określają odświeżenie zawartości stołu montażowego, zmianę wymiarów okna aplikacji, wciśnięcie klawisza klawiatury do włączenia i wyłączenia muzyki oraz wciśnięcie i zwolnienie przycisku myszy do kontroli pozycji kamery.
Rozdział 10. Dźwięk
483
W następnej kolejności stworzyliśmy widok ze standardowymi ustawieniami oraz dwie kule z identycznymi wartościami: sph i sphHelper. Do zawartości sceny dodaliśmy jednak tylko sph, ponieważ druga kula ma charakter obiektu pomocniczego, z którego potrzebujemy jedynie danych bryły w jej pierwotnej formie, bez konieczności prezentowania jej budowy na scenie. Zwróć uwagę, że zmieniona została wartość właściwości radius w obiekcie pomocniczym, a nie właściwym. Otóż w dalszych częściach kodu pozycje każdego z punktów Vertex pobierane są z obiektu sphHelper, a co za tym idzie — nie ma konieczności osobnego ustalania rozmiarów sph. Jedyne, co musi się zgadzać, to liczba tych punktów, stąd w obu obiektach liczby segmentów są identyczne. view = new View3D(); addChild(view); sph = new Sphere(); sph.segmentsH = 28; sph.segmentsW = 28; sphHelper = new Sphere(); sphHelper.segmentsH = 28; sphHelper.segmentsW = 28; sphHelper.radius = 150; view.scene.addChild(sph);
Aby zobaczyć, jak na modyfikowanym obiekcie prezentuje się dowolny materiał, dodaliśmy również linijkę kodu, w której przypisujemy obiektowi sph teksturę z zewnętrznego pliku graficznego. Ponieważ na początek lepiej widzieć, jak zmienia się położenie punktów Vertex wewnątrz kuli, umieściliśmy ten kod w komentarzu. //sph.material = new BitmapFileMaterial('../../resources/ bitmaps/specular.jpg');
W następnej kolejności wewnątrz konstruktora stworzyliśmy obiekt kamery o nazwie camera, której wartości właściwości panAngle oraz tiltAngle przyrównaliśmy do 0. Dzięki temu kamera ze swojej domyślnej pozycji płynnie przejdzie do nowej, spoglądając na obiekt z boku. Po utworzeniu i ustawieniu kamery w widoku view przypisaliśmy właściwości camera nasz obiekt camera. camera = new HoverCamera3D(); camera.panAngle = 0; camera.tiltAngle = 0; view.camera = camera;
Na końcu konstruktora stworzyliśmy obiekt o nazwie music, którego źródłem dźwięku jest strumień internetowej radiostacji, z kolei do jego obsługi posłużyliśmy się obiektem musicChannel. Po uruchomieniu odtwarzania odwołaliśmy się do metody onResize(), aby ustawić wszystkie elementy w odpowiedniej pozycji. music = new Sound(new URLRequest ('http://scfire-ntc-aa03.stream.aol.com:80/stream/1025' ));
484
Flash i ActionScript. Aplikacje 3D od podstaw musicChannel = music.play(); onResize();
W metodzie onKeyDown() zastosowaliśmy instrukcję warunkową switch, aby wychwycić zdarzenie wciśnięcia klawisza spacji. Wewnątrz bloku kodu tego przypadku zapisaliśmy kolejną instrukcję warunkową, tym razem if, w której sprawdzana jest wartość zmiennej musicOn. Jeżeli muzyka jest włączona, czyli wartość zmiennej musicOn jest równa true, to wywoływany zostaje kod zatrzymujący odtwarzanie, przypisujący właściwości musicOn wartość false, a zmiennym ang i mod wartość 0. musicChannel.stop(); musicOn = false; ang = mod = 0;
W przeciwnym razie, jeżeli dźwięk ma być na nowo odtwarzany, to znów uruchamiany zostaje musicChannel, właściwości musicOn przypisywana jest prawda, a zmiennej mod jej wartość początkowa 0.05. musicChannel = music.play(); musicOn = true; mod = .05;
Użytkownik, wciskając przycisk myszy, uruchamia metodę onMouseDown(), w której zapisywane są wszystkie potrzebne informacje do ustalenia nowej pozycji kamery. Z chwilą wciśnięcia przycisku zmienne lpa oraz lta przybierają wartości pobrane z właściwości panAngle oraz tiltAngle obiektu kamery. Wewnątrz metody onEnterFrame() są one traktowane jako ostatnio pobrane wartości, które posłużą do wyznaczenia nowych. Stąd też zastosowane w nazwach skróty można rozszerzyć jako lastPanAngle oraz lastTiltAngle. Analogicznie zapisujemy wartości pozycji kursora do zmiennych lmx oraz lmy, z kolei przypisanie właściwości move wartości true określa ingerencję użytkownika. lpa = camera.panAngle; lta = camera.tiltAngle; lmx = stage.mouseX; lmy = stage.mouseY; move = true;
W metodzie onMouseUp() przypisaliśmy zmiennej move wartość false, co oznacza, że użytkownik nie wykonuje już akcji przeciągania kamery do nowej pozycji. W kwestii dźwięku i ukształtowania kuli najważniejsze operacje zachodzą wewnątrz metody onEnterFrame(), w której poza odświeżaniem zawartości sceny zapisaliśmy również cały mechanizm zmiany ukształtowania powierzchni kuli. Na wstępie warto wspomnieć, że cały proces deformacji nie opiera się jedynie na wartościach poziomu głośności pobieranych z lewego i prawego kanału obiektu musicChannel. Gdyby tak było, to kula zmieniałaby swoją formę jedynie na zewnątrz, i to bez
Rozdział 10. Dźwięk
485
płynnych przejść. Aby uniknąć takiego zachowania, zastosowaliśmy metody klasy Math wyliczające sinusy oraz cosinusy danego kąta, co spowodowało falowe i płynne przejścia między ujemnymi a dodatnimi wartościami pozycji wierzchołków. Na początku metody onEnterFrame() zapisaliśmy przyrost wartości wcześniej zdefiniowanej zmiennej ang, z której wyliczane są wartości sinus. Zmienna ta potrzebna nam była w kolejnych dwóch linijkach, w których tworzone są dwa obiekty liczbowe: ampL i ampR. Ich wartości są jednymi ze składowych potrzebnych do wyliczenia nowych pozycji punktów Vertex wewnątrz kuli sph. Wzory zastosowane w ampL i ampR są przykładowe, jedno, o czym należy pamiętać, to aby ampL zawierał w swoich obliczeniach właściwość leftPeak, a ampR właściwość rightPeak. Dzięki temu będzie można uzyskać zróżnicowane zniekształcenia. ang += mod; var ampL:Number = Math.sin(ang) * musicChannel.leftPeak * 4; var ampR:Number = Math.cos(ang) * musicChannel.rightPeak * 4;
Dalej w metodzie onEnterFrame() dodaliśmy pętlę for, która wykonuje len iteracji, umożliwiając tym samym dotarcie do każdego z obiektów klasy Face zawartych wewnątrz kul sph i sphHelper. Jak wiemy, każdy z tych obiektów składa się z trzech punktów Vertex. Aby móc operować na każdym z nich, zapisaliśmy kolejną pętlę for, która tym razem wykonuje trzy powtórzenia. W bloku drugiej pętli zapisaliśmy cały schemat zmiany pozycji każdego z punktów, zaczynając od przypisania obiektowi v3d wartości początkowych pochodzących od kuli sphHelper. v3d.x = sphHelper.geometry.faces[i].vertices[j].x; v3d.y = sphHelper.geometry.faces[i].vertices[j].y; v3d.z = sphHelper.geometry.faces[i].vertices[j].z;
Nową pozycję obiektu v3d przypisaliśmy obiektowi _v3d i na nim wywołaliśmy metodę normalize(), która przekształciła go w wektor jednostkowy. _v3d.x = v3d.x; _v3d.y = v3d.y; _v3d.z = v3d.z; _v3d.normalize();
Wektory jednostkowe mają określony kierunek oraz zwrot, a ich długość jest zawsze równa 1. Stosowanie ich w obliczeniach eliminuje wpływ długości wektora na wynik działania, co w naszym przypadku pozwoliło uniknąć chaosu w przemieszczaniu wierzchołków.
W kolejnych trzech linijkach wykonaliśmy działania, które w naszym przypadku opierają się na tych samych wyliczeniach. Aktualne wartości właściwości pozycji wektora _v3d pomnożyliśmy przez wynik przykładowego wzoru: Math.sin( v3d.x * 0.5 ) * ampL * power
486
Flash i ActionScript. Aplikacje 3D od podstaw
Wzór ten składa się z sinusa wyliczonego dla połowy pozycji punktu na osi X, zmodyfikowanej wartości głośności lewego kanału strumienia dźwięku oraz mocy, z jaką pozycja wierzchołka ma być zmieniona. Po wykonaniu tych obliczeń ich wyniki posłużyły nam do zmiany wartości aktualnych właściwości x, y oraz z w obiekcie v3d, kończąc w ten sposób operacje modyfikacji powierzchni kuli względem głośności lewego kanału emitowanego dźwięku. v3d.x += _v3d.x; v3d.y += _v3d.y; v3d.z += _v3d.z;
Przy uwzględnieniu głośności prawego kanału w wizualizacji wykonaliśmy podobne kroki jak przy lewym kanale, zaczynając od przypisania wartości właściwości pozycji z obiektu v3d do _v3d. W następnym kroku, stosując metodę normalize(), zmieniliśmy na wektor jednostkowy obiekt _v3d, po czym zastosowaliśmy nowy wzór do zmiany jego pozycji z uwzględnieniem zmiennej ampR. Math.cos( v3d.y * 0.5 ) * ampR * power;
Wyniki tych obliczeń posłużyły do zmiany wartości właściwości pozycji obiektu v3d, a jego nowo ustawione właściwości x, y i z zastosowaliśmy w metodzie setValue(). Wywoływanie jej w drugiej pętli jest kluczem do zmiany wyglądu powierzchni bryły, ponieważ jej argumenty określają pozycję aktualnie przetwarzanego wierzchołka. Zamiast setValue() można było odwołać się bezpośrednio do właściwości pozycji każdego z punktów w tablicy vertices, ale wymagałoby to dodania kolejnych wierszy kodu. Zastosowanie setValue() zajęło nam jedną linijkę kodu: sph.geometry.faces[i].vertices[j].setValue(v3d.x, v3d.y, v3d.z);
W metodzie onEnterFrame() poza wyliczaniem pozycji wierzchołków kuli zapisaliśmy również instrukcję warunkową sprawdzającą, czy został wciśnięty przycisk myszki. Jeżeli wartość zmiennej move jest prawdą, to w zależności od pozycji kursora w oknie ekranu zmieniamy położenie i kąt nachylenia kamery. if (move) { camera.panAngle = (stage.mouseX - lmx) * .5 + lpa; camera.tiltAngle = (stage.mouseY - lmy) * .5 + lta; } camera.hover();
To tyle w kwestii modyfikowania siatki obiektu przy użyciu strumienia dźwięku. Sposobów na podobne kontrolowanie powierzchni brył jest wiele, wszystko zależy od potrzeb projektu i — jak wspomniałem wcześniej — wyobraźni programisty.
Rozdział 10. Dźwięk
487
Podsumowanie Obiekt klasy Sound3D tworzy niewidoczny, lecz słyszalny obiekt w przestrzeni trójwymiarowej, którego pozycję można zmieniać tak samo jak widzialne elementy. Ważniejsze operacje związane z dźwiękiem wykonywane są na obiektach klasy SimplePanVolumeDriver, w których stosowane są standardowe klasy języka ActionScript 3.0 służące do obsługi dźwięku. O głośności obiektu dźwiękowego decyduje dystans między jego pozycją a słuchaczem. Przeważnie jako słuchacza wykorzystuje się obiekt aktywnej kamery. Bez własnej ingerencji w kod klasy Sound3D zmiana głośności dźwięku występuje jedynie przy zmianie pozycji obiektu klasy Sound3D. Stosując standardowe klasy: Sound, SoundTransform, SoundChannel oraz SoundMixer, można ingerować w strukturę siatki obiektów 3D, ich kolory oraz wymiary. Dodatkowo można uruchamiać różne metody w rytm granych dźwięków. Sterowanie dźwiękiem daje dużo ciekawych możliwości do tworzenia interaktywnych aplikacji, takich jak gry.
488
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 11. Tekst W zwykłych aplikacjach Flash istnieją dwa sposoby na dodanie i wyświetlanie tekstów. Pierwszy z nich, najbardziej podstawowy, polega na umieszczeniu napisu bezpośrednio na stole montażowym i przypisaniu mu odpowiednich ustawień w panelu Properties wybranego elementu. Drugi polega na stworzeniu odpowiednich obiektów TextField i TextFormat w kodzie ActionScript 3.0 i dodaniu ich do zasobów obiektu klasy Stage bądź innego kontenera. W przypadku wyświetlania tekstów w przestrzeni sceny Away3D nie ma takiego prostego wyboru. Wręcz istnieje kilka specjalnych warunków, które muszą zostać spełnione, aby oczekiwany napis pojawił się na ekranie. Najważniejsze jest dobre zaplanowanie, w jakich sytuacjach tekst będzie używany oraz w jakich ilościach umieszczony na scenie. Od tego zależy wybór jednego ze sposobów osadzenia napisu w przestrzeni Away3D; jest ich kilka i każdy wymaga zastosowania innych operacji. Czytając ten rozdział, dowiesz się właśnie: Jakimi sposobami i w jakich sytuacjach umieszczać tekst na scenie Away3D. Jak przygotować czcionkę do wyświetlania jej w przestrzeni. Do czego służy klasa VectorText. Jakich materiałów można użyć do pokrycia napisu. Jakimi technikami sprawić, aby napis był przestrzenny. Czym jest klasa PathAlignModifier i jak się nią posługiwać. Jak zdeformować napis.
490
Flash i ActionScript. Aplikacje 3D od podstaw
Przygotowanie czcionki Zmagania z tekstem zaczniemy od przygotowania czcionki do jednego ze sposobów prezentacji napisów na scenie. Biblioteka Away3D nie ma w swoich zasobach klas pozwalających na bezpośrednie stosowanie zainstalowanych czcionek, należy wcześniej odpowiednio je przygotować. W pierwszej kolejności należy stworzyć nowy dokument Flash, a w nim umieścić pole tekstowe, które pozwala na dynamiczne umieszczanie tekstu z poziomu języka ActionScript 3.0. Dodany obiekt trzeba nazwać, żeby móc się do niego odwoływać; w tym przypadku pole tekstowe nazwano awayText. Na rysunku 11.1 przedstawiono panel Properties, w którym wykonano ten krok. Rysunek 11.1.
Panel właściwości pola tekstowego w Adobe Flash CS5
W kolejnej sekcji panelu Properties należy wybrać wstępne ustawienia dla czcionki pola tekstowego. Powinno się zadeklarować jej rodzaj, styl, kolor i rozmiar, tak aby bez dodatkowych ustawień w kodzie móc uzyskać oczekiwany efekt. Rysunek 11.2 przedstawia wstępne ustawienia dla czcionki pola tekstowego awayText. Rysunek 11.2.
Panel właściwości czcionki w Adobe Flash CS5
Po wybraniu standardowego wyglądu napisu należy zadeklarować znaki, jakie może zawierać. Zwróć uwagę na zaznaczony przycisk Embed… Uruchamia on dodatkowe okno, w którym określa się nazwę dla utworzonej czcionki, a co ważniejsze, wybiera
Rozdział 11. Tekst
491
zestawy dozwolonych znaków. Na rysunku 11.3 przedstawiono ten panel z nowymi ustawieniami czcionki. Rysunek 11.3.
Panel osadzania czcionki w Adobe Flash CS5
Pierwsze pole od góry zawiera nową nazwę dla utworzonego schematu czcionki. Na potrzeby przykładów nazwę tę zmieniono na awayFont. W sekcji Character ranges zaznaczono dozwolone przedziały znakowe, udostępnione standardowo w programie Adobe Flash. W przypadku polskich znaków trzeba byłoby zaznaczyć kolejne zakresy wraz z nieinteresującymi nas znakami. Każdy dodatkowy zestaw powiększa objętość pliku swf, więc zamiast tego w polu Also include these characters dopisano polskie znaki małymi i wielkimi literami. Po wybraniu wszystkich potrzebnych ustawień należy kliknąć przycisk OK i wyeksportować dokument do pliku swf. Tak przygotowany plik z czcionką można zastosować do wyświetlenia napisu w przestrzeni Away3D. Zanim jednak na ekranie wyświetli się napis, trzeba pobrać zawartość pliku swf, stosując na przykład obiekt klasy URLLoader. W następującym kodzie źródłowym przedstawiono sposób pobierania przygotowanego pliku z czcionką. Zwróć uwagę na format, jaki ustawiono we właściwości dataFormat. loader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, onFontLoaded); loader.addEventListener(IOErrorEvent.IO_ERROR, onFontLoadError); loader.load(new URLRequest("font.swf"));
Pobraną zawartość pliku należy odczytać i wydobyć zawartą w nim czcionkę. Do tego celu służy specjalnie przygotowana klasa VectorText, umieszona w pakiecie
492
Flash i ActionScript. Aplikacje 3D od podstaw wumedia.vector.
Jak wskazuje lokalizacja, VectorText nie należy bezpośrednio do biblioteki Away3D, ale jest uwzględniony wraz z innymi dodatkowymi klasami jako integralna część całego silnika.
Klasa VectorText zawiera metodę extractFont(), w której właściwości podaje się zawartość pobranego pliku w postaci ByteArray. Dla przykładu: stosując metodę z wcześniejszego kodu źródłowego, można zastosować zapis loader.data, a całość powinna wyglądać następująco: VectorText.extractFont(loader.data);
Zadaniem metody extractFont() jest wyodrębnienie osadzonych czcionek z pobranej zawartości i dodanie ich do aktualnych zasobów.
Sposoby wyświetlania tekstu Jak wspomniano we wstępie tego rozdziału, Away3D oferuje kilka możliwości wyświetlania tekstu w przestrzeni trójwymiarowej. Różnice między nimi określają sposoby prezentacji oraz zróżnicowanie w zużywaniu zasobów procesora i pamięci. Dwie metody bazują tylko i wyłącznie na zasobach biblioteki Away3D, a pozostałe sięgają po standardowe klasy języka ActionScript 3.0. Wybór odpowiedniej opcji uzależniony jest od celów oraz warunków, w jakich wybrany tekst ma być wyświetlony. Poniżej przedstawiono wszystkie z nich, zaczynając od tych, które bazują jedynie na zasobach silnika Away3D.
Tekst płaski Biblioteka Away3D ma w swoich zasobach klasę o nazwie TextField3D, umieszczoną wraz z innymi klasami generującymi trójwymiarowe obiekty w pakiecie away3d.primitives. Ponieważ w długiej linii TextField3D wywodzi się z klasy Object3D, ma podstawowe właściwości oraz metody umożliwiające osadzenie jego obiektu w przestrzeni sceny Away3D. Do samego stworzenia tekstu niezbędna jest czcionka w postaci pliku swf, z której w pierwszej kolejności należy wydobyć ustawienia zawartego pola tekstowego, posługując się obiektem klasy VectorText. Gdy czcionka jest już przygotowana, można zająć się tworzeniem pola tekstowego. Wykonanie tej czynności należy zacząć od podania w pierwszym argumencie konstruktora nazwy wcześniej przygotowanej czcionki. Następnie, stosując właściwości klasy TextField3D, można określić rozmiar czcionki, sposób wyświetlania tekstu, wyrównanie oraz wymiary samego pola tekstowego.
Rozdział 11. Tekst
493
Klasa TextField3D sama w sobie generuje płaskie pole tekstowe bez jakichkolwiek dodatkowych ścian, tak jak to przedstawiono na rysunku 11.4. Rysunek 11.4.
Pole tekstowe w postaci obiektu TextField3D
Następujący kod źródłowy generuje przybliżony efekt do przedstawionego na rysunku 11.4. package { import import import import import import import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.net.URLLoader; flash.net.URLLoaderDataFormat; flash.net.URLRequest; flash.events.Event; flash.geom.Vector3D; away3d.containers.ObjectContainer3D; away3d.containers.View3D; away3d.primitives.TextField3D; wumedia.vector.VectorText;
public class TextField3DExample extends Sprite { private var view:View3D; private var loader:URLLoader; private var all:ObjectContainer3D = new ObjectContainer3D(); public function TextField3DExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view);
494
Flash i ActionScript. Aplikacje 3D od podstaw loader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, onFontLoaded); loader.load(new URLRequest("font.swf")); onResize(); } private function onFontLoaded(e:Event):void { VectorText.extractFont(loader.data); var textfield:TextField3D = new TextField3D("Arial", { size:100 } ); textfield.text = "Away3D Test"; textfield.bothsides = true; textfield.align = VectorText.CENTER; all.addChild(textfield); view.scene.addChild(all); } private function onEnterFrame(e:Event):void { all.rotationY--; view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
Najważniejszymi klasami, które zaimportowaliśmy w tym przykładzie, są Text Field3D oraz VectorText. Wygenerowany tekst umieściliśmy w kontenerze, dlatego musieliśmy dodać jeszcze klasę ObjectContainer3D. Powodem, dla którego zdecydowaliśmy się przedstawić obiekt w taki sposób, było umożliwienie wykorzystania tego przykładu w następnych podpunktach. W klasie TextField3DExample zdefiniowaliśmy trzy ogólnie dostępne obiekty. Pierwszy z nich to standardowo obiekt widoku View3D. Drugi to obiekt klasy URLLoader o nazwie loader, który odpowiada za pobranie pliku swf zawierającego czcionkę. Z kolei obiekt all jest przechowującym tekst kontenerem, do którego odwoływaliśmy się w metodzie onEnterFrame() w celu wykonania animacji obrotu. W konstruktorze klasy w pierwszej kolejności dodaliśmy standardowe ustawienia dla obiektu klasy Stage, dzięki którym bez względu na rozmiar okna scena widoku będzie zawsze na jego środku.
Rozdział 11. Tekst
495
W następnej kolejności stworzyliśmy i dodaliśmy widok view bez dodatkowych ustawień. Jedynie odwołując się do metody onResize(), umieściliśmy go na środku okna. view = new View3D(); addChild(view); onResize();
Aby pobrać plik z czcionką w postaci binarnej, skorzystaliśmy z obiektu klasy URLLoader oraz URLLoaderDataFormat. Dzięki temu będzie można odczytać programową zawartość pliku font.swf. Profilaktycznie operacje na pobranych danych wykonujemy po ich załadowaniu, dlatego zastosowaliśmy detektor zdarzenia Event.COMPLETE, który uruchamia metodę onFontLoaded() po zakończeniu pobierania. loader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, onFontLoaded); loader.load(new URLRequest("font.swf"));
Z pobranych danych w metodzie onFontLoaded() za pomocą klasy VectorText oraz jej metody extractFont() wyodrębniliśmy czcionkę i dodaliśmy ją do zawartości naszego przykładu. Następnie stworzyliśmy obiekt klasy TextField3D, w którym do wyświetlania tekstu użyliśmy czcionki Arial z przypisanym rozmiarem równym 100. VectorText.extractFont(loader.data); var textfield:TextField3D = new TextField3D("Arial", { size:100 } ); textfield.text = "Away3D";
Aby napis był widoczny z obu stron, właściwości bothsides przypisaliśmy wartość true. Z kolei jeśli przypiszemy nowe wyrównanie pola tekstowego VectorText.CENTER we właściwości align, napis będzie się obracał wokół swojego środka, a nie standardowo wokół lewego górnego rogu. textfield.bothsides = true; textfield.align = VectorText.CENTER;
Na końcu metody onFontLoaded() całość umieściliśmy w obiekcie all i umieściliśmy go na scenie. all.addChild(textfield); view.scene.addChild(all);
Wszystkie dostępne metody oraz właściwości klasy TextField3D zostały uwzględnione w tabelach 11.1 i 11.2.
496
Flash i ActionScript. Aplikacje 3D od podstaw
Tabela 11.1. Właściwości klasy TextField3D
Nazwa
Rodzaj
Wartość
Opis
align
String
"TL"
Określa wyrównanie akapitu pola tekstowego
leading
Number
20
Określa przerwę między wierszami
letterSpacing
Number
0
Określa przerwę między znakami
size
Number
20
Określa rozmiar czcionki
text
String
Określa tekst zawarty w polu tekstowym
width
Number
Określa szerokość pola tekstowego
Tabela 11.2. Metody klasy TextField3D
Nazwa
Opis
TextField3D(font:String, init:Object = null)
Konstruktor
addHitBox(paddingWidth:Number, paddingHeight:Number, debug:Boolean, colorMaterial:ColorMaterial):Face
Dodaje na powierzchni tekstu pole reagujące na zdarzenia ingerencji kursorem
Tekst przestrzenny Wiemy z rozdziału 7. „Praca z obiektami”, że w pakiecie away3d.extrusions znajdują się klasy służące do modyfikowania powierzchni obiektów. W tym samym pakiecie umieszczona jest również klasa TextExtrusion, która na wzór tekstu obiektu TextField3D tworzy ściany o określonej długości. Należy zwrócić tutaj uwagę na to, że TextExtrusion nie ma ścian tylnej oraz przedniej. Schowanie obiektów Text Field3D spowoduje wyświetlenie tekstu w formie, jaką pokazano na rysunku 11.5. Rysunek 11.5.
Obiekt klasy TextExtrusion
Jak pokazano, TextExtrusion tworzy jedynie ściany boczne, dlatego przeważnie łączy się obiekt tej klasy z TextField3D wewnątrz kontenera ObjectContainer3D, aby móc wykonywać działania jednocześnie na obu obiektach. TextExtrusion z własnych właściwości i metod ma jedynie konstruktor, ale ponieważ jest potomną klasy Mesh, można ustawić jej położenie oraz wygląd. Zastosowanie TextExtrusion zaczyna się od podania w pierwszym argumencie źródła tekstu, czyli
Rozdział 11. Tekst
497
obiektu TextField3D, a następnie szeregu właściwości definiujących wyświetlaną formę, położenie oraz zachowanie przy zajściu różnego rodzaju zdarzeń. Do zastosowania TextExtrusion posłużymy się poprzednim kodem źródłowym. W pierwszej kolejności dodaj klasę TextExtrusion, dopisując poniższą linijkę kodu między innymi zaimportowanymi klasami. import away3d.extrusions.TextExtrusion;
W następnej kolejności zmień zawartość metody onFontLoaded() na następującą: VectorText.extractFont(loader.data); var textfield:TextField3D = new TextField3D("Arial", { size:100 } ); textfield.text="Away3D"; textfield.bothsides=true; //textfield.visible=false; var textExtrude:TextExtrusion = new TextExtrusion(textfield, { depth:20, bothsides:true } ); all.addChild(textfield); all.addChild(textExtrude); all.movePivot(textfield.width * .5, textfield.objectHeight * .5, 0); view.scene.addChild(all);
Dopisaliśmy linijkę kodu tworzącą obiekt klasy TextExtrusion, który generuje widoczne z obu stron ściany na wzór obiektu klasy TextField3D. Następnie dodaliśmy textExtrude do kontenera o nazwie all.
Materiały dla tekstu W rozdziale 4. „Materiały” omówiliśmy różnego rodzaju pokrycia, które mogą być stosowane na trójwymiarowych obiektach. TextField3D i TextExtrusion, jako że pochodzą od klasy Object3D, również zalicza się do takich, ale są zarazem wyjątkami, w których nie można korzystać ze wszystkich materiałów dostępnych w bibliotece Away3D. Problem polega na tym, że TextField3D i TextExtrusion mają formę o nieregularnych kształtach, dlatego wszelkie materiały bazujące na współrzędnych UV nie będą z tekstem działały poprawnie. Można spróbować stosować materiały wypełniające kolorem i reagujące na światło, jednak odbicia nie zawsze będą reagowały w oczekiwany sposób. Jedynymi w pełni działającymi z tekstem materiałami są WireframeMaterial, WireColorMaterial oraz ColorMaterial. Na rysunku 11.6 przedstawiono efekt nałożenia różnego rodzaju materiałów na napisy „Away3D”.
498
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 11.6.
Zastosowanie różnych materiałów na obiektach tekstowych
Tekst jako BitmapData Może się zdarzyć, że realizowany projekt w swoich założeniach wymaga wyświetlenia dużej liczby tekstów, na przykład nazw miejscowości. Ponieważ klasa TextField3D generuje złożony obiekt wektorowy, umieszczenie kilkudziesięciu obiektów tej klasy może spowodować spadek wydajności działania aplikacji. Aby tego uniknąć, można skorzystać z dwóch innych sposobów implementacji płaskiego tekstu przestrzeni. Pierwszy z nich opiera się na zasadzie generowania zwykłego pola tekstowego TextField i zamiany go na obiekt BitmapData. Spreparowany w ten sposób tekst można umieścić jako materiał na dowolnym obiekcie trójwymiarowym, takim jak Plane czy Cube. Oczywiście jakość wyświetlanego tekstu przy dużym przybliżeniu będzie znacznie gorsza niż w przypadku obiektu klasy TextField3D, ale zaoszczędzimy w ten sposób nieco zużywanych zasobów procesora. Rysunek 11.7 przedstawia zastosowanie tekstu w postaci obiektu BitmapData na powierzchni elementu Plane. Rysunek 11.7.
Obiekt Plane z polem tekstowym jako obiekt BitmapData
Rozdział 11. Tekst
499
Korzystanie z obiektów BitmapData ma wady, które są zauważalne przy projektowaniu zwykłych dwuwymiarowych aplikacji Flash. Każde wygenerowanie takiego obiektu, w szczególności niewłaściwe, może zajmować sporą część pamięci, tym samym powodując wzrost zużycia zasobów potrzebnych do przetworzenia obrazu. Może to nie być tak odczuwalne w porównaniu ze stosowaniem dużej liczby obiektów TextField3D, ale warto mieć to na uwadze i robić testy wytrzymałościowe aplikacji.
Tekst w obiekcie MovieClip Inny sposób przedstawienia płaskiego tekstu w przestrzeni trójwymiarowej polega na zastosowaniu obiektu klasy MovieClip bądź Sprite. Jeżeli projekt wymaga użycia większej liczby tekstów, które muszą się dobrze prezentować w przybliżonych ujęciach, można stworzyć pole tekstowe TextField, umieścić je w obiekcie Sprite lub MovieClip i traktować jako materiał pokrywający. Idąc dalej tym tropem — korzystając z materiału MovieMaterial, można pokryć powierzchnię dowolnego obiektu 3D i umieścić go w przestrzeni. Wybrany napis powinien być zawsze skierowany w stronę kamery, element Sprite zawierający tekst, można umieścić na scenie poprzez jeden z obiektów z pakietu away3d.sprites. Raz jeszcze wybór najlepszej opcji zależy od wymagań aplikacji oraz przetestowania dostępnych możliwości. Na rysunku 11.8 przedstawiono obiekt Sprite3D z tekstem osadzonym w polu tekstowym TextField wewnątrz elementu Sprite. Rysunek 11.8.
Obiekt Sprite3D z polem tekstowym klasy TextField
Deformowanie tekstu Biblioteka Away3D umożliwia ciekawe wykorzystanie klas generujących ścieżki w połączeniu z tekstem. Istnieje bowiem możliwość deformowania i przemieszczania napisu wzdłuż stworzonych linii prostych bądź krzywych. Do stworzenia
500
Flash i ActionScript. Aplikacje 3D od podstaw
tego efektu poza klasami tekstu niezbędne są Path, PathCommand oraz PathAlignModifier. Dwie pierwsze poznaliśmy już w rozdziale 7. „Praca z obiektami”, z kolei PathAlignModifier krótko przedstawimy tym punkcie. Zadaniem klasy PathAlignModifier jest zmodyfikowanie powierzchni oraz pozycji wybranego obiektu względem przypisanej ścieżki. Aby uzyskać taki efekt, należy wykonać dwie czynności. Po pierwsze, trzeba stworzyć obiekt klasy PathAlignModifier i umieścić we właściwościach jego konstruktora odwołania do obiektu i ścieżki. Po drugie, na utworzonym obiekcie należy wywołać metodę execute() w celu dokonania zmian. Korzystając z kodu źródłowego punktu „Tekst płaski” z poprzedniego podrozdziału, stworzymy prostą symulację obracającego się wokół powierzchni kuli napisu „Away3D przedstawia”. Całość będzie wyglądała tak, jak to przedstawiono na rysunku 11.9. Rysunek 11.9.
Obiekt TextField3D obracający się wokół powierzchni kuli
Aby osiągnąć ten cel, w pierwszej kolejności należy dodać następujące klasy: import import import import import import
away3d.core.base.Mesh; away3d.core.geom.Path; away3d.modifiers.PathAlignModifier; away3d.core.utils.Cast; away3d.primitives.Sphere; away3d.materials.*;
Rozdział 11. Tekst
501
Klasy Path oraz PathAlignModifier posłużą do stworzenia ścieżki i dostosowania do niej powierzchni tekstu. Ponieważ konstruktor klasy PathAlignModifier w pierwszej właściwości przyjmuje jako wartość obiekty klasy Mesh, w naszym przypadku musimy również ją uwzględnić. Kolejne klasy: Cast i Sphere, a także materiały będą potrzebne do stworzenia obiektu kuli i pokrycia jej teksturą earthMap, umieszczoną w zasobach pliku fla. W klasie TextField3DExample należy również zdefiniować obiekt klasy Sphere, ogólnie dostępny wewnątrz całego ciała klasy. Nazwiemy go earth, co będzie wskazywało na jego rolę. private var earth:Sphere;
Natomiast wewnątrz konstruktora stworzymy wspomniany obiekt i nadamy mu odpowiedni rozmiar, liczbę segmentów oraz teksturę pobraną z zasobów projektu, posługując się klasą Cast. Dodatkowo aby uniknąć przekłamań między siatką kuli a napisu, właściwości pushback przypiszemy wartość true. earth = new Sphere( { material:new BitmapMaterial(Cast.bitmap('earthMap')), radius:256, segmentsH:16, segmentsW:16, pushback:true } ); view.scene.addChild(earth);
Ponieważ klasy Path i PathAlignModifier nie działają prawidłowo z obiektem klasy TextExtrusion, wewnątrz metody onFontLoaded() musimy ukryć obiekt textExtrude. Można to wykonać, przypisując właściwości visible tego obiektu wartość false, bądź umieścić w komentarzu linijkę dodającą textExtrude do kontenera all. Na końcu onFontLoaded() odwołamy się do nowej metody o nazwie wrapText(), w której umieścimy cały proces zniekształcania tekstu. W pierwszej kolejności wewnątrz metody wrapText()stworzymy tablicę pathVectors, która będzie zawierała współrzędne punktów ścieżki służącej do deformacji obiektu tekstu. var pathVectors:Array = [ new Vector3D(0, 0, -300), new Vector3D(225, 0, -225), new Vector3D(300, 0, 0), new Vector3D(300, 0, 0), new Vector3D(225, 0, 225), new Vector3D(0, 0, 300), new Vector3D(0, 0, 300), new Vector3D(-225, 0, 255), new Vector3D(-300, 0, 0), new Vector3D(-300, 0, 0), new Vector3D(-225, 0, -225), new Vector3D(0, 0, -300) ];
502
Flash i ActionScript. Aplikacje 3D od podstaw
Ustalone współrzędne będą tworzyły orbitę wokół kuli ziemskiej. Kolejność podanych punktów ma istotne znaczenie w ustaleniu kierunku wykrzywienia obiektu.
W następnej kolejności stworzymy obiekt ścieżki o nazwie path, podając w jej konstruktorze wcześniej utworzoną i wypełnioną współrzędnymi tablicę pathVectors. var path:Path = new Path(pathVectors);
Na końcu metody wrapText() stworzymy obiekt klasy PathAlignModifier, odwołując się do stworzonej ścieżki i napisu. Ponieważ w naszym przypadku jedynie TextField3D działa poprawnie z tą klasą, do obiektu textfield umieszczonego wewnątrz kontenera musimy się odwołać, stosując właściwość children z odpowiednim indeksem. Aby uniknąć błędu wymuszenia innej wartości niż oczekiwana, zastosujmy zapis traktujący tekst jako obiekt klasy Mesh. var pathAlign:PathAlignModifier = new PathAlignModifier(all.children[0] as Mesh, path);
Po utworzeniu obiektu pathAlign skorzystamy z jego metody execute() do wywołania zmian. pathAlign.execute();
W metodzie onFontLoaded() należy zmienić tekst zawarty w obiekcie textfield na „Away3D przedstawia”, a następnie na końcu tej metody wywołać wcześniej opisaną wrapText(). Aby wprawić obiekt earth w ruch przeciwny do animacji napisu „Away3D przedstawia”, w metodzie onEnterFrame() przed wywołaniem na widoku metody render() należy dodać następującą linijkę kodu: earth.rotationY++;
Na koniec tego podrozdziału umieszczono tabele 11.3 oraz 11.4, które zawierają spis właściwości oraz metod klasy PathAlignModifier. Tabela 11.3. Właściwości klasy PathAlignModifier
Nazwa
Rodzaj
Wartość
Opis
arcLengthPrecision
Number
0.01
Określa dokładność generowanych zniekształceń względem krzywych. Im wartość bliższa zera, tym dokładniejsze są zniekształcenia
fast
Boolean
false
Określa szybsze i mniej dokładne generowanie zniekształceń
Rozdział 11. Tekst
503
Tabela 11.3. Właściwości klasy PathAlignModifier (ciąg dalszy)
Nazwa
Rodzaj
Wartość
Opis
offset
Vector3D
Vector3D(0, 0, 0) Określa wektor przesunięcia siatki
obiektu przed wykonaniem zmian pathLength
Number
restrain
Boolean
Zwraca długość używanej ścieżki false
Określa, czy wyrównanie na osi Y ma być zwrócone względem jednego kierunku
Tabela 11.4. Metody klasy PathAlignModifier
Nazwa
Opis
PathAlignModifier(mesh:Mesh, path:Path, init:Object)
Konstruktor
execute():void
Wywołuje wykonanie zmian
updatePath(path:Path, precision:Number):Number
Aktualizuje ścieżkę używaną do deformacji obiektu
Podsumowanie Czcionki do użytku na trójwymiarowych napisach należy przygotować w osobnym pliku swf. Przygotowany plik swf należy pobrać, stosując na przykład obiekt klasy URLLoader z ustawieniami odczytującymi wartości binarne. Z pobranych wartości binarnych pliku swf klasą VectorText wyodrębnia się zapisane czcionki. Do generowania tekstów płaskich służy klasa TextField3D. Do generowania tekstów przestrzennych służy klasa TextExtrusion. Obiekt klasy TextExtrusion nie ma ściany tylnej oraz przedniej, dlatego do stworzenia zamkniętej powierzchni tekstu powinno się umieszczać obiekty TextField3D i TextExtrusion w obiekcie ObjectContainer3D. Jedynymi w pełni działającymi z tekstem materiałami są WireframeMaterial, WireColorMaterial oraz ColorMaterial. Światło i wszelkie materiały bazujące na współrzędnych UV nie działają poprawnie z obiektami klas TextField3D i TextExtrusion, ponieważ są one nieregularnych kształtów.
504
Flash i ActionScript. Aplikacje 3D od podstaw
Jedynymi materiałami w pełni działającymi z obiektami klas TextField3D i TextExtrusion są WireframeMaterial, WireColorMaterial oraz ColorMaterial. W przypadku umieszczenia dużej liczby tekstów na scenie lepiej korzystać z obiektów BitmapData lub MovieClip osadzonych na powierzchni obiektu typu Plane, niż stosować obiekt TextField3D. Poza TextField3D tekst można osadzić w postaci obiektu BitmapData jako źródło tekstury BitmapMaterial. Interaktywny bądź statyczny napis można umieścić w obiekcie Sprite i traktować jako źródło dla materiału MovieMaterial bądź obiektów klas potomnych od Sprite3D. Klasa PathAlignModifier służy do tworzenia zniekształceń dla obiektów trójwymiarowych. Kierunki oraz formę zniekształceń określają punkty Vector3D umieszczone jako tablica w obiekcie Path. Efektu deformacji nie uzyskamy na obiekcie TextExtrusion.
Rozdział 12. Optymalizacja Na koniec pozostało omówienie kilku istotnych kwestii, które pomogą Ci w zaprojektowaniu lepszej i bardziej stabilnej aplikacji. Nie bez powodu omówione w tym rozdziale sposoby były pomijane w poprzednich. Celem tej książki było poprowadzenie Cię przez różne działy omawiające możliwości, jakie daje biblioteka Away3D. Po poznaniu sposobów zwiększania wydajności będziesz mógł rozwinąć poprzednie przykłady, by ćwiczyć i doskonalić swoje umiejętności. W tym rozdziale wskazówki podzielono na kilka podrozdziałów, niektóre z nich zawierają nowe definicje, inne natomiast są spisem uwag. Czytając ten rozdział, dowiesz się: Jakimi sposobami można kontrolować wyświetlanie sceny w widoku. W jakich sytuacjach stosować poszczególne rodzaje przycinania widoku. Jak działają i do czego służą filtry ZDepthFilter oraz MaxPolyFilter. Jak kontrolować promień widocznych obiektów. Jak wykluczać obiekty z procesu odświeżania. Co kryje się pod nazwą LOD. Do czego służy narzędzie Weld. Jak optymalizować użycie tekstur i światła. Jakich praktyk unikać, a jakie stosować.
506
Flash i ActionScript. Aplikacje 3D od podstaw
Statystyki aplikacji Ważne jest, aby każdą aplikację, którą tworzymy, stale kontrolować pod względem wydajności. Takie podejście pozwoli uniknąć ewentualnych drastycznych zmian, gdy dojdziemy do punktu, w którym stwierdzimy, że kod źródłowy nie przechodzi testów wytrzymałościowych w najbardziej stresowych sytuacjach dla programu. W przypadku używania biblioteki silnika 3D dobrym przyzwyczajeniem jest kontrolowanie zużywanych zasobów pamięci, procesora, prędkości wyświetlanych klatek oraz liczby obiektów umieszczonych na scenie. W celu monitorowania tych właściwości można skorzystać z kilku różnych sposobów. My omówimy trzy z nich.
Away3D Project stats Biblioteka Away3D w swoich zasobach ma dwie klasy wyświetlające informacje o aktualnym stanie aplikacji. Pierwsza z nich nosi nazwę Stats i jest zlokalizowana w pakiecie away3d.core.stats. Standardowo z utworzeniem widoku generowany jest również obiekt tej klasy, do którego można się odwołać przez właściwość statsPanel. Stosując go i wywołując metodę displayStats(), można uruchomić okno statystyk. Wcześniej należy jednak metodą addObject() uwzględnić obiekty, które mają brać udział w testach. Innym, łatwiejszym sposobem na uruchomienie tego panelu jest kliknięcie prawym przyciskiem myszki w dowolnym miejscu sceny i wybranie opcji Away3D Project stats. Na rysunku 12.1 pokazano otwarte menu kontekstowe zawierające wspomnianą opcję. Rysunek 12.1.
Menu kontekstowe aplikacji Flash
Po uruchomieniu zaznaczonej opcji na ekranie pojawi się panel z podstawowymi informacjami, co pokazano na rysunku 12.2.
Rozdział 12. Optymalizacja
507
Rysunek 12.2.
Panel statystyk Away3D Project stats
Przedstawione okno zawiera następujące informacje: FPS — aktualna liczba wyświetlanych klatek na sekundę. AFPS — średnia liczba wyświetlanych klatek na sekundę. Max — maksymalna liczba wyświetlanych klatek na sekundę. MS — czas wyrenderowania ostatniej klatki, określony w milisekundach. SWF FR — ustawiona dla aplikacji maksymalna wartość FPS. RAM — zużycie pamięci przez aplikację. MESHES — liczba wyświetlanych obiektów. T ELEMENTS — łączna liczba wielokątów formujących trójwymiarowe obiekty. R ELEMENTS — liczba aktualnie wyświetlanych wielokątów. Dodatkowo na rysunku 12.3 przedstawiono dwie zakładki panelu Away3D Project stats, które informują o ustawieniach i pozycji kamery oraz dodanych do sceny trójwymiarowych obiektach. Rysunek 12.3.
Zakładki panelu statystyk Away3D Project stats
508
Flash i ActionScript. Aplikacje 3D od podstaw
AwayStats Klasa AwayStats zlokalizowana jest w pakiecie away3d.debug, a jej obiekt generuje panel zawierający informacje o aktualnym stanie aplikacji i zużywanych przez nią zasobach. Dzięki temu panelowi jesteśmy w stanie sprawdzić aktualną ilość FPS w stosunku do maksymalnej ustalonej w projekcie aplikacji. Poza tym w formie grafu oraz liczbowo informuje o aktualnym stanie zużywanej pamięci i jej maksymalnym wskaźniku. Obiekt klasy AwayStats może być wyświetlony w dwóch formach, które pokazano na rysunku 12.4. Rysunek 12.4.
Panel AwayStats w dwóch odsłonach
Z lewej strony rysunku 12.4 przedstawiono panel w postaci standardowej, z kolei po prawej w zminimalizowanej. Przejście między trybami można regulować, klikając na przycisk w prawym górnym rogu lub ustawiając odpowiednią wartość właściwości w konstruktorze. Aby móc stosować ten panel, należy zaimportować do projektu klasę AwayStats, stosując zapis: import away3d.debug.AwayStats;
W wybranym miejscu, na przykład w konstruktorze głównej klasy projektu, należy utworzyć obiekt panelu i w pierwszej właściwości podać odwołanie do widoku Away3D. Dodatkowo w kolejnych argumentach konstruktora można określić podstawowe ustawienia wyświetlania i wartości do wykonywania obliczeń. Po zdefiniowaniu wszystkich potrzebnych właściwości utworzony obiekt trzeba dodać do stołu montażowego aplikacji. Na przykład tak, jak to pokazano w następującym kodzie źródłowym: var stats: AwayStats = new AwayStats(view, true); addChild(stats);
W tabelach 12.1 i 12.2 wypisano podstawowe metody klasy AwayStats i argumenty jej konstruktora.
Rozdział 12. Optymalizacja
509
Tabela 12.1. Metody klasy AwayStats
Nazwa
Opis
AwayStats(view3d:View3D, minimized:Boolean, transparent:Boolean, meanDataLength:uint, enableClickToReset:Boolean, enableModifyFrameRate:Boolean)
Konstruktor
registerView(view3d:View3D):void
Metoda dodaje do listy widoków obiekt klasy View3D, aby ten był brany pod uwagę przy wykonywaniu obliczeń, między innymi wszystkich wyświetlanych wielokątów
unregisterView(view3d:View3D):void
Metoda usuwa wskazany obiekt klasy View3D z listy widoków, z których pobierane są dane do wykonywania obliczeń statystycznych
Tabela 12.2. Argumenty konstruktora AwayStats
Nazwa
Rodzaj
Wartość domyślna
Opis
view3d
View3D
minimized
Boolean
false
Określa, czy panel ma być w postaci zminimalizowanego okna
transparent
Boolean
false
Określa, czy panel ma być przezroczysty
meanDataLength
uint
0
Liczba klatek, na podstawie których obliczana jest średnia prędkość. Domyślna wartość oznacza użycie wszystkich klatek
enableClickToReset
Boolean
true
Określa, czy dane będą resetowane po kliknięciu myszką w panel
enableModifyFrameRate Boolean
true
Określa, czy kliknięcie na grafie będzie modyfikowało aktualną wartość ilości FPS
Odwołanie do widoku Away3D
Hi-ReS-Stats Na koniec tego podrozdziału pokażemy inną zewnętrzną bibliotekę do wyświetlania statystyk włączonej aplikacji. Stworzona przez programistów Mr.doob i Theo jest darmową klasą, którą można pobrać ze strony: https://github.com/mrdoob/ Hi-ReS-Stats. Obiekt tej klasy generuje w lewym górnym rogu okna mały prostokąt z informacjami o uruchomionej aplikacji. W postaci tekstowej i grafu przedstawia aktualną wartość FPS, czasu potrzebnego do wyświetlenia klatki, aktualny oraz maksymalny stan zużycia pamięci. Wygląd tego niewielkiego panelu przedstawiono na rysunku 12.5.
510
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 12.5.
Panel Hi-ReS-Stats
Aby móc korzystać z tej klasy, pobraną bibliotekę umieść w katalogu, gdzie znajdują się inne biblioteki używane w aplikacji. Następnie zaimportuj plik Stats.as, dodając poniższą linijkę kodu do Twojego programu: import net.hires.debug.Stats;
W kolejnym kroku zdefiniuj obiekt tej klasy i dodaj go bezpośrednio do zawartości stołu montażowego, na przykład tak: stats:Stats = new Stats(); addChild(stats);
Wyświetlanie zawartości W pierwszej kolejności przyjrzymy się technikom oraz nowym terminom, które pomogą Ci w dobraniu odpowiednich ustawień do wyświetlania zawartości sceny. Musisz wiedzieć, że biblioteka Away3D udostępnia wiele możliwości optymalizacji procesu renderowania widoku, lecz nie każda w połączeniu z inną da oczekiwany efekt. Dobranie odpowiednich ustawień często będzie wymagało wykonania kilku testów, ale jeśli poznasz umieszczone w tym podrozdziale informacje, pomogą Ci skrócić czas potrzebny na dostosowanie właściwości widoku i sceny.
Sposoby przycinania widoku Biblioteka Away3D ma w swoich zasobach kilka klas przycinających widok. Zanim je omówimy, ustalimy, na czym w ogóle to zjawisko polega. Jak wiesz z poprzednich rozdziałów, widok jest płaszczyzną, która w dwuwymiarowy sposób przedstawia zawartość sceny. W tym celu korzysta z klas umieszczonych w pakiecie away3d.core.clip, które określają sposób wyświetlania obiektów znajdujących się najbliżej krawędzi rzutni bądź częściowo poza nią.
Rozdział 12. Optymalizacja
511
Każdą z klas omówimy w poniższych punktach. Zaczniemy od najbardziej podstawowej, jednak zanim to nastąpi, przepisz poniższy kod źródłowy będący podstawą do wykonywania porównań i wyciągania wniosków. package { import import import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; away3d.core.clip.*; away3d.containers.View3D; away3d.materials.WireColorMaterial; away3d.primitives.Plane;
public class ClippingExample extends Sprite { private var view:View3D; private var fpp:FPP; public function ClippingExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); view.camera.z = 0; addChild(view); fpp = new FPP(stage, { freeCamera:true,height:0 }); var plane:Plane = new Plane( { width:512, height:512, depth:512, segmentsW:10, segmentsH:10, material:new WireColorMaterial(0xFF0000, { wireColor:0x000000 } ) }); view.scene.addChild(plane); onResize(); } private function onEnterFrame(e:Event):void {
512
Flash i ActionScript. Aplikacje 3D od podstaw fpp.updateCamera(); view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
Przedstawiony kod źródłowy generuje płaszczyznę o ustalonych wymiarach, której przypisaliśmy większą liczbę segmentów oraz materiał wyświetlający linie siatki. Dzięki temu różnice w działaniu stosowanych klas przycinających będą lepiej widoczne.
Clipping Obiekt klasy Clipping mimo swojej nazwy jest klasą, która nie przycina elementów wychodzących poza zakres widoku, tylko je usuwa. W swoich standardowych ustawieniach nie wykonuje ona praktycznych modyfikacji poprawiających działanie aplikacji. Klasa Clipping traktowana jest bardziej jako klasa bazowa dla kolejnych umieszczonych w pakiecie. W tabelach 12.3 i 12.4 wypisano właściwości oraz metody klasy Clipping. Tabela 12.3. Właściwości klasy Clipping
Nazwa
Rodzaj
Wartość domyślna Opis
maxX
Number
Infinity
Maksymalna wartość wyświetlania na osi X
maxY
Number
Infinity
Maksymalna wartość wyświetlania na osi Y
maxZ
Number
Infinity
Maksymalna wartość wyświetlania na osi Z
minX
Number
-Infinity
Minimalna wartość wyświetlania na osi X
minY
Number
-Infinity
Minimalna wartość wyświetlania na osi Y
minZ
Number
-Infinity
Minimalna wartość wyświetlania na osi Z
objectCulling
Boolean
false
Określa, czy ma być stosowane wykluczanie z procesu renderowania obiektów, które znajdują się poza powierzchnią rzutni
view
View3D
Obiekt widoku
Rozdział 12. Optymalizacja
513
Tabela 12.4. Metody klasy Clipping
Nazwa
Opis
Clipping(init:Object = null)
Konstruktor
addOnClippingUpdate (listener:Function):void
Metoda dodająca detektor zdarzenia clippingUpdated
addOnScreenUpdate (listener:Function):void
Metoda dodająca detektor zdarzenia screenUpdated
checkPrimitive(renderer:Renderer, priIndex:uint):Boolean
Metoda zwraca wartość typu Boolean, która określa, czy wybrany obiekt jest przycięty, czy nie
rect(minX:Number, minY:Number, maxX:Number, maxY:Number):Boolean
Metoda zwraca wartość typu Boolean, która określa, czy powierzchnia o ustalonych właściwościach przycina jakikolwiek obiekt
removeOnClippingUpdate (listener:Function):void
Metoda usuwa detektor dla zdarzenia clippingUpdated
removeOnScreenUpdate (listener:Function):void
Metoda usuwa detektor dla zdarzenia screenUpdated
RectangleClipping Standardowym, tworzonym wraz z widokiem obiektem kontrolującym przycinanie widoku jest obiekt klasy RectangleClipping, która w niewielkim stopniu modyfikuje działanie klasy macierzystej. W przypadku RectangleClipping metoda check Primitive() zwraca odpowiednią wartość dla sprawdzanego obiektu, stosując instrukcje warunkowe i właściwości granic wyświetlania sceny. Poza tym wszelkie wierzchołki obiektu znajdujące się poza ustalonym obszarem nie podlegają odświeżeniu, co może powodować znikanie poszczególnych kawałków prezentowanego obiektu. Klasa RectangleClipping poza konstruktorem nie ma własnych nienadpisywanych metod, a właściwości ma jedynie dziedziczone po klasie Clipping. Dlatego w tym punkcie nie zostały umieszczone tabele z informacjami o właściwościach i metodach tej klasy. Są one wypisane w tabelach 12.3 i 12.4. W przykładach umieszczonych w tej książce przeważnie korzystaliśmy z obiektu klasy RectangleClipping. Mimo to warto pokazać efekt jego działania. Zwróć uwagę na brakujący fragment podłogi przedstawionej na rysunku 12.6. Ponieważ obiekt klasy RectangleClipping nie ma metod wypełniających artefakty, pola znajdujące się blisko krawędzi widoku nie są wyświetlane.
514
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 12.6.
Działanie obiektu klasy RectangleClipping
NearfieldClipping Klasa NearfieldClipping jest kolejną klasą stosowaną do przycinania wyświetlanych obiektów. W przeciwieństwie do przedstawionych wcześniej klas NearfieldClipping ma dodatkowe algorytmy, które wypełniają luki powstałe na skutek zbliżenia się obiektu do krawędzi widoku. Łatanie powstałych artefaktów polega na dzieleniu pustej powierzchni na mniejsze fragmenty, aktualizowaniu ich współrzędnych i uzupełnianiu odpowiednim materiałem. Efekt zastosowania tej klasy przedstawiono na rysunku 12.7. Zwróć uwagę na dodatkową linię wyświetloną w lewym dolnym rogu okna. Dzięki niej fragment podłogi nie znika jak na rysunku 12.6. Klasa NearfieldClipping — tak samo jak RectangleClipping — poza konstruktorem nie ma własnych metod i właściwości.
FrustumClipping Ostatnią klasą odpowiedzialną za przycinanie obiektów wychodzących poza zakres wyświetlanego obszaru jest klasa FrustumClipping. Na pierwszy rzut oka, bez zastosowania materiału typu WireColorMaterial, używanie FrustumClipping niczym się nie różni od NearfieldClipping. To tylko pozory ponieważ — w przeciwieństwie do
Rozdział 12. Optymalizacja
515
Rysunek 12.7.
Działanie obiektu klasy NearfieldClipping
NearfieldClipping
— działa na wszystkich obiektach stykających się z krawędziami widoku. Gdy używamy materiału z zaznaczonymi liniami siatki, od razu widać zwiększoną liczbę dodatkowych linii dzielących segmenty powierzchni obiektu. Porównaj rysunek 12.7 z rysunkiem 12.8. Przedstawiają one ten sam obiekt z niewielką różnicą kąta nachylenia kamery. Zwróć uwagę na liczbę linii siatki płaszczyzny.
Rysunek 12.8.
Działanie obiektu klasy FrustumClipping
516
Flash i ActionScript. Aplikacje 3D od podstaw
Stosowanie filtrów Jednym ze sposobów przyspieszenia działania aplikacji w trójwymiarowych przestrzeniach jest stosowanie odpowiednich filtrów. W bibliotece Away3D uwzględniono kilka mniej lub bardziej widowiskowych filtrów umożliwiających kontrolę wyświetlania obiektów w zależności od ich odległości od obserwatora, czyli kamery. W tym podrozdziale poznamy dwa takie filtry oraz jeden, który całkowicie ogranicza liczbę wyświetlanych obiektów na scenie. Zanim jednak zaczniemy ich omawianie, przepisz poniższy kod źródłowy, który będziemy stopniowo uzupełniali. package { import import import import import import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D; away3d.containers.View3D; away3d.core.filter.*; away3d.core.render.*; away3d.materials.*; away3d.containers.ObjectContainer3D; away3d.primitives.Sphere;
public class FiltersExample extends Sprite { private var view:View3D; private var sphs:Array = []; public function FiltersExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); initFilter(); fillScene(); onResize(); } private function initFilter():void { } private function fillScene():void {
Rozdział 12. Optymalizacja
517
for (var i:Number = 0; i < 30; i++) { var sph:Sphere = new Sphere( { radius:50, segmentsW:16, segmentsH:16} ); sph.x = -stage.stageWidth * .5 + (Math.random() * stage.stageWidth); sph.y = -stage.stageHeight * .5 + (Math.random() * stage.stageHeight); sph.z = -(Math.random() * 8000); view.scene.addChild(sph); sphs.push(sph); } } private function onEnterFrame(e:Event):void { for each (var sph:Sphere in sphs) { sph.z += 60; if (sph.z > 5000) sph.z = -(Math.random() * 8000); } view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
W efekcie skompilowania i uruchomienia tego kodu na ekranie zobaczymy obiekty kul ułożone w czterech kolumnach.
FogFilter FogFilter to taki filtr, który poza praktycznymi właściwościami może nadać ciekawy
charakter programowi. Jak pierwszy człon nazwy wskazuje, zadaniem tego filtra jest nałożenie efektu mgły na widoczną część sceny. Taka mgła nie pokrywa całej przestrzeni, tylko określony granicami obszar. Taki efekt uzyskujemy, ustawiając przodem do kamery określoną liczbę oddalonych od siebie prostokątnych płaszczyzn. Wypełnione ustalonym kolorem przybierają większe wartości właściwości alpha wraz ze wzrostem odległości od kamery. Zastosowanie tego efektu ma również praktyczny aspekt — otóż elementy znajdujące się poza dalszą granicą mgły nie są wyświetlane, co owocuje zmniejszeniem liczby potrzebnych procesów obliczeniowych.
518
Flash i ActionScript. Aplikacje 3D od podstaw
Aby uruchomić ten filtr, wpisz w metodzie initFilter() następujący kod: var fog:FogFilter = new FogFilter( { minZ:1000, maxZ:3000, subdivisions:20, material:new ColorMaterial(0xFFFFFF) }); view.renderer = new BasicRenderer(fog);
Każda z użytych właściwości w konstruktorze klasy FogFilter spełnia konkretną funcję. W powyższym kodzie zastosowanie minZ oraz maxZ określa granice działania mgły, subdivisions liczbę płaszczyzn, z których się składa, a material definiuje jej pokrycie. Po ustaleniu wyglądu mgły należy ją dodać do obiektu renderera, w tym przypadku jest to BasicRenderer. W ostatnim kroku, stosując właściwość renderer widoku Away3D, należy przypisać stworzony obiekt renderera. Aby uzyskać efekt przedstawiony na rysunku 12.9, w konstruktorze klasy do tego podrozdziału wpisz kod omówionego sposobu implementacji. Rysunek 12.9.
Efekt użycia filtra FogFilter
W tabelach 12.5 i 12.6 wypisano właściwości oraz metody klasy FogFilter.
Rozdział 12. Optymalizacja
519
Tabela 12.5. Właściwości klasy FogFilter
Nazwa
Rodzaj
Wartość domyślna
Opis
material
ColorMaterial
materials
Array
maxZ
Number
5000
Określa maksymalną odległość mgły na osi Z
minZ
Number
1000
Określa początek mgły na osi Z
subdivisions
Number
20
Określa liczbę warstw użytych wewnątrz mgły
Określa kolor mgły w postaci materiału ColorMaterial
Określa tablicę materiałów, nadpisując tym samym wartość właściwości material i subdivisions
Tabela 12.6. Metody klasy FogFilter
Nazwa
Opis
FogFilter(init:Object = null)
Konstruktor
filter(renderer:Renderer):void
Uruchamia filtr
updateMaterialColor(color:uint):void
Zmienia kolor mgły w trakcie jej wyświetlania
ZDepthFilter Filtr ZDepthFilter działa podobnie jak wcześniej przedstawiony FogFilter, ale ten nie generuje efektu mgły. Celem tej klasy jest ograniczenie widoczności do określonej w argumencie konstruktora odległości od kamery. Właściwość, która określa tę długość, to maxZ. Na tej odległości znajduje się jakby niewidzialna ściana, która zakrywa fragmenty obiektów wychodzących poza ustalony zakres. Implementacja tego filtra jest bardzo prosta, wystarczy stworzyć obiekt klasy i podać jedną wartość określającą dystans. W kolejnym kroku należy umieścić stworzony filtr w argumencie konstruktora obiektu renderera i przypisać go do widoku. Aby uruchomić filtr ZDepthFilter w przykładzie, należy zamienić kod zawarty w metodzie initFilter() na następujący: ZDepthFilter
var zdepth:ZDepthFilter = new ZDepthFilter(1000); view = new View3D(); view.renderer = new BasicRenderer(zdepth);
Po skompilowaniu i uruchomieniu kodu uzyskasz efekt, który przedstawiono na rysunku 12.10.
520
Flash i ActionScript. Aplikacje 3D od podstaw
Rysunek 12.10.
Efekt użycia filtra ZDepthFilter
MaxPolyFilter Ostatnim omawianym w tej książce filtrem jest MaxPolyFilter. Jego działanie również powoduje ograniczenie w wyświetlaniu elementów, ale opiera się ono na nieco innych zasadach. W przeciwieństwie do pozostałych filtrów ten nie ogranicza się do ustalonego dystansu, tylko do maksymalnej liczby wyświetlanych wielokątów obiektów umieszczonych na scenie. Nie oznacza to jednak, że wraz z uruchomieniem aplikacji odgórnie ustalane jest, które wielokąty są wyświetlane, a które nie. Wybór uzależniony jest od pozycji kamery oraz współrzędnych wielokątów. Pierwszeństwo w wyświetlaniu mają te, które znajdują się najbliżej kamery. Aby stosować ten filtr, należy stworzyć obiekt klasy MaxPolyFilter, w jej konstruktorze podając wartość liczbową dla właściwości maxP, ograniczającą liczbę wyświetlanych wielokątów. W następnej kolejności, podobnie jak w przypadku poprzednich filtrów, stworzony obiekt klasy MaxPolyFilter należy umieścić jako argument, tworząc obiekt renderera, i przypisać go do widoku. Aby uruchomić ten filtr, wpisz w metodzie initFilter() następujący kod: var maxpoly:MaxPolyFilter = new MaxPolyFilter(500); view.renderer = new BasicRenderer(maxpoly);
Efekt działania tego filtra przedstawiono na rysunku 12.11.
Rozdział 12. Optymalizacja
521
Rysunek 12.11.
Efekt użycia filtra MaxPolyFilter
Ogólne wskazówki Stosuj właściwości visible lub ownCanvas, by wykluczyć niepotrzebnie rysowane obiekty. Jest to szczególnie przydatne, gdy obiekt nie zmienia swojego stanu, a jest uwzględniony w procesie odświeżania. Stosuj różne wartości stałych klasy StageQuality. Dobrą praktyką jest ustawienie jakości na poziomie StageQuality.LOW w sytuacjach, gdy na scenie występują zmiany bądź kamera zmienia swoje położenie. Po zakończeniu modyfikacji można powrócić do wartości StageQuality.MEDIUM bądź StageQuality.HIGH. W miarę możliwości stosuj małe wymiary okna aplikacji. Kontroluj zachowanie aplikacji, w której używasz rendererów: Renderer.CORRECT_Z_ORDER lub Renderer.INTERSECTING_OBJECTS. Poprawiają one kolejność wyświetlanych wierzchołków, ale zarazem są obciążające dla procesora. Unikaj umieszczania odwołania do metody render() w metodach zdarzenia Event.ENTER_FRAME. Zamiast tego wywołuj je w sytuacjach wykonywania zmian, na przykład przy użyciu klasy TweenMax. Jeżeli w swojej aplikacji nie możesz zastosować takiego rozwiązania, kontroluj dodawanie i usuwanie detektora zdarzenia Event.ENTER_FRAME.
522
Flash i ActionScript. Aplikacje 3D od podstaw
Obiekty Stosowanie obiektów LOD W złożonych aplikacjach, takich jak gry komputerowe, które bazują na grafice trójwymiarowej, częstą praktyką poza ograniczaniem widoczności jest używanie obiektów LOD (Level of Detail — poziom szczegółowości). Idea ich stosowania polega na tworzeniu kontenerów podobnych do ObjectContainer3D i umieszczaniu w ich wnętrzu ustalonej liczby wersji tego samego obiektu. Każda z wersji różni się od siebie szczegółowością siatki oraz nałożonych tekstur. W zależności od odległości obiektu LOD od kamery wyświetlana jest odpowiednia wersja. Im dalej obiekt ten znajduje się od obserwatora, tym mniej szczegółów jest wyświetlanych, i na odwrót — im bardziej obiekt jest widoczny, tym więcej jego detali powinno być uwzględnionych. W bibliotece Away3D obiekty LOD generuje klasa o nazwie LODObject, zlokalizowana w pakiecie away3d.containers. Używanie tego typu obiektów polega na utworzeniu ich w określonej liczbie, która odpowiada etapom przemiany wybranego obiektu. W każdym z tych obiektów — stosując metodę addChild() bądź bezpośrednio w argumencie konstruktora — dodaje się odpowiednią wersję obiektu. W kolejnym kroku trzeba ustalić przestrzeń, w której ma występować obiekt wybranego poziomu. W tym celu stosuje się właściwości minp oraz maxp, które określają tę granicę. Podawane w tych właściwościach wartości nie wyznaczają odległości w pikselach na osi Z, tylko określają skalę rzutu perspektywicznego obiektu. Właściwość minp wyznacza mniejszą skalę, co oznacza, że obiekt jest bardziej oddalony od kamery, z kolei maxp wyznacza większą skalę i tym samym bliższą pozycję względem obserwatora. Na rysunku 12.12 przedstawiono cztery wersje kuli wyświetlane w określonych granicach skali, czyli odległości od kamery. Obiekt lod0, znajdujący się po lewej stronie, reprezentuje wersję najbardziej oddaloną od kamery, stąd jego najmniejszy rozmiar. Z kolei lod3 przedstawia kulę w pozycji najbliższej kamerze. Przedstawiony rysunek to w zasadzie cztery różne ujęcia przykładu zastosowania obiektu LODObject. Aby uzyskać taki efekt, przepisz i skompiluj następujący kod źródłowy: package { import away3d.materials.WireColorMaterial; import flash.display.StageAlign; import flash.display.StageQuality;
Rozdział 12. Optymalizacja
Rysunek 12.12. Różne poziomy obiektów LODObject import import import import
flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D;
import import import import
away3d.containers.View3D; away3d.containers.LODObject; away3d.containers.ObjectContainer3D; away3d.primitives.Sphere;
public class LODObjectExample extends Sprite { private var view:View3D; private var reverse:Boolean = false; private var obj:ObjectContainer3D; public function LODObjectExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; addEventListener(Event.ENTER_FRAME, onEnterFrame); stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); // obj = new ObjectContainer3D(); view.scene.addChild(obj); var lod0:LODObject = new LODObject(new Sphere( { radius:50, segmentsW:4, segmentsH:4, material:new WireColorMaterial (0xFF0000, { wireColor:0x000000 } ) } )); lod0.minp = 0; lod0.maxp = .5; obj.addChild(lod0);
523
524
Flash i ActionScript. Aplikacje 3D od podstaw // var lod1:LODObject = new LODObject(new Sphere( { radius:50, segmentsW:8, segmentsH:8, material:new WireColorMaterial (0xFF0000, { wireColor:0x000000 } ) } )); lod1.minp = .5; lod1.maxp = 1; obj.addChild(lod1); // var lod2:LODObject = new LODObject(new Sphere( { radius:50, segmentsW:12, segmentsH:12, material:new WireColorMaterial (0xFF0000, { wireColor:0xE50000 } ) } )); lod2.minp = 1; lod2.maxp = 1.5; obj.addChild(lod2); // var lod3:LODObject = new LODObject(new Sphere( { radius:50, segmentsW:16, segmentsH:16, material:new WireColorMaterial (0xFF0000, { wireColor:0xD00000 } ) } )); lod3.minp = 1.5; lod3.maxp = Infinity; obj.addChild(lod3); // onResize(); } private function onEnterFrame(e:Event):void { if (obj.z > 1000) reverse = true; if (obj.z < -1000) reverse = false; if (reverse) obj.z -= 20; else obj.z += 20; view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
Celem tego przykładu jest pokazanie, jak wyświetlają się utworzone obiekty kuli w zależności od ich odległości od obserwatora. Do jego realizacji posłużyliśmy się klasą LODObject, której obiekty zawierają w sobie bryły różnych kul reprezentowanych przez klasę Sphere. Wszystkie obiekty umieściliśmy w kontenerze ObjectContainer3D, aby operować tylko na jego współrzędnych podczas animacji przybliżania i oddalania obiektu.
Rozdział 12. Optymalizacja
525
W konstruktorze klasy LODObjectExample w pierwszej kolejności nadaliśmy nowe ustawienia obiektowi klasy Stage oraz dodaliśmy detektory zdarzeń Event.ENTER_ FRAME i Event.RESIZE. W następnej kolejności stworzyliśmy widok bez jakichkolwiek dodatkowych ustawień, po czym zabraliśmy się za tworzenie obiektów LOD. Na początku dodaliśmy obiekt kontenera o nazwie obj, potem kolejno tworzyliśmy wersje kul, które mają być wyświetlane. obj = new ObjectContainer3D(); view.scene.addChild(obj);
Schemat tworzenia poszczególnych wersji jest powtarzalny. Pierwszy zdefiniowany obiekt lod0 odpowiada za wersję najbardziej oddaloną od kamery. W konstruktorze LODObject zapisaliśmy kod tworzący kulę o promieniu radius równym 50 pikselom i liczbie segmentów w pionie i poziomie równej 4. Materiał nałożony na obiekt typu Sphere ma wyraźnie pokazywać konstrukcję siatki, dlatego zastosowaliśmy WireColorMaterial z czerwonym wypełnieniem i czarnymi krawędziami. var lod0:LODObject = new LODObject(new Sphere( { radius:50, segmentsW:4, segmentsH:4, material:new WireColorMaterial(0xFF0000, { wireColor:0x000000 } ) } ));
Po utworzeniu obiektu lod0 przypisaliśmy mu granice jego wyświetlania, nadając nowe wartości właściwościom minp i maxp. Po ustawieniu wszystkich właściwości umieściliśmy ten obiekt w kontenerze obj. lod0.minp = 0; lod0.maxp = .5; obj.addChild(lod0);
Następnym w kolejce obiektem klasy LODObject jest lod1. W jego konstruktorze również stworzyliśmy kulę, tylko że z dwukrotnie większą liczbą segmentów. Ustawiliśmy również większe wartości dla właściwości minp oraz maxp, tak aby obiekt lod1 wyświetlał się w bliższej odległości, niż zapisano to w obiekcie lod0. lod1.minp = .5; lod1.maxp = 1; obj.addChild(lod1);
Trzecią wersję zapisaliśmy jako obiekt lod2, a w jego wnętrzu utworzyliśmy kolejną odsłonę kuli, tym razem z liczbą segmentów w pionie i poziomie równą 12. Dodatkowo zmieniliśmy w materiale czarny kolor krawędzi na ciemniejszy odcień czerwieni. Pozostałe ustawienia kuli są takie same jak w poprzednich wersjach.
526
Flash i ActionScript. Aplikacje 3D od podstaw
Nowe granice obiektu lod2 zwiększyliśmy o wartość równą 0.5 zarówno dla minp, jak i maxp, dzięki temu zachowujemy ciągłość przejścia między wersjami. lod2.minp = 1; lod2.maxp = 1.5; obj.addChild(lod2);
Ostatnią odsłonę obiektu klasy LODObject nazwaliśmy lod3. W jej konstruktorze utworzyliśmy obiekt klasy Sphere, którego liczba segmentów zarówno w pionie, jak i poziomie równa jest 16. W połączeniu z nowo ustawionymi granicami skali przy najmniejszej odległości obiektu od kamery uzyskaliśmy najdokładniejszy kulisty kształt. lod3.minp = 1.5; lod3.maxp = Infinity; obj.addChild(lod3);
Na końcu konstruktora klasy LODObjectExample odwołaliśmy się do metody onResize() w celu wyśrodkowania sceny względem okna aplikacji. W metodzie onEnterFrame(), która jest wywoływana przy każdym wystąpieniu zdarzenia Event.ENTER_FRAME, zapisaliśmy animację zmiany pozycji względem osi Z dla obiektu obj. Zastosowaliśmy trzy instrukcje warunkowe if. Pierwsze dwie sprawdzają, czy zostały przekroczone ustalone granice. W zależności od ich wyniku zmiennej reverse przypisywana jest wartość true bądź false. Zmienna ta potrzebna jest do ustalenia kierunku przemieszczania obiektu kontenera. W przypadku gdy reverse jest równe true, zapisaliśmy zmniejszenie wartości pozycji na osi Z. Gdy wartość reverse jest równa false, nadając większe wartości właściwości z dla obiektu obj, odsuwamy go od kamery. if (obj.z > 1000) reverse = true; if (obj.z < -1000) reverse = false; if (reverse) obj.z -= 20; else obj.z += 20;
W tabelach 12.7 i 12.8 wypisano właściwości oraz metody klasy LODObject. Tabela 12.7. Właściwości klasy LODObject
Nazwa
Rodzaj
Wartość domyślna
Opis
maxp
Number
Infinity
Maksymalna wartość skali perspektywy, w jakiej widoczna ma być wybrana wersja
minp
Number
0
Minimalna wartość skali perspektywy, w jakiej widoczna ma być wybrana wersja
Rozdział 12. Optymalizacja
527
Tabela 12.8. Metody klasy LODObject
Nazwa
Opis
LODObject(…initarray)
Konstruktor
matchLOD(camera:Camera3D):Boolean
Zwraca wartość true bądź false w zależności od tego, czy obiekt jest widoczny
Stosowanie narzędzia Weld Weld to klasa umieszczona w pakiecie away3d.tools, której zadaniem jest zoptymalizowanie geometrii wybranego trójwymiarowego obiektu. Jej działanie polega na usuwaniu duplikatów wierzchołków i współrzędnych uv. Używając modeli z różnych, często niesprawdzonych źródeł, można natrafić na takie, które zawierają dużą liczbę zbędnych informacji spowalniających działanie aplikacji. Nawet gdy wybrany model dobrze się prezentuje na scenie, warto sprawdzić jego zawartość. Szczególnie jeżeli aplikacja korzysta z większej liczby różnorodnych obiektów trójwymiarowych.
Należy zaznaczyć, że samo użycie obiektu klasy Weld nie zmieni wyglądu wybranego modelu. Na rysunku 12.13 przedstawiono znany nam model statku kosmicznego, na którym zastosowano narzędzie Weld. Rysunek 12.13.
Model pojazdu kosmicznego z usuniętymi podwójnymi wierzchołkami
528
Flash i ActionScript. Aplikacje 3D od podstaw
Jak widać, struktura modelu nie uległa widocznym modyfikacjom. Mimo to wyczyszczono go z 432 zdublowanych wierzchołków i 516 punktów uv. Aby sprawdzić działanie i skutki całego procesu optymalizacji takiego modelu, przepisz następujący kod źródłowy. package { import import import import import import import import import import import import
flash.display.StageAlign; flash.display.StageQuality; flash.display.StageScaleMode; flash.display.Sprite; flash.events.Event; flash.geom.Vector3D; away3d.containers.View3D; away3d.tools.Weld; away3d.loaders.Loader3D; away3d.loaders.Max3DS; away3d.events.Loader3DEvent; away3d.core.base.Mesh;
public class WeldExample extends Sprite { private var view:View3D; private var modelLoader:Loader3D; private var mdl:Mesh; public function WeldExample() { stage.quality = StageQuality.LOW; stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.addEventListener(Event.RESIZE, onResize); view = new View3D(); addChild(view); // modelLoader = Max3DS.load('../../resources/models/max3ds/ spaceship/spaceship.3DS'); modelLoader.addOnSuccess(onModelLoaded); // onResize(); } private function onModelLoaded(e:Loader3DEvent):void { mdl = modelLoader.handle as Mesh; mdl.position = new Vector3D(0, 0, 0); mdl.scale(16); view.scene.addChild(mdl); // var weld:Weld = new Weld();
Rozdział 12. Optymalizacja
529
weld.apply(mdl); weld.doUVs = true; trace("Usunięto", weld.countvertices, "zbędnych wierzchołków i", weld.countuvs,"współrzędnych UV"); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(e:Event):void { mdl.rotationY--; view.render(); } private function onResize(e:Event = null):void { view.x = stage.stageWidth * .5; view.y = stage.stageHeight * .5; } } }
Po skompilowaniu i uruchomieniu tego kodu w oknie aplikacji pojawi się obiekt statku kosmicznego, który obraca się wokół osi Y. Do jego implementacji zastosowaliśmy klasy Max3DS oraz Loader3D wraz ze zdarzeniem Loader3DEvent, służące do pobierania modeli w formacie 3DS. Oczywiście zamiast tego formatu można umieszczać inne obsługiwane przez bibliotekę Away3D. W tym przykładzie ważne jest to, aby model zawierał zbędne informacje. W klasie WeldExample zdefiniowaliśmy trzy ogólnie dostępne w jej wnętrzu obiekty, są nimi: private var view:View3D; private var modelLoader:Loader3D; private var mdl:Mesh;
W konstruktorze klasy stworzyliśmy widok oraz nowy obiekt modelLoader pobierający z ustalonej lokalizacji plik spaceship.3DS. Optymalizację oraz samo umieszczenie obiektu na scenie zapisaliśmy w metodzie wywołanej po całkowitym pobraniu modelu. modelLoader = Max3DS.load('../../resources/models/max3ds/ spaceship/spaceship.3DS'); modelLoader.addOnSuccess(onModelLoaded);
W metodzie onModelLoaded() pobraną zawartość zapisaliśmy w postaci obiektu klasy Mesh o nazwie mdl. Ponieważ model ten jest niewielkich rozmiarów, znacznie zwiększyliśmy jego skalę, tak aby był dobrze widoczny na środku sceny. mdl = modelLoader.handle as Mesh; mdl.position = new Vector3D(0, 0, 0); mdl.scale(16); view.scene.addChild(mdl);
530
Flash i ActionScript. Aplikacje 3D od podstaw
Po umieszczeniu modelu na scenie stworzyliśmy obiekt klasy Weld i ustawiliśmy wartość true właściwości doUVs tak, aby również współrzędne uv zostały zoptymalizowane. Do uruchomienia całego procesu użyliśmy metody apply(), podając w jej argumencie odwołanie do obiektu mdl. var weld:Weld = new Weld(); weld.doUVs = true; weld.apply(mdl);
Po wykonanej optymalizacji, stosując trace(), wyświetliliśmy liczbę wierzchołków oraz współrzędnych uv modelu. Następnie dodaliśmy detektor zdarzenia Event.ENTER_FRAME. Umieściliśmy go w tym miejscu, aby nie dopisywać do odwoływanej metody instrukcji warunkowej sprawdzającej istnienie obiektu mdl. trace("Usunięto", weld.countvertices, "zbędnych wierzchołków i", weld.countuvs,"współrzędnych UV"); addEventListener(Event.ENTER_FRAME, onEnterFrame);
W metodzie onEnterFrame() zapisaliśmy odświeżanie widoku oraz nieustanne obracanie obiektu mdl wokół osi Y. W tabelach 12.9 i 12.10 wypisano właściwości oraz metody klasy Weld. Tabela 12.9. Właściwości klasy Weld
Nazwa
Rodzaj
Wartość domyślna Opis
countuvs
Number
0
Zwraca liczbę usuniętych współrzędnych uv
countvertices
Number
0
Zwraca liczbę usuniętych wierzchołków
doUVs
Boolean
true
Określa, czy optymalizacja ma również uwzględnić współrzędne uv
Tabela 12.10. Metody klasy Weld
Nazwa
Opis
Weld(doUVs:Boolean)
Konstruktor
apply(object3d:Object3D):void
Wykonuje optymalizację na wybranym obiekcie
Ogólne wskazówki W sytuacjach, w których jest to możliwe, stosuj obiekty typu Sprite z teksturą zamiast trójwymiarowych modeli. W miarę możliwości optymalizuj modele w programach do ich tworzenia. Używaj jak najmniejszej liczby wielokątów.
Rozdział 12. Optymalizacja
531
Staraj się przypisywać true właściwości ownCanvas w przypadku każdego trójwymiarowego obiektu. Pozwoli to wyodrębnić obiekty do procesu odświeżania. Do modeli statycznych lepiej używaj formatu 3DS. Używaj modeli zajmujących mniejszą powierzchnię. Mniejsze wartości współrzędnych wierzchołków mogą powodować przyspieszenie rysowania obiektów. Przypisanie właściwości bothsides wartości true może przyspieszyć odświeżanie obiektu, ale z drugiej strony może również powodować występowanie artefaktów. Stosowanie pushfront oraz pushback jest dobrą praktyką, gdy obiekty nie zmieniają swoich kątów nachylenia względem kamery.
Tekstury i światło Ogólne wskazówki Nie stosuj właściwości smooth na materiałach, gdy nie jest to konieczne. Nie używaj dużych tekstur, zamiast tego skaluj pokryty obiekt. Przed stosowaniem tekstury upewnij się, że jest ona wystarczająco zoptymalizowana. Czasami lepiej od razu w programie graficznym zmniejszyć rozmiar pliku, „przycinając” jakość w miejscach, gdzie nie odgrywa ona istotnej roli. Unikaj powielania tego samego materiału wewnątrz pętli for. Zdefiniuj obiekt tekstury przed pętlą. Niepotrzebne obiekty BitmapData usuwaj z pamięci, stosując metodę dispose(). MovieMaterial oraz VideoMaterial zużywają dużą ilość zasobów, dlatego w miarę możliwości staraj się używać AnimatedBitmapMaterial. W przypadku MovieMaterial oraz VideoMaterial kontroluj wyświetlanie animacji. W sytuacjach gdy jest ona zbędna, wyłączaj ją. Ograniczaj liczbę świateł do minimum. Stosując światła, przypisuj właściwościom surfaceCache wartość true. Zwolni to zużyte zasoby procesora. Stosuj tekstury, na których rozrysowano cienie i światła, zamiast generować je w Away3D. Różnica może być niewielka, a skok wydajności znaczący.
532
Flash i ActionScript. Aplikacje 3D od podstaw
Rozdział 13. Więcej zabawy z 3D Gratulacje! Zdobyłeś już podstawową wiedzę na temat tworzenia trójwymiarowych aplikacji w technologii Adobe Flash. Teraz możesz śmiało pisać własne nieprzeciętne gry i strony. Nie myśl sobie jednak, że to już koniec nauki. Jeżeli poważnie traktujesz zajmowanie się tą tematyką, to czeka Cię nieustanne poszerzanie wiedzy i śledzenie trendów. Branża informatyczna nie stoi w miejscu, stale jest coś rozwijane i poprawiane. Czy się tego chce czy nie, wciąż trzeba być na bieżąco. Niektórzy specjaliści mniej przychylni technologii Flash uważają (już któryś raz z rzędu), że nie warto się nią zajmować, bo to ostatnie chwile jej żywota. Otóż nic bardziej mylnego! Są dziedziny, w których pomimo wciąż rosnącej liczby innych możliwości Flash jest nadal przodującą technologią, do tego poszerzającą swoje pole manewrów chociażby o gry przeglądarkowe lub na urządzenia mobilne. Dodając pewnego smaczku do całości, w tym rozdziale pokażę kilka bibliotek również związanych z technologią Flash, które można połączyć z silnikiem Away3D. Nie jest to wiedza niezbędna do stworzenia prostych aplikacji, ale gdy już zdobędziesz większą wprawę, możesz nabrać chęci do tworzenia czegoś ciekawszego i bardziej złożonego. Po raz ostatni użyję stosowanego w całej książce sformułowania „Czytając ten rozdział, dowiesz się”: Jakie biblioteki odpowiadają za symulowanie fizyki w aplikacjach Flash, również tych trójwymiarowych. Czym jest rzeczywistość rozszerzona oraz jakich bibliotek używać do jej stworzenia. Jak w inny sposób wykorzystać technologię Adobe Flash, czyli zabawa z kontrolerami ruchu konsoli Xbox oraz Wii.
534
Flash i ActionScript. Aplikacje 3D od podstaw
Fizyka i kolizje Niektórym Czytelnikom słowo „fizyka” może kojarzyć się z bólem brzucha i trudnymi sprawdzianami w szkole, ale nawet takie osoby docenią jej aspekt w grach, w których wściekłymi ptaszkami zbija się prosiaczki. Stosowanie podstawowych praw fizyki w aplikacjach nie tylko trójwymiarowych znacznie poprawia ich wiarygodność i potęguje przyjemność z oglądania wyświetlanych efektów. Wielokrotnie słyszałem, jak bardzo graczy bawiło realne traktowanie modeli postaci, efekty wybuchów, zderzeń czy też upadków. W aplikacjach pisanych w technologii Flash również można zastosować tego typu efekty.
Wykrywanie kolizji w Away3D Omawiając kwestie fizyki w aplikacjach 3D, warto również wspomnieć o wykrywaniu kolizji pomiędzy dwoma lub więcej obiektami. Jest to ważna kwestia, jeżeli planujemy stworzenie realnego środowiska, w którym obiekty nie mogą przenikać siebie nawzajem lub przynajmniej powinny informować o zajściu takiej sytuacji. Biorąc za przykład zwykłą ścianę, jeżeli postać nie posiada nadprzyrodzonych umiejętności, nie może przez nią przechodzić, tylko powinna się zatrzymać przed nią. W przypadku przenikania obiektów przeważnie stosuje się ten zabieg w sytuacjach symulowania dotarcia pocisku do ofiary. Wtedy występuje zdarzenie zranienia lub zlikwidowania celu, o czym należy poinformować szereg innych składników aplikacji.
Określanie odległości pomiędzy obiektami Dzięki zastosowaniu biblioteki Away3D istnieje kilka możliwości sprawdzania, czy doszło do kolizji pomiędzy obiektami. Najprostsza metoda bazuje na sprawdzeniu, czy odległość pomiędzy punktami centralnymi obu obiektów nie jest mniejsza niż suma ich promieni. W poniższym kodzie źródłowym przedstawiono prosty przykład takiej sytuacji: sph1=new Sphere(); sph2=new Sphere(); private function collisionDetected():Boolean { var dist:Number = Vector3D.distance(sph1.position,sph2.position); var minDist:Number = sph1.boundingRadius + sph2.boundingRadius; return dist