J3DT_3 - Jednoduché vytváření obsahu scény

Cíle kapitoly

Po přečtení této kapitoly budete schopni:

Jako třetí kapitola celého modulu "Začínáme", představuje tato kapitola jednodušší způsoby vytváření vizuálního obsahu scény. Kapitoly jedna a dva představují základní způsoby vytváření virtuálních světů, což zahrnuje i vytváření vizuálních objektů z geometrických tříd. Stačí jen malá programátorská zkušenost abyste zjistili že vytváření komplexního vizuálního obsahu po jednom trojúhelníku je nemyslitelné. Naštěstí existují i jiné způsoby jak vytvořit vizuální obsah. Tato kapitola vás provede několika metodami vytváření obsahu spolu s jejich problémy, které sahají za hranice jednoduché geometrie.

3.1 Co najdete v této kapitole

Pokud chcete vytvořit velký nebo komplexní vizuální objekt, musíte napsat opravdu velký kus kódu pro určení souřadnic a normál (normálových vektorů, pozn. překladatele). Pokud chcete zajistit dobrý výkon, strávíte většinu času a napíšete více řádků kódu abyste dostali co nejméně trojůhelníků. Kódování geometrie do detailu může spolknout velkou část vašeho času potřebného na vývoj aplikace. naštěstí existují způsoby jak vytvořit vizuální objekty které vyžadují méně kódu, což se projeví méně chybami, a obvykle zaberou také méně času.

Sekce 3.2 představuje třídu utilit GeometryInfo, která se používá k automatizování některých detailů při psaní geometrie trojúhelník po trojúhelníku. Třídy GeometryInfo, Triangulator, Stripifier a NormalGeneration vám umožní specifikovat geometrii vizuálního objektu jako polygony. tyto třídy převádí polygony na trojúhelníky, vytváří pásy z trojúhelníků a počítají normály pro trojúhelníky za běhu, což vám může ušetřit čas při psaní kódu.

Sekce 3.3 představuje loadery (třídy pro načítání vizuálního obsahu ze souborů, pozn. překladatele). Loadery jako jedna z alternativ k ručně kódované geometrii, vytváří Java3D vizuální objekty ze souborů vytvořených softwarem pro modelování 3D objektů. Dnes existují loadery pro Alias soubory .obj, soubory VRML, Lightwave soubory .lw3d, AutoCAD soubory .dfx a jiné. Také se neustále vyvíjejí nové loadery. Nejdůležitější schopností je možnost napsat svůj vlastní loader pro prostředí Java3D, což je diskutováno v sekci 3.4.

Další tři sekce představují konkrétní techniky pro vytváření obsahu. Sekce 3.5 a 3.6 představují třídy Text2D a Text3D. Tyto dvě třídy představují dva jednoduché způsoby jak přidat text do obsahu virtuálního světa. Sekce 3.7 představuje třídu Background. Třída Background vám umožní určit barvu, obrázek nebo geometrii které budou sloužit jako pozadí vašeho virtuálnho světa.

Další sekce nemá až tak nic společného s vytvářením obsahu. Sekce 3.8 hovoří o použití rozhraní UserData třídy SceneGraphObject.

Samozřejmě kapitola končí cvičením pro ty dobrodružnější z vás.

3.2 GeometryInfo

Pokud momentálně nemáte po ruce soubory obsahující geometrický model, nebo software pro jejich tvorbu, musíte si je naspat sami. Jak je zmíněno v úvodu kapitoly, psaní kódu pro vytvoření geometrie často zabere mnoho času a je náchylné k chybám. Jak už víte, když použijete základní třídy k určení geometrie, jste omezeni na trojúhelníky a čtyřúhelníky. Použití třídy utilit GeometryInfo vám může pomoci toto úsilí zmírnit. Místo toho abyste určili polohu každého trojúhelníku, můžete určit pouze polohu jednotlivých vrcholů polygonů, které mohou být konkávní nebo konvexní i s "dírami" (i když ve třídě GeometryInfo můžete specifikovat konvexní polygony a objekt Triangulator z nich vytvoří povrch, tak konvexní kontury netvoří jednotný povrch. Jinými slovy, pokud vytvoříte konvexní konturu, nemusí Triangulator vytvořit povrch správně). Objekt GeometryInfo a ostatní třídy utilit převádí geomtrii na trojúhelníky které může Java3D vykreslit.

Například, pokud chcete vytvořit auto v prostředí Java3D, tak místo toho abyste určovali polohu každého trojúhelníku, můžete pouze určit profil auta jako polygon v objektu GeometryInfo. Poté, za použití objektu Trinagulator, může být povrch rozdělen na jednotlivé trojúhelníky. Obrázek 3.1 vlevo představuje auto jako polygon. Obrázek vpravo představuje polygon rozdělený na trojúhelníky (obrázek nemusí vypovídat o kvalitě triangulace třídy Triangulator).

doplnit fig 3.1, str. 3-2

Pokud se zajímáte o výkon apliakce, a kdo ne, použijte objekt Stripifier k převodu trojúhelníků na pásy trojúhelníků. Pokud chcete vizuální objekt stínovat, použijte třídu NormalGenerator k výpočtu normálových vektorů povrchu geometrie (stínování, viz. kapitola 6).

Jako ukázkový program poslouží GeomInfoApp.java který používá třídy GeometryInfo, Triangulator, Stripifier a NormalGenerator k vytvoření auta, a můžete ho najít v adresáři examples/easyContent. Obrázek 3.2 představuje výstup z programu GeomInfoApp.java. Na obou částech modrá kontura znázorňuje objekt GeometryInfo červené trojúhelníky (vyplněné a stínované v levé části) byly automaticky vypočítány objekty GeometryInfo, Triangulation, NormalGeneration a Stipification.

doplnit fig 3.2, str. 3-3

Jeden rovinný polygon, podobný tomu na obrázku 3-1 tvoří profil auta (jeden pro každou stranu). čtyřúhelníky tvoří přední část auta, kapotu, střechu, kryt kufru a ostatní části auta.

3.2.1 Jednoduchý příklad použití třídy GeometryInfo

