Hlavní navigace

Open Inventor: Vesmírná scéna (5)

Pavel Treutner 19. 4. 2004

V dnešním dílu si ukážeme složitější chování planet, které se budou pohybovat na základě působení gravitačních sil. V aplikaci také pomocí toolkitu Qt vytvoříme jednoduché uživatelské rozhraní.

Sepsání dnešního pojednání jsem se výjimečně ujal já, protože téma přibližně odpovídá programu, na němž minulý semestr závisel můj zápočet. Další díly seriálu (o kterých jsem už něco zaslechl :)) budou od původního autora.

Hned na úvod bych rád zdůraznil, že účelem článku není demonstrovat nejlepší algoritmus simulace pohybu vesmírných těles, a skutečnost, že se planety v příkladu pohybují po spirále, může být zapříčiněna nejen použitou metodou, ale i nevhodně zvolenými vlastnostmi prostředí (gravitační konstanta, hmotnost, výchozí vektor rychlosti planet atd.).

Senzory v Open Inventoru

Obíhání těles bylo v předchozích ukázkách vyřešeno pomocí noduSoRotation­XYZ, jeho připojením k engine SoElapsedTime a posunem ze středu scény. My si vytvoříme třídu planet, kde budou uchovány všechny podstatné informace (poloha, hmotnost, vektor rychlosti a ukazatel na node SoTranslation zajišťující přesun planety na správné místo). Dále potřebujeme funkci, která vypočítá novou pozici planet a bude jednou za čas volána. Právě k tomu se hodí senzory.

Senzory jsou objekty, které sledují určité události a v případě jejich výskytu zavolají programátorem vytvořenou funkci. Mezi možné sledované události patří změna hodnoty pole (SoFieldSensor), uplynutí časového kvanta (SoAlarmSensor, SoTimerSensor), nečinnost aplikace (SoIdleSensor) a další.

Pro potomky abstraktní třídy SoDelayQueueSen­sor platí, že jakmile se objeví sledovaná událost, jsou přidáni do fronty Delay queue, kde jsou seřazeni podle priority (tu je možné ovlivnit), a někdy v budoucnu budou zpracováni (bude zavolána jejich callback funkce). Zařazení do fronty je možné dosáhnout i bez výskytu události metodou void schedule().

Senzory odvozené od abstraktní třídy SoTimerQueueSen­sor (SoAlarmSensor a SoTimerSensor) musí být do frontyTimer queue přidány některou z jejich metod. Také nejsou řazeny podle prioritym ale podle času, kdy se mají zpracovat.

My do scény přidáme SoOneShotSensor. Prvním parametrem v konstruktoru je ukazatel na callback funkci, druhým jsou data jí předávaná (typ void *).

SoOneShotSensor *sensor = new SoOneShotSensor(sensorCallback, data);
sensor->schedule(); 

Ve funkci sensorCallback zjistíme čas uplynulý od jejího minulého volání, provedeme výpočet nových pozic těles a metodou schedule() opět zařadíme senzor do fronty (v proměnné sensor je ukazatel na senzor, který funkci zavolal). Tím máme zajištěno, že bude dosaženo maximálního možného počtu vyrenderovaných snímků za sekundu.

void sensorCallback(void *data, SoSensor *sensor) {
    ...
    sensor->schedule();
}

Tvorba UI

Práci s Qt zde nebudu popisovat nijak důkladně, omezím se jen na základní principy potřebné k pochopení příkladu. Zájemcům o podrobnější informace doporučuji tutoriál a dokumentaci na stránkách Trolltechu.

V každém programu využívajícím toolkit Qt musí být vytvořena právě jedna instance třídy QApplication. Je přístupná přes globální ukazatel qApp a stará se o počáteční inicializaci, zpracování událostí, ukončení aplikace a další záležitosti.

Okna jsou v nejjednodušším případě odvozena od třídy QWidget, přestože existují i specializované varianty (QMainWindow, QDockWindow…). Prvním parametrem konstruktoru je ukazatel na rodiče, druhým jméno widgetu. Vztah rodič-potomek v tomto případě nemá nic společného s dědičností. Jestliže je widget renderWindow potomkemmainWin­dow, znamená to, že renderWindow je umístěn uvnitř widgetu mainWindow. Při zrušení rodiče jsou zrušeni i všichni jeho potomci. Prvky Qt tedy vytvářejí stromovou hierarchii, jejímž kořenem (tzv. top-level window) je instance třídy, které byl jako rodič předán nulový ukazatel. Pokud se aplikace skládá z několika oken, potom existuje i několik takovýchto stromů.

Metoda int QApplication::e­xec() spustí hlavní smyčku programu, během níž jsou zpracovávány a obsluhovány příchozí události. Stejný princip je použit v knihovně GLUT (funkce void glutMainLoop()) nebo GTK+ (void gtk_main()).

int main(int argc, char **argv) {
    QApplication a(argc, argv);
    mainWindow w;
    w.setGeometry(100, 100, 650, 500);
    a.setMainWidget(&w);
    w.show();
    return a.exec();
}

