Minule jsme jednoduchou aplikací HelloCone zahájili první sérii příkladů, které nás mají uvést do inventorského světa. Dnes na HelloCone navážeme dalšími třemi příklady:
1.2 – Cone and Engine | engine jako motor pro pohyb ve scéně |
1.3 – Cone with Manipulator | manipulátory pro manipulaci s objekty |
1.4 – Cone and Viewer | pohyb pozorovatele ve scéně |
Nebudeme tedy otálet a přejdeme hned k prvnímu příkladu.
Příklad 1.2: Cone and Engine (Kužel a engine)
Open Inventor umožňuje ukládat do grafu scény i chování objektů. K tomu slouží množina tříd odvozených ze SoEngine (engine = motor). Cílem tohoto příkladu tedy bude rotující kužel, jak je ukázáno na obrázku:
Graf scény, který budeme vytvářet, je oproti minulému příkladu HelloCone obohacen o nod SoRotationXYZ a engine typu SoElapsedTime. SoRotationXYZ provádí rotaci podle jedné ze souřadných os. Pro pochopení celého principu se musíme nodu SoRotationXYZ podívat blíže na zoubek.
SoRotationXYZ obsahuje dvě datové položky: axis a angle. Axis říká, podle které osy budeme rotovat, a angle o kolik. Podíváme-li se blíže, zjistíme, že axis není prostý c-čkovský enum s možnostmi X,Y a Z, nýbrž objekt typu SoSFEnum a angle není float, ale SoSFFloat. Proč takové složitosti? Tyto třídy totiž umožňují Open Inventoru provádět mnoho kouzel. Jednou z nich je například připojení jedné datové položky na jinou. A právě to provedeme my v našem přikladu.
SoElapsedTime je engine pracující s časem. Má několik vstupů, kterými můžeme ovlivnit její funkčnost. Pro nás však bude nejdůležitější její výstup zvaný „timeOut“, tedy výstup času. Ten obsahuje hodnotu, která se s časem zvětšuje. Pokud neměníme vstup speed, je rychlost shodná s plynutím času, tj. zvýší se o jedničku za sekundu.
Graf scény, který budeme konstruovat, vypadá takto:
Vidíme, že graf scény je velmi podobný s příkladem HelloCone. Navíc přibývá pouze kód vytvoření SoRotationXYZ a SoElapsedTime:
SoRotationXYZ *rotXYZ = new SoRotationXYZ;
rotXYZ->axis = SoRotationXYZ::X;
root->addChild(rotXYZ);
Kód pro SoElapsedEngine a její připojení na SoRotationXYZ::angle:
SoElapsedTime *counter = new SoElapsedTime;
rotXYZ->angle.connectFrom(&counter->timeOut);
Zde je kód celého příkladu:
//
// Příklad 1.2 - Cone And Engine
//
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoRotationXYZ.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/engines/SoElapsedTime.h>
int main(int , char **argv)
{
// Inicializuj Inventor
QWidget *window = SoQt::init(argv[0]);
if (window == NULL) exit(1);
// Vytvoř kořen scény
SoSeparator *root = new SoSeparator;
root->ref();
// Vlož kameru do scény
SoPerspectiveCamera *camera = new SoPerspectiveCamera;
root->addChild(camera);
// Světlo
root->addChild(new SoDirectionalLight);
// Materiál
SoMaterial *material = new SoMaterial;
material->diffuseColor.setValue(1.0, 0.0, 0.0);
root->addChild(material);
// Rotační node
SoRotationXYZ *rotXYZ = new SoRotationXYZ;
rotXYZ->axis = SoRotationXYZ::X;
root->addChild(rotXYZ);
// Připojení engine na vstup rotačního node
SoElapsedTime *counter = new SoElapsedTime;
rotXYZ->angle.connectFrom(&counter->timeOut);
// Kužel
root->addChild(new SoCone);
// Vytvoř renderovací okno
SoQtRenderArea *renderArea = new SoQtRenderArea(window);
// Nastav parametry kamery
camera->viewAll(root, renderArea->getViewportRegion());
// Nastav renderovací okno
renderArea->setSceneGraph(root);
renderArea->setTitle("Engine Spin");
renderArea->show();
// Zobraz okna a rozjeď renderovací smyčku
SoQt::show(window);
SoQt::mainLoop();
// Uvolni paměť
delete renderArea;
root->unref();
return 0;
}
Příklad je k dispozici ke stažení opět ve dvou verzích – pro Linux a Windows.
Vše je myslím vcelku jasné, neboť většina kódu pochází z prvního příkladu. Pozorný programátor se snad jen může ptát, kde se v našem příkladu uvolňují objekty, které vytváříme při konstrukci scény. Odpověď je, že ve volání root->unref() na závěr našeho příkladu. Open Inventor totiž používá techniku „reference counting“, přeložitelnou snad jako počítání referencí. Každý objekt scény má počítadlo referencí a pokud toto počítadlo klesne na nulu, objekt je uvolněn z paměti. V příkladu vidíme sice „zareferencování“ a „odreferencování“ pouze jediného objektu – root, ve skutečnosti však referencování provádí Inventor vnitřně kdykoliv je to potřeba. Například pokud vytvoříme objekt a přidáme jej jako syna jiného nodu, tento syn je automaticky zareferencován jeho otcem. Když pak v závěru příkladu odreferencováváme root, klesne jeho počítadlo referencí na nulu a objekt je uvolněn z paměti. Během tohoto procesu on odreferencuje své syny. A protože synové nejsou nikým jiným referencováni, klesnou jejich počítadla také na nulu a celá scéna je tímto způsobem uvolněna. To se týká nejen všech našich nodů, ale i engine, která je součástí naší scény a která je zareferencována díky jejímu připojení na SoRotationXYZ::angle.
Příklad 1.3: Cone with Manipulator (Kužel s manipulátorem)
Manipulátory slouží k jednoduché manipulaci s objekty scény. My v našem příkladu použijeme trackballový manipulátor – SoTrackballManip. Ten slouží k „rotační“ manipulaci – objekt můžeme zrotovat do polohy, jaké chceme. Neslouží tedy k přemísťování objektů. Pro tento účel bychom museli použít jiné manipulátory, například SoHandleBoxManip nebo SoTransformBoxManip.
Zkonstruujeme tedy graf scény v této podobě:
Kód tedy zůstává skoro stejný, až na vytvoření manipulátoru:
SoTrackballManip *manip = new SoTrackballManip;
root->addChild(manip);
Zdrojáky si můžete stáhnout pro Linux a Windows.
Výsledný screenshot:
Ovládání trackballového manipulátoru je vcelku jednoduché. Pokud chytnete za jeden ze tří prstenců, můžete s objektem podle osy tohoto prstence rotovat. Pokud manipulátor chytnete kdekoliv jinde, vyberou se všechny tři prstence a můžeme si užít pravé trackballové manipulace.
Pokud chcete zkusit jiný druh manipulátoru, stačí jen nahradit všechny výskyty SoTrackballManip za jiný manipulátor.
Příklad 1.4: Cone and Viewer (Kužel a prohlížeč)
Až dosud jsme si „užívali“ jen velmi jednoduchých scén, ve které jsme se navíc ani nemohli pohybovat. První nedostatek odstraníme až příště, ale pohybovat se ve scéně se naučíme už nyní.
Základní operace pro manipulaci s pohledem pozorovatele do scény jsou zapouzdřeny ve třídě SoQtViewer (na Windows SoWinViewer) a tříd od ní odvozených. Jen pro dokreslení skutečnosti: SoQtViewer je odvozena od SoQtRenderArea, kterou jsme používali až dosud pro zobrazování scény. A od SoQtViewer se odvozují další třídy, mezi které patří i SoQtExaminerViewer, kterou budeme v tomto příkladu používat.
Kód příkladu je velmi podobný našemu HelloCone. Nahrazení SoQtRenderArea za SoQtExaminerViewer je málem provedeno pouhou záměnou. Zde je odpovídající kód:
// Vytvoř okno prohlížeče
SoQtExaminerViewer *viewer = new SoQtExaminerViewer(window);
viewer->setSceneGraph(root);
viewer->setTitle("Examiner Viewer");
viewer->show();
// Zobraz okna a rozjeď renderovací smyčku
SoQt::show(window);
SoQt::mainLoop();
// Uvolni paměť
delete viewer;
root->unref();
Další změna v kódu je při samotné konstrukci scény – zmizela nám kamera a světlo. Důvod je v tom, že všichni potomci třídy SoQtViewer, tedy i náš SoQtExaminerViewer, obslouží tyto věci za nás. Pokud scéna neobsahuje kameru, SoQtViewer ji přidá automaticky. Mnoho scén také neobsahuje žádná světla, proto SoQtViewer jedno umístí do pozice pozorovatele. Tato kamera a světlo se nepřidávají přímo do naší scény, což by mohlo mít nežádoucí efekty, ale do skryté scény SoQtVieweru, takže naše scéna zůstává nedotčená. Zde je kód konstrukce scény:
// Vytvoř kořen scény
SoSeparator *root = new SoSeparator;
root->ref();
// Materiál
SoMaterial *material = new SoMaterial;
material->diffuseColor.setValue(1.0, 0.0, 0.0);
root->addChild(material);
// Kužel
root->addChild(new SoCone);
Můžeme si stáhnout kód pro Linux a Windows.
Po spuštění se nám naskytne pohled podobný následujícímu obrázku:
Vidíme, že nám na okrajích okna přibyly ovládací prvky a kolečka pro ovládání pohledu do scény. ExaminerViewer je určen k prohlížení modelů, proto nečekejme chování kamery podobné těm ve 3D hrách. Pro to je mnohem vhodnější SoQtFlyViewer, se kterým se seznámíme v přespříštím díle.
Nyní k tomu, co všechno ExaminerViewer umí. Myší můžeme manipulovat s modelem, dokonce ho můžeme i roztočit. Kolečkama rotujeme a přibližujeme se k němu. Nejzajímavější bude asi použití menu, do kterého se dostaneme pravým tlačítkem. V něm vybereme Draw Styles → Still Drawstile → a vybereme wireframe nebo hidden line a naskytne se nám pohled na tzv. drátěný model. Někdy je to docela zajímavá podívaná, hlavně u složitějších modelů.
Z dalších tlačítek, které se nám mohou hodit, je „s“ pro „seek mode“ – tedy posunutí. Klikneme někde na kužel a kamera „přiletí“ k tomuto místu. Jedno upozornění: Vrchol kužele není jediné místo, které je zajímavé :-) . Pokud se při „seekování“ nechtěně ztratíte někde v prostoru, je zde tlačítko home, které nás vrátí do výchozí pozice kamery. Poslední tlačítko, které si uvedeme, je „q“ pro ukončení aplikace.
Závěr
Přeji mnoho zábavy s příklady a těším se na příští díl, kdy si ukážeme už něco ze složitějších a zábavnějších scén, se kterými stvoříme naši sluneční soustavu.