/*-------------------------------------------------------------
 *  Dies ist ein Beispiel vom "The Inventor Mentor" 
 *  (Kapitel 10, 2. Beispiel), welches von Stephan Siemen
 *  geaendert wurde um mit Coin und SoQt zu kompileren. 
 *
 *  Der Code kann mit 
 *    g++ SoQtDraw.cpp -o SoQtDraw -lCoin -lSoQt -I$QTDIR/include
 *  uebersetzt werden.
 *
 *-----------------------------------------------------------*/

#include <Inventor/Sb.h>
#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/nodes/SoCamera.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoPointSet.h> 
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/sensors/SoTimerSensor.h>

// Timer Sensor - global definiert 
// Rotiert 90 Grad/Sekunde und wird 30 mal/Sekunde ausgefuehrt
SoTimerSensor *ticker;
#define ROTATION_WINKEL M_PI/60.0
#define UPDATE_RATE 1.0/30.0

void Projektion(SoQtRenderArea *renderFlaeche,int mausX,int mausY,SbVec3f &schnittpunkt)
{
   // Nimm die x un y position der maus und normalise diese zu [0,1]
   SbVec2s groesse = renderFlaeche->getSize();
   float x = float(mausX) / groesse[0];
   float y = float(groesse[1] - mausY) / groesse[1];
   
   // Erhalte einen Zeiger zur Kamera und erhalte das Kamera-Volumen
   SoGroup  *wurzel = (SoGroup *)  renderFlaeche->getSceneGraph();
   SoCamera *kamera = (SoCamera *) wurzel->getChild(0);
   SbViewVolume kameraVolumen;
   kameraVolumen = kamera->getViewVolume();
   
   // Erhalte den Anfangs- und Endpunkt der Linie die durch das 
   // Kameravolumen geht und den Pfad des Mauszeiger durch die 
   // Szene darstellt.
   SbVec3f p0, p1;
   kameraVolumen.projectPointToLine(SbVec2f(x,y), p0, p1);
   
   // Mittelpunkt dieser Linie dient als Punkt zum zeichnen
   schnittpunkt = (p0 + p1) / 2.0;
}



void neuerPunkt(SoQtRenderArea *renderFlaeche, const SbVec3f point)
{
   // Zeiger auf die Elemente im Szenegraphen die veraendert werden soll
   SoGroup *wurzel = (SoGroup *) renderFlaeche->getSceneGraph();
   SoCoordinate3 *PunktKoordinaten = (SoCoordinate3 *) wurzel->getChild(2);
   SoPointSet *punkte = (SoPointSet *) wurzel->getChild(3);
   
   // der neue Punkt wird in die Liste eingefuegt
   PunktKoordinaten->point.set1Value(PunktKoordinaten->point.getNum(), point);
   punkte->numPoints.setValue(PunktKoordinaten->point.getNum());
}



void loeschePunkte(SoQtRenderArea *renderFlaeche)
{
   // Zeiger auf die Elemente im Szenegraphen die veraendert werden soll
   SoGroup *wurzel = (SoGroup *) renderFlaeche->getSceneGraph();
   SoCoordinate3 *PunktKoordinaten = (SoCoordinate3 *) wurzel->getChild(2);
   SoPointSet *punkte = (SoPointSet *) wurzel->getChild(3);
   
   // Loesche alle Werte vom ersten Wert (Index=0)
   PunktKoordinaten->point.deleteValues(0); 
   punkte->numPoints.setValue(0);
}



void tickerCallback(void *userData, SoSensor *)
{
   SoCamera   *Kamera = (SoCamera *) userData;
   SbRotation  rot;
   SbMatrix    mtx;
   SbVec3f     pos;
   
   // Rotieren der Kamera
   pos = Kamera->position.getValue();
   rot = SbRotation(SbVec3f(0,1,0), ROTATION_WINKEL);
   mtx.setRotate(rot);
   mtx.multVecMatrix(pos, pos);
   Kamera->position.setValue(pos);
   
   // Korrektur der Ausrichtung der Kamera
   Kamera->orientation.setValue(Kamera->orientation.getValue() * rot);
}



