#include "ExaminerViewer.h"
#include "SimNode.h"
#include "SimTriangle.h"
#include "RTMSimulator.h"

#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoExtSelection.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoOrthographicCamera.h>
#include <Inventor/actions/SoBoxHighlightRenderAction.h>
#include <Inventor/details/SoFaceDetail.h>
#include <Inventor/SoPrimitiveVertex.h>
#include <Inventor/SoOffscreenRenderer.h>
#include <Inventor/Qt/SoQtRenderArea.h>
#include <Inventor/actions/SoCallbackAction.h>

#include <Inventor/Qt/SoQt.h>
#include <Inventor/SbBSPTree.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoIndexedLineSet.h>
#include <Inventor/nodes/SoPointSet.h>
#include <Inventor/Qt/widgets/SoQtThumbWheel.h>

//---------------
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoTriangleStripSet.h>
//---------------

#include <QFileDialog>
#include <QImage>
#include <QGridLayout>
#include <QSlider>
#include <QLabel>
#include <QLineEdit>
#include <iostream>
#include <vector>
#include <algorithm>

#define MAX_TRANSPARENCY 10


SoExtSelection*         ExaminerViewer::s_pSelection;
bool                    ExaminerViewer::s_fViewerIsEmpty;
SbList<int32_t>         ExaminerViewer::s_myidxlist;
SbBSPTree               ExaminerViewer::s_mybsptree;
SbBSPTree               ExaminerViewer::s_mypointbsptree;
SoCoordinate3*          ExaminerViewer::s_mycoords = NULL;
SoIndexedLineSet*       ExaminerViewer::s_mylineset = NULL;
SoCoordinate3*          ExaminerViewer::s_mypointcoords = NULL;
SoPointSet*             ExaminerViewer::s_mypointset = NULL;
std::vector<SimNode*>   ExaminerViewer::s_SelectedNodes;
RTMSimulator*           ExaminerViewer::s_pSimulator;

int ExaminerViewer::s_selection_trianglecounter =0;
int ExaminerViewer::s_selection_linecounter =0;
int ExaminerViewer::s_selection_pointcounter =0;


//***********************************************************************************
// class ExaminerViewer
// This class was mainly copied from SoQt library. Minor Changes have been made,
// for adding some additional controls to the ExaminerViewer class.
//***********************************************************************************

/**
 * Creates an ExaminerViewer object to display 3D models.
 * This class derives from the SoQtExaminerViewer class.
 * It extends its base class with some further controlls specific to RTMSimulator.
 * @param parent Parent Widget for the new created object
 * @param pSimulator RTMSimulator object that will deliver the simulation graph to the new created viewer object.
 */
ExaminerViewer::ExaminerViewer(QWidget * parent,RTMSimulator* pSimulator)
    : SoQtExaminerViewer(parent, NULL, TRUE, SoQtFullViewer::BUILD_ALL, SoQtFullViewer::BROWSER, FALSE),
    m_numVertices(0),
    m_absRoot(NULL)
{
    s_pSimulator = pSimulator;
    m_appButtons = new SbPList;

    // Explicitly trigger the construction of viewer decorations.
    m_pBaseWidget = this->buildWidget(this->getParentWidget());
    this->setBaseWidget(m_pBaseWidget);
}


/**
 * Destructor for ExaminerViewer objects
 */
ExaminerViewer::~ExaminerViewer()
{}

/**
 * Builds controls on left side of the viewer area.
 * Compared to the base class SoQtExaminerViewer, a control for the cell size has been added.
 * @param parent Parent widget for the new created objects.
 * @returns Pointer to the new created widget, that contains the created controls.
 */
QWidget* ExaminerViewer::buildLeftTrim(QWidget * parent)
{
    QWidget * w = new QWidget(parent);
    w->setFixedWidth(45);

    QGridLayout * gl = new QGridLayout(w);
    gl->addWidget(this->buildAppButtons(w), 0, 0);

    SoQtThumbWheel * t = new SoQtThumbWheel(SoQtThumbWheel::Vertical, w);
    this->leftWheel = t;
    t->setRangeBoundaryHandling(SoQtThumbWheel::ACCUMULATE);
    this->leftWheelVal = t->value();

    QObject::connect(t, SIGNAL(wheelMoved(float)), this, SLOT(leftWheelChanged(float)));
    QObject::connect(t, SIGNAL(wheelPressed()), this, SLOT(leftWheelPressed()));
    QObject::connect(t, SIGNAL(wheelReleased()), this, SLOT(leftWheelReleased()));


    QLabel* cellSizeLabel = new QLabel(w);
    cellSizeLabel->setMaximumHeight(30);
    cellSizeLabel->setPixmap(QIcon(":/img/cell_size.png").pixmap(20,20));
    m_pCellSizeWheel = new SoQtThumbWheel(SoQtThumbWheel::Vertical, w);
    m_pCellSizeWheel->setMaximumHeight(70);
    QObject::connect(m_pCellSizeWheel, SIGNAL(wheelMoved(float)), this, SLOT(cellSizeWheelChanged(float)));

    m_pCellSizeDisplayLabel = new QLabel(w);
    m_pCellSizeDisplayLabel->setMaximumHeight(30);
    m_pCellSizeDisplayLabel->setMaximumWidth(24);

    QVBoxLayout* cellSizeLayout = new QVBoxLayout;
    cellSizeLayout->addWidget(cellSizeLabel);
    cellSizeLayout->addWidget(m_pCellSizeDisplayLabel);
    cellSizeLayout->addWidget(m_pCellSizeWheel);

    gl->addLayout(cellSizeLayout, 2, 0, Qt::AlignBottom | Qt::AlignHCenter);
    gl->addWidget(t, 4, 0, Qt::AlignBottom | Qt::AlignHCenter);
    gl->activate();

    return w;
}