Použití objektu třídy GeometryInfo je stejně jednoduché jako použití základní třídy GeometryArray, ne-li jednodušší. Při vyvtáření objektu GeometryInfo jednoduše specifikujte typ geometrie který budete potřebovat. Můžete vybírat z konstant POLYGON_ARRAY, QUAD_ARRAY, TRIANGLE_ARRAY, TRIANGLE_FAN_ARRAY a TRIANGLE_STRIP_ARRAY. Poté určíte souřadnice a počet pruhů geometrie. Nemusíte říkat objektu GeometryInfo kolik souřadnic data obsahují; to si spočítá sám.

Následující ukázka kódu je příkladem třídy GeometryInfo. řádky 1 až 3 znázorňují vytváření objektu GeometryInfo a nastavení geometrie. Poté co máme objekt GeometryInfo vytvořen, můžeme použít ostatní třídy. Pokud chcete například použít NormalGenerator, pak vytvořte její objekt a jako argument mu předejte objekt GeometryInfo, viz. řádky 8 a 9.

1.	GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
2.	gi.setCoordinates(coordinateData);
3.	gi.setStripCounts(stripCounts);
4.	
5.	Triangulator tr = new Triangulator();
6.	tr.triangulate(gi);
7.
8.	NormalGenerator ng = new NormalGenerator();
9.	ng.generateNormals(gi);
10.	
11.	Stripifier st = new Stripifier();
12.	st.stripify(gi);
13.
14.	Shape3D part = new Shape3D();
15.	part.setAppearance(appearance);
16.	part.setGeometry(gi.getGeometryArray());

Používání třídy GeometryInfo

Existují správné, a co je důležitější, špatné způsoby jak používat GeometryInfo a podobné třídy utilit. Například byste neměli používat Stripifier před použitím NormalGenerator. Pokud použijete obě třídy, použijte napřed NormalGenerator. Správným pořadím operací se vyhnete mnoha zbytečným výpočtům což se projeví lepší výkonností.

Třídy NormalGenerator a Stripifier pracují jen s indexovanými trojúhelníky. Nezáleží na tom v jakém formátu (POLYGON_ARRAY, QUAD_ARRAY...) předáte data objektu GeometryInfo protože je interně automaticky převede na indexované trojúhelníky. Když je s prací hotov, složí trojúhelníky zpátky do původní podoby. Důsledekm toho může být změněný geometrický model.To znamená, že pokud jste ve vaší aplikaci použili například TRIANGLE_STRIP_ARRAY, poté je předali NormalGenerator, výsledekm mohou být různé proužky. Avšak kdyby původní data byla ve formátu POLYGON_ARRAY, pak výstupní formát dat bude vždy obsahovat trojúhelníky. Také si všimněte že nemusíte volat Triangulator před použitím tříd NormalGenerator nebo Stripifier.

Třídy spolupracující s třídou GeometryInfo

Třída GeometryInfo a jí podobné třídy nalezneme v balíčku com.sun.j3d.util.geometry a jsou podtřídami třídy Object. Podívejte se na obrázek 3-3.

doplnit fig 3-3, str. 3-4

Třída GeometryInfo má jen jeden konstruktor a tím specifikuje typ dat. Viz následující referenční blok.

Konstruktor třídy GeometryInfo

Balíček: com.sun.j3d.utils.geometry

Rozšiřuje: java.lang.Object

Objekt GeometryInfo je objektem do něhož vkládte svá data když chcete použít knihovnu utilit Java3D. Když už máte svá data vložna v objektu GeometryInfo, můžete je předat jakékoli (nebo všem) třídám utilit a provádět požadované operace, jako je generování normál nebo převod na dlohé proužky pro efektivnější vykreslování ("stripifikace"). Geometrie načtena podobně jako do objektu GeometryArray, ale s menšími možnostmi jak je načíst. GeometryInfo obsahují některé jednoduché utility, jako je výpočet indexů pro neindexovaná data ("indexace") a getting rid nepoužitých dat v indexovaných datech ("compacting").

GeometryInfo(int primitive)

Vytvoří objekt GeometryInfo kde primitive je jedním z:

POLYGON_ARRAYvícevrcholové, nerovinné polygony
QUAD_ARRAYkaždá množina čtyř vrcholů tvoří jeden čtverec
TRIANGLE_ARRAYkaždá množina tří vrcholů tvoří jeden trojúhelník
TRIANGLE_FAN_ARRAYstripCounts určuje kolik vrcholů se má použít pro vějíř trojúhleníků
TRIANGLE_STRIP_ARRAYstripCounts určuje kolik vrcholů se má použít pro jeden trojúhleníkový pás

Třída GeometryInfo má mnoho metod. Většina metod nastavuje nebo vrací souřadnice, informace o barvě, indexu, normále nebo o souřadnicích textur. Většina aplikací využije jen pár z těchto metod. Avšak je obvyklé mít možnost určit úroveň geometrie a zbytek nechat dopočítat.

Metody třídy GeometryInfo, neúplný seznam

void recomputeIndices()

Přepočítává indexy aby zajistila návaznost vrcholů. Používá se jen při nesprávných indexovaných souřadnicích.

void reverse()

Obrácené pořadí seznamů.

void setColorIndices(int[] colorIndices)

Přiřazuje pole indexů do pole Color

void setColors(color4f[] colors)

Nastavuje pole barev. Tato metoda je přetížená.

void setContourCounts(int[] countourCounts)

Nastavuje seznam contourCounts

void setCoordinateIndices(int[] coordinateIndices)

Přiřazuje pole indexů do pole Coordinate (souřadnic)

void setCoordinates(Point3d[] coordinates)

Vytváří pole souřadnic. Tato metoda je přetížená.

void setNormalIndices(int[] normalIndices)

Přiřazuje pole indexů do pole Normal

void setNormals(Vector3f[] normals)

Vytváří pole normál.

void setNormals(float[] normals)

Vytváří pole normál.

void setStripCounts(int[] stripCounts)

Nastavuje pole stripCounts

void setTextureCoordinateIndices(int[] texCoordIndices)

Přiřazuje pole indexů do pole TextureCoordinate

void setTextureCoordinates(Point2f[] texCoords)