SbBool ereignisHandler(void *userData, QEvent *einEreignis)
{
   SoQtRenderArea *renderFlaeche = (SoQtRenderArea *) userData;
   QMouseEvent *MausEvent;
   SbVec3f vektor;
   SbBool handled = TRUE;

   switch(einEreignis->type()){
   
     // falls Maustaste gedrueckt wurde ...
     case QEvent::MouseButtonPress:

         MausEvent = (QMouseEvent *) einEreignis;
         if(MausEvent->button() == Qt::LeftButton) {
            Projektion(renderFlaeche,MausEvent->x(), MausEvent->y(), vektor);
            neuerPunkt(renderFlaeche, vektor);
         }
	 else if(MausEvent->button() == Qt::MidButton) {
            ticker->schedule();  // start das rotieren der Kamera
         } 
	 else if(MausEvent->button() == Qt::RightButton) {
           loeschePunkte(renderFlaeche);  // loesche alle Punkte
         }
         break;
	 
     // falls Maustaste losgelassen wurde ...
     case QEvent::MouseButtonRelease:

         MausEvent = (QMouseEvent *) einEreignis;
         if(MausEvent->button() == Qt::MidButton) {
           ticker->unschedule();  // stoppen das rotieren der Kamera
         }
         break;
	 
     // falls die Maus bewegt wurde ...
     case QEvent::MouseMove:
     
         MausEvent = (QMouseEvent *) einEreignis;
         if( MausEvent->state()){ 
           Projektion(renderFlaeche,MausEvent->x(), MausEvent->y(), vektor);
           neuerPunkt(renderFlaeche, vektor);
         }
         break;
      
     default:
         handled = FALSE;
         break;
   }// Ende des switch
      
   return handled;
}


// Hauptfunktion
//  -> erzeugt den Szenengraphen und den Betrachter
int main(int argc, char **argv)
{
   printf("Benutzung von SoQtDraw:\n");
   printf("\tLinke Maustaste:    fuegt Punkte hinzu.\n");
   printf("\tMittlere Maustaste: rotiert die Szene\n");
   printf("\tRechte Maustaste:   loescht alle Punkte\n");

   // Initialisierung von Coin (Open Inventor) und SoQt
   QWidget *HauptFenster = SoQt::init(argv[0]);
   if(HauptFenster == NULL) exit(1);

   // Erzeugen der Wurzel des Szenengraphen
   SoSeparator *wurzel = new SoSeparator;
   wurzel->ref();

   // Hinzufuegen einer Kamera
   SoPerspectiveCamera *kamera = new SoPerspectiveCamera;
   wurzel->addChild(kamera);  // Knoten 0
   
   // Hinzufuegen eines einfachen Lichtmodels
   SoLightModel *licht = new SoLightModel;
   licht->model = SoLightModel::BASE_COLOR;
   wurzel->addChild(licht); // Knoten 1
   
   // Setup der Kamera
   kamera->position.setValue(0, 0, 4);
   kamera->nearDistance.setValue(1.0);
   kamera->farDistance.setValue(7.0);
   kamera->heightAngle.setValue(M_PI/3.0);   
   
   // Knoten um 3D Koordinaten zu speichern
   SoCoordinate3 *punktKoordinaten = new SoCoordinate3;
   // Geometrieknoten um Punkte zu zeichnen
   SoPointSet *punkte = new SoPointSet;
   wurzel->addChild(punktKoordinaten);    // Knoten 2
   wurzel->addChild(punkte); // Knoten 3

   // Timer Sensor um Zeitimpulse an die Kamera zu geben waehrend 
   // die mittlere Maustaste gedrueckt ist
   ticker = new SoTimerSensor(tickerCallback, kamera);
   ticker->setInterval(UPDATE_RATE);

   // Erstellen einer render area for viewing the scene
   SoQtExaminerViewer *renderFlaeche = new SoQtExaminerViewer(HauptFenster);
   renderFlaeche->setSceneGraph(wurzel);
   renderFlaeche->setTitle("SoQtDraw");

   // Setzen des Ereignisbehandlung
   renderFlaeche->setEventCallback(ereignisHandler, renderFlaeche);

   // Anzeigen des Hauptfenster
   renderFlaeche->show();
   SoQt::show(HauptFenster);
   // Endlos-Schleife zur Ereignisbehandlung
   SoQt::mainLoop();

  return 0;
}