/**
 * Builds controls on right side of the viewer area.
 * @param parent Parent widget for the new created objects.
 * @returns Pointer to the widget that contains the new created controls.
 */
QWidget * ExaminerViewer::buildRightTrim(QWidget * parent)
{
    QWidget * w = new QWidget(parent);
    w->setFixedWidth(30);

    SoQtThumbWheel * t = new SoQtThumbWheel(SoQtThumbWheel::Vertical, w);
    this->rightWheel = t;
    t->setRangeBoundaryHandling(SoQtThumbWheel::ACCUMULATE);
    this->rightWheelVal = t->value();

    QSlider* gridTransparencySlider = new QSlider(Qt::Vertical, w);
    gridTransparencySlider->setMaximum(MAX_TRANSPARENCY);
    gridTransparencySlider->setMinimum(0);
    gridTransparencySlider->setTickInterval(1);
    gridTransparencySlider->setValue(0);
    gridTransparencySlider->setMaximumHeight(50);
    gridTransparencySlider->setFixedWidth(8);

    QSlider* nodeTransparencySlider = new QSlider(Qt::Vertical, w);
    nodeTransparencySlider->setMaximum(MAX_TRANSPARENCY);
    nodeTransparencySlider->setMinimum(0);
    nodeTransparencySlider->setTickInterval(1);
    nodeTransparencySlider->setValue(0);
    nodeTransparencySlider->setMaximumHeight(50);
    nodeTransparencySlider->setFixedWidth(8);

    QLabel* transparencyLabel = new QLabel(w);
    transparencyLabel->setMaximumHeight(30);
    transparencyLabel->setPixmap(QIcon(":/img/alpha.png").pixmap(20,20));
    QVBoxLayout* transparencyLayout = new QVBoxLayout;
    transparencyLayout->addWidget(transparencyLabel);
    QHBoxLayout* sliderLayout = new QHBoxLayout;
    sliderLayout->addWidget(nodeTransparencySlider);
    sliderLayout->addWidget(gridTransparencySlider);
    transparencyLayout->addLayout(sliderLayout);

    QObject::connect(t, SIGNAL(wheelMoved(float)), this, SLOT(rightWheelChanged(float)));
    QObject::connect(t, SIGNAL(wheelPressed()), this, SLOT(rightWheelPressed()));
    QObject::connect(t, SIGNAL(wheelReleased()), this, SLOT(rightWheelReleased()));

    QObject::connect(gridTransparencySlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeTransparency(int)));
    QObject::connect(nodeTransparencySlider, SIGNAL(valueChanged(int)), this, SLOT(setNodeTransparency(int)));

    QGridLayout * l = new QGridLayout(w); //, 3, 1, 2, -1);
    l->setMargin(0);
    l->addWidget(this->buildViewerButtons(w), 0, 0);
    l->addLayout(transparencyLayout, 2, 0, Qt::AlignBottom | Qt::AlignHCenter);
    l->addWidget(t, 4, 0, Qt::AlignBottom | Qt::AlignHCenter);
    l->activate();

    return w;
}

/**
 * Defines the transparency of shapes inside the viewer area.
 * @param value Transparency value: 0 = fully visible, 10 = toatally transparent
 */
void ExaminerViewer::setShapeTransparency(int value)
{
    float alpha = value;
    alpha=alpha/MAX_TRANSPARENCY;

    std::map<int,SimTriangle*>*	triangles = s_pSimulator->triangles();;
    std::for_each(triangles->begin(),triangles->end(), SetTriangleTransparency(alpha));
}

/**
 * Defines the transparency of simulation nodes inside the viewer area.
 * @param value Transparency value: 0 = fully visible, 10 = toatally transparent
 */
void ExaminerViewer::setNodeTransparency(int value)
{
    float alpha = value;
    alpha=alpha/MAX_TRANSPARENCY;
    m_nodeMaterial->transparency.setValue(alpha);
    m_nodeMaterial->touch();
}

/**
 * @returns Transparency value of nodes in the viewer area.
 */
float ExaminerViewer::nodeTransparency()
{
    return *m_nodeMaterial->transparency.getValues(0);
}

/**
 * Moves the viewing camera to a specified position.
 * @param x Position of camera on X axis
 * @param y Position of camera on Y axis
 * @param z Position of camera on Z axis
 */
void ExaminerViewer::moveCamera(float x, float y, float z)
{
    SoCamera* cam = getCamera();
    cam->position.setValue(x,y,z);
}

/**
 * Creates a rotation for the camera based on a quaternion.
 */
void ExaminerViewer::rotateCamera(float val0, float val1, float val2, float val3)
{
    SoCamera* cam = getCamera();
    cam->orientation.setValue(SbRotation(val0,val1,val2,val3));
}

/**
 * Slot that should be called when the left rotary wheel is pressed, to get ready for a movement.
 */
void ExaminerViewer::leftWheelPressed(void) { leftWheelStart(); }

/**
 * Slot that should be called when the left rotary wheel is changed, to keep track with the current value.
 * @param v New value of the rotary wheel control
 */
void ExaminerViewer::leftWheelChanged(float v) { leftWheelMotion(v); }

/**
 * Slot that should be called when the left wheel is released, to stop moving the scene graph with the mouse movement.
 */
void ExaminerViewer::leftWheelReleased(void) { leftWheelFinish(); }

/**
 * Slot that should be called when the right rotary wheel is pressed, to get ready for a movement.
 */
void ExaminerViewer::rightWheelPressed(void) { rightWheelStart(); }

/**
 * Slot that should be called when the right rotary wheel is changed, to keep track with the current value.
 * @param v New value of the rotary wheel control
 */
void ExaminerViewer::rightWheelChanged(float v) { rightWheelMotion(v); }

/**
 * Slot that should be called when the right wheel is released, to stop moving the scene graph with the wheel movement.
 */
void ExaminerViewer::rightWheelReleased(void) { rightWheelFinish(); }

/**
 * Slot that should be called when the cell size rotary wheel is changed, to keep track with the current value.
 */
void ExaminerViewer::cellSizeWheelChanged(float v)
{
    if(v >= 0)
    {
        m_pCellSizeDisplayLabel->setText(QString().setNum(v));
        s_pSimulator->setCellSize(v);
    }
    else
    {
        m_pCellSizeWheel->setValue(0);
        cellSizeWheelChanged(0);
    }
}

/**
 * Changes the size of the simulation nodes in the viewer area.
 * @param size New size of the simulation nodes.
 */
void ExaminerViewer::setCellSize(float size)
{
    m_pCellSizeWheel->setValue(size);
    m_pCellSizeDisplayLabel->setText(QString().setNum(size));
    cellSizeWheelChanged(size);
}

/**
 * Checks or unchecks the checkable unit stretch button.
 */
void ExaminerViewer::setUnitStretch(bool m)
{
    m_pUnitStretchButton->setChecked(m);
}

/**
 * @returns Pointer to the application butten specified by index.
 * @param idx Index value for the desired application button.
 */
QPushButton * ExaminerViewer::getAppbutton(const int idx)
{
    return (QPushButton *)m_appButtons->get(idx);
}

/**
 * Creates the appolication buttons belonging to the ExaminerViewer and sets size and focus policies.
 * @param parent Parent widget for the widget containing all the application buttons.
 * @returns Pointer to the widget that contains all the application buttons.
 */
QWidget* ExaminerViewer::buildAppButtons(QWidget * parent)
{
    QWidget * w = new QWidget(parent);
    this->createAppButtons(w, m_appButtons);
    w->move(0, 0);
    w->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));

    QGridLayout * l = new QGridLayout(w);
    l->setMargin(0);
    l->setSpacing(0);

    const int numAppButtons = m_appButtons->getLength();
    for (int i = 0; i < numAppButtons; i++) {
        QPushButton * b = getAppbutton(i);
        b->setFixedSize(30, 30);
        b->setFocusPolicy(Qt::NoFocus);
        l->addWidget(b, i, 0);
    }

    l->activate();
    return w;
}


/**
 * Creates the viewer buttons for the ExaminerViewer widget. (right side of the viewer area)
 * Creates all the signal-slot connections for the buttons.
 * @param parent Parent widget for the buttons.
 * @param buttonlist List to append the buttons to.
 */
void  ExaminerViewer::createViewerButtons(QWidget * parent, SbPList * buttonlist)
{
    QPushButton * pSelectButton = new QPushButton(QIcon(":/img/select.png"),/*tr("select")*/"",parent);
    QPushButton * pSingleSelectButton = new QPushButton(QIcon(":/img/selectSingle.png"),/*tr("select")*/"",parent);
    QPushButton * pDragButton = new QPushButton(QIcon(":/img/drag.png"),/*tr("drag")*/"",parent);
    QPushButton * pHomeButton = new QPushButton(QIcon(":/img/home.png"),/*tr("home")*/"",parent);
    QPushButton * pSetHomeButton = new QPushButton(QIcon(":/img/sethome.png"),/*tr("set home")*/"",parent);
    QPushButton * pViewAllButton = new QPushButton(QIcon(":/img/viewfit.png"),/*tr("view fit")*/"",parent);
    m_pBackgroundColorButton = new QPushButton(QIcon(":/img/backgroundcolor.png"),"",parent);
    m_pPerspectiveButton = new QPushButton(QIcon(":/img/perspective.png"),/*tr("perspective / orthagonal")*/"",parent);

    pSelectButton->setFocusPolicy(Qt::NoFocus);
    pSingleSelectButton->setFocusPolicy(Qt::NoFocus);
    pDragButton->setFocusPolicy(Qt::NoFocus);
    pHomeButton->setFocusPolicy(Qt::NoFocus);
    pSetHomeButton->setFocusPolicy(Qt::NoFocus);
    pViewAllButton->setFocusPolicy(Qt::NoFocus);
    m_pBackgroundColorButton->setFocusPolicy(Qt::NoFocus);
    m_pPerspectiveButton->setFocusPolicy(Qt::NoFocus);

    pSelectButton->setIconSize(QSize(24, 24));
    pSingleSelectButton->setIconSize(QSize(24, 24));
    pDragButton->setIconSize(QSize(24, 24));
    pHomeButton->setIconSize(QSize(24, 24));
    pSetHomeButton->setIconSize(QSize(24, 24));
    pViewAllButton->setIconSize(QSize(24, 24));
    m_pBackgroundColorButton->setIconSize(QSize(24, 24));
    m_pPerspectiveButton->setIconSize(QSize(24, 24));

    pSelectButton->adjustSize();
    pSingleSelectButton->adjustSize();
    pDragButton->adjustSize();
    pHomeButton->adjustSize();
    pSetHomeButton->adjustSize();
    pViewAllButton->adjustSize();
    m_pBackgroundColorButton->adjustSize();
    m_pPerspectiveButton->adjustSize();

    buttonlist->append(pSelectButton);
    buttonlist->append(pSingleSelectButton);
    buttonlist->append(pDragButton);
    buttonlist->append(pHomeButton);
    buttonlist->append(pSetHomeButton);
    buttonlist->append(pViewAllButton);
    buttonlist->append(m_pPerspectiveButton);
    buttonlist->append(m_pBackgroundColorButton);

    m_pPerspectiveButton->setCheckable(TRUE);
    m_pPerspectiveButton->setChecked(TRUE);
    m_pBackgroundColorButton->setCheckable(TRUE);
    m_pBackgroundColorButton->setChecked(FALSE);

    QObject::connect(pSelectButton, SIGNAL(clicked()), this, SLOT(buttonSelectClicked()));
    QObject::connect(pSingleSelectButton, SIGNAL(clicked()), this, SLOT(buttonSingleSelectClicked()));
    QObject::connect(pDragButton, SIGNAL(clicked()), this, SLOT(buttonDragClicked()));
    QObject::connect(pHomeButton, SIGNAL(clicked()), this, SLOT(buttonHomeClicked()));
    QObject::connect(pSetHomeButton, SIGNAL(clicked()), this, SLOT(buttonSetHomeClicked()));
    QObject::connect(pViewAllButton, SIGNAL(clicked()), this, SLOT(buttonViewAllClicked()));
    QObject::connect(m_pBackgroundColorButton, SIGNAL(toggled(bool)), this, SLOT(buttonDefineBackgroundColorClicked(bool)));
    QObject::connect(m_pPerspectiveButton, SIGNAL(toggled(bool)), this, SLOT(setPerspectiveView(bool)));
}

/**
 * Creates all the application buttons for the ExaminerViewer widget. (left side of the viewer area)
 * @param parent Parent widget for the buttons.
 * @param buttonlist List to append the buttons to.
 */
void  ExaminerViewer::createAppButtons(QWidget * parent, SbPList * buttonlist)
{
    m_pUnitStretchButton = new QPushButton(QIcon(":/img/m.png"),"",parent);
    m_pUnitStretchButton->setFocusPolicy(Qt::NoFocus);
    m_pUnitStretchButton->setIconSize(QSize(24, 24));
    m_pUnitStretchButton->adjustSize();
    buttonlist->append(m_pUnitStretchButton);

    m_pUnitStretchButton->setCheckable(TRUE);
    m_pUnitStretchButton->setChecked(FALSE);

    QObject::connect(m_pUnitStretchButton, SIGNAL(toggled(bool)), this, SLOT(setMilliMeterUnit(bool)));
}

/**
 * @returns True if the viewer area is empty.
 */
bool ExaminerViewer::isViewerEmpty()
{
    return s_fViewerIsEmpty;
}

/**
 * Initializes the graph displayed in the ExaminerViewer widget.
 * Defines default colors, creates material nodes, sets default transparency settings,
 * initializes selection controls.
 */
void ExaminerViewer::initGraph()
{
    s_fViewerIsEmpty = TRUE;
    setBackgroundColor(SbColor(0.0, 0.0, 0.0));

    m_root = new SoSeparator();
    m_baseColor = new SoBaseColor;
    m_baseColor->rgb = SbColor(1.0f, 0.0f, 0.0f); //grey
    m_nodeMaterial = new SoMaterial();
    m_nodeMaterial->transparency.setValue(0.0f);
    m_root->addChild(m_nodeMaterial);
    m_root->addChild(m_baseColor);
    initSelection();
    setFeedbackVisibility(TRUE);

    //set up root nodes for displaying shapes
    m_shapeRoot = new SoSeparator;
    m_shapeDrawStyle = new SoDrawStyle();
    m_shapeDrawStyle->style = SoDrawStyle::FILLED;
    m_shapeRoot->addChild(m_shapeDrawStyle);
    m_root->addChild(m_shapeRoot);
    // Shape hints tells the ordering of polygons.
    // This ensures double-sided lighting.
    SoShapeHints *shapeHints = new SoShapeHints;
    shapeHints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE;
    m_shapeRoot->addChild(shapeHints);

    if(isViewerEmpty())
    {
        // allow red lines for selection
        SoSeparator * overlaylines = make_line_sg();
        m_absRoot->addChild(overlaylines);
    }
}

/**
 * Clears the ExaminerViewer graph.
 */
void ExaminerViewer::clear()
{
    m_numVertices=0;
    setSceneGraph(NULL);
    m_absRoot->removeAllChildren();
    setSceneGraph(m_absRoot);

    initGraph();
}

/**
 * Saves a single snapshot to a PNG file.
 * @param strFileName Fully qualified path to the file where the pictures should be stored.
 */
void ExaminerViewer::createSnapShot(QString strFileName)
{
    SoOffscreenRenderer imgRenderer(getViewportRegion());
    SoNode * root = getSceneManager()->getSceneGraph();
    imgRenderer.setBackgroundColor(getBackgroundColor());
    imgRenderer.render(root);

    if( strFileName.isEmpty() )
    {
        strFileName = QFileDialog::getSaveFileName( s_pSimulator, tr("Save snapshot as"), QString(), "Images (*.png)");
    }
    if(!strFileName.isEmpty())
    {
        QImage image;
        writeToImage(image,imgRenderer);
        image.save(strFileName,"png");
    }
}

/**
 * Initializes the selection Controls.
 * Defines all the selection settings such as lasso mode, all shapes, behavior of shift key, etc.
 */
void ExaminerViewer::initSelection()
{
    s_pSelection = new SoExtSelection;
    s_pSelection->lassoPolicy = SoExtSelection::PART;
    s_pSelection->policy = SoSelection::SHIFT;
    s_pSelection->lassoType = SoExtSelection::LASSO;
    s_pSelection->lassoMode = SoExtSelection::ALL_SHAPES;

    s_pSelection->setLassoColor(SbColor(0.9f,0.7f,0.2f));
    s_pSelection->setLassoWidth(3);
    s_pSelection->setOverlayLassoPattern(0xf0f0);
    s_pSelection->animateOverlayLasso(FALSE);
    s_pSelection->addStartCallback(start_callback);
    s_pSelection->addFinishCallback(finish_callback);
    s_pSelection->setTriangleFilterCallback(triangleCB, NULL);
    s_pSelection->setLineSegmentFilterCallback(lineCB, NULL);
    s_pSelection->setPointFilterCallback(pointCB, NULL);


    SoEventCallback * cb = new SoEventCallback;
    cb->addEventCallback(SoKeyboardEvent::getClassTypeId(), event_cb, NULL);
    m_root->insertChild(cb, 0);

    s_pSelection->addChild(m_root);

    if(m_absRoot == NULL)
    {
        m_absRoot = new SoSeparator;
        m_absRoot->ref();
        SoPerspectiveCamera* cam = new SoPerspectiveCamera;
        cam->position.setValue(100,0,0);
        m_absRoot->addChild(cam);
        setCamera(cam);
        setSceneGraph(m_absRoot);
    }
    m_absRoot->addChild(s_pSelection);
    setTransparencyType(SoGLRenderAction::SORTED_OBJECT_BLEND);
    setGLRenderAction(new SoBoxHighlightRenderAction(SbViewportRegion()));
}

/**
 * Adds a single simulation node to the ExaminerViewer.
 * @param id ID for the new created node.
 * @param x X coordinate for the node.
 * @param y Y coordinate for the node.
 * @param z Z coordinate for the node.
 * @returns Pointer to the new created SimNode object.
 */
SimNode* ExaminerViewer::addNode(int id, double x, double y, double z)
{
    if(isViewerEmpty())
    {
        // allow red lines for selection
        SoSeparator * overlaylines = make_line_sg();
        m_absRoot->addChild(overlaylines);
    }

    SimNode* new_SimNode = new SimNode(id,x,y,z,s_pSimulator);
    m_root->addChild(new_SimNode);
    return new_SimNode;
}

/**
 * Creates a new triangle surface with simulation nodes as vertices and adds it to the scene graph.
 * @param triangleId ID for the new created triangle.
 * @param node1 First vertex
 * @param node2 Second vertex
 * @param node3 Third vertex
 * @returns Pointer to the new created SimTriangle object
 */
SimTriangle* ExaminerViewer::addTriangle(int triangleId, SimNode* node1, SimNode* node2, SimNode* node3)
{
    SimTriangle* triangle = new SimTriangle(triangleId,node1,node2,node3);
    m_shapeRoot->addChild(triangle);
    return triangle;
}

/**
 * Creates a new triangle surface. All required settings for the triangle will be taken from the specified TriangleSettings structure.
 * @param settings Structure that defines all the settings for the new triangle.
 * @returns Pointer to the new created SimTriangle object.
 */
SimTriangle* ExaminerViewer::addTriangle(TriangleSettings &settings)
{
    SimTriangle* triangle = new SimTriangle(settings);
    m_shapeRoot->addChild(triangle);
    return triangle;
}

/**
 * Removes a single node from the scene graph.
 * @param node Pointer to the node object to be removed from the scene graph.
 */
void ExaminerViewer::removeNode(SimNode* node)
{
    m_root->removeChild(node);
}

/**
 * Switches the ExaminerViewer widget to selection mode.
 */
void ExaminerViewer::buttonSelectClicked()
{
    if(isViewing())
        setViewing(FALSE);
    s_pSelection->policy = SoSelection::SHIFT;
    //without the line below SoQt will disable the perspective button
    m_pPerspectiveButton->setEnabled(TRUE);
}

/**
 * Switches the ExaminerViewer widget to single node selection mode.
 */
void ExaminerViewer::buttonSingleSelectClicked()
{
    if(isViewing())
        setViewing(FALSE);
    s_pSelection->policy = SoSelection::SINGLE;
    //without the line below SoQt will disable the perspective button
    m_pPerspectiveButton->setEnabled(TRUE);
}

/**
 * Switches the ExaminerViewer widget to drag mode.
 */
void ExaminerViewer::buttonDragClicked()
{
    if(!isViewing())
        setViewing(TRUE);
}

/**
 * Stores the current camera position as home position.
 */
void ExaminerViewer::buttonSetHomeClicked()
{
    saveHomePosition();
}

/**
 * Moves the camera to a position where all objects in the ExaminerViewer are visible.
 */
void ExaminerViewer::buttonViewAllClicked()
{
    viewAll();
}

/**
 * Switches the background color of the scene graph.
 * @param white If true, the background will change to white, otherwise it will be changed to black.
 */
void ExaminerViewer::buttonDefineBackgroundColorClicked(bool white)
{
    if(white)
    {
        setBackgroundColor(SbColor(1.0f, 1.0f, 1.0f));
    }
    else
    {
        setBackgroundColor(SbColor(0.0f, 0.0f, 0.0f));
    }
}

/**
 * Moves the viewer camera back to home position.
 */
void ExaminerViewer::buttonHomeClicked()
{
    resetToHomePosition();
}

/**
 * Switches between perspective and orthotype camera.
 * @param perspective If true, perspective camera will be used.
 */
void ExaminerViewer::setPerspectiveView(bool perspective)
{
    SoType perspectivetype = SoPerspectiveCamera::getClassTypeId();
    SoType orthotype = SoOrthographicCamera::getClassTypeId();

    if(perspective)
    {
        setCameraType(perspectivetype);
        m_pPerspectiveButton->setIcon(QIcon(":/img/perspective.png"));
    }
    else
    {
        setCameraType(orthotype);
        m_pPerspectiveButton->setIcon(QIcon(":/img/orthagonal.png"));
    }
}

/**
 * Switches the view between mm and m units.
 * @param mm If true all translations will be multiplied with 1000. If set to false all translations will be multiplied with 1/1000.
 */
void ExaminerViewer::setMilliMeterUnit(bool mm)
{
    if(mm)
    {
        s_pSimulator->rescaleTranslations(1000);
        m_pUnitStretchButton->setIcon(QIcon(":/img/mm.png"));
    }
    else
    {
        s_pSimulator->rescaleTranslations(0.001);
        m_pUnitStretchButton->setIcon(QIcon(":/img/m.png"));
    }
}


//*****************************CALL BACK FUNCTIONS*****************************

/**
 * Callback function for the lasso selection mode. Should be called when select action starts.
 * Sets up and reinitializes the selection controls.
 * @param sel Pointer to the selection object to use.
 */
void ExaminerViewer::start_callback(void * /*userdata*/, SoSelection * sel)
{
    s_selection_trianglecounter = 0;
    s_selection_linecounter = 0;
    s_selection_pointcounter = 0;
    if (sel->policy.getValue() == SoSelection::SINGLE ||
        (sel->policy.getValue() == SoSelection::SHIFT && !((SoExtSelection*)sel)->wasShiftDown()))
    {
        s_mybsptree.clear();
        s_mypointbsptree.clear();
        s_myidxlist.truncate(0);

        // This line is very important or the 'selection-lines' will be selected during the next selection as
        // a part of the original object.
        s_mylineset->coordIndex.setNum(0);
    }
}

/**
 * Callback function for the lasso selection mode. Should be called when the select action finishes for post processing.
 * Adds the SimNode objects in the selection to s_SelectedNodes. And updates the selected objects in the scene graph.
 * @param sel Pointer to the used selection object.
 */
void ExaminerViewer::finish_callback(void * /*userdata*/, SoSelection * sel)
{
    s_SelectedNodes.clear();
    const SoPathList* pListSelected = sel->getList();
    int length = pListSelected->getLength();

    for(int i = 0; i < length; i++)
    {
        SoPath* path = (*pListSelected)[i];
        SoNode* pTempNode = path->getTail();
        if(pTempNode != NULL)
        {
            for(int j=path->getLength()-1 ; pTempNode->getTypeId() != SimNode::getClassTypeId() && pTempNode != path->getHead() && j > 0 ; j--)
            {
                pTempNode = path->getNode(j);
            }
            if(pTempNode->getTypeId() == SimNode::getClassTypeId())
            {
                SimNode* sim_node = (SimNode*) pTempNode;
                s_SelectedNodes.push_back(sim_node);
            }
        }
    }
    sel->touch();

    int n = s_mybsptree.numPoints();
    s_mycoords->point.setNum(n);
    memcpy(s_mycoords->point.startEditing(), s_mybsptree.getPointsArrayPtr(), n*sizeof(SbVec3f));
    s_mycoords->point.finishEditing();

    n = s_myidxlist.getLength();
    s_mylineset->coordIndex.setNum(n);
    memcpy(s_mylineset->coordIndex.startEditing(), s_myidxlist.getArrayPtr(), n*sizeof(int32_t));
    s_mylineset->coordIndex.finishEditing();

    n = s_mypointbsptree.numPoints();
    s_mypointcoords->point.setNum(n);
    memcpy(s_mypointcoords->point.startEditing(), s_mypointbsptree.getPointsArrayPtr(), n*sizeof(SbVec3f));
    s_mypointcoords->point.finishEditing();

    s_pSimulator->updateSelectionData();
}

