#include "scenewidget.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../widget/scenetoolmode.hpp" #include "../../model/prefs/shortcut.hpp" #include "../../model/prefs/state.hpp" #include "cameracontroller.hpp" #include "mask.hpp" namespace { class DayNightSwitchVisitor : public osg::NodeVisitor { public: DayNightSwitchVisitor(int index) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mIndex(index) { } void apply(osg::Switch& switchNode) override { constexpr unsigned noIndex = static_cast(-1); unsigned initialIndex = noIndex; if (!switchNode.getUserValue("initialIndex", initialIndex)) { for (size_t i = 0; i < switchNode.getValueList().size(); ++i) { if (switchNode.getValueList()[i]) { initialIndex = static_cast(i); break; } } if (initialIndex != noIndex) switchNode.setUserValue("initialIndex", initialIndex); } if (CSMPrefs::get()["Rendering"]["scene-day-night-switch-nodes"].isTrue()) { if (switchNode.getName() == Constants::NightDayLabel) switchNode.setSingleChildOn(mIndex); } else if (initialIndex != noIndex) { switchNode.setSingleChildOn(initialIndex); } traverse(switchNode); } private: int mIndex; }; } namespace CSVRender { RenderWidget::RenderWidget( std::shared_ptr resourceSystem, QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , mRootNode(nullptr) { SceneUtil::registerSerializers(false); mView = new osgViewer::View; mWidget = new osgQOpenGLWidget(this); // Not the most idiomatic place to do this, but osgQt needs its guts rearranging to do things properly. // We *should* be setting this (or relying on the fact that we want the default value) on the default // osg::DisplaySettings instance or the View's instance. // Then osgQt should, but doesn't, use that to create a GraphicsTraits instance, and propagate the details from // that to Qt for us. mWidget->setTextureFormat(GL_RGB8); mRenderer = mWidget->getCompositeViewer(); osg::ref_ptr 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 lightMgr = new SceneUtil::LightManager(SceneUtil::LightSettings{}, resourceSystem.get()); lightMgr->setLightingMask(Mask_Lighting); mSunLight = new SceneUtil::Light; mSunLight->setDiffuse(osg::Vec4f(0, 0, 0, 1)); mSunLight->setAmbient(osg::Vec4f(0, 0, 0, 1)); mSunLight->setSpecular(osg::Vec4f(0, 0, 0, 0)); mSunLight->setConstantAttenuation(1.f); lightMgr->setSunlight(mSunLight); mRootNode = std::move(lightMgr); mStateUpdater = new SceneUtil::StateUpdater(); mRootNode->addUpdateCallback(mStateUpdater); mSharedUniformStateUpdater = new SceneUtil::SharedUniformStateUpdater(1.0); mRootNode->addUpdateCallback(mSharedUniformStateUpdater); mPerViewUniformStateUpdater = new SceneUtil::PerViewUniformStateUpdater(resourceSystem->getSceneManager()); mRootNode->addCullCallback(mPerViewUniformStateUpdater); mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); mRootNode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); SceneUtil::initTexMatForStateSet(*mRootNode->getOrCreateStateSet()); 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); mView->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mView->getCamera()->setName(Constants::SceneCamera); osg::ref_ptr defaultMat(new SceneUtil::Material); defaultMat->updateStateSet(mRootNode->getOrCreateStateSet()); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("alpha", 1.f)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("actorFade", 1.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); updateCameraParameters(width() / static_cast(height())); } 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(mView->getCamera()->getGraphicsContext()); window->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KEY_S); window->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KEY_S); } // --------------------------------------------------- SceneWidget::SceneWidget(std::shared_ptr resourceSystem, QWidget* parent, Qt::WindowFlags f, bool retrieveInput) : RenderWidget(resourceSystem, parent, f) , mResourceSystem(std::move(resourceSystem)) , mHasDefaultAmbient(false) , mIsExterior(true) , 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(LightingMode::Day); 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 SceneWidget::createGradientRectangle(QColor& bgColour, QColor& gradientColour) { osg::ref_ptr geometry = new osg::Geometry; osg::ref_ptr 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 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 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_DEPTH_TEST, osg::StateAttribute::OFF); return geometry; } osg::ref_ptr SceneWidget::createGradientCamera(QColor& bgColour, QColor& gradientColour) { osg::ref_ptr 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 gradientQuad = createGradientRectangle(bgColour, gradientColour); camera->addChild(std::move(gradientQuad)); return camera; } void SceneWidget::updateGradientCamera(QColor& bgColour, QColor& gradientColour) { osg::ref_ptr gradientRect = createGradientRectangle(bgColour, gradientColour); // Replaces previous rectangle mGradientCamera->setChild(0, gradientRect.get()); } void SceneWidget::setLighting(LightingMode mode) { int index = 0; osg::Vec4f ambient(mDefaultAmbient); osg::Vec4f diffuse(1.f, 1.f, 1.f, 1.f); // FIXME: this doesn't match the engine // Should be set from UnpagedWorldspaceWidget::update() osg::Vec4f position = osg::Vec4f(0.f, 0.f, 1.f, 0.f); switch (mode) { case LightingMode::Day: if (!mHasDefaultAmbient) ambient = osg::Vec4f(0.7f, 0.7f, 0.7f, 1.f); break; case LightingMode::Night: diffuse = osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f); if (!mHasDefaultAmbient) ambient = osg::Vec4f(0.2f, 0.2f, 0.2f, 1.f); index = mIsExterior ? 1 : 0; break; case LightingMode::Bright: ambient = osg::Vec4f(1.f, 1.f, 1.f, 1.f); break; } mSunLight->setPosition(position); mSunLight->setDiffuse(diffuse); mSunLight->setSpecular(diffuse); mSunLight->setAmbient(ambient); mStateUpdater->setAmbientColor(ambient); DayNightSwitchVisitor visitor(index); mRootNode->accept(visitor); flagAsModified(); } 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(LightingMode::Day); } else if (mode == "night") { backgroundColour = CSMPrefs::get()["Rendering"]["scene-night-background-colour"].toColor(); gradientColour = CSMPrefs::get()["Rendering"]["scene-night-gradient-colour"].toColor(); setLighting(LightingMode::Night); } else if (mode == "bright") { backgroundColour = CSMPrefs::get()["Rendering"]["scene-bright-background-colour"].toColor(); gradientColour = CSMPrefs::get()["Rendering"]["scene-bright-gradient-colour"].toColor(); setLighting(LightingMode::Bright); } 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" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Strong directional light source
  • " "
  • This mode closely resembles day time in-game
