ResInsight/cafViewer/cafViewer.cpp

792 lines
26 KiB
C++

//##################################################################################################
//
// Custom Visualization Core library
// Copyright (C) 2011-2012 Ceetron AS
//
// This library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <<http://www.gnu.org/licenses/gpl.html>>
// for more details.
//
//##################################################################################################
#include "cafViewer.h"
#include "cvfCamera.h"
#include "cvfRendering.h"
#include "cvfRenderSequence.h"
#include "cvfOpenGLResourceManager.h"
#include "cvfRenderQueueSorter.h"
#include "cvfScene.h"
#include "cvfModel.h"
#include "cvfTextureImage.h"
#include "cvfOverlayImage.h"
#include "cvfqtOpenGLContext.h"
#include "cvfRay.h"
#include "cvfPart.h"
#include "cvfDrawable.h"
#include "cvfDrawableGeo.h"
#include "cvfTransform.h"
#include "cvfRayIntersectSpec.h"
#include "cvfHitItemCollection.h"
#include "cvfDebugTimer.h"
#include "cvfqtPerformanceInfoHud.h"
#include "cvfqtUtils.h"
#include "cafNavigationPolicy.h"
#include "cafCadNavigation.h"
#include "cafFrameAnimationControl.h"
#include <QInputEvent>
#include <QHBoxLayout>
#include <QDebug>
std::list<caf::Viewer*> caf::Viewer::sm_viewers;
cvf::ref<cvf::OpenGLContextGroup> caf::Viewer::sm_openGLContextGroup;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::Viewer::Viewer(const QGLFormat& format, QWidget* parent)
: caf::OpenGLWidget(contextGroup(), format, new QWidget(parent), sharedWidget()),
m_navigationPolicy(NULL),
m_minNearPlaneDistance(0.05),
m_maxFarPlaneDistance(cvf::UNDEFINED_DOUBLE),
m_releaseOGLResourcesEachFrame(false),
m_paintCounter(0),
m_navigationPolicyEnabled(true),
m_isOverlyPaintingEnabled(true)
{
m_layoutWidget = parentWidget();
QHBoxLayout* layout = new QHBoxLayout(m_layoutWidget);
layout->addWidget(this);
layout->setContentsMargins(0, 0, 0, 0);
setAutoFillBackground(false);
setMouseTracking(true);
// Needed to get keystrokes
setFocusPolicy(Qt::ClickFocus);
m_mainCamera = new cvf::Camera;
m_mainCamera->setFromLookAt(cvf::Vec3d(0,0,-1), cvf::Vec3d(0,0,0), cvf::Vec3d(0,1,0));
m_renderingSequence = new cvf::RenderSequence();
m_mainRendering = new cvf::Rendering();
m_animationControl = new caf::FrameAnimationControl(this);
connect(m_animationControl, SIGNAL(changeFrame(int)), SLOT(slotSetCurrentFrame(int)));
connect(m_animationControl, SIGNAL(endAnimation()), SLOT(slotEndAnimation()));
this->setNavigationPolicy(new caf::CadNavigation);
m_overlayTextureImage = new cvf::TextureImage;
m_overlayImage = new cvf::OverlayImage(m_overlayTextureImage.p());
m_overlayImage->setBlending(cvf::OverlayImage::TEXTURE_ALPHA);
m_overlayImage->setUnmanagedPosition(cvf::Vec2i(0,0));
setupMainRendering();
setupRenderingSequence();
m_showPerfInfoHud = false;
sm_viewers.push_back(this);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::Viewer::~Viewer()
{
this->cvfShutdownOpenGLContext();
sm_viewers.remove(this);
// To delete the layout widget
if (m_layoutWidget) m_layoutWidget->deleteLater();
}
//--------------------------------------------------------------------------------------------------
/// This method is supposed to setup the contents of the main rendering
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setupMainRendering()
{
m_mainRendering->setCamera(m_mainCamera.p());
m_mainRendering->setRenderQueueSorter(new cvf::RenderQueueSorterBasic(cvf::RenderQueueSorterBasic::EFFECT_ONLY));
// Set fixed function rendering if QGLFormat does not support directRendering
if (!this->format().directRendering())
{
m_mainRendering->renderEngine()->enableForcedImmediateMode(true);
}
updateOverlayImagePresence();
}
//--------------------------------------------------------------------------------------------------
/// This method is supposed to assemble the rendering sequence (cvf::RenderSequence) based on
/// the Viewers renderings. THe ViewerBase has only one rendering. The m_mainRendering
/// Reimplement to build more advanced rendering sequences
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setupRenderingSequence()
{
m_renderingSequence->addRendering(m_mainRendering.p());
updateCamera(width(), height());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::Viewer* caf::Viewer::sharedWidget()
{
if (sm_viewers.size() > 0)
{
return *(sm_viewers.begin());
}
return NULL;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::OpenGLContextGroup* caf::Viewer::contextGroup()
{
if (sm_openGLContextGroup.isNull())
{
sm_openGLContextGroup = new cvf::OpenGLContextGroup();
}
return sm_openGLContextGroup.p();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Camera* caf::Viewer::mainCamera()
{
return m_mainCamera.p();
}
//--------------------------------------------------------------------------------------------------
/// Set the scene to be rendered when the animation is inactive (Stopped)
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setMainScene(cvf::Scene* scene)
{
m_mainScene = scene;
m_mainRendering->setScene(scene);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Scene* caf::Viewer::mainScene()
{
return m_mainScene.p();
}
//--------------------------------------------------------------------------------------------------
/// Return the currently rendered scene
//--------------------------------------------------------------------------------------------------
cvf::Scene* caf::Viewer::currentScene()
{
return m_renderingSequence->firstRendering()->scene();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::updateCamera(int width, int height)
{
if (width < 1 || height < 1) return;
m_mainCamera->viewport()->set(0, 0, width, height);
if (m_mainCamera->projection() == cvf::Camera::PERSPECTIVE)
{
m_mainCamera->setProjectionAsPerspective(40, m_mainCamera->nearPlane(), m_mainCamera->farPlane());
}
else
{
cvf::BoundingBox bb = m_renderingSequence->boundingBox();
if (bb.isValid())
{
m_mainCamera->setProjectionAsOrtho(bb.extent().length(), m_mainCamera->nearPlane(), m_mainCamera->farPlane());
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::canRender() const
{
if (m_renderingSequence->renderingCount() < 1) return false;
if (m_mainCamera.isNull()) return false;
if (m_mainCamera->viewport()->width() < 1) return false;
if (m_mainCamera->viewport()->height() < 1) return false;
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::optimizeClippingPlanes()
{
if (m_mainCamera->projection() == cvf::Camera::PERSPECTIVE)
{
cvf::BoundingBox bb = m_renderingSequence->boundingBox();
if (!bb.isValid()) return;
cvf::Vec3d eye, vrp, up;
m_mainCamera->toLookAt(&eye, &vrp, &up);
cvf::Vec3d viewdir = (vrp - eye).getNormalized();
double distEyeBoxCenterAlongViewDir = (bb.center() - eye)*viewdir;
double farPlaneDist = distEyeBoxCenterAlongViewDir + bb.radius();
farPlaneDist = CVF_MIN(farPlaneDist, m_maxFarPlaneDistance);
double nearPlaneDist = distEyeBoxCenterAlongViewDir - bb.radius();
if (nearPlaneDist < m_minNearPlaneDistance) nearPlaneDist = m_minNearPlaneDistance;
if ( m_navigationPolicy.notNull())
{
double pointOfInterestDist = (eye - m_navigationPolicy->pointOfInterest()).length();
nearPlaneDist = CVF_MIN( nearPlaneDist, pointOfInterestDist*0.2);
}
if (farPlaneDist <= nearPlaneDist) farPlaneDist = nearPlaneDist + 1.0;
m_mainCamera->setProjectionAsPerspective(m_mainCamera->fieldOfViewYDeg(), nearPlaneDist, farPlaneDist);
}
}
//--------------------------------------------------------------------------------------------------
/// Forward all events classified as QInputEvent to the navigation policy
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::event(QEvent* e)
{
if (e && m_navigationPolicy.notNull() && m_navigationPolicyEnabled)
{
switch (e->type())
{
case QEvent::ContextMenu:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::ShortcutOverride:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::Wheel:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
if (m_navigationPolicy->handleInputEvent(static_cast<QInputEvent*>(e)))
return true;
else return QGLWidget::event(e);
break;
default:
return QGLWidget::event(e);
break;
}
}
else return QGLWidget::event(e);
}
//--------------------------------------------------------------------------------------------------
/// Set the pointer to the navigation policy to be used. Stored as a cvf::ref internally
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setNavigationPolicy(caf::NavigationPolicy* navigationPolicy)
{
m_navigationPolicy = navigationPolicy;
if (m_navigationPolicy.notNull()) m_navigationPolicy->setViewer(this);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::rayPick(int winPosX, int winPosY, cvf::HitItemCollection* pickedPoints)
{
CVF_ASSERT(m_mainRendering.notNull());
int translatedMousePosX = winPosX;
int translatedMousePosY = height() - winPosY;
cvf::ref<cvf::RayIntersectSpec> ris = m_mainRendering->rayIntersectSpecFromWindowCoordinates(translatedMousePosX, translatedMousePosY);
if (ris.notNull())
{
bool retVal = m_mainRendering->rayIntersect(*ris, pickedPoints);
return retVal;
}
else
{
return false;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::resizeGL(int width, int height)
{
if (width < 1 || height < 1) return;
updateCamera(width, height);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::enablePerfInfoHud(bool enable)
{
m_showPerfInfoHud = enable;
updateOverlayImagePresence();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::isPerfInfoHudEnabled()
{
return m_showPerfInfoHud;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::paintEvent(QPaintEvent* event)
{
makeCurrent();
cvf::ref<cvf::OpenGLContext> myOglContext = cvfOpenGLContext();
CVF_CHECK_OGL(myOglContext.p());
CVF_ASSERT(myOglContext->isContextValid());
QPainter painter(this);
if (m_renderingSequence.isNull() || !canRender())
{
QColor bgClr(128, 128, 128);
painter.fillRect(rect(), bgClr);
return;
}
// If Qt overlay painting is enabled, paint to an QImage, and set it to the cvf::OverlayImage
if (m_isOverlyPaintingEnabled || m_showPerfInfoHud)
{
// Set up image to draw to, and painter
if (m_overlayPaintingQImage.size() != this->size())
{
m_overlayPaintingQImage = QImage(this->size(), QImage::Format_ARGB32);
}
m_overlayPaintingQImage.fill(Qt::transparent);
QPainter overlyPainter(&m_overlayPaintingQImage);
// Call virtual method to allow subclasses to paint on the OpenGlCanvas
if (m_isOverlyPaintingEnabled)
{
this->paintOverlayItems(&overlyPainter);
}
// Draw performance overlay
if (m_showPerfInfoHud )
{
cvfqt::PerformanceInfoHud hud;
hud.addStrings(m_renderingSequence->performanceInfo());
hud.addStrings(*m_mainCamera);
hud.addString(QString("PaintCount: %1").arg(m_paintCounter++));
hud.draw(&overlyPainter, width(), height());
}
// Convert the QImage into the cvf::TextureImage,
// handling vertical mirroring and (possible) byteswapping
if (m_overlayTextureImage->height() != this->height() || m_overlayTextureImage->width() != this->width())
{
m_overlayTextureImage->allocate(this->width(), this->height());
}
int height = m_overlayTextureImage->height();
int width = m_overlayTextureImage->width();
#pragma omp parallel for
for (int y = 0 ; y < height; ++y)
{
int negy = height - 1 - y;
for (int x = 0 ; x < width; ++x)
{
// Should probably do direct conversion on byte level. Would be faster
// QImage.bits() and cvf::TextureImage.ptr() (casting away const)
QRgb qtRgbaVal = m_overlayPaintingQImage.pixel(x, negy);
cvf::Color4ub cvfRgbVal(qRed(qtRgbaVal), qGreen(qtRgbaVal), qBlue(qtRgbaVal), qAlpha(qtRgbaVal));
m_overlayTextureImage->setPixel(x, y, cvfRgbVal);
}
}
m_overlayImage->setImage(m_overlayTextureImage.p());
m_overlayImage->setPixelSize(cvf::Vec2ui(this->width(), this->height()));
}
#if QT_VERSION >= 0x040600
// Qt 4.6
painter.beginNativePainting();
#endif
if (isShadersSupported())
{
cvfqt::OpenGLContext::saveOpenGLState(myOglContext.p());
}
optimizeClippingPlanes();
// Do normal drawing
m_renderingSequence->render(myOglContext.p());
CVF_CHECK_OGL(cvfOpenGLContext());
if (isShadersSupported())
{
cvfqt::OpenGLContext::restoreOpenGLState(myOglContext.p());
}
#if QT_VERSION >= 0x040600
// Qt 4.6
painter.endNativePainting();
#endif
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setMinNearPlaneDistance(double dist)
{
m_minNearPlaneDistance = dist;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setMaxFarPlaneDistance(double dist)
{
m_maxFarPlaneDistance = dist;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::setView(const cvf::Vec3d& alongDirection, const cvf::Vec3d& upDirection)
{
if (m_navigationPolicy.notNull())
{
m_navigationPolicy->setView(alongDirection, upDirection);
update();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::zoomAll()
{
cvf::BoundingBox bb;
cvf::Scene* scene = m_renderingSequence->firstRendering()->scene();
if (scene)
{
bb = scene->boundingBox();
}
if (!bb.isValid())
{
return;
}
cvf::Vec3d eye, vrp, up;
m_mainCamera->toLookAt(&eye, &vrp, &up);
m_mainCamera->fitView(bb, vrp-eye, up);
update();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::addFrame(cvf::Scene* scene)
{
m_frameScenes.push_back(scene);
m_animationControl->setNumFrames(static_cast<int>(m_frameScenes.size()));
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::removeAllFrames()
{
m_frameScenes.clear();
m_animationControl->setNumFrames(0);
m_renderingSequence->firstRendering()->setScene(m_mainScene.p());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::isAnimationActive()
{
cvf::Scene* currentScene = m_renderingSequence->firstRendering()->scene();
if (!currentScene)
{
return false;
}
if (m_mainScene.notNull() && m_mainScene.p() == currentScene)
{
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::slotSetCurrentFrame(int frameIndex)
{
if (frameIndex < 0 || static_cast<size_t>(frameIndex) >= m_frameScenes.size() || m_frameScenes.at(frameIndex) == NULL) return;
if(m_releaseOGLResourcesEachFrame)
{
releaseOGlResourcesForCurrentFrame();
}
m_renderingSequence->firstRendering()->setScene(m_frameScenes.at(frameIndex));
update();
}
void caf::Viewer::releaseOGlResourcesForCurrentFrame()
{
if (isAnimationActive())
{
cvf::Scene* currentScene = m_renderingSequence->firstRendering()->scene();
makeCurrent();
cvf::uint modelCount = currentScene->modelCount();
for (cvf::uint i = 0; i < modelCount; ++i)
{
cvf::Collection<cvf::Part> partCollection;
currentScene->model(i)->allParts(&partCollection);
for (size_t pIdx = 0; pIdx < partCollection.size(); ++pIdx)
{
if (partCollection[pIdx].notNull() && partCollection[pIdx]->drawable())
{
partCollection[pIdx]->drawable()->releaseBufferObjectsGPU();
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::slotEndAnimation()
{
if(m_releaseOGLResourcesEachFrame)
{
releaseOGlResourcesForCurrentFrame();
}
m_renderingSequence->firstRendering()->setScene(m_mainScene.p());
update();
}
//--------------------------------------------------------------------------------------------------
/// This only updates the boundingboxes yet. Might want to do other things as well
//--------------------------------------------------------------------------------------------------
void caf::Viewer::updateCachedValuesInScene()
{
if (m_mainScene.notNull())
{
cvf::uint midx;
for (midx = 0; midx < m_mainScene->modelCount() ; ++midx)
{
m_mainScene->model(midx)->updateBoundingBoxesRecursive();
}
}
size_t sIdx;
for (sIdx = 0; sIdx < m_frameScenes.size(); ++sIdx)
{
cvf::uint midx;
for (midx = 0; midx < m_frameScenes[sIdx]->modelCount() ; ++midx)
{
m_frameScenes[sIdx]->model(midx)->updateBoundingBoxesRecursive();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::isShadersSupported()
{
QGLFormat::OpenGLVersionFlags flags = QGLFormat::openGLVersionFlags();
bool hasOpenGL_2_0 = QGLFormat::OpenGL_Version_2_0 & flags;
if (hasOpenGL_2_0)
{
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Scene* caf::Viewer::frame(size_t frameIndex)
{
if (frameIndex < m_frameScenes.size())
return m_frameScenes[frameIndex].p();
else
return NULL;
}
//--------------------------------------------------------------------------------------------------
/// Helper function used to write out the name of all parts in a rendering sequence
//--------------------------------------------------------------------------------------------------
void caf::Viewer::debugShowRenderingSequencePartNames()
{
qDebug() << "\n\n";
size_t globalPartCount = 0;
cvf::uint rIdx = m_renderingSequence->renderingCount();
for (rIdx = 0; rIdx < m_renderingSequence->renderingCount(); rIdx++)
{
cvf::Rendering* rendering = m_renderingSequence->rendering(rIdx);
if (rendering && rendering->scene())
{
cvf::uint mIdx;
for (mIdx = 0; mIdx < rendering->scene()->modelCount(); mIdx++)
{
cvf::Model* model = rendering->scene()->model(mIdx);
if (model)
{
cvf::Collection<cvf::Part> parts;
model->allParts(&parts);
size_t pIdx;
for (pIdx = 0; pIdx < parts.size(); pIdx++)
{
cvf::Part* part = parts.at(pIdx);
qDebug() << QString("%1").arg(globalPartCount++) << cvfqt::Utils::toQString(part->name());
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::enableNavigationPolicy(bool enable)
{
m_navigationPolicyEnabled = enable;
if (enable && m_navigationPolicy.notNull()) m_navigationPolicy->init();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize caf::Viewer::sizeHint() const
{
return QSize(500, 400);
}
void caf::Viewer::enableForcedImmediateMode(bool enable)
{
m_mainRendering->renderEngine()->enableForcedImmediateMode(enable);
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int caf::Viewer::currentFrameIndex()
{
if (m_animationControl) return m_animationControl->currentFrame();
else return 0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool caf::Viewer::isOverlyPaintingEnabled() const
{
return m_isOverlyPaintingEnabled;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::enableOverlyPainting(bool val)
{
m_isOverlyPaintingEnabled = val;
updateOverlayImagePresence();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void caf::Viewer::updateOverlayImagePresence()
{
if (m_isOverlyPaintingEnabled || m_showPerfInfoHud)
{
m_mainRendering->addOverlayItem(m_overlayImage.p(), cvf::OverlayItem::UNMANAGED, cvf::OverlayItem::VERTICAL);
}
else
{
m_mainRendering->removeOverlayItem(m_overlayImage.p());
}
}