Nastavuje pole TextureCoordinates. Tato metoda je přetížená.

Každá z "pomocných tříd" třídy GeometryInfo se používá podobně jako třída GeometryInfo. Následující referenční bloky obsahují konstruktory a metody tříd Triangulator, Stripifier a NormalGenerator. V tomto pořadí byste je měli používat s daty POLYGON_ARRAY.

Třída Triangulator se používá jen s daty POLYGON_ARRAY. Objekty GeometryInfo s jinýi daty by měly používat jen třídy Stripifier a NormalGenerator. Základní konstruktor třídy Triangulatorjednoduše vytváří objekt této třídy. více viz. referenční blok.

Konstruktor třídy Triangulator

Balíček: com.sun.j3d.utils.geometry

Rozšiřuje: java.lang.Object

Třída Triangulator slouží k převodu polygonů na trojúhelníky tak aby mohly být vykresleny jádrem Java3D. Polygony mohou být konkávní, konvexní a mohou obsahovat díry (viz. GeometryInfo)

Triangulator()

Vytváří novou instanci

Jediná metodou třídy Triangulator slouží k triangulaci pole polygonů objektu GeometryInfo.

Metody třídy Triangulator

void triangulate(GeometryInfo gi)

Tato metoda převádí objekt GeometryInfo obsahujícího typ POLYGON_ARRAY na typ TRIANGLE_ARRAY pomocí technik rozkladu.

Jediný konstruktor třídy Stripifier vytváří objekt pro stripifikaci.

Konstruktor třídy Stripifier

Balíček: com.sun.j3d.utils.geometry

Rozšiřuje: java.lang.Object

Třída Stripify převede primitiva obsažená v objektu GeometryInfo na pásy trojúhleníků. Pásy se vytváří pomocí analýzy trojúhleníků obsažených v původních datech a jejich následného spojení.

Pro dosažení nejlepšího výsledku by generace normál měla být provedena před stripifikací.

Stripifier()

Vytváří objekt Stripifier.

Jediná metoda třídy Stripifier slouží ke stripifikaci geometrie obsažené v objektu GeometryInfo

Metody třídy Stripifier

void stripify(GeometryInfo gi)

Převede geometrii v objektu GeometryInfo na pole trojúhelníků.

Třída NormalGenerator má dva konstruktory. První vytváří NormalGenerator s daným úhlem. Druhý konstruktor vám dovolí tuto hodnotu určit. Viz. referenční blok.

Konstruktory třídy NormalGenerator

Balíček: com.sun.j3d.utils.geometry

Rozšiřuje: java.lang.Object

Třída NormalGenerator spočítá a doplní normály objektu GeometryInfo. Výpočet normál je založen na analýze indexovaných souřadnic. Pokud vaše data nejsou indexována, pak je vytvořen seznam indexů.

Pokud jsou v modelu dva (nebo více) trojúleníků které sdílí stejné souřadnice indexu pak generátor normál vygeneruje jen jednu normálu pro daný vrchol, čímž se dosáhne vyhlazeného povrchu. Pokud dvě souřadnice nemají stejný index, pak se vygenerují dvě normály, i když mají stejnou pozici. Důsledkem bude zvrásnění povrchu objektu. Pokud máte podezření že vaše data nejsou správně indexována, pak zavolejte metodu GeometryInfo.recomputeIndexes().

Samozřejmě někdy model může obsahovat zvrásnění. Pokud se dvě normály trojúhelníku liší více než o creaseAngle, pak se vygenerují dvě normály pro jeden vrchol tvořící lom. Toho se například využívá pro tvorbu hran stolu nebo roh krychle.

NormalGenerator()

Vytvoří NormalGenerator s defaultním úhlem (0.76794 radiánů, neboli 44 stupňů)

NormalGenerator(double radians)

Vytváří NormalGenerator s určeným úhlem v radiánech.

Metody třídy NormalGenerator zahrnují i ty pro nastavení úhlu lomu a pro výpočet normál objektu GeometryInfo. Viz. referenční blok výše.

Metody třídy NormalGenerator

void generateNormals(GeometryInfo gi)

Vygeneruje normály pro daný objekt GeometryInfo

double getCreaseAngle()

Vrací aktuální hodnotu úhlu lomu v radiánech

setCreaseAngle(double radians)

Nastaví hodnotu úhlu lomu v radiánech.

3.3 Loadery

Loadery jsou třídy načítající 3D grafiku ze souborů (ne Java3D soubory) a vytváří Java3D reprezentace obsahu těchto souborů, který může být přidán do Java3D světa. Balíček utilit com.sun.j3d.loaders nabízí možnosti jak načítat obsahy takových souborů vytvořencýh jinými aplikacemi do Java3D aplikací. Loadery implementují rozhraní Loader definované v balíčku com.sun.j3d.loaders.

Protože existuje velké množství formátů které mají za účel reprezentovat 3D scény (.obj, .vrml atd.) a vždy budou vznikat nové, neobsahuje balíček kód k načtení souboru; je v něm obsaženo jen rozhraní pro načítání. Díky definici tohto rozhraní může uživatel Java3D vyvíjet třídy pro načítání se stejným rozhraním jako mají ostatní třídy.

Loadery mohou načítat jak soubory na disku, tak soubory ze sítě.

3.3.1 Jednoduchý příklad použití Loaderu

Bez třídy která čte samotný soubor není kožné načíst obsah tohoto souboru. S loaderem je to snazší.

  1. najděte si vám vyhovující loader (pokud není k dispozici, napište si ho: viz. sekce 3.4)
  2. importujte loader reprezentující váš formát souboru (viz. sekce 3.3.2)
  3. importujte ostatní nezbytné třídy
  4. deklarujte odkaz na objekt Scene (nepoužívejte konstruktor)
  5. vytvořte objekt loaderu
  6. načtěte soubor v bloku try a přiřaïte výsledek do odkazu na objekt Scene
  7. vložte objekt Scene do grafu scény

Loader načítající soubory formátu .obj je součástí Java3D SDK. Třída ObjectFile je distribuována v balíčku com.sun.j3d.loaders.

Třída ObjectFile