"); tool->addButton(":scenetoolbar/night", "night", "Night" "
  • Cell specific ambient in interiors
  • " "
  • Low ambient in exteriors
  • " "
  • Weak directional light source
  • " "
  • This mode closely resembles night time in-game
"); tool->addButton(":scenetoolbar/bright", "bright", "Bright" "
  • Maximum ambient
  • " "
  • Strong directional light source
"); connect(tool, &CSVWidget::SceneToolMode::modeChanged, this, &SceneWidget::selectLightingMode); return tool; } void SceneWidget::setDefaultAmbient(const osg::Vec4f& colour) { mDefaultAmbient = colour; mHasDefaultAmbient = true; } void SceneWidget::setExterior(bool isExterior) { mIsExterior = isExterior; } void SceneWidget::mouseMoveEvent(QMouseEvent* event) { QPointF pos = event->position(); mCurrentCamControl->handleMouseMoveEvent(pos.x() - mPrevMouse.x(), pos.y() - mPrevMouse.y()); mPrevMouse = pos; } void SceneWidget::wheelEvent(QWheelEvent* event) { mCurrentCamControl->handleMouseScrollEvent(event->angleDelta().y()); } void SceneWidget::resizeEvent(QResizeEvent* event) { updateCameraParameters(width() / static_cast(height())); } void SceneWidget::showEvent(QShowEvent* event) { updateCameraParameters(width() / static_cast(height())); } void SceneWidget::update(double dt) { if (mCamPositionSet) { mCurrentCamControl->update(dt); } else { mCurrentCamControl->setup(mRootNode, Mask_Reference | Mask_Terrain, CameraController::WorldUp); mCamPositionSet = true; } if (mSelectionMarkerNode) { osg::MatrixList worldMats = mSelectionMarkerNode->getWorldMatrices(); if (!worldMats.empty()) { osg::Matrixd markerWorldMat = worldMats[0]; osg::Vec3f eye, _; mView->getCamera()->getViewMatrix().getLookAt(eye, _, _); osg::Vec3f cameraLocalPos = eye * osg::Matrixd::inverse(markerWorldMat); bool isInFrontRightQuadrant = (cameraLocalPos.x() > 0.1f) && (cameraLocalPos.y() > 0.1f); bool isSignificantlyBehind = (cameraLocalPos.x() < 1.f) && (cameraLocalPos.y() < 1.f); if (!isInFrontRightQuadrant && isSignificantlyBehind) { osg::Quat current = mSelectionMarkerNode->getAttitude(); mSelectionMarkerNode->setAttitude(current * osg::Quat(osg::PI, osg::Vec3f(0, 0, 1))); } float distance = (markerWorldMat.getTrans() - eye).length(); float scale = std::max(distance / 75.0f, 1.0f); mSelectionMarkerNode->setScale(osg::Vec3(scale, scale, scale)); } } } 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") { setLighting(mLightingMode); } } void RenderWidget::updateCameraParameters(double overrideAspect) { const float nearDist = 1.0; const float farDist = 8192.0 * 100; 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(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(width()) / static_cast(height()), nearDist, farDist); } mSharedUniformStateUpdater->setScreenRes(width(), height()); mSharedUniformStateUpdater->setNear(nearDist); mSharedUniformStateUpdater->setFar(farDist); mPerViewUniformStateUpdater->setProjectionMatrix(mView->getCamera()->getProjectionMatrix()); mStateUpdater->setFogStart(nearDist); mStateUpdater->setFogEnd(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(); } } }