Java a 3D grafika – GeometryInfo, NormalGenerator, Striptifier

11. listopadu 2004

V tomto článku se seznámíme s třídami, které nám značně ulehčí práci při vytváření geometrií trojrozměrných objektů. Ukážeme si, jak s pomocí objektů GeometryInfo a NormalGenerator vygenerovat normálové vektory, nebo jak seskupit jednotlivé trojúhelníky do pásů s využitím objektů GeometryInfo a Striptifier.

Třída GeometryInfo

Nejdůležitější třídou, s níž budeme v tomto článku pracovat, bude třída GeometryInfo, kterou si můžeme představit jako takovou „nafukovací“ geometrii, do níž se vkládají data, která mají být zpracována. „Nafukovací“ proto, že do jejích instancí můžeme vkládat souřadnice vrcholů (a samozřejmě i normálové vektory a další), aniž bychom předem určili jejich počet.

Ve třídě GeometryInfo jsou definovány dva konstruktory. První z nich, GeometryInfo(int primitive), nám umožňuje vytvořit prázdný objekt GeometryInfo, do něhož pak můžeme vkládat některá z primitiv – trojúhelníky, jejich pásy nebo trsy, čtyřúhelníky nebo mnohoúhelníky. O jaká primitiva půjde, určuje parametr primitive, který může nabývat hodnoty jedné z konstant definovaných v této třídě: TRIANGLE_ARRAY, QUAD_ARRAY, TRIANGLE_FAN_ARRAY, TRIANGLE_STRIP_ARRAY a POLYGON_ARRAY. S druhým konstruktorem, GeometryInfo(GeometryArray ga), můžeme vytvořit objekt GeometryInfo, který bude obsahovat data objektu GeometryArray, jenž mu předáváme jako parametr.

Pokud vytvoříme prázdný objekt GeometryInfo, musíme ho pochopitelně nejprve naplnit daty, než s ním začneme pracovat. K tomu slouží například metody setCoordinates, setColors a setNormals, které všechny přebírají jako jediný parametr pole s daty. Vkládání dat do objektů GeometryInfo je tedy stejné, jako když pracujeme s objekty GeometryArray. Pouze při vkládání pásů (a samozřejmě také trsů) musíme ještě specifikovat, kolik vrcholů bude v jednotlivých pásech uloženo, voláním metody setStripCounts(int stripCounts[]).

A dále, při vkládání mnohoúhelníků musíme použít tutéž metodu pro specifikování počtu bodů v konturách jednotlivých mnohoúhelníků. Následující úryvek kódu vytváří s pomocí třídy GeometryInfo pětiúhelník, připomínající štít domečku (samozřejmě mu chybí normálové vektory, ale to v tuto chvíli nevadí):

GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
float coords[] = {0,0,0, 1,0,0, 1,1,0, 0.5f,1.5f,0, 0,1,0};
geometryInfo.setCoordinates(coords);
int stripCounts[] = {5};
geometryInfo.setStripCounts(stripCounts);
Shape3D shape = new Shape3D();
shape.setGeometry(geometryInfo.getGeometryArray());

Dobrá, to bychom měli. Ale co když budeme chtít, aby měl v sobě takto vytvořený mnohoúhelník díry? Pak budeme potřebovat ještě metodu setContourCounts(int contourCounts[]), které se předává jako parametr pole typu int obsahující počty kontur ohraničujících daný mnohoúhelník. Jako příklad využijeme předcházející pětiúhelník (domeček), kterému přidáme dveře:

GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
float coords[] = {0,0,0, 1,0,0, 1,1,0, 0.5f,1.5f,0, 0,1,0, 0.4f,0,0, 0.6f,0,0, 0.6f,0.4f,0, 0.4f,0.4f,0};
geometryInfo.setCoordinates(coords);
//délky jednotlivých hranic mnohoúhelníků
int stripCounts[] = {5, 4};
//první mnohoúhelník má dvě hranice
int contourCounts[] = {2};
geometryInfo.setStripCounts(stripCounts);
geometryInfo.setContourCounts(contourCounts);
Shape3D shape = new Shape3D();
shape.setGeometry(geometryInfo.getGeometryArray());

Třída NormalGenerator

Přestože je třída GeometryInfo docela užitečná i sama o sobě, budete ji asi nejčastěji využívat ve spojení s dalšími třídami z balíčku com.sun.j3d.utils.geometry, a to s již zmíněnými třídami Striptifier a NormalGenerator. Pojďme se tedy podívat, jak se s těmito třídami zachází.

Třída NormalGenerator, jak už bylo řečeno, slouží k vytváření normálových vektorů. Její konstruktor NormalGenerator(double creaseAngle) přebírá jediný parametr, jímž je maximální úhel mezi dvěma trojúhelníky, kdy se ještě počítají jejich normálové vektory tak, aby vznikl dojem zaoblené plochy. (O tom, jak se počítají normálové vektory a jak se to dělá, aby vznikl dojem oblé plochy, jsme se bavili v článku o normálových vektorech.) K vlastnímu vygenerování normálových vektorů dojde v okamžiku, kdy zavoláme metodu generateNormals(GeometryInfo gi).