Balíček: com.sun.j3d.loaders

Implementuje: Loader

Třída ObjectFile implementuje rozhraní Loader k načtení souboru .obj Wavefront, standardního typu souboru vytvořeného aplikací Wavefront Advanced VisualizerTM. Tyto soubory jsou textovými soubory podposrujícími jak polygonální tak křivkovou geometrii. Java3D .obj loader podporuje podmnožinu tohoto formátu, ale je dostatečně výkonný k načtení všech běžných souborů .obj. Křivková geometrie není podporována (křivky a povrchy).

Následující kód ukazuje použití loaderu dle našeho receptu.

17. import com.sun.j3d.loaders.objectfile.ObjectFile;
18. import com.sun.j3d.loaders.ParsingErrorException;
19. import com.sun.j3d.loaders.IncorrectFormatException;
20. import com.sun.j3d.loaders.Scene;
21. import java.applet.Applet;
22. import javax.media.j3d.*;
23. import javax.vecmath.*;
24. import java.io.*;
25.
26. public class ObjLoad extends Applet {
27.
28.	private String filename = null;
29.
30.	public BranchGroup createSceneGraph() {
31.		// vytvoříme kořen grafu scény
32.		BranchGroup objRoot = new BranchGroup();
33.		
34.		ObjectFile f = new ObjectFile();
35.		Scene s = null;
36.		try {
37.			s = f.load(filename);
38.		} catch (FileNotfoundException e) {
39.			System.err.println(e);
40.			System.exit(1);
41.		} catch (ParsingErrorException e) {
42.			System.err.println(e);
43.			System.exit(1);
44.		} catch (IncorrectFormatException e) {
45.			System.err.println(e);
46.			System.exit(1);
47.		}
48.
49.		objRoot.addChild(s.getSceneGroup());
50.	}

Tento program pokračuje přidáváním chování (rotace, interakce myší pokrytá v kapitole 4) a světel (kapitola 6) aby bylo možno model osvětlit. Samozřejmě můžete s 3D modelem v Java3D provádět mnoho dalších věcí jako je animace, vkládání ostatních geometrických objektů, měnit barvu modelu atd.

3.3.2 Veřejně dostupné loadery

Existuje mnoho loaderů pro prostředí Java3D. Následující tabulka obsahuje seznam veřejně dostupných loaderů. V době psaní tohoto tutoriálu byl k dispozici alespoň jeden loader pro každý typ souboru uvedený v tabulce.

Formát SouboruPopis
.3ds3D - Studio
.cobCaligari trueSpace
.demDigital Elevation Map
.dxfAutoCAD Drawing Interchange File
.iobImagine
.lwsLightwave Scene Format
.nffWorldToolKit NFF format
.objWavefront
.pdbProtein Data Bank
.playPLAY
.sldSolid Works (.prt a .asm soubory)
.vrtSuperscape VRT
.vtkVisual Toolkit
.wrlVirtual Rality Modelling Language

Pro aktuální seznam dostupných loaderů ddopručujeme prohledat internet.

3.3.3 Balíček loaderů s rozhraními a základními třídami

Počet a množství druhů loaderů existují protože návrháři Java3D umožnili je jednoduše napsat. Třídy loaderů jsou imlementacemi orzhraní Loader které usnadňuje tvorbu loaderů. Mnohem důležitějším faktem ale je, že rozhraní nutí různé loadery mít konzistentní rozhraní.

Stejně jako v ukázce výše, program načítající soubor obsahující 3D data používá jak objekt loaderu tak objekt scény. Loader čte, parsuje a vytváří Java3D reprezentaci obsahu souboru. Objekt scény pak uchovává graf scény vytvořený loaderem. Je možné načítat scény z více než jednoho souboru (stejného formátu) za použití jednoho objektu loaderu vytvářejícího více objektů scény. Soubory různých formátů mohou být kombinovány v Jav3D programu za použití příslušných loaderů.

Následující referenční blok obsahuje seznam rozhraní v balíčku com.sun.j3d.loaders. Loader implementuje rozhraní Loader a používá třídu implementující rozhraní Scene

Rozhraní balíčku com.sun.j3d.laoders

Loader Rozhraní se používá k určení polohy a prvků formátu souboru který se má načíst.

Scene  Rozhraní je sadou metod používaných k extrakci informace o Java3D grafu scény z loaderu.

Mimo tyto rozhraní obsahuje balíček com.sun.j3d.loaders základní implementace těchto rozhraní.

Třídy balíčku com.sun.j3d.loaders

LoaderBase tato třída implementuje rozhraní Loader a poskytuje konstruktory. Tuto třídu můžete rozšířit k vytvoření specifických loaderů.

SceneBase  tato třída implementuje rozhraní Scene a rozšiřuje ho k začlenění metod používaných loadery. Tato třída je využívána programy které používají loadery.

Metody definované tímto rozhraním jsou používány programátory používajícími loadery.

Metody rozhraní Loader

Balíček: com.sun.j3d.loaders

Rozhraní Loader se pooužívá k určení polohy a prvků formátu souboru který se má načíst. Rozhraní je umožňuje loaderům různých formátů souborů mít společné veřejné rozhraní. Ideálně rozhraní Scene umožňuje uživateli mít jednotné rozhraní k extrakci dat.

Scene load(java.io.Reader reader)

Tato metoda načítá Reader vrací objekt Scene obsahující scénu.

Scene load(java.lang.String filename)

Tato metoda načítá jmenovaný soubor a vrací objekt Scene obsahující scénu.

Scene load(java.net.URL url)

Tato metoda načítá soubor z url a vrací objekt Scene obsahující scénu.

void setBasePath(java.lang.String pathname)

Tato metoda nastavuje cestu k datovým souborům asociovaným se souborem předaným metodě load(String)

void setBaseUrl(java.net.URL url)

Tato metoda nastavuje url k datovým souborům asociovaným se souborem předaným metodě load(URL)

void setFlags(int flags)

Tato metoda nastavuje příznaky souboru:

LOAD_ALLDo scény se načtou všechny objekty
LOAD_BACKGROUND_NODESDo scény se načtou objekty pozadí
LOAD_BEHAVIOR_NODESDo scény se načtou všechna chování
LOAD_FOG_NODESDo scény se načtou objekty mlhy
LOAD_LIGHT_NODESDo scény se načtou objekty světel
LOAD_SOUND_NODESDo scény se načtou objekty zvuku
LOAD_VIEW_GROUPSDo scény se načtou objekty kamer

Třída Loaderbase obsahuje implementaci každé ze tří verzí metody load() rozhraní Loader. Třída LoaderBase také implementuje dva konstruktory. všimněte si že všechny tři metody vrací objekt Scene.

Konstruktory třídy LoaderBase

Balíček: com.sun.j3d.loaders

Implementuje: Loader

Tato třída implementuje rozhraní Loader. Autor nového loaderu by měl tuto třídu rozšířit. Uživatel loaderu by měl používat tyto metody.

LoaderBase()

Vytváří Loader s defaultními hodnotami všech proměnných

LoaderBase(int flags)

Vytváří Loader s danými příznaky

Mimo těchto konstruktorů používají programátoři metody v následujícím referenčním bloku. Při psaní loaderu může autor nového loaderu rozšířit třídu LoaderBase definovanou v balíčku com.sun.j3d.loaders. Nový loader poté používá třídu SceneBase ze stejného balíčku.

Budoucí loadery mohou rozšířit buï třídu SceneBase nebo implementovat rozhraní Scene přímo, i když použití třídy SceneBase je opravdu přímočaré. Třída SceneBase je zodpovědná za ukádání a získávání dat vytvořených loaderem při čtení souboru. Metody k získávání dat (používáné primárně uživateli Loader) jsou všechny metody get*.

Při rozšiřování třídy loaderu bude většina práce spočívat v napsání metod rozpoznávajících rozdílné typy obsahu které představují formát souboru. Každá z těchto metod pak vytvoří odpovídající komponentu grafu scény Java3D a uloží ji v objektu Scene. V následujícím referenčním bloku najdete seznam konstruktorů třídy SceneBase.

Konstruktor třídy SceneBase

Balíček: com.sun.j3d.loaders

Rozšiřuje: Scene

Tato třída implementuje rozhraní Scene a rozšiřuje jej k začlenění utilit které mohou používat loadery. Tato třída je zodpovědná za ukládání a získávání dat z objektu Scene.

SceneBase()

Vytváří objekt SceneBase - neměl by být žádný důvod k používání tohoto konstruktoru kromě implementace nové třídy loaderu.

Metody třídy SceneBase

Tento referenční blok jen jednoduše uvádí seznam metod.

Background[] getBackgroundNodes()

Behavior[] getBehaviorNodes()

java.lang.String getDescription()

Fog[] getFogNodes()

float[] getHorizontalFOVs()

Light[] getLightNodes()

java.util.Hashtable getNamedObjects()

BranchGroup getSceneGroup()

Sound[] getSoundNodes()

TransformGroup[] getViewGroups()

Jak jsme si řekli výše, jednou z nejdůležitějších možností loaderů je napsat si svůj vlastní - což znamená že všichni další uživatelé Jav3D mohou také!

3.4 Píšeme loader

Psaní loaderu je velmi komplexní úloha kterou ne všichni vývojáří Java3D aplikací podstoupí. I když už teï víte že nebudete chtít naspat si svůj vlastní loader, může vám tato sekce poskytnout různé informace:

  • kdy použít loader
  • porozumět používání objektu GeometryInfo
  • porozumět implementaci třídy ObjectLoader

Avšak pokud vás tyto body nezaujaly a nechcete si zkusit napasat vlastní loader nyní, klidně se přesuňte na další sekci.

Pro ty kteří mají zájem vytvořit vlastní loader pokračuje tato sekce přehledem některých komlexníxch úloh při psaní loaderu, následovaným konstrukcí jednoduchého loaderu a v závěru vás čeká malá analýza struktury OBJLoader.

3.4.1 Co loadery dělají

Předtím než podstoupíme složitou operaci psaní loaderu, můžeme trochu popřemýšlet o věcech které loader může dělat. Obvykle je základní úlohou loaderu číst geometrii ze souboru a vyprodukovat graf scény který reprezentuje obsah souboru. Ale může dělat i spoustu jiných věcí. Například, loader může:

  • vypočítat povrchové normály pro polygony
  • umístit geometrii na počátek souřadnic
  • upravit rozměry na rozmezí -1 až 1 podél každé osy
  • vyhladit geometrii
  • provést stripifikaci
  • vytvořit mnohonásobnou reprezentaci geometrie pro techniku LOD

Některé z těchto vlastností jsou implementovány v dostupných loaderech a některé z nich jsou implementovány třídou GeometryInfo a jiných třídách utilit Java3D.

Samozřejmě loader nemusí implementovat všechny tyto vlastnosti; může jen jednoduše splnit svou základní funkci. To je přesně to co náš první loader bude dělat. Později si povíme jak ho upravit aby uměl dělat i ostatní věci ze seznamu.

3.4.2 Konstrukce základního loaderu

Psaní loaderu je zajímavý proces který zahrnuje detailní znalosti o formátu souboru pro který chceme loader napsat. Psaní loaderu také zahrnuje znalost Java datových proudů, což je mimo hranice tohoto tutoriálu.

Existuje mnoho způsobů jak napsat vlastní loader. To záleží na zamýšleném použití loaderu. Pro maximální pružnost návrhu uvažujme o třídách Scenebase a LoaderBase. Loader postavený na těchto třídách umožní uživateli vybrat které části souboru se použijí v aplikaci. Toto je standardní metoda pro vývoj loaderu. Jiná alternativa zahrnuje vytvoření loaderů pro specifickou apliakci bez tříd SceneBase a LoaderBase.

Jinou proměnnou při vytváření loaderu je typ informace obsažený v souboru který se má načíst. Některé formáty souborů jsou velmi jednoduché; jiné velmi komplexní. Nejjednodušší jsou soubory obsahující jen samotnou geometrii. Komplexnější formáty souborů se mohou odkazovat na jiné soubory anebo mohou obsahovat popis grafu scény. S tím souvisí i fakt že třída loaderu může obsahovat pár set nebo pár tisíc řádek kódu.

Složitost vytváření loaderu je ulehčena díky používání softwarových vrstev. Počet těchto vrstev se může lišit v závislosti na složitosti formátu souboru. Následující tabulka ukazuje možnou sadu vrstev.

Vrstva / ÚroveňFunkce
tokenizerpřevádí znaky na tokeny (symboly)
objektpřevádí vizuální tokeny souboru na objekty grafu scény (geometrii, světla, textury...)
scénapřevádí seznamy objektů grafu scény na graf(y) scény
souborprovádí operace se soubory a url

Tokenizer

Tokenizer je nejnižší vrstvou. Tato vrstva načítá soubor a tokenizuje jeho obsah. Jeden token je nejmenší jednotkou informace v souboru. Jeho příkladem může být číslo, slovo nebo jeden znak. Tokeny mohou tvořit větší části informací. Například poloha vrcholu může být složena ze tří (x, y, z) číselných tokenů.

Objekt

Objektová vrstva vytváří grafické objekty z tokenů v souboru. "Objekty" mohou být vrcholy, normály, polygony, světla, textury nebo jakýkoli jiný objekt scény v Java3D API. Tyto objekty jsou složkami scény.

Scéna

Kolekce grafických objektů tvoří scénu nebo scény. tato softwarová vrstva je obvykle jednoduchá. Výjimkou je pouze loader formátu souboru který představuje graf scény. Takovými formáty souborů jsou OpenInventor a OOOGL formát.

Soubor

Tato softwarová vrstva řeší problémy s pojmenováváním zdrojů (url nebo jména souborů) a ostatní detaily načtítání scény. tato vrstva se lehce dotýká informací o geometrii obsažených v souboru.

3.4.3 Vytvoření velmi jednoduchého loaderu

Prvním úkolem je vybrat formát souboru pro který budeme loader psát. Obvykle je tento výběr řízen nějakým vnějším vlivem (tzn. máme nějaký konkrétní formát který chceme načíst). Druhým úkolem je nasbírat informace o formátu. Nezbytně nutné jsou definice (specifikace) formátu souboru a soubor pro experimentování.

Pro tento tutoriál jsme vybrali dobře zdokumentovaný formát pro který v době psaní tohoto tutoriálu žádný loader neexistoval. Popravdě, nevybrali jsme jen jeden formát ale rodinu formátů - těch co patří do rodiny OOGL.

Formát OOGL znamená "Object Oriented Graphics Library". tato knihovna se používá pro více úloh než jen pro formáty souborů; používá ji aplikace Geomview, interaktvní prohlížeč 3D objektů. Více informací o programu Geomview můžete najít na www.geomview.org. Tčást knihovny souborů je také známa jako "Geom File Formats" nebo "Geom View File Formats".

Informace o formátu QUAD

Opět, prvním krokem při psaní loaderu je znalost formátu souboru. Tato úloha je mnohem jednodušší pokud je formát souboru zdokumentován a dokumentace je volně přístupná. Pokud nemáte jeho definici, může zkusit nejaít něco na webu. Alternativou je dekódovat formát souboru z různých souborů se známým obsahem. Proes dekódování je ale obvykle cestou pokusů a omylů a zde se jím nebudeme zabývat.

Dostupnost dokumentace formátu QUAD byla jedním z hlavních kritérií proč jsme pro tento tutoriál. tento formát je dobře zdokumentovaný v sekci čtyři dokumentace k programu Geomview která je dostupná online. Sekce čtyři obsahuje obsahuje následující informace o formátech OOGL:

Většina OOGL formátů jsou ASCII soubory - mohou obsahovat jakékoli množství bílých znaků (mezery, tabulátory, nové řádky) mezi tokeny (čísla, klíčová slova atd.). Zarážky jsou nevýznamné, kromě pár výjimek. Komentáře začínají znakem "#" a platí do konce řádku.

Mezi formáty OOGL najdeme i formát QUAD. Tento formát definuje kolekci čtyřúhelníků. Dále je ve specifikaci uvedeno:

Hlavička souboru "QUAD" identifikuje formát souboru. Soubor QUAD je seznam 4 * n vrcholů kde n je počet čtyřúhelníků. Vrcholy jsou určeny jednoduše pomocí souřadnic x, y, a z bodu.

Následující příklad ukazuje obsah jednoduchého QUAD souboru. Přesněji, čtyřúhelník je jednoduchý čtverec v rovině xy na pozici z = 0.

QUAD
-1 -1  0
 1 -1  0
 1  1  0
-1  1  0

Formát QUAD může být mnohem složitější než předchozí ukázka, ale úkolem této sekce je vyvinout jednoduchý loader a proto potřebujeme jednoduchost. Pro více informací o formátu QUAD se podívejte do sekcí 4.2 a 4.1.3 dokumentace programu Geomview.

Teď když dobře rozumíme formátu souboru, můžeme implementovat loader. Následující tabulka obsahuje třídy a vztahy mezi nimi při implementaci jednoduchého loaderu formátu QUAD. Tato tabulka obsahuje jen jednu možnou implementaci.

Tabulka také obsahuje vrstvu o které jsme se ještě nezmiňovali; aplikační vrstvu. Tato vrstva je program který byste měli napsat pro jednoduché načítání. Každé jméno třídy vyznačené tučně koresponduje se jménem souboru v adresáři examples/loader.

Vrstva / ÚroveňImplementace
tokenizerQuadFileParser
objektSimpleQuadObject importuje QuadFileParser
scénaSimpleQuadScene rozšiřuje SimpleQuadObject
souborSimpleQuadFileLoader rozšiřuje SimpleQuadScene a implementuje Loader
aplikaceSimpeQuadLoad importuje SimpleQuadFileLoader

V následujících podsekcích budeme jednotlivé třídy implementovat. V tomto projektu budeme vytvářet jednu vrstvu po druhé a začneme tou nejnižší (tokenizerem). Kód tohto projektu není orginální - hodně je ho převzato ze třídy ObjectLoader Java3D API. Avšak je velmi zjednodušen aby se mu dalo lehce porozumět.

Implementace vrstvy tokenizer

Prvním krokem při vytváření loaderu je konstrukce tokenizeru. To zahrnuje úpravy třídy StringTokenizer. Třída StringTokenizer čte proud znaků a převádí ho na proud tokenů. Je lepší si něco přečíst o třídě StringTokenizer před tím než budete psát svůj loader.

Například v našem loaderu je třída StreamTokenizer upravena rozšířením na třídu nazvanou QuadFileParser. Tato třída obsahuje následující metody.

metoda / konstruktorpopis / účel
void setup()používá se ke specifikaci parametrů tokenizeru (tzn. které znaky jsou povoleny ve "slově"); tuto metodu volá konstruktor
boolean getToken()metoda pro získání tokenu z objektu Reader; tato metoda používá metodu nextToken() třídy StreamTokenizer a zachycuje výjimku vyhazovanou metodou nextToken()
void printToken()tiskne hodnotu tokenu a informace přes System.out; používá se pro ladění
QuadFileParser(Reader r)konstruktor volá metodu setup() a asociuje tokenizer s objektem Reader

Metoda setup() je místo pro kód k formátu souboru QUAD. Je to místo kde se nastavují specifika formátu souborů pro toeknizer. Podstatné úpravy tokenizeru zahrnují:

  • které znaky mají speciální význam
  • zda mají znaky nového řádku nějaký význam
  • který znak, pokud nějaký, značí komentář

Tyto informace poskytuje formát souborů.

1. class QuadFileParser extends StreamTokenizer {
2.	// metoda setup() nastavuje StreamTokenizer pro čtení OOGL souborů
3.	void setup() {
4.		resetSyntax();
5.		// znaky nového řádku v QUAD formátu ignorujeme
6.		eolIsSignificant(false);
7.		wordChars('A', 'z');
8.		// necháme StreamTokenizer parsovat čísla (s dvojitou přesností)
9.		parseNumbers();
10.		// komentáře začínají znakem # a platí až do konce řádku
11.		commentChar('#');
12.		// bílé znaky obkopují čísla a znaky
13.		// mezery, taby a ynakz nového řádku jsou považovány v OOGL za bílé znaky
14.		whitespaceChars('\t', '\r'); // ht, lf, ff, vt, cr
15.		whitespaceChars(' ', ' '); // mezera
16.	}
17.
18.	/* metoda getToken()
19.	 * získá token z proudu. Vloží jednu ze čtyř konstant 
20.	 * (TT_WORD, TT_NUMBER, TT_EOL nebo TT_EOF) a hodnotu
21.	 * tokenu do objektu ttype.
22.	*/
23.	boolean getToken() {
24.		int t;
25.		boolean done = false;
26.		try {
27.			t = nextToken();
28.		} catch (IOException ioe) {
29.			System.err.println(
30.				"IO error on line "+ lineno() +": "+ ioe.getMessage());
31.			return false;
32.		}
33.		return true;
34.	}
35.
36.	QuadFileParser(Reader r) {
37.		super(r);
38.		setup();
39.	}
40.}

Tokenizer pro loader QUAD souborů by mohl být jednodušší. Například metoda nextToken() kořenové třídy (čili StreamTokenizer) mohla být použita bez "rozšíření" metodou getToken(). Metoda getToken() přidává zachycování výjimek a jejich reportování v této vrstvě. Také metoda printToken(), primárně určená pro ladění, by mohla být považována za zbytečnou.

Pokud byste zde uvedený kód použili ke psaní svého vlastního formátu souborů, asi byste prvně změnili metodu setup() tak, aby byla vhodná pro váš formát. Také mějte na paměti že StreamTokenizer nerozpozná vědeckou notaci (systém vyjdáření čísel pomocí dekadických exponentů) a tudíž vrací pouze číselné hodnoty typu double.

Když máme dokončený tokenizer, přesuneme se ve stavbě loaderu o vrstvu výše k objektu.

Implementace vrstvy objekt

Účel této vrstvy je převést proud tokenů z tokenizeru na grafické objekty. V tomto případě je to jednoduché v tom, že prakticky existuje jen jeden druh grafického objektu, a tím je geometrie složená z čtyřúhelníků. Avšak jistá míra komplexity zde pořád přetrvává. Jedna potíž vyvstává z toho že není známo kolik čtyřúhelníků existuje. O tom bude dikuze později. Nyní se podívejme na implementaci.

Objektová vrstva je implementována jako SimpleQuadObject. Následující tabulka obshuje seznam metod třídy SimpleQuadObject.

metodapopis / účel
boolean readVertex(QuadFileParser qfp)načte vertex z QUAD souboru který obsahuje hodnoty x, y a z souřadnic a jiné hodnoty vertexu (povrchovou normálu, barvu, atd.)
boolean readQuad(QuadFileParser qfp)načítá čtyřúhelník z QUAD souboru, čili čtyři vertexy najednou
void readQuadFile(QuadFileParser qfp) čte soubory QUAD, což znamená ověření interního tagu souboru, určení složení vertexů a načtení všech čtyřúhelníků

Cílem této třídy / vrstvy je složit tokeny do grafických objektů. V tomto případě jsou grafické objekty to stejné co vertexy. Kolekce vertexů je vložena do objektu ArrayList (deklarovaného na řádku v následující ukázce kódu). Použití objektu ArrayList umožňuje měnit velikost za běhu. Ve scéně bude seznam polí převeden na pole a to pak na geometrický objekt.

Kdyby měla být tato třída rozšiřitelná, měli by zde být chráněné členy. Jediný člen v této třídě je flags. V kódu však není použit a byl ponechán jako příklad kde by byly použity datové členy při deklaraci více vrstev.

Ukázka kódu níže obsahuje většinu z implementace objektové vrstvy tohoto loaderu. Samozřejmě kompletní implementaci najdete v .jar souboru.

1. import QuadFileParser;
2. // ostatní příkazy import
3.
4. public class SimpleQuadObject {
5.	protected int flags;
6.	protected int fileType;
7.	// konstanty indikující typ souboru
8.	protected static final int COORDINATE = GeometryArray.COORDINATES;
9.	// seznam bodů které jsou čteny z .quad souboru do tohot pole
10.
11.	protected ArrayList coordList; // obsahuje Point3f
12.	// readVertex() načítá souřadnice vertexu
13.	boolean readVertex(QuadFileParser qfp) {
14.		Point3f p = new Point3f;
15.		qfp.getToken();
16.		if (qfp.ttype == qfp.TT_EOF)
17.			return false;
18.		p.x = (float) qfp.nval;
19.		qfp.getToken();
20.		p.y = (float) qfp.nval;
21.		qfp.getToken();
22.		p.z = (float) qfp.nval;
23.		// vložíme vertex do pole
24.		coordList.add(p);
25.		return true;
26.	}
27.
28.	// readQuad() načítá čtyři vertexy správného typu
29.	// (kterým jsou v této verzi jen souřadnice)
30.	boolean readQuad(QuadFileParser qfp) {
31.		int v;
32.		boolean result = false;
33.		for (v = 0; v < 4; v++)
34.			result = readVertex(qfp);
35.		return result;
36.	}
37.	
38.	// readFile() čte ze souboru data modelu
39.	void readQuadFile(QuadFileParser qfp) {
40.		ověříme typ souboru
41.		qfp.getToken();
42.		if (qfp.sval.equals("QUAD") || qfp.sval.equals("POLY"))
43.			fileType = COORDINATE;
44.		else throw new ParsingErrorException("Bad file type: "+ qfp.sval);
45.		// načteme vertexy
46.		while (readQuad(qfp));
47.	}
48. }

Kdyby existovaly ostatní informace o vertexu (vektory povrchových normál, barva, souřadnice textury) které by se daly načíst ze souboru, použily by se pro uložení těchto informací další objekty ArrayList. Tyto informace by také byly převedeny do polí a ve scéně aby se mohly vytvořit odpovídající geometrické objekty.

Je to právě tahle vrstva kde se zpracovává nejvíce informací formátu souborů. Pokud byste psali vás vlastní loader, museli byste implementovat všechny metody potřebné pro čtení různých dat ze souboru.

V této fázi je objektová vrstva hotová. Když máme dokončenou objektovou vrstvu, přesuneme se ve stavbě loaderu o vrstvu výše, ke scéně.

Implementace vrstvy scény

Tato vrstva je implementována ve třídě SimpleQuadScene. Následující tabulka obsahuje seynam metody třídy SimpleQuadScene.

metodapopis / účel
SceneBase makeScene()přebírá ArrayList se souřadnicemi vytvořený v objektové vrstvě, vytvoří odpovídající geometrický objekt a graf scény do kterého tento objekt lze vložit
Point3f[] objectToPoint3fArray(ArrayList al)převede ArrayList na pole objektů Point3f; tuto metodu používá metoda makeScene() k vytvoření geometrického objektu
Scene load(Reader reader)1. vytvoří objekt QuadFileParser; 2. zavolá QuadFileLoad (vrstva scény); 3. zavolá metodu makeScene()
1. class SimpleQuadScene extends SimpleQuadObject {
2.	// coordList je převedeno na pole pro vytvoření geometrie
3.	private Point3f[] coordArray = null;
4.
5.	private Point3f[] objectToPoint3fArray(ArrayList in) {
6.		Point3f[] out = new Point3f[in.size()];
7.		for (int i = 0; i < in.size(); i++)
8.			out[i] = (Point3f) in.get(i);
9.
10.		return out;
11.	}
12.
13.	private SceneBase makeScene() {
14.		// Vytvoří objekt Scene který navrací
15.		SceneBase scene = new SceneBase();
16.		BranchGroup group = new BranchGroup();
17.		scene.setSceneGroup(group);
18.		// model bude tvořen jedním objektem Shape3D
19.		Shape3D shape = new Shape3D();
20.		coordArray = objectToPoint3fArray(coordList);
21.		QuadArray qa = new QuadArray(coordArray.length, fileType);
22.		qa.setCoordinates(0, coordArray);
23.		// vložíme geometrii do objektu Shape3D
24.		shape.setGeometry(qa);
25.		group.addChild(shape);
26.		scene.addNamedObject("no name", shape);
27.		return scene;
28.	}
29.	
30.	public Scene load(Reader reader) throws FileNotFoundException,
31.						IncorrectFormatException,
32.						ParsingErrorException {
33.		// objekt QuadFileParser provede lexikální analýzu
34.		QuadFileParser qfp = new QuadFileParser(reader);
35.		coordList = new ArrayList();
36.		readQuadFile(qfp);
37.		return makeScene();
39.	}
40. }

Tato implementace loaderu je tak jednoduchá jak jen může být. Metoda makeScene() přebírá geometrii z QUAD souboru a vytváří z ní jeden objekt QuadArray. Tento objekt je vložen do objektu Shape3D a ten následně do scény.

Je to právě tato vrstva kde lze implementovat mnoho "pokročilých funkcí" loaderu zmiňovaných v sekci 3.4.1 "Co loadery dělají". Například lze do této vrstvy přidat stripifikaci a generování vektorů povrchových normál předáním souřadnic objektu GeometryInfo (viz sekce 3.2).

Pro tuto ukázku jsme udělali vše co bylo nezbytné a nyní se můžeme přesunout o vrstvu výše, k souboru.

Implementace vrstvy souboru

Vrstva souboru loaderu je jednoduchá. Většina kódu je přesně to samé co kód pro ObjectLoader poskytovaný pomocnými balíčky Java 3D API. Autor loaderu by se klidně mohl vyhnout použití tohoto kódu nebo napsání podobného kódu použitím třídy LoaderBase diskutované v sekci 3.3.3.

Vrstva souboru je implementována jako SimpleQuadFileLoader. Tato vrstva je obecná v tom smyslu, že neobsahuje nic, co by se přímo týkalo QUAD formátu. Také si všimněte že výsledná třída implementuje rozhraní Loader a že seznam metod třídy SimpleQuadFileLoader je stejný jako ten v přehledu k rozhraní Loader.

Implementace vrstvy souboru uzavírá konstrukci loaderu. Aplikace která používá tento loader se jmenuje SimpleQuadLoad.