Ke komunikaci mezi objekty v Qt slouží mechanismus slotů a signálů. Když uživatel klikne na tlačítko, dojde k vyslání signálu clicked(). Ten je možné např. metodou bool QObject::connec­t(const QObject *sender, const char *signal, const QObject *receiver, const char *member) spojit se slotem – funkcí, jež je při vyslání signálu zavolána. Takto lze předávat i parametry. Hodně objektů má množství zabudovaných slotů i signálů, ale nic nám nebrání vytvořit si vlastní (tímto způsobem můžeme propojit i dva signály – při vyslání prvního signálu je vyslán i signál druhý).

QPopupMenu *file = new QPopupMenu(this);
file->insertItem("&Konec", qApp, SLOT(quit()), CTRL+Key_Q); 

V příkladu je slider propojen s widgetem renderWindow, který obsahuje slot void setIntensity(int) sloužící k nastavování intenzity ambientního světla prostředí. Ve všech objektech, které obsahují signály nebo sloty, musí být uvedeno makro Q_OBJECT.

// deklarace tridy renderWindow
class renderWindow: public QWidget {
    Q_OBJECT
private:
    SoEnvironment *envir;
public:
    renderWindow(QWidget *parent = NULL, const char *name = NULL);
    ~renderWindow();
public slots:
    void setIntensity(int);
};

void renderWindow::setIntensity(int i) {
    envir->ambientIntensity.setValue(i / 99.0f);
}

QSlider *slider = new QSlider(Vertical, this, "slider");
renderWindow *render = new renderWindow(this, "render");
connect(slider, SIGNAL(valueChanged(int)), render, SLOT(setIntensity(int))); 

Dosud jsme aplikaci tvořili bez ohledu na knihovnu Coin. Hlavní okno jsme za použití QGridLayout rozdělili na čtyři části (dva řádky, dva sloupce), do nichž jsme umístili menu, slider a okno s 3D scénou (renderWindow). V konstruktoru třídy renderWindow musíme inicializovat knihovnu SoQt (void SoQt::init(QWidget *toplevelwidget)) a klasickým způsobem vytvoříme scénu. Ukazatel na instanci renderWindow je nutné předat i konstruktoru třídySoQtFlyVi­ewer ve funkci void createScene(QWidget *parent, SoEnvironment **envir).

renderWindow::renderWindow(QWidget *parent, const char *name): QWidget(parent, name) {
    SoQt::init(this);
    createScene(this, &envir);
} 

Na závěr přikládám zdrojové kódy příkladu. Kdybyste měli problémy s jejich kompilací, zkuste vygenerovat nástrojem qmake nový Makefile (mezi knihovny je potom potřeba doplnit soqt-config --ldflags soqt-config -libs ).

main.h, main.cpp
hlavní okno
render.h, render.cpp
renderovací okno
scene.h, scene.cpp
vytvoření a zrušení scény
planet.h, planet.cpp
třída planet
freeFlyer.h, freeFlyer.cpp
třída freeFlyer (vesmírná loď)
Našli jste v článku chybu?

19. 4. 2004 12:25

Pavel Treutner (neregistrovaný)

Mozna si jen nerozumime. Ja to myslel tak, ze i v pripade, ze pouziju "nejlepsi" metodu pro vypocet pohybu planet a nastavim treba prilis velkou pocatecni rychlost, ulitne mi planeta pryc. Ale ta metoda je celkem nepodstatna, v clanku slo spis o senzory a Qt. :)

19. 4. 2004 11:10

malis (neregistrovaný)

Hned na úvod bych rád zdůraznil, že účelem článku není demonstrovat nejlepší algoritmus simulace pohybu vesmírných těles, a skutečnost, že se planety v příkladu pohybují po spirále, může být zapříčiněna nejen použitou metodou, ale i nevhodně zvolenými vlastnostmi prostředí (gravitační konstanta, hmotnost, výchozí vektor rychlosti planet atd.).

tak na tech velicinach v zavorce to prave vubec nezalezi ;)



Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

DigiZone.cz: Česká televize mění schéma ČT :D

Česká televize mění schéma ČT :D

Lupa.cz: Slevové šílenství je tu. Kde nakoupit na Black Friday?

Slevové šílenství je tu. Kde nakoupit na Black Friday?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

120na80.cz: Bojíte se encefalitidy?

Bojíte se encefalitidy?

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Lupa.cz: Avast po spojení s AVG propustí 700 lidí

Avast po spojení s AVG propustí 700 lidí

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Podnikatel.cz: Prodává přes internet. Kdy platí zdravotko?

Prodává přes internet. Kdy platí zdravotko?

Lupa.cz: Není sleva jako sleva. Jak obchodům nenaletět?

Není sleva jako sleva. Jak obchodům nenaletět?

120na80.cz: Jak oddálit Alzheimera?

Jak oddálit Alzheimera?

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0