/**
 * Callback function for the selection rubber band.
 */
SbBool ExaminerViewer::lineCB(void * /*userdata*/, SoCallbackAction * action, const SoPrimitiveVertex * v1, const SoPrimitiveVertex * v2)
{
    const SbMatrix & mm = action->getModelMatrix();

    ++s_selection_linecounter;

    SbVec3f vec1 = v1->getPoint();
    SbVec3f vec2 = v2->getPoint();

    mm.multVecMatrix(v1->getPoint(),vec1);
    mm.multVecMatrix(v2->getPoint(),vec2);

    s_myidxlist.append(s_mybsptree.addPoint(vec1)); // Ugly hack. A 'line' must have 3 points...
    s_myidxlist.append(s_mybsptree.addPoint(vec2));
    s_myidxlist.append(s_mybsptree.addPoint(vec1));

    s_myidxlist.append(-1);

    return FALSE;
}

/**
 * Callback function for the points clicked while selecting a region with the rubber band.
 */
SbBool ExaminerViewer::pointCB(void * /*userdata*/, SoCallbackAction * action, const SoPrimitiveVertex * v1)
{
    const SbMatrix & mm = action->getModelMatrix();

    ++s_selection_pointcounter;

    SbVec3f vec1 = v1->getPoint();

    mm.multVecMatrix(v1->getPoint(),vec1);

    s_mypointbsptree.addPoint(vec1);

    return FALSE;
}

/**
 * Callback function for the triangle selection mode.
 */
SbBool ExaminerViewer::triangleCB(void * /*userdata*/, SoCallbackAction * action, const SoPrimitiveVertex * v1, const SoPrimitiveVertex * /*v2*/, const SoPrimitiveVertex * /*v3*/)
{
    const SbMatrix & mm = action->getModelMatrix();

    SbVec3f op;
    SbVec3f wp;

    ++s_selection_trianglecounter;

    const SoDetail * detail = v1->getDetail();
    if(!detail || !detail->isOfType(SoFaceDetail::getClassTypeId()))
    {
        return TRUE;
    }

    const SoFaceDetail * fd = (const SoFaceDetail*) detail;
    assert(fd->isOfType(SoFaceDetail::getClassTypeId()));
    assert(fd);
    const SoPointDetail * pd;

    for (int i = 0; i <= fd->getNumPoints(); i++)
    {
        pd = fd->getPoint(i%fd->getNumPoints());
        assert(pd);
        assert(pd->getCoordinateIndex() < action->getNumCoordinates());
        op = action->getCoordinate3(pd->getCoordinateIndex());
        mm.multVecMatrix(op, wp);
        s_myidxlist.append(s_mybsptree.addPoint(wp));
    }

    s_myidxlist.append(-1);
    return FALSE;
}

/**
 * Draws a line to display the rubber band.
 */
SoSeparator* ExaminerViewer::make_line_sg(void)
{
    SoSeparator * sep = new SoSeparator;
    SoLightModel * lm = new SoLightModel;
    lm->model = SoLightModel::BASE_COLOR;
    sep->addChild(lm);
    SoBaseColor * col = new SoBaseColor;
    col->rgb = SbColor(1.0f, 0.0f, 0.0f);
    sep->addChild(col);
    SoDrawStyle * ds = new SoDrawStyle;
    ds->lineWidth = 2;
    ds->pointSize = 4;
    sep->addChild(ds);

    SoSeparator *linesep = new SoSeparator;
    sep->addChild(linesep);
    s_mycoords = new SoCoordinate3;
    s_mylineset = new SoIndexedLineSet;
    linesep->addChild(s_mycoords);
    linesep->addChild(s_mylineset);
    SoSeparator *pointsep = new SoSeparator;
    sep->addChild(pointsep);
    s_mypointcoords = new SoCoordinate3;
    s_mypointcoords->point.setNum(0);
    s_mypointset = new SoPointSet;
    pointsep->addChild(s_mypointcoords);
    pointsep->addChild(s_mypointset);
    return sep;
}

/**
 * Callback function triggered for selection / deselection.
 * Updates the selected region.
 */
void ExaminerViewer::made_selection( void * /*userdata*/, SoPath * /*path*/ )
{
    s_pSelection->touch();
}

/**
 * General event callback function for the ExaminerViewer.
 * Determines appropriate action by event type.
 */