Jak vytvořit normálové vektory pro štít našeho domečku, ukazuje následující kód:

//…
NormalGenerator ng = new NormalGenerator(0);
ng.generateNormals(geometryInfo);
Shape3D shape = new Shape3D();
shape.setGeometry(geometryInfo.getGeometryArray());
//…

domeček

Třída Striptifier

Pro rychlejší renderování a úsporu místa v paměti je užitečné, aby měla geometrie objektu formu pásů, čehož můžeme docílit velice snadno s využitím třídy Striptifier. Konstruktor třídy Striptifier() nepřebírá žádné parametry a k vytvoření pásů je potřeba pouze zavolat metodu striptify(GeometryInfo gi). Doporučuje se vytvářet pásy až poté, co jsou vygenerovány normálové vektory. Už jenom kvůli tomu, že v pásech jsou vrcholy, a tedy také normálové vektory, sdíleny mezi jednotlivými trojúhelníky. Proto, pokud jsou mezi trojúhelníky ostré hrany, musí NormalGenerator tyto pásy druhotně rozdělit.

Příklad – rotační tělesa

Nyní toho již víme dost na to, abychom mohli získané znalosti převést do praxe. Ukážeme si, jak lze velice snadno vytvořit třídu pro vytváření geometrií rotačních těles a jak nám při tom pomohou právě třídy GeometryInfo a NormalGenerator.

Nejdůležitější metodou v našem příkladu bude metoda generate, která bude přebírat jako parametr pole se souřadnicemi (x, y) bodů zadávajících útvar, který budeme rotovat. Ta nebude dělat nic jiného, než že bude tyto body otáčet kolem osy y a spojovat vždy dvě sousední pozice rotovaného útvaru pásem trojúhelníků.

Zdrojový kód třídy RotGeomGenerator:

package interval.j3d;
import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.*;
public class RotGeomGenerator{
 protected int steps = 30;
 protected float creaseAngle = (float)Math.PI/4;
 
 public RotGeomGenerator() {
 }
 
 /**
  * V poli shape jsou uloženy x-ové a y-ové souřadnice
  * křivky, kterou budeme rotovat kolem osy y.
  */

 public Geometry generate(float shape[]){
  float coordinates[] = new float[3*shape.length*steps];
  //v poli contour budou uloženy souřadnice x,z
  float contour[] = new float[shape.length];
  for (int i = 0; i < contour.length; i+=2){
   contour[i] = shape[i];
   contour[i+1] = 0;
  }
  //v poli contour2 budou rovněž uloženy souřadnice x,z,
  //v tomto poli budeme uchovávat souřadnice otočené vůči
  //souřadnicím v poli contour o úhel 2*PI/step

  float contour2[] = new float[contour.length];
  //tyto hodnoty budeme dále využívat k rotaci
  float sin = (float)Math.sin(2*Math.PI/steps);
  float cos = (float)Math.cos(2*Math.PI/steps);
  //výsledné souřadnice vložíme do objektu GeometryInfo,
  //protože s nimi budeme ještě dále pracovat

  GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.TRIANGLE_STRIP_ARRAY);
  //počet bodů v jednotlivých pásech
  int stripCounts[] = new int[steps];
  for (int i = 0; i < stripCounts.length; i++)
   stripCounts[i] = shape.length;
  geometryInfo.setStripCounts(stripCounts);
  int count = 0;
  for (int i = 0; i < steps; i++){
   for (int j = 0; j < contour.length; j+=2){
    contour2[j] = contour[j];
    contour2[j+1] = contour[j+1];
    //otočíme body v poli contour
    contour[j] = contour2[j]*cos – contour2[j+1]*sin;
    contour[j+1] = contour2[j]*sin + contour2[j+1]*cos;
   }
   //nastavíme body v poli coordinates
   for (int j = 0; j < contour.length; j+=2){
    coordinates[count++] = contour[j];
    coordinates[count++] = shape[j+1];
    coordinates[count++] = contour[j+1];
    coordinates[count++] = contour2[j];
    coordinates[count++] = shape[j+1];
    coordinates[count++] = contour2[j+1];
   }
  }
  //vytvoříme normálové vektory
  NormalGenerator ng = new NormalGenerator(creaseAngle);
  geometryInfo.setCoordinates(coordinates);
  ng.generateNormals(geometryInfo);
  return geometryInfo.getGeometryArray();
 }
 
 public void setCreaseAngle(float creaseAngle){
  this.creaseAngle = creaseAngle;
 }
 
 public void setStepCount(int steps){
  this.steps = steps;
 }
}

Celý zdrojový kód je celkem krátký, a to hlavně díky tomu, že se nemusíme starat o vytváření normálových vektorů, protože to dělá objekt NormalGenerator za nás. Následující obrázek ukazuje, jakého výsledku dosáhneme, zvolíme-li jako rotovaný útvar obdélník.

rotační těleso

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *