Irrlicht - from Noob to Pro: Translation, Rotation, Skalierung


Vorwort Bearbeiten

Im vorherigen Kapitel dieses Buches haben wir uns mit dem Laden einer von Blender exportierten Mesh-Datei in die Irrlicht-Engine beschäftigt. Dieses Mesh wurde um einen Rotations-Animator ergänzt und mit einer aus Fotos erstellten Textur überzogen.
In diesem Abschnitt des Buches will ich etwas in die grundlegenden Animationsarten eingehen: Rotation, Translation und Skalierung.
Für alle, welche nicht mit diesen 3 Begriffen vertraut sind, werden diese hier erklärt. Es ist von Vorteil, diese 3 Begriffe zu verinnerlichen, da sie in der Welt der Animationen zum Grundwissen gehören.

Rotation Bearbeiten

Die Rotation bezeichnet die Drehung eines Objekts um seine Achse(n). Ist ein Objekt in seiner Grundstellung, so dreht es sich um seinen Nullpunkt. Der Fachbegriff für den Nullpunkt ist der “Pivot”-Punkt (engl. Pivot = Achse, Drehachse).
Wenn Sie z.B. in Blender einen Würfel erstellen, welcher im Koordinatensystem auf allen drei Achsen 0.5 Einheiten lang ist und diesen nicht verschieben, so wird dieser als zuvor exportiertes und in Irrlicht geladenes Mesh auch um den Nullpunkt rotiert. Verschieben Sie den selben Würfel nun um -0.5 Einheiten auf einer Achse, so verschiebt sich auch der Mittelpunkt des Würfels um den selben Wert und er dreht sich nicht mehr um seine Achse, sondern wandert dann um eine Umlaufbahn um den Mittelpunkt. Dies kann verwendet werden, um z.B. ein Solarsystem zu animieren, indem man durch die Entfernung des Objekts zum Nullpunkt den Orbit festlegt.
Wenn Sie in Blender feststellen wollen, wo sich der Mittelpunkt des selektierten Objekts befindet, so drücken Sie im Object-Mode die Taste “N” oder klicken Sie im Menü der 3D-Ansicht auf “Object” und dann auf “Transform Properties”.

Translation Bearbeiten

Die Translation ist der Begriff für die Bewegung eines Objekts entlang einer oder mehrerer Achsen. Dies kann über verschiedene Methoden geschehen, was je nach verhalten des Objekts variiert. Eine Möglichkeit ist die Multiplikation der Positionsmatrix des Objekts mit einem Richtungsvektor, welcher die Geschwindigkeit eines Objekts in einer bestimmten Richtung speichert. Hätte nun ein Objekt eine Geschwindigkeit von 0.5 Einheiten entlang der X-Achse und 0.7 Einheiten entlang der Z-Achse, so würde man sich zuerst einmal den aktuellen Positionsvektor der Scene-Node abholen und in einen Vektor speichern :

core::vector3df v3dPosVec = node->getPosition();

Danach erstellt man einen Richtungsvektor, welcher die Geschwindigkeit des Objekts darstellt und addiert diesen zum Positionsvektor des Objekts :

core::vector3df v3dSpeedVec = core::vector3df(0.5f, 0.0f, 0.7f);
node->setPosition(v3dPosVec + v3dSpeedVec);

Skalierung Bearbeiten

Die Skalierung bezeichnet eine Methode, ein Objekt in seiner Größe entlang einer oder mehrerer Achsen zu verändern. Wenn z.B. ein geladenes Mesh zu groß ist, so kann man es mit

node->setScale(core::vector3df(0.5f, 0.5f, 0.5f));

auf die Hälfte seiner Größe skalieren. Sollten Sie den Wert der Skalierung in den negativen Wertebereich setzen, so kehren sich die Normalen des Objekts um. Wird zum Beispiel ein Würfel mit

node->setScale(core::vector3df(-1.0f, -1.0f, -1.0f));

skaliert, so erscheint dieser Würfel plötzlich als Raum, da seine Normalen nun anstatt nach außen jetzt nach innen zeigen. Dies ist allerdings keine gängige Praxis.

Eine Beispielanwendung Bearbeiten

 
Das Fenster der Beispielanwendung

In diesem Abschnitt wollen wir eine Beispielanwendung programmieren, die drei Würfel darstellt, von welchen sich einer um die Z-Achse rotiert, einer über die X-Achse skaliert und einer entlang der Y-Achse bewegt ("translationiert") wird.
Zuerst erstellen wir wieder wie gewohnt unser Irrlicht-Device, den Video-Treiber und den Szene-Manager, welchem wir dann 3 Würfel mit einer Seitenlänge von 2.0 Einheiten hinzufügen :

//Erstellen der SceneNodes
scene::ISceneNode * Linker_Wuerfel = smgr->addCubeSceneNode(2.0f);
scene::ISceneNode * Rechter_Wuerfel = smgr->addCubeSceneNode(2.0f);
scene::ISceneNode * Oberer_Wuerfel = smgr->addCubeSceneNode(2.0f);

Danach laden wir die Textur für die 3 Würfel und legen fest, dass wir keine Beleuchtung verwenden :

//Laden der Texturen
Linker_Wuerfel->setMaterialTexture(0, driver->getTexture("IntP_Brick.png"));
Rechter_Wuerfel->setMaterialTexture(0, Linker_Wuerfel->getMaterial(0).getTexture(0));	
Oberer_Wuerfel->setMaterialTexture(0, Linker_Wuerfel->getMaterial(0).getTexture(0));

//Keine Lichtberechnung
Linker_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);
Rechter_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);
Oberer_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);

Da wir die Würfel nicht alle auf der selben Position haben wollen, verschieben wir diese an den jeweils gewünschten Platz:

//Positionieren der 3 Würfel
Linker_Wuerfel->setPosition(core::vector3df(-2, -1, 3)); //Nach links hinten
Rechter_Wuerfel->setPosition(core::vector3df(2, -1, 3)); //Nach rechts hinten
Oberer_Wuerfel->setPosition(core::vector3df(0, 1.5, 3)); //Nach oben hinten

Da wir einen Würfel drehen wollen, benötigen wir eine globale Variable zum Speichern des Rotationswinkels:

//Unser 32Bit-Float zum Speichern des Rotationswinkels
f32 fWinkel = 0.0;

Den Winkel in der Variable fWinkel lassen wir bei jedem Ablauf der Delta-Zeit um 1 Grad erhöhen und setzen ihn bei erreichen von 360 Grad wieder zurück. Anschließend erteilen wir dem linken Würfel die Anweisung, dass er sich auf die in fWinkel enthaltene Gradzahl um die Z-Achse drehen soll:

//Den linken Würfel um die Z-Achse drehen
Linker_Wuerfel->setRotation(core::vector3df(0.0f, 0.0f, fWinkel));

Nun geht es darum, den rechten Würfel zu skalieren. Da wir nicht in den negativen Wertebereich fallen wollen und aber der Würfel seine Skalierung ständig ändern soll, bietet sich die Verwendung der Sinus-Funktion an. Wir können anhand des Sinus-Wertes der Gradzahl, welche in fWinkel enthalten ist, eine animierte Bewegung der Skalierung des rechten Würfels erhalten. Da der Sinus-Wert uns aber je nach Gradzahl einen Wert von -1,0 bis +1.0 liefert, addieren wir einfach 1.0 zum Wert hinzu, um im Bereich von 0.0 bis 2.0 zu bleiben. Da unsere Skalierung dann aber zu schnell geht, teilen wir einfach fWinkel durch 20, um die Skalierung etwas zu bremsen :

//Den rechten Würfel skalieren
Rechter_Wuerfel->setScale(core::vector3df((1.0f+sin(fWinkel / 20.0f)), 1.0f, 1.0f));

Nun können wir auch den oberen Würfel entlang der Y-Achse hin und her bewegen, indem wir einfach das selbe Prinzip wie beim rechten Würfel anwenden. Hier lassen wir den oberen Würfel entlang einer Sinus-Kurve auf und ab bewegen. Damit er weit genug oben bleibt, addieren wir zum Sinus-Wert 1.5 hinzu und bremsen die Animation etwas, indem wir fWinkel durch 20 teilen :

//Den oberen Würfel verschieben
Oberer_Wuerfel->setPosition(core::vector3df(0, (1.5f + sin(fWinkel / 20.0f)), 3));

Hier nochmal der komplette Quellcode zum Beispiel :

//Einbinden der Header-Datei von Irrlicht
#include <irrlicht.h>

//Einbinden der Namespaces
using namespace irr;
using namespace core;
using namespace video;

//Die Hauptprozedur main()
int main()
{
	//Unser Irrlicht-Device erstellen und initialisieren
	IrrlichtDevice *device =
		createDevice( video::EDT_OPENGL, dimension2d<u32>(640, 480), 32,
			false, false, false, 0);

	//Konnte das Device erstellt werden ?
	if (!device)
		return 1; //Falls nicht, Fehlercode zurückgeben und Programm abbrechen

	//Den Text des Hauptfensters festlegen
	device->setWindowCaption(L"Rotation,Translation u. Skalierung von Objekten !");

	//Den Videotreiber erstellen und Zeiger aus dem Device abholen
	IVideoDriver* driver = device->getVideoDriver();	
	
	//Einen Szene-Manager erstellen und Zeiger aus dem Device abholen
	scene::ISceneManager* smgr = device->getSceneManager();
	
	//Erstellen der SceneNodes
	scene::ISceneNode * Linker_Wuerfel = smgr->addCubeSceneNode(2.0f);
	scene::ISceneNode * Rechter_Wuerfel = smgr->addCubeSceneNode(2.0f);
	scene::ISceneNode * Oberer_Wuerfel = smgr->addCubeSceneNode(2.0f);
	
	//Laden der Texturen
	Linker_Wuerfel->setMaterialTexture(0, driver->getTexture("IntP_Brick.png"));
	Rechter_Wuerfel->setMaterialTexture(0, Linker_Wuerfel->getMaterial(0).getTexture(0));	
	Oberer_Wuerfel->setMaterialTexture(0, Linker_Wuerfel->getMaterial(0).getTexture(0));

	//Keine Lichtberechnung
	Linker_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);
	Rechter_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);
	Oberer_Wuerfel->setMaterialFlag(EMF_LIGHTING, false);

	//Positionieren der 3 Würfel
	Linker_Wuerfel->setPosition(core::vector3df(-2, -1, 3)); //Nach links hinten
	Rechter_Wuerfel->setPosition(core::vector3df(2, -1, 3)); //Nach rechts hinten
	Oberer_Wuerfel->setPosition(core::vector3df(0, 1.5, 3)); //Nach oben hinten
	
	//Unser 32Bit-Float zum Speichern des Rotationswinkels
	f32 fWinkel = 0.0;	

	//Unsere Variablen zur Zeitsteuerung
	u32 uiZuletztGestoppt = device->getTimer()->getTime(); //Aktuelle Zeit stoppen
	const u32 uiDeltaTime = 50; //Winkel in fWinkel alle 50ms erhöhen

	//Einen GUI_Manager erstellen und Zeiger aus dem Device abholen
	gui::IGUIEnvironment* guienv = device->getGUIEnvironment();

	//Ein Text-Element definieren und an den GUI-Manager übergeben
	gui::IGUIStaticText* GUI_debug_text = guienv->addStaticText
		(L"",rect<s32>(5, 5, 300, 300),false,true,0,-1,false);

	//Eine Kamera erstellen
	smgr->addCameraSceneNode(0, core::vector3df(0,0,-2), core::vector3df(0,0,0));	

	//Während das Device aktiv ist ...
	while(device->run())
	{		
		//Szene beginnen
		driver->beginScene(true, true, SColor(3,150,203,255));
		
		//Nach Ablauf von Delta Time
		if (device->getTimer()->getTime() > (uiZuletztGestoppt + uiDeltaTime))
		{
			//Neuen Zeitpunkt merken
			uiZuletztGestoppt = device->getTimer()->getTime();
			//Winkel erhöhen
			fWinkel += 1.0f;
			//Nicht über 360 Grad drehen
			if (fWinkel > 360.0f) fWinkel =0.00f;
		}
		
		//Den linken Würfel um die Z-Achse drehen
		Linker_Wuerfel->setRotation(core::vector3df(0.0f, 0.0f, fWinkel));

		//Den rechten Würfel skalieren
		Rechter_Wuerfel->setScale(core::vector3df((1.0f+sin(fWinkel / 20.0f)), 1.0f, 1.0f));

		//Den oberen Würfel entlang Y verschieben
		Oberer_Wuerfel->setPosition(core::vector3df(0, (1.5f + sin(fWinkel / 20.0f)), 3));

		//Dem Szenemanager sagen, dass er alle Nodes zeichnen soll
		smgr->drawAll();		

		//String erstellen
		core::stringw tmp(L"Rotation,Translation u. Skalierung von Objekten in Irrlicht\nTreiber : ");
		tmp += driver->getName();
		tmp += L"\n FPS: ["; tmp += driver->getFPS();
		tmp += "]\nDreiecke gezeichnet : ";
		tmp += driver->getPrimitiveCountDrawn();;
		tmp += "\nGrafikkarte ist von : ";
		tmp += driver->getVendorInfo();
		tmp += "\nLetzter Zeitstopp : ";
		tmp += uiZuletztGestoppt;
		tmp += "\nRotationswinkel: ";
		tmp += fWinkel;
		tmp += "\nSkalierungsfaktor : ";
		tmp += (1.0f+sin(fWinkel / 20.0f));
		tmp += "\nTranslation (Y-Achse): ";
		tmp += (1.5f + sin(fWinkel / 20.0f));

		//String anzeigen
		GUI_debug_text->setText(tmp.c_str());		

		//Dem GUI-Manager sagen, dass er alle GUIs zeichnen soll 
		guienv->drawAll();

		//Szene beenden
		driver->endScene();
	}
	//Das Device freigeben
	device->drop();
	
	//Keinen Fehler zurückgeben
	return 0;
}