You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/apps/opencs/view/render/scenewidget.cpp

565 lines
20 KiB
C++

#include "scenewidget.hpp"
#include <chrono>
#include <exception>
#include <thread>
#include <QLayout>
#include <QMouseEvent>
#include <QSurfaceFormat>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/view/render/lightingbright.hpp>
#include <apps/opencs/view/render/lightingday.hpp>
#include <apps/opencs/view/render/lightingnight.hpp>
#include <extern/osgQt/CompositeOsgRenderer.hpp>
#include <extern/osgQt/osgQOpenGLWidget.hpp>
#include <osg/Array>
#include <osg/Camera>
#include <osg/DisplaySettings>
#include <osg/GL>
#include <osg/Geometry>
#include <osg/GraphicsContext>
#include <osg/Group>
#include <osg/LightModel>
#include <osg/Material>
#include <osg/Matrix>
#include <osg/PrimitiveSet>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Transform>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/Vec4ub>
#include <osg/View>
#include <osg/Viewport>
#include <osgGA/EventQueue>
#include <osgGA/GUIEventAdapter>
#include <osgViewer/GraphicsWindow>
#include <osgViewer/View>
#include <osgViewer/ViewerBase>
#include <osgViewer/ViewerEventHandlers>
#include <components/debug/debuglog.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/glextensions.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "../widget/scenetoolmode.hpp"
#include "../../model/prefs/shortcut.hpp"
#include "../../model/prefs/state.hpp"
#include "cameracontroller.hpp"
#include "lighting.hpp"
#include "mask.hpp"
namespace CSVRender
{
RenderWidget::RenderWidget(QWidget* parent, Qt::WindowFlags f)
: QWidget(parent, f)
, mRootNode(nullptr)
{
mView = new osgViewer::View;
updateCameraParameters(width() / static_cast<double>(height()));
mWidget = new osgQOpenGLWidget(this);
mRenderer = mWidget->getCompositeViewer();
osg::ref_ptr<osgViewer::GraphicsWindowEmbedded> window
= new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height());
mWidget->setGraphicsWindowEmbedded(window);
mRenderer->setRealizeOperation(new SceneUtil::GetGLExtensionsOperation());
int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt();
mRenderer->setRunMaxFrameRate(frameRateLimit);
mRenderer->setUseConfigureAffinity(false);
QLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(mWidget);
setLayout(layout);
mView->getCamera()->setGraphicsContext(window);
osg::ref_ptr<SceneUtil::LightManager> lightMgr = new SceneUtil::LightManager;
lightMgr->setStartLight(1);
lightMgr->setLightingMask(Mask_Lighting);
mRootNode = std::move(lightMgr);
mView->getCamera()->setViewport(new osg::Viewport(0, 0, width(), height()));
mView->getCamera()->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON);
mView->getCamera()->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
osg::ref_ptr<osg::Material> defaultMat(new osg::Material);
defaultMat->setColorMode(osg::Material::OFF);
defaultMat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1));
defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 1, 1));
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
mView->getCamera()->getOrCreateStateSet()->setAttribute(defaultMat);
mView->setSceneData(mRootNode);
// Add ability to signal osg to show its statistics for debugging purposes
mView->addEventHandler(new osgViewer::StatsHandler);
mRenderer->addView(mView);
mRenderer->setDone(false);
}
RenderWidget::~RenderWidget()
{
try
{
mRenderer->removeView(mView);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Error in the destructor: " << e.what();
}
delete mWidget;
}
void RenderWidget::flagAsModified()
{
mView->requestRedraw();
}
void RenderWidget::setVisibilityMask(unsigned int mask)
{
mView->getCamera()->setCullMask(mask | Mask_ParticleSystem | Mask_Lighting);
}
osg::Camera* RenderWidget::getCamera()
{
return mView->getCamera();
}
void RenderWidget::toggleRenderStats()
{
osgViewer::GraphicsWindow* window
= static_cast<osgViewer::GraphicsWindow*>(mView->getCamera()->getGraphicsContext());
window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S);
window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S);
}
// ---------------------------------------------------
SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSystem, QWidget* parent,
Qt::WindowFlags f, bool retrieveInput)
: RenderWidget(parent, f)
, mResourceSystem(std::move(resourceSystem))
, mLighting(nullptr)
, mHasDefaultAmbient(false)
, mIsExterior(true)
, mPrevMouseX(0)
, mPrevMouseY(0)
, mCamPositionSet(false)
{
mFreeCamControl = new FreeCameraController(this);
mOrbitCamControl = new OrbitCameraController(this);
mCurrentCamControl = mFreeCamControl;
mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain);
mOrbitCamControl->setConstRoll(CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue());
// set up gradient view or configured clear color
QColor bgColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue())
{
QColor gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
mGradientCamera = createGradientCamera(bgColour, gradientColour);
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
else
{
mView->getCamera()->setClearColor(osg::Vec4(bgColour.redF(), bgColour.greenF(), bgColour.blueF(), 1.0f));
}
// we handle lighting manually
mView->setLightingMode(osgViewer::View::NO_LIGHT);
setLighting(&mLightingDay);
mResourceSystem->getSceneManager()->setParticleSystemMask(Mask_ParticleSystem);
// Recieve mouse move event even if mouse button is not pressed
setMouseTracking(true);
setFocusPolicy(Qt::ClickFocus);
connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, &SceneWidget::settingChanged);
// TODO update this outside of the constructor where virtual methods can be used
if (retrieveInput)
{
CSMPrefs::get()["3D Scene Input"].update();
CSMPrefs::get()["Tooltips"].update();
}
connect(mRenderer, &CompositeOsgRenderer::simulationUpdated, this, &SceneWidget::update);
// Shortcuts
CSMPrefs::Shortcut* focusToolbarShortcut = new CSMPrefs::Shortcut("scene-focus-toolbar", this);
connect(
focusToolbarShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &SceneWidget::focusToolbarRequest);
CSMPrefs::Shortcut* renderStatsShortcut = new CSMPrefs::Shortcut("scene-render-stats", this);
connect(
renderStatsShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &SceneWidget::toggleRenderStats);
}
SceneWidget::~SceneWidget()
{
// Since we're holding on to the resources past the existence of this graphics context, we'll need to manually
// release the created objects
mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
}
osg::ref_ptr<osg::Geometry> SceneWidget::createGradientRectangle(QColor& bgColour, QColor& gradientColour)
{
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, -1.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, -1.0f));
vertices->push_back(osg::Vec3(0.0f, 1.0f, -1.0f));
vertices->push_back(osg::Vec3(1.0f, 1.0f, -1.0f));
geometry->setVertexArray(vertices);
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0);
// triangle 1
primitives->push_back(0);
primitives->push_back(1);
primitives->push_back(2);
// triangle 2
primitives->push_back(2);
primitives->push_back(1);
primitives->push_back(3);
geometry->addPrimitiveSet(primitives);
osg::ref_ptr<osg::Vec4ubArray> colours = new osg::Vec4ubArray;
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(gradientColour.red(), gradientColour.green(), gradientColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
colours->push_back(osg::Vec4ub(bgColour.red(), bgColour.green(), bgColour.blue(), 1.0f));
geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);
geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
geometry->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
return geometry;
}
osg::ref_ptr<osg::Camera> SceneWidget::createGradientCamera(QColor& bgColour, QColor& gradientColour)
{
osg::ref_ptr<osg::Camera> camera = new osg::Camera();
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1.0f, 0, 1.0f));
camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
camera->setViewMatrix(osg::Matrix::identity());
camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
camera->setAllowEventFocus(false);
// draw subgraph before main camera view.
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
osg::ref_ptr<osg::Geometry> gradientQuad = createGradientRectangle(bgColour, gradientColour);
camera->addChild(std::move(gradientQuad));
return camera;
}
void SceneWidget::updateGradientCamera(QColor& bgColour, QColor& gradientColour)
{
osg::ref_ptr<osg::Geometry> gradientRect = createGradientRectangle(bgColour, gradientColour);
// Replaces previous rectangle
mGradientCamera->setChild(0, gradientRect.get());
}
void SceneWidget::setLighting(Lighting* lighting)
{
if (mLighting)
mLighting->deactivate();
mLighting = lighting;
mLighting->activate(mRootNode, mIsExterior);
osg::Vec4f ambient = mLighting->getAmbientColour(mHasDefaultAmbient ? &mDefaultAmbient : nullptr);
setAmbient(ambient);
flagAsModified();
}
void SceneWidget::setAmbient(const osg::Vec4f& ambient)
{
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
lightmodel->setAmbientIntensity(ambient);
stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);
stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);
stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON);
mRootNode->setStateSet(stateset);
}
void SceneWidget::selectLightingMode(const std::string& mode)
{
QColor backgroundColour;
QColor gradientColour;
if (mode == "day")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-day-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-day-gradient-colour"].toColor();
setLighting(&mLightingDay);
}
else if (mode == "night")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor();
setLighting(&mLightingNight);
}
else if (mode == "bright")
{
backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor();
gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor();
setLighting(&mLightingBright);
}
if (CSMPrefs::get()["Rendering"]["scene-use-gradient"].isTrue())
{
if (mGradientCamera.get() != nullptr)
{
// we can go ahead and update since this camera still exists
updateGradientCamera(backgroundColour, gradientColour);
if (!mView->getCamera()->containsNode(mGradientCamera.get()))
{
// need to re-attach the gradient camera
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
}
else
{
// need to create the gradient camera
mGradientCamera = createGradientCamera(backgroundColour, gradientColour);
mView->getCamera()->setClearMask(0);
mView->getCamera()->addChild(mGradientCamera.get());
}
}
else
{
// Fall back to using the clear color for the camera
mView->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mView->getCamera()->setClearColor(
osg::Vec4(backgroundColour.redF(), backgroundColour.greenF(), backgroundColour.blueF(), 1.0f));
if (mGradientCamera.get() != nullptr && mView->getCamera()->containsNode(mGradientCamera.get()))
{
// Remove the child to prevent the gradient from rendering
mView->getCamera()->removeChild(mGradientCamera.get());
}
}
}
CSVWidget::SceneToolMode* SceneWidget::makeLightingSelector(CSVWidget::SceneToolbar* parent)
{
CSVWidget::SceneToolMode* tool = new CSVWidget::SceneToolMode(parent, "Lighting Mode");
/// \todo replace icons
tool->addButton(":scenetoolbar/day", "day",
"Day"
"<ul><li>Cell specific ambient in interiors</li>"
"<li>Low ambient in exteriors</li>"
"<li>Strong directional light source</li>"
"<li>This mode closely resembles day time in-game</li></ul>");
tool->addButton(":scenetoolbar/night", "night",
"Night"
"<ul><li>Cell specific ambient in interiors</li>"
"<li>Low ambient in exteriors</li>"
"<li>Weak directional light source</li>"
"<li>This mode closely resembles night time in-game</li></ul>");
tool->addButton(":scenetoolbar/bright", "bright",
"Bright"
"<ul><li>Maximum ambient</li>"
"<li>Strong directional light source</li></ul>");
connect(tool, &CSVWidget::SceneToolMode::modeChanged, this, &SceneWidget::selectLightingMode);
return tool;
}
void SceneWidget::setDefaultAmbient(const osg::Vec4f& colour)
{
mDefaultAmbient = colour;
mHasDefaultAmbient = true;
setAmbient(mLighting->getAmbientColour(&mDefaultAmbient));
}
void SceneWidget::setExterior(bool isExterior)
{
mIsExterior = isExterior;
}
void SceneWidget::mouseMoveEvent(QMouseEvent* event)
{
mCurrentCamControl->handleMouseMoveEvent(event->x() - mPrevMouseX, event->y() - mPrevMouseY);
mPrevMouseX = event->x();
mPrevMouseY = event->y();
}
void SceneWidget::wheelEvent(QWheelEvent* event)
{
mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y());
}
void SceneWidget::update(double dt)
{
if (mCamPositionSet)
{
mCurrentCamControl->update(dt);
}
else
{
mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp);
mCamPositionSet = true;
}
}
void SceneWidget::settingChanged(const CSMPrefs::Setting* setting)
{
if (*setting == "3D Scene Input/p-navi-free-sensitivity")
{
mFreeCamControl->setCameraSensitivity(setting->toDouble());
}
else if (*setting == "3D Scene Input/p-navi-orbit-sensitivity")
{
mOrbitCamControl->setCameraSensitivity(setting->toDouble());
}
else if (*setting == "3D Scene Input/p-navi-free-invert")
{
mFreeCamControl->setInverted(setting->isTrue());
}
else if (*setting == "3D Scene Input/p-navi-orbit-invert")
{
mOrbitCamControl->setInverted(setting->isTrue());
}
else if (*setting == "3D Scene Input/s-navi-sensitivity")
{
mFreeCamControl->setSecondaryMovementMultiplier(setting->toDouble());
mOrbitCamControl->setSecondaryMovementMultiplier(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-wheel-factor")
{
mFreeCamControl->setWheelMovementMultiplier(setting->toDouble());
mOrbitCamControl->setWheelMovementMultiplier(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-free-lin-speed")
{
mFreeCamControl->setLinearSpeed(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-free-rot-speed")
{
mFreeCamControl->setRotationalSpeed(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-free-speed-mult")
{
mFreeCamControl->setSpeedMultiplier(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-orbit-rot-speed")
{
mOrbitCamControl->setOrbitSpeed(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-orbit-speed-mult")
{
mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble());
}
else if (*setting == "3D Scene Input/navi-orbit-const-roll")
{
mOrbitCamControl->setConstRoll(setting->isTrue());
}
else if (*setting == "Rendering/framerate-limit")
{
mRenderer->setRunMaxFrameRate(setting->toInt());
}
else if (*setting == "Rendering/camera-fov" || *setting == "Rendering/camera-ortho"
|| *setting == "Rendering/camera-ortho-size")
{
updateCameraParameters();
}
else if (*setting == "Rendering/scene-day-night-switch-nodes")
{
if (mLighting)
setLighting(mLighting);
}
}
void RenderWidget::updateCameraParameters(double overrideAspect)
{
const float nearDist = 1.0;
const float farDist = 1000.0;
if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue())
{
const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt();
const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast<double>(height()));
const float halfH = size * 10.0;
const float halfW = halfH * aspect;
mView->getCamera()->setProjectionMatrixAsOrtho(-halfW, halfW, -halfH, halfH, nearDist, farDist);
}
else
{
mView->getCamera()->setProjectionMatrixAsPerspective(CSMPrefs::get()["Rendering"]["camera-fov"].toInt(),
static_cast<double>(width()) / static_cast<double>(height()), nearDist, farDist);
}
}
void SceneWidget::selectNavigationMode(const std::string& mode)
{
if (mode == "1st")
{
mCurrentCamControl->setCamera(nullptr);
mCurrentCamControl = mFreeCamControl;
mFreeCamControl->setCamera(getCamera());
mFreeCamControl->fixUpAxis(CameraController::WorldUp);
}
else if (mode == "free")
{
mCurrentCamControl->setCamera(nullptr);
mCurrentCamControl = mFreeCamControl;
mFreeCamControl->setCamera(getCamera());
mFreeCamControl->unfixUpAxis();
}
else if (mode == "orbit")
{
mCurrentCamControl->setCamera(nullptr);
mCurrentCamControl = mOrbitCamControl;
mOrbitCamControl->setCamera(getCamera());
mOrbitCamControl->reset();
}
}
}