Flash i ActionScript. Aplikacje 3D od podstaw

echnologia Flash na dobre zagościła w świecie interaktywnych animacji i gier komputerowych 2D. Stała się również (obok HTML5) standardem prezentacji tego rodzaju treści na stronach WWW. Taki stan rzeczy to efekt połączenia samego Flasha z Actionscriptem, językiem programowania ułatwiającym sterowanie pracą aplikacji i reagowanie na działania podejmowane przez użytkownika. Jednak użytkownicy internetu oczekują obecnie czegoś więcej niż tylko zwykłe animacje i gry 2D — oczekują interaktywnych treści 3D, ponieważ coraz większe stają się możliwości odtwarzania tego typu materiałów. Do tej pory brakowało narzędzi do tworzenia takich aplikacji, a także technologii, które je wspomagały. Na szczęście z odsieczą przychodzi Flash, który śmiało wkracza w świat 3D, oferuje ciekawe biblioteki i nie ma przy tym praktycznie żadnej konkurencji. Jeśli znasz już trochę Flasha, a teraz chcesz poszerzyć swoją wiedzę o znajomość szybko rozwijającej się technologii 3D, sięgnij po książkę „Flash i Actionscript. Aplikacje 3D od podstaw” . Prostym, lecz precyzyjnym językiem przedstawiono w niej najważniejsze kwestie związane z projektowaniem i tworzeniem aplikacji 3D z wykorzystaniem technologii Flash i języka Actionscript, zaprezentowano sposoby używania bezpłatnej biblioteki Away3D oraz pokazano, jak za jej pomocą budować i optymalizować własne rozwiązania. Wybór, pobieranie i instalacja właściwej biblioteki 3D dla technologii Flash Podstawowe komponenty biblioteki Away3D i budowanie aplikacji za jej pomocą Używanie materiałów i korzystanie z różnych rodzajów świateł Tworzenie, importowanie i stosowanie modeli oraz animacji 3D w aplikacjach Flash Zapewnianie interakcji aplikacji z użytkownikiem i obsługa urządzeń wejściowych Definiowanie kamer z perspektywy pierwszej i trzeciej osoby oraz ich używanie Obsługa dźwięku i elementów tekstowych Interaktywne aplikacje 3D to przyszłość. Zacznij ją tworzyć już dziś!

119 downloads 6K Views 14MB Size

Recommend Stories

Empty story

Idea Transcript


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

Smile Life

When life gives you a hundred reasons to cry, show life that you have a thousand reasons to smile

Get in touch

© Copyright 2015 - 2024 AZPDF.TIPS - All rights reserved.