void ExaminerViewer::event_cb(void * /*userdata*/, SoEventCallback * node)
{
    const SoEvent * event = node->getEvent();

    if (SO_KEY_PRESS_EVENT(event, V))
    {
        s_pSelection->lassoMode = SoExtSelection::VISIBLE_SHAPES;
        fprintf(stdout,"VISIBLE_SHAPES mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, A))
    {
        s_pSelection->lassoMode = SoExtSelection::ALL_SHAPES;
        fprintf(stdout,"ALL_SHAPES mode\n");
    }

    if (SO_KEY_PRESS_EVENT(event, F))
    {
        s_pSelection->lassoPolicy = SoExtSelection::FULL;
        fprintf(stdout,"FULL mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, P))
    {
        s_pSelection->lassoPolicy = SoExtSelection::PART;
        fprintf(stdout,"PART mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, NUMBER_1))
    {
        s_pSelection->lassoPolicy = SoExtSelection::FULL_BBOX;
        fprintf(stdout,"FULL_BBOX mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, NUMBER_2))
    {
        s_pSelection->lassoPolicy = SoExtSelection::PART_BBOX;
        fprintf(stdout,"PART_BBOX mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, L))
    {
        s_pSelection->lassoType = SoExtSelection::LASSO;
        fprintf(stdout,"Lasso mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, R))
    {
        s_pSelection->lassoType = SoExtSelection::RECTANGLE;
        fprintf(stdout,"Rectangle mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, N))
    {
        s_pSelection->lassoType = SoExtSelection::NOLASSO;
        fprintf(stdout,"NoLasso mode\n");
    }
    if (SO_KEY_PRESS_EVENT(event, S))
    {
        s_pSelection->policy = SoSelection::SINGLE;
        fprintf(stdout,"Single selection\n");
    }
    if (SO_KEY_PRESS_EVENT(event, H))
    {
        s_pSelection->policy = SoSelection::SHIFT;
        fprintf(stdout,"Shift selection\n");
    }
    if (SO_KEY_PRESS_EVENT(event, T))
    {
        s_pSelection->policy = SoSelection::TOGGLE;
        // FIXME: should write up the necessary code in this example to
        // make toggle work for polygon/line/point primitive sets
        // aswell. 20020808 mortene.
        fprintf(stdout,"Toggle selection (note: only works for full shapes)\n");
    }
}
//**************************END OF CALL BACK FUNCTIONS*************************

/**
 * @returns Vector containing pointers to all currently selected simulation nodes.
 */
std::vector<SimNode*> ExaminerViewer::getSelection()
{
    return s_SelectedNodes;
}

/**
 * Writes the camera view to an image.
 * @param img Image object to write to.
 * @param renderer SoOffscreenRenderer to use for writing the image.
 */
bool ExaminerViewer::writeToImage (QImage& img, SoOffscreenRenderer& renderer /*, const char * filetypeext*/)
{
    const unsigned char * bytes = renderer.getBuffer();
    SbVec2s size = renderer.getViewportRegion().getViewportSizePixels();

    int numcomponents = (int) renderer.getComponents();
    int width = (int)size[0];
    int height = (int)size[1];;

    QImage image(width, height, QImage::Format_RGB32);

    QRgb * bits = (QRgb*) image.bits();
    for (int y = 0; y < height; y++) {
        const unsigned char * line = &bytes[width*numcomponents*(height-(y+1))];
        for (int x = 0; x < width; x++) {
            switch (numcomponents) {
                                default:
                                case 1:
                *bits++ = qRgb(line[0], line[0], line[0]);
                break;
                                case 2:
                *bits++ = qRgba(line[0], line[0], line[0], line[1]);
                break;
                                case 3:
                *bits++ = qRgb(line[0], line[1], line[2]);
                break;
                                case 4:
                *bits++ = qRgba(line[0], line[1], line[2], line[3]);
                break;
            }
            line += numcomponents;
        }
    }
    img=image;
    return true;
} 

#ifdef DEBUG
void ExaminerViewer::testMultipleTriangleStrips()
{
    SoSeparator* triangleSeparator1 = new SoSeparator;
    m_shapeRoot->addChild(triangleSeparator1);

    SoMaterial* material1 = new SoMaterial;
    float colors1[1][3] =	{	{ 0.0, 0.0,  1.0 }	};
    material1->diffuseColor.setValues(0, 1, colors1);
    material1->transparency.setValue(0.0);
    triangleSeparator1->addChild(material1);

    int32_t numVerticesPerStripSet = 3;
    SoCoordinate3* triangle1 = new SoCoordinate3;
    triangle1->point.set1Value(0,0,0,0);
    triangle1->point.set1Value(1,1,0,0);
    triangle1->point.set1Value(2,0,1,0);

    SoTriangleStripSet *strip1 = new SoTriangleStripSet;
    strip1->numVertices.setValues(0, 1, &numVerticesPerStripSet);
    triangleSeparator1->addChild(triangle1);
    triangleSeparator1->addChild(strip1);

    SoSeparator* triangleSeparator2 = new SoSeparator;
    m_shapeRoot->addChild(triangleSeparator2);

    SoMaterial* material2 = new SoMaterial;
    float colors2[1][3] =	{	{ 1.0, 0.0,  0.0 }	};
    material2->diffuseColor.setValues(0, 1, colors2);
    material2->transparency.setValue(0.0);
    triangleSeparator2->addChild(material2);

    SoCoordinate3* triangle2 = new SoCoordinate3;
    triangle2->point.set1Value(0,1,1,0);
    triangle2->point.set1Value(1,2,1,0);
    triangle2->point.set1Value(2,1,2,0);

    SoTriangleStripSet *strip2 = new SoTriangleStripSet;
    strip2->numVertices.setValues(0, 1, &numVerticesPerStripSet);
    triangleSeparator2->addChild(triangle2);
    triangleSeparator2->addChild(strip2);
}
#endif //DEBUG
