/***************************************************************************
* Copyright (C) 2004 - 2005 by Raphael Langerhorst *
* raphael-langerhorst@gmx.at *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
#include "GOpenGLFrame.h"
#include <GElement.h>
#include <GEnergy.h>
#include <GObject.h>
#include <GDataController.h>
#include <GXmlDataController.h>
#include <GObject.h>
#include <exception>
#include <qapplication.h>
#include <qptrstack.h>
#include <qdom.h>
// @todo GOpenGLForm is depracted, thus the client engine needs to be converted as well.
using namespace std;
using namespace GCS;
using namespace GWE;
namespace GCE
{
GOpenGLFrame::GOpenGLFrame(const GweController* gwe, QWidget* parent, const char* name)
: QGLWidget(parent,name),
Gwe(gwe),
CameraParent(0),
FramesPerSecond(0),
TimeSinceFpsUpdate(0)
{
connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(stopRendering()));
connect(&RedrawTimer,SIGNAL(timeout()),this,SLOT(update()));
}
void GOpenGLFrame::transform(const GForm* form) const //please make sure this is called in a valid OpenGL context!!
{
glTranslated(form->Position.x,form->Position.y,form->Position.z);
if (form->Rotation.x)
glRotated(form->Rotation.x,1,0,0);
if (form->Rotation.y)
glRotated(form->Rotation.y,0,1,0);
if (form->Rotation.z)
glRotated(form->Rotation.z,0,0,1);
}
void GOpenGLFrame::render(const GForm* form, float* RGBA) const
{
GLUquadric* QuadricObject = gluNewQuadric();
Q_CHECK_PTR(QuadricObject);
if (QuadricObject)
{
glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,RGBA);
//@todo draw an ellipsoid, not a sphere
gluSphere(QuadricObject,form->getRadiusMax(),16,16);
gluQuadricNormals(QuadricObject,GLU_SMOOTH);
gluDeleteQuadric(QuadricObject);
}
}
void GOpenGLFrame::renderElement(const GElement* element)
{
// qDebug(QString("drawing child element: ") + QString::number(element->getElementID().getID()));
const GDataController* data = Gwe->getDataController();
GWE::GXmlDataController* data_xml = NULL;
if (data->inherits("GWE::GXmlDataController"))
{
data_xml = (GWE::GXmlDataController*)data;
}
const GObject* object = element->getObject();
if (object->hasForm())
{
const GForm* f = object->getForm();
// const GEnergy* e = object->getEnergy();
float RGBA[4];
const QDomDocument* element_data = object->getElementData();
QDomElement dom_child = element_data->elementsByTagName("appearance").item(0).toElement();
if (dom_child.isNull())
{
// qWarning("element data has no appearance data, using half opaque white colour by default");
RGBA[0]=1;
RGBA[1]=1;
RGBA[2]=1;
RGBA[3]=0.5;
}
dom_child = dom_child.elementsByTagName("colour").item(0).toElement();
if (dom_child.isNull())
{
// qWarning("element data has no colour data, using half opaque white colour by default");
RGBA[0]=1;
RGBA[1]=1;
RGBA[2]=1;
RGBA[3]=0.5;
}
bool ok;
RGBA[0] = dom_child.elementsByTagName("r").item(0).toElement().text().toDouble(&ok);
if (!ok)
{
// qWarning("element data has no red colour data, using default 1");
RGBA[0] = 1;
}
RGBA[1] = dom_child.elementsByTagName("g").item(0).toElement().text().toDouble(&ok);
if (!ok)
{
// qWarning("element data has no green colour data, using default 1");
RGBA[1] = 1;
}
RGBA[2] = dom_child.elementsByTagName("b").item(0).toElement().text().toDouble(&ok);
if (!ok)
{
// qWarning("element data has no blue colour data, using default 1");
RGBA[2] = 1;
}
RGBA[3] = dom_child.elementsByTagName("a").item(0).toElement().text().toDouble(&ok);
if (!ok)
{
// qWarning("element data has no alpha colour data, using default 0.5");
RGBA[3] = 0.5;
}
// qDebug("element position: " + QString::number(f.Position.x) + ", " + QString::number(f.Position.y) + ", " + QString::number(f.Position.z));
glPushMatrix();
transform(f);
//render children
QValueList<GElementID> children = element->getObject()->getChildren();
for ( QValueListIterator<GElementID> it = children.begin(); it != children.end(); it++)
{
const GElement* element = data->read((*it));
Q_CHECK_PTR(element);
if (element)
{
renderElement(element);
}
}
if (RGBA[3] > 0.001)
{
//in case we have a networked environment, let's also see the server
if (data_xml!=NULL && CameraParent == element->getObject()->getParent())
{
QString s = data_xml->getManagingServerOfElement(element->getElementID());
if (!s.isEmpty())
{
this->qglColor(Qt::white);
this->renderText(f->Position.x-this->ViewProperties.ViewPosition.x,f->Position.y-this->ViewProperties.ViewPosition.y,f->Position.z-this->ViewProperties.ViewPosition.z,s);
}
}
render(f,RGBA);
}
glPopMatrix();
}
else
{
//render children
QValueList<GElementID> children = element->getObject()->getChildren();
for ( QValueListIterator<GElementID> it = children.begin(); it != children.end(); it++)
{
const GElement* element = data->read((*it));
Q_CHECK_PTR(element);
if (element)
{
renderElement(element);
}
}
}
}
void GOpenGLFrame::startRendering(int sleep_ms_between_updates_min)
{
this->StopRendering = false;
this->RedrawSleepTime_ms = sleep_ms_between_updates_min;
this->RedrawTimer.start(this->RedrawSleepTime_ms);
this->FpsTimer.start();
}
void GOpenGLFrame::stopRendering()
{
this->StopRendering = true;
}
void GOpenGLFrame::initializeGL()
{
this->makeCurrent();
glClearColor(0,0,0,0); //black background
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //start off with an empty background
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST); //enable depth test
glEnable(GL_BLEND); //enable blending - needed for alpha blending
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //alpha blending
glEnable(GL_LIGHTING); //globally enable lighting
// glEnable(GL_POLYGON_SMOOTH);
//@todo: the light sources are ... a bit outdated, they were useful until 0.3.x versions. A new lighting model should be created.
// create a light source
glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION,1);
float v[] = {-1,5,-3,1};
glLightfv(GL_LIGHT0,GL_POSITION,v);
v[0] = v[1] = v[2] = 0.5;
v[3] = 1;
glLightfv(GL_LIGHT0,GL_AMBIENT,v);
v[0] = 0.5;
v[1] = 0.5;
v[2] = 0.5;
v[3] = 1;
glLightfv(GL_LIGHT0,GL_DIFFUSE,v);
glLightfv(GL_LIGHT0,GL_SPECULAR,v);
glEnable(GL_LIGHT0);
//another one:
// glLightf(GL_LIGHT1,GL_CONSTANT_ATTENUATION,1);
// v[0] = 0;
// v[1] = -5;
// v[2] = 3;
// v[3] = 1;
// glLightfv(GL_LIGHT1,GL_POSITION,v);
// v[0] = v[1] = v[2] = 0;
// v[3] = 0;
// glLightfv(GL_LIGHT1,GL_AMBIENT,v);
// v[0] = 0.5;
// v[1] = 0.5;
// v[2] = 0.5;
// v[3] = 1;
// glLightfv(GL_LIGHT1,GL_DIFFUSE,v);
// glLightfv(GL_LIGHT1,GL_SPECULAR,v);
// glEnable(GL_LIGHT1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GOpenGLFrame::resizeGL(int w, int h)
{
ViewProperties.width = w;
ViewProperties.height = h;
ViewProperties.update();
}
void GOpenGLFrame::paintGL()
{
emit this->beforeRendering();
// qDebug("rendering OpenGL frame");
this->lock();
Q_CHECK_PTR(Gwe);
const GDataController* data = Gwe->getDataController();
Q_CHECK_PTR(data);
if (TopElements.isEmpty() || Gwe == NULL)
{
// qWarning("No top element given for rendering!");
}
else
{
//@todo we DO get a problem here if we do not have double buffering
// because the screen gets cleared all the time (so nothing to
// be seen...)
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// glClear(GL_DEPTH_BUFFER_BIT );
glMatrixMode(GL_MODELVIEW);
//draw top forms:
for ( QValueListIterator<GElementID> it = TopElements.begin(); it != TopElements.end(); it++)
{
const GElement* element = data->read((*it));
if (element == NULL)
{
qWarning(QString("top element with ID %1 not found!").arg((*it).getID()));
continue; //well, nothing to render here
}
glLoadIdentity();
//start with default values:
GVector3 view_position(ViewProperties.ViewPosition);
GVector3 view_target(ViewProperties.ViewTarget);
GVector3 view_up(ViewProperties.ViewUp);
//load element to which the camera is set:
const GElement* camera_element = data->read(ViewProperties.CameraElement);
if (camera_element)
{
this->CameraParent = camera_element->getObject()->getParent();
QPtrStack<const GElement> camera_element_hierarchy;
camera_element_hierarchy.push(camera_element);
//push parent elements onto stack until we either reach the current top element or there is no further parent
while (camera_element && camera_element->getElementID() != camera_element->getObject()->getParent() && camera_element->getElementID() != (*it))
{
camera_element = data->read(camera_element->getObject()->getParent());
if (camera_element)
camera_element_hierarchy.push(camera_element);
}
//now we have the whole element hierarchy that holds the camera element in one stack
//so we can apply the camera transformations now to get final view positions and directions
for (const GElement* el = camera_element_hierarchy.pop(); camera_element_hierarchy.isEmpty() == false; el = camera_element_hierarchy.pop())
{
const GObject* object = el->getObject();
if (object->hasForm())
{
const GForm* f = object->getForm();
//@todo we need to build translation and rotation matrix functionality into GMatrix44!!
//first rotate
view_position.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
view_position.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
view_position.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
view_target.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
view_target.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
view_target.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
view_up.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
view_up.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
view_up.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
//then translate
view_position.add(f->Position);
view_target.add(f->Position);
}
}
}
else
{
// qWarning("No camera element set for the view properties or element not available!");
}
gluLookAt(view_position.x,view_position.y,view_position.z,
view_target.x,view_target.y,view_target.z,
view_up.x,view_up.y,view_up.z);
//do the actual hierarchical rendering
renderElement(element);
}
//show some position and direction information:
this->qglColor(Qt::white);
this->renderText(10,20,QString("Camera Position: %1").arg(this->ViewProperties.ViewPosition.toString()));
this->renderText(10,40,QString("User Position: %1").arg(this->ViewProperties.ViewTarget.toString()));
this->renderText(10,60,QString("Direction: %1").arg((this->ViewProperties.ViewTarget - this->ViewProperties.ViewPosition).toString()));
//@todo, remove navigation instructions! They should be shown on startup in a messagebox...
this->renderText(10,100,"Navigation:");
this->renderText(20,120,"spacebar: hold down to move forward");
this->renderText(20,140,"enter: full stop");
this->renderText(20,160,"arrow keys: turn camera around");
if (!this->hasFocus())
{
this->renderText(10,this->height()-40,"OpenGL Frame does not have focus, click here to enable navigation!");
}
// FPS calculations
double delta_t = FpsTimer.restart();
this->TimeSinceFpsUpdate += delta_t;
this->RenderingPeriods.append(delta_t);
if (this->TimeSinceFpsUpdate >= 1000)
{
//recalculate fps:
int n = this->RenderingPeriods.count();
double sum_t = 0;
QValueList<double>::iterator it;
for (it = this->RenderingPeriods.begin(); it != this->RenderingPeriods.end(); ++it)
{
sum_t += *it;
}
// qDebug(QString("fps: n = %1, sum_t = %2").arg(QString::number(n)).arg(QString::number(sum_t)));
if (sum_t > 0)
this->FramesPerSecond = n/sum_t*1000;
else
this->FramesPerSecond = 0;
this->RenderingPeriods.clear();
this->TimeSinceFpsUpdate = 0;
}
//show FPS
this->renderText(this->width()-100,20,QString("FPS: ") + QString::number(this->FramesPerSecond,'f',2));
//at the end load an identitiy matrix
glLoadIdentity();
glFlush(); //make sure everything is drawn
}
if (!this->StopRendering)
{
// qDebug("Restaring rendering timer.");
this->RedrawTimer.start(this->RedrawSleepTime_ms,TRUE);
}
this->unlock();
emit this->afterRendering();
}
void GOpenGLFrame::addTopElement(const GElementID& element)
{
QMutexLocker m(this);
this->TopElements.append(element);
}
void GOpenGLFrame::removeTopElement(const GElementID& element)
{
QMutexLocker m(this);
this->TopElements.remove(element);
}
}
syntax highlighted by Code2HTML, v. 0.9.1