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.
1035 lines
34 KiB
C++
1035 lines
34 KiB
C++
#include "vrgui.hpp"
|
|
|
|
#include <cmath>
|
|
|
|
#include "vranimation.hpp"
|
|
#include "vrenvironment.hpp"
|
|
#include "vrpointer.hpp"
|
|
#include "vrsession.hpp"
|
|
#include "openxrinput.hpp"
|
|
#include "openxrmanagerimpl.hpp"
|
|
|
|
#include <osg/Texture2D>
|
|
#include <osg/ClipNode>
|
|
#include <osg/FrontFace>
|
|
#include <osg/BlendFunc>
|
|
#include <osg/Depth>
|
|
#include <osg/Fog>
|
|
#include <osg/LightModel>
|
|
|
|
|
|
#include <osgViewer/Renderer>
|
|
|
|
#include <components/sceneutil/visitor.hpp>
|
|
#include <components/sceneutil/shadow.hpp>
|
|
#include <components/myguiplatform/myguirendermanager.hpp>
|
|
#include <components/myguiplatform/additivelayer.hpp>
|
|
#include <components/myguiplatform/scalinglayer.hpp>
|
|
#include <components/misc/constants.hpp>
|
|
#include <components/misc/stringops.hpp>
|
|
#include <components/resource/resourcesystem.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
#include <components/shader/shadermanager.hpp>
|
|
|
|
#include "../mwrender/util.hpp"
|
|
#include "../mwrender/renderbin.hpp"
|
|
#include "../mwrender/renderingmanager.hpp"
|
|
#include "../mwrender/camera.hpp"
|
|
#include "../mwrender/vismask.hpp"
|
|
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
#include "../mwgui/windowbase.hpp"
|
|
|
|
#include "../mwbase/statemanager.hpp"
|
|
|
|
#include <MyGUI_Widget.h>
|
|
#include <MyGUI_ILayer.h>
|
|
#include <MyGUI_InputManager.h>
|
|
#include <MyGUI_WidgetManager.h>
|
|
#include <MyGUI_Window.h>
|
|
#include <MyGUI_FactoryManager.h>
|
|
#include <MyGUI_OverlappedLayer.h>
|
|
#include <MyGUI_SharedLayer.h>
|
|
|
|
namespace osg
|
|
{
|
|
// Convenience
|
|
const double PI_8 = osg::PI_4 / 2.;
|
|
}
|
|
|
|
namespace MWVR
|
|
{
|
|
|
|
// When making a circle of a given radius of equally wide planes separated by a given angle, what is the width
|
|
static osg::Vec2 radiusAngleWidth(float radius, float angleRadian)
|
|
{
|
|
const float width = std::fabs(2.f * radius * tanf(angleRadian / 2.f));
|
|
return osg::Vec2(width, width);
|
|
}
|
|
|
|
/// RTT camera used to draw the osg GUI to a texture
|
|
class GUICamera : public osg::Camera
|
|
{
|
|
public:
|
|
GUICamera(int width, int height, osg::Vec4 clearColor)
|
|
{
|
|
setRenderOrder(osg::Camera::PRE_RENDER);
|
|
setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
setCullingActive(false);
|
|
|
|
// Make the texture just a little transparent to feel more natural in the game world.
|
|
setClearColor(clearColor);
|
|
|
|
setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
|
|
setReferenceFrame(osg::Camera::ABSOLUTE_RF);
|
|
setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
|
|
setName("GUICamera");
|
|
|
|
setCullMask(MWRender::Mask_GUI);
|
|
setCullMaskLeft(MWRender::Mask_GUI);
|
|
setCullMaskRight(MWRender::Mask_GUI);
|
|
setNodeMask(MWRender::Mask_RenderToTexture);
|
|
|
|
setViewport(0, 0, width, height);
|
|
|
|
// No need for Update traversal since the mSceneRoot is already updated as part of the main scene graph
|
|
// A double update would mess with the light collection (in addition to being plain redundant)
|
|
setUpdateCallback(new MWRender::NoTraverseCallback);
|
|
|
|
// Create the texture
|
|
mTexture = new osg::Texture2D;
|
|
mTexture->setTextureSize(width, height);
|
|
mTexture->setInternalFormat(GL_RGBA);
|
|
mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
|
|
mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
|
|
mTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
|
|
mTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
|
|
attach(osg::Camera::COLOR_BUFFER, mTexture);
|
|
// Need to regenerate mipmaps every frame
|
|
setPostDrawCallback(new MWRender::MipmapCallback(mTexture));
|
|
|
|
// Do not want to waste time on shadows when generating the GUI texture
|
|
SceneUtil::ShadowManager::disableShadowsForStateSet(getOrCreateStateSet());
|
|
|
|
// Put rendering as early as possible
|
|
getOrCreateStateSet()->setRenderBinDetails(-1, "RenderBin");
|
|
|
|
}
|
|
|
|
void setScene(osg::Node* scene)
|
|
{
|
|
if (mScene)
|
|
removeChild(mScene);
|
|
mScene = scene;
|
|
addChild(scene);
|
|
Log(Debug::Verbose) << "Set new scene: " << mScene->getName();
|
|
}
|
|
|
|
osg::Texture2D* getTexture() const
|
|
{
|
|
return mTexture.get();
|
|
}
|
|
|
|
private:
|
|
osg::ref_ptr<osg::Texture2D> mTexture;
|
|
osg::ref_ptr<osg::Node> mScene;
|
|
};
|
|
|
|
|
|
//class LayerUpdateCallback : public osg::Callback
|
|
//{
|
|
//public:
|
|
// LayerUpdateCallback(VRGUILayer* layer)
|
|
// : mLayer(layer)
|
|
// {
|
|
|
|
// }
|
|
|
|
// bool run(osg::Object* object, osg::Object* data)
|
|
// {
|
|
// mLayer->update();
|
|
// return traverse(object, data);
|
|
// }
|
|
|
|
//private:
|
|
// VRGUILayer* mLayer;
|
|
//};
|
|
|
|
VRGUILayer::VRGUILayer(
|
|
osg::ref_ptr<osg::Group> geometryRoot,
|
|
osg::ref_ptr<osg::Group> cameraRoot,
|
|
std::string layerName,
|
|
LayerConfig config,
|
|
VRGUIManager* parent)
|
|
: mConfig(config)
|
|
, mLayerName(layerName)
|
|
, mGeometryRoot(geometryRoot)
|
|
, mCameraRoot(cameraRoot)
|
|
{
|
|
osg::ref_ptr<osg::Vec3Array> vertices{ new osg::Vec3Array(4) };
|
|
osg::ref_ptr<osg::Vec2Array> texCoords{ new osg::Vec2Array(4) };
|
|
osg::ref_ptr<osg::Vec3Array> normals{ new osg::Vec3Array(1) };
|
|
|
|
auto extent_units = config.extent * Constants::UnitsPerMeter;
|
|
|
|
float left = mConfig.center.x() - 0.5;
|
|
float right = left + 1.f;
|
|
float top = 0.5f + mConfig.center.y();
|
|
float bottom = top - 1.f;
|
|
|
|
// Define the menu quad
|
|
osg::Vec3 top_left(left, 1, top);
|
|
osg::Vec3 bottom_left(left, 1, bottom);
|
|
osg::Vec3 bottom_right(right, 1, bottom);
|
|
osg::Vec3 top_right(right, 1, top);
|
|
(*vertices)[0] = bottom_left;
|
|
(*vertices)[1] = top_left;
|
|
(*vertices)[2] = bottom_right;
|
|
(*vertices)[3] = top_right;
|
|
mGeometry->setVertexArray(vertices);
|
|
(*texCoords)[0].set(0.0f, 0.0f);
|
|
(*texCoords)[1].set(0.0f, 1.0f);
|
|
(*texCoords)[2].set(1.0f, 0.0f);
|
|
(*texCoords)[3].set(1.0f, 1.0f);
|
|
mGeometry->setTexCoordArray(0, texCoords);
|
|
(*normals)[0].set(0.0f, -1.0f, 0.0f);
|
|
mGeometry->setNormalArray(normals, osg::Array::BIND_OVERALL);
|
|
// TODO: Just use GL_TRIANGLES
|
|
mGeometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLE_STRIP, 0, 4));
|
|
mGeometry->setDataVariance(osg::Object::STATIC);
|
|
mGeometry->setSupportsDisplayList(false);
|
|
mGeometry->setName("VRGUILayer");
|
|
|
|
// Create the camera that will render the menu texture
|
|
std::string filter = mLayerName;
|
|
if (!mConfig.extraLayers.empty())
|
|
filter = filter + ";" + mConfig.extraLayers;
|
|
mGUICamera = new GUICamera(config.pixelResolution.x(), config.pixelResolution.y(), config.backgroundColor);
|
|
osgMyGUI::RenderManager& renderManager = static_cast<osgMyGUI::RenderManager&>(MyGUI::RenderManager::getInstance());
|
|
mMyGUICamera = renderManager.createGUICamera(osg::Camera::NESTED_RENDER, filter);
|
|
mGUICamera->setScene(mMyGUICamera);
|
|
|
|
// Define state set that allows rendering with transparency
|
|
osg::StateSet* stateSet = mGeometry->getOrCreateStateSet();
|
|
auto texture = menuTexture();
|
|
texture->setName("diffuseMap");
|
|
stateSet->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
|
|
|
|
osg::ref_ptr<osg::Material> mat = new osg::Material;
|
|
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
|
stateSet->setAttribute(mat);
|
|
|
|
// Position in the game world
|
|
mTransform->setScale(osg::Vec3(extent_units.x(), 1.f, extent_units.y()));
|
|
mTransform->addChild(mGeometry);
|
|
|
|
// Add to scene graph
|
|
mGeometryRoot->addChild(mTransform);
|
|
mCameraRoot->addChild(mGUICamera);
|
|
|
|
// Edit offset to account for priority
|
|
if (!mConfig.sideBySide)
|
|
{
|
|
mConfig.offset.y() -= 0.001f * static_cast<float>(mConfig.priority);
|
|
}
|
|
|
|
//mTransform->addUpdateCallback(new LayerUpdateCallback(this));
|
|
|
|
auto* tm = Environment::get().getTrackingManager();
|
|
mTrackingPath = tm->stringToVRPath(mConfig.trackingPath);
|
|
tm->bind(this, "uisource");
|
|
}
|
|
|
|
VRGUILayer::~VRGUILayer()
|
|
{
|
|
mGeometryRoot->removeChild(mTransform);
|
|
mCameraRoot->removeChild(mGUICamera);
|
|
}
|
|
osg::Camera* VRGUILayer::camera()
|
|
{
|
|
return mGUICamera.get();
|
|
}
|
|
|
|
osg::ref_ptr<osg::Texture2D> VRGUILayer::menuTexture()
|
|
{
|
|
if (mGUICamera)
|
|
return mGUICamera->getTexture();
|
|
return nullptr;
|
|
}
|
|
|
|
void VRGUILayer::setAngle(float angle)
|
|
{
|
|
mRotation = osg::Quat{ angle, osg::Z_AXIS };
|
|
updatePose();
|
|
}
|
|
|
|
void VRGUILayer::onTrackingUpdated(VRTrackingSource& source, DisplayTime predictedDisplayTime)
|
|
{
|
|
auto tp = source.getTrackingPose(predictedDisplayTime, mTrackingPath);
|
|
if (!!tp.status)
|
|
{
|
|
mTrackedPose = tp.pose;
|
|
updatePose();
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void VRGUILayer::updatePose()
|
|
{
|
|
|
|
auto orientation = mRotation * mTrackedPose.orientation;
|
|
|
|
if(mLayerName == "StatusHUD" || mLayerName == "VirtualKeyboard")
|
|
{
|
|
orientation = osg::Quat(osg::PI_2, osg::Vec3(0, 0, 1)) * orientation;
|
|
}
|
|
|
|
// Orient the offset and move the layer
|
|
auto position = mTrackedPose.position + orientation * mConfig.offset * Constants::UnitsPerMeter;
|
|
|
|
mTransform->setAttitude(orientation);
|
|
mTransform->setPosition(position);
|
|
}
|
|
|
|
void VRGUILayer::updateRect()
|
|
{
|
|
auto viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
|
mRealRect.left = 1.f;
|
|
mRealRect.top = 1.f;
|
|
mRealRect.right = 0.f;
|
|
mRealRect.bottom = 0.f;
|
|
float realWidth = static_cast<float>(viewSize.width);
|
|
float realHeight = static_cast<float>(viewSize.height);
|
|
for (auto* widget : mWidgets)
|
|
{
|
|
auto rect = widget->mMainWidget->getAbsoluteRect();
|
|
mRealRect.left = std::min(static_cast<float>(rect.left) / realWidth, mRealRect.left);
|
|
mRealRect.top = std::min(static_cast<float>(rect.top) / realHeight, mRealRect.top);
|
|
mRealRect.right = std::max(static_cast<float>(rect.right) / realWidth, mRealRect.right);
|
|
mRealRect.bottom = std::max(static_cast<float>(rect.bottom) / realHeight, mRealRect.bottom);
|
|
}
|
|
|
|
// Some widgets don't capture the full visual
|
|
if (mLayerName == "JournalBooks")
|
|
{
|
|
mRealRect.left = 0.f;
|
|
mRealRect.top = 0.f;
|
|
mRealRect.right = 1.f;
|
|
mRealRect.bottom = 1.f;
|
|
}
|
|
|
|
if (mLayerName == "Notification")
|
|
{
|
|
// The latest widget for notification is always the top one
|
|
// So i just stretch the rectangle to the bottom.
|
|
mRealRect.bottom = 1.f;
|
|
}
|
|
}
|
|
|
|
void VRGUILayer::update()
|
|
{
|
|
if (mConfig.sideBySide)
|
|
{
|
|
// The side-by-side windows are also the resizable windows.
|
|
// Stretch according to config
|
|
// This genre of layer should only ever have 1 widget as it will cover the full layer
|
|
auto* widget = mWidgets.front();
|
|
auto* myGUIWindow = dynamic_cast<MyGUI::Window*>(widget->mMainWidget);
|
|
auto* windowBase = dynamic_cast<MWGui::WindowBase*>(widget);
|
|
if (windowBase && myGUIWindow)
|
|
{
|
|
auto w = mConfig.myGUIViewSize.x();
|
|
auto h = mConfig.myGUIViewSize.y();
|
|
windowBase->setCoordf(0.f, 0.f, w, h);
|
|
windowBase->onWindowResize(myGUIWindow);
|
|
}
|
|
}
|
|
updateRect();
|
|
|
|
float w = 0.f;
|
|
float h = 0.f;
|
|
for (auto* widget : mWidgets)
|
|
{
|
|
w = std::max(w, (float)widget->mMainWidget->getWidth());
|
|
h = std::max(h, (float)widget->mMainWidget->getHeight());
|
|
}
|
|
|
|
// Pixels per unit
|
|
float res = static_cast<float>(mConfig.spatialResolution) / Constants::UnitsPerMeter;
|
|
|
|
if (mConfig.sizingMode == SizingMode::Auto)
|
|
{
|
|
mTransform->setScale(osg::Vec3(w / res, 1.f, h / res));
|
|
}
|
|
if (mLayerName == "Notification")
|
|
{
|
|
auto viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
|
h = (1.f - mRealRect.top) * static_cast<float>(viewSize.height);
|
|
mTransform->setScale(osg::Vec3(w / res, 1.f, h / res));
|
|
}
|
|
|
|
// Convert from [0,1] range to [-1,1]
|
|
float menuLeft = mRealRect.left * 2. - 1.;
|
|
float menuRight = mRealRect.right * 2. - 1.;
|
|
// Opposite convention
|
|
float menuBottom = (1.f - mRealRect.bottom) * 2. - 1.;
|
|
float menuTop = (1.f - mRealRect.top) * 2.f - 1.;
|
|
|
|
if(mLayerName == "InputBlocker")
|
|
mMyGUICamera->setProjectionMatrixAsOrtho2D(menuRight, menuLeft, menuTop, menuBottom);
|
|
else
|
|
mMyGUICamera->setProjectionMatrixAsOrtho2D(menuLeft, menuRight, menuBottom, menuTop);
|
|
}
|
|
|
|
void
|
|
VRGUILayer::insertWidget(
|
|
MWGui::Layout* widget)
|
|
{
|
|
for (auto* w : mWidgets)
|
|
if (w == widget)
|
|
return;
|
|
mWidgets.push_back(widget);
|
|
}
|
|
|
|
void
|
|
VRGUILayer::removeWidget(
|
|
MWGui::Layout* widget)
|
|
{
|
|
for (auto it = mWidgets.begin(); it != mWidgets.end(); it++)
|
|
{
|
|
if (*it == widget)
|
|
{
|
|
mWidgets.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
class VRGUIManagerUpdateCallback : public osg::Callback
|
|
{
|
|
public:
|
|
VRGUIManagerUpdateCallback(VRGUIManager* manager)
|
|
: mManager(manager)
|
|
{
|
|
|
|
}
|
|
|
|
bool run(osg::Object* object, osg::Object* data)
|
|
{
|
|
mManager->update();
|
|
return traverse(object, data);
|
|
}
|
|
|
|
private:
|
|
VRGUIManager* mManager;
|
|
};
|
|
|
|
static const LayerConfig createDefaultConfig(int priority, bool background = true, SizingMode sizingMode = SizingMode::Auto, std::string extraLayers = "Popup")
|
|
{
|
|
return LayerConfig{
|
|
priority,
|
|
false, // side-by-side
|
|
background ? osg::Vec4{0.f,0.f,0.f,.75f} : osg::Vec4{}, // background
|
|
osg::Vec3(0.f,0.66f,-.25f), // offset
|
|
osg::Vec2(0.f,0.f), // center (model space)
|
|
osg::Vec2(1.f, 1.f), // extent (meters)
|
|
1024, // Spatial resolution (pixels per meter)
|
|
osg::Vec2i(2048,2048), // Texture resolution
|
|
osg::Vec2(1,1),
|
|
sizingMode,
|
|
"/ui/input/stationary/pose",
|
|
extraLayers
|
|
};
|
|
}
|
|
|
|
static const float sSideBySideRadius = 1.f;
|
|
static const float sSideBySideAzimuthInterval = -osg::PI_4;
|
|
|
|
static const LayerConfig createSideBySideConfig(int priority)
|
|
{
|
|
LayerConfig config = createDefaultConfig(priority, true, SizingMode::Fixed, "");
|
|
config.sideBySide = true;
|
|
config.offset = osg::Vec3(0.f, sSideBySideRadius, -.25f);
|
|
config.extent = radiusAngleWidth(sSideBySideRadius, sSideBySideAzimuthInterval);
|
|
config.myGUIViewSize = osg::Vec2(0.70f, 0.70f);
|
|
return config;
|
|
};
|
|
|
|
static osg::Vec3 gLeftHudOffsetTop = osg::Vec3(-0.200f, -.05f, .066f);
|
|
static osg::Vec3 gLeftHudOffsetWrist = osg::Vec3(-0.200f, -.090f, -.033f);
|
|
|
|
void VRGUIManager::setGeometryRoot(osg::Group* root)
|
|
{
|
|
mGeometriesRootNode->removeChild(mGeometries);
|
|
mGeometriesRootNode = root;
|
|
mGeometriesRootNode->addChild(mGeometries);
|
|
}
|
|
|
|
void VRGUIManager::setCameraRoot(osg::Group* root)
|
|
{
|
|
mGUICamerasRootNode->removeChild(mGUICameras);
|
|
mGUICamerasRootNode = root;
|
|
mGUICamerasRootNode->addChild(mGUICameras);
|
|
}
|
|
|
|
VRGUIManager::VRGUIManager(
|
|
osg::ref_ptr<osgViewer::Viewer> viewer,
|
|
Resource::ResourceSystem* resourceSystem,
|
|
osg::Group* rootNode)
|
|
: mOsgViewer(viewer)
|
|
, mResourceSystem(resourceSystem)
|
|
, mGeometriesRootNode(rootNode)
|
|
, mGUICamerasRootNode(rootNode)
|
|
, mUiTracking(new VRGUITracking("pcworld"))
|
|
{
|
|
mGeometries->setName("VR GUI Geometry Root");
|
|
mGeometries->setUpdateCallback(new VRGUIManagerUpdateCallback(this));
|
|
mGeometries->setNodeMask(MWRender::VisMask::Mask_3DGUI);
|
|
mGeometriesRootNode->addChild(mGeometries);
|
|
|
|
auto stateSet = mGeometries->getOrCreateStateSet();
|
|
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
|
|
stateSet->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
|
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
|
// assign large value to effectively turn off fog
|
|
// shaders don't respect glDisable(GL_FOG)
|
|
osg::ref_ptr<osg::Fog> fog(new osg::Fog);
|
|
fog->setStart(10000000);
|
|
fog->setEnd(10000000);
|
|
stateSet->setAttributeAndModes(fog, osg::StateAttribute::OFF);
|
|
|
|
osg::ref_ptr<osg::LightModel> lightmodel = new osg::LightModel;
|
|
lightmodel->setAmbientIntensity(osg::Vec4(1.0, 1.0, 1.0, 1.0));
|
|
stateSet->setAttributeAndModes(lightmodel, osg::StateAttribute::ON);
|
|
|
|
SceneUtil::ShadowManager::disableShadowsForStateSet(stateSet);
|
|
mGeometries->setStateSet(stateSet);
|
|
|
|
mGUICameras->setName("VR GUI Cameras Root");
|
|
mGUICameras->setNodeMask(MWRender::VisMask::Mask_3DGUI);
|
|
mGUICamerasRootNode->addChild(mGUICameras);
|
|
|
|
LayerConfig defaultConfig = createDefaultConfig(1);
|
|
LayerConfig loadingScreenConfig = createDefaultConfig(1, true, SizingMode::Fixed, "Menu");
|
|
LayerConfig mainMenuConfig = createDefaultConfig(5, true);
|
|
LayerConfig journalBooksConfig = createDefaultConfig(2, false, SizingMode::Fixed);
|
|
LayerConfig defaultWindowsConfig = createDefaultConfig(3, true);
|
|
LayerConfig videoPlayerConfig = createDefaultConfig(4, true, SizingMode::Fixed);
|
|
LayerConfig messageBoxConfig = createDefaultConfig(6, false, SizingMode::Auto);;
|
|
LayerConfig notificationConfig = createDefaultConfig(7, false, SizingMode::Fixed);
|
|
LayerConfig listBoxConfig = createDefaultConfig(10, true);
|
|
|
|
LayerConfig statsWindowConfig = createSideBySideConfig(0);
|
|
LayerConfig inventoryWindowConfig = createSideBySideConfig(1);
|
|
LayerConfig spellWindowConfig = createSideBySideConfig(2);
|
|
LayerConfig mapWindowConfig = createSideBySideConfig(3);
|
|
LayerConfig inventoryCompanionWindowConfig = createSideBySideConfig(4);
|
|
LayerConfig dialogueWindowConfig = createSideBySideConfig(5);
|
|
LayerConfig chatWindowConfig = createSideBySideConfig(6);
|
|
LayerConfig consoleWindowConfig = createSideBySideConfig(7);
|
|
|
|
osg::Vec3 leftHudOffset = gLeftHudOffsetWrist;
|
|
|
|
std::string leftHudSetting = Settings::Manager::getString("left hand hud position", "VR");
|
|
if (Misc::StringUtils::ciEqual(leftHudSetting, "top"))
|
|
leftHudOffset = gLeftHudOffsetTop;
|
|
|
|
osg::Vec3 vkeyboardOffset = leftHudOffset + osg::Vec3(0,0.0001,0);
|
|
|
|
LayerConfig virtualKeyboardConfig = LayerConfig{
|
|
10,
|
|
false,
|
|
osg::Vec4{0.f,0.f,0.f,.75f},
|
|
vkeyboardOffset, // offset (meters)
|
|
osg::Vec2(0.f,0.5f), // center (model space)
|
|
osg::Vec2(.25f, .25f), // extent (meters)
|
|
2048, // Spatial resolution (pixels per meter)
|
|
osg::Vec2i(2048,2048), // Texture resolution
|
|
osg::Vec2(1,1),
|
|
SizingMode::Auto,
|
|
"/user/hand/left/input/aim/pose",
|
|
""
|
|
};
|
|
LayerConfig statusHUDConfig = LayerConfig
|
|
{
|
|
0,
|
|
false, // side-by-side
|
|
osg::Vec4{}, // background
|
|
leftHudOffset, // offset (meters)
|
|
osg::Vec2(0.f,0.5f), // center (model space)
|
|
osg::Vec2(.1f, .1f), // extent (meters)
|
|
1024, // resolution (pixels per meter)
|
|
osg::Vec2i(1024,1024),
|
|
defaultConfig.myGUIViewSize,
|
|
SizingMode::Auto,
|
|
"/user/hand/left/input/aim/pose",
|
|
""
|
|
};
|
|
|
|
LayerConfig popupConfig = LayerConfig
|
|
{
|
|
0,
|
|
false, // side-by-side
|
|
osg::Vec4{0.f,0.f,0.f,0.f}, // background
|
|
osg::Vec3(-0.025f,-.200f,.066f), // offset (meters)
|
|
osg::Vec2(0.f,0.5f), // center (model space)
|
|
osg::Vec2(.1f, .1f), // extent (meters)
|
|
1024, // resolution (pixels per meter)
|
|
osg::Vec2i(2048,2048),
|
|
defaultConfig.myGUIViewSize,
|
|
SizingMode::Auto,
|
|
"/user/hand/right/input/aim/pose",
|
|
""
|
|
};
|
|
|
|
mLayerConfigs = std::map<std::string, LayerConfig>
|
|
{
|
|
{"DefaultConfig", defaultConfig},
|
|
{"StatusHUD", statusHUDConfig},
|
|
{"Tooltip", popupConfig},
|
|
{"JournalBooks", journalBooksConfig},
|
|
{"InventoryCompanionWindow", inventoryCompanionWindowConfig},
|
|
{"InventoryWindow", inventoryWindowConfig},
|
|
{"SpellWindow", spellWindowConfig},
|
|
{"MapWindow", mapWindowConfig},
|
|
{"StatsWindow", statsWindowConfig},
|
|
{"DialogueWindow", dialogueWindowConfig},
|
|
{"MessageBox", messageBoxConfig},
|
|
{"Windows", defaultWindowsConfig},
|
|
{"ListBox", listBoxConfig},
|
|
{"MainMenu", mainMenuConfig},
|
|
{"Notification", notificationConfig},
|
|
{"InputBlocker", videoPlayerConfig},
|
|
{"Menu", videoPlayerConfig},
|
|
{"LoadingScreen", loadingScreenConfig},
|
|
{"VirtualKeyboard", virtualKeyboardConfig},
|
|
{"Chat", chatWindowConfig},
|
|
{"Console", consoleWindowConfig},
|
|
};
|
|
}
|
|
|
|
VRGUIManager::~VRGUIManager(void)
|
|
{
|
|
}
|
|
|
|
static std::set<std::string> layerBlacklist =
|
|
{
|
|
"Overlay",
|
|
"AdditiveOverlay",
|
|
};
|
|
|
|
void VRGUIManager::updateSideBySideLayers()
|
|
{
|
|
// Nothing to update
|
|
if (mSideBySideLayers.size() == 0)
|
|
return;
|
|
|
|
std::sort(mSideBySideLayers.begin(), mSideBySideLayers.end(), [](const auto& lhs, const auto& rhs) { return *lhs < *rhs; });
|
|
|
|
int n = mSideBySideLayers.size();
|
|
|
|
float span = sSideBySideAzimuthInterval * static_cast<float>(n - 1); // zero index, places lone layers straight ahead
|
|
float low = -span / 2;
|
|
|
|
for (unsigned i = 0; i < mSideBySideLayers.size(); i++)
|
|
{
|
|
mSideBySideLayers[i]->setAngle(low + static_cast<float>(i) * sSideBySideAzimuthInterval);
|
|
}
|
|
}
|
|
|
|
void VRGUIManager::insertLayer(const std::string& name)
|
|
{
|
|
LayerConfig config{};
|
|
auto configIt = mLayerConfigs.find(name);
|
|
if (configIt != mLayerConfigs.end())
|
|
{
|
|
config = configIt->second;
|
|
}
|
|
else
|
|
{
|
|
Log(Debug::Warning) << "Layer " << name << " has no configuration, using default";
|
|
config = mLayerConfigs["DefaultConfig"];
|
|
}
|
|
|
|
auto layer = std::shared_ptr<VRGUILayer>(new VRGUILayer(
|
|
mGeometries,
|
|
mGUICameras,
|
|
name,
|
|
config,
|
|
this
|
|
));
|
|
mLayers[name] = layer;
|
|
|
|
layer->mGeometry->setUserData(new VRGUILayerUserData(mLayers[name]));
|
|
|
|
if (config.sideBySide)
|
|
{
|
|
mSideBySideLayers.push_back(layer);
|
|
updateSideBySideLayers();
|
|
}
|
|
|
|
Resource::SceneManager* sceneManager = mResourceSystem->getSceneManager();
|
|
sceneManager->recreateShaders(layer->mGeometry);
|
|
}
|
|
|
|
void VRGUIManager::insertWidget(MWGui::Layout* widget)
|
|
{
|
|
auto* layer = widget->mMainWidget->getLayer();
|
|
auto name = layer->getName();
|
|
|
|
auto it = mLayers.find(name);
|
|
if (it == mLayers.end())
|
|
{
|
|
insertLayer(name);
|
|
it = mLayers.find(name);
|
|
if (it == mLayers.end())
|
|
{
|
|
Log(Debug::Error) << "Failed to insert layer " << name;
|
|
return;
|
|
}
|
|
}
|
|
|
|
it->second->insertWidget(widget);
|
|
|
|
if (it->second.get() != mFocusLayer)
|
|
setPick(widget, false);
|
|
}
|
|
|
|
void VRGUIManager::removeLayer(const std::string& name)
|
|
{
|
|
auto it = mLayers.find(name);
|
|
if (it == mLayers.end())
|
|
return;
|
|
|
|
auto layer = it->second;
|
|
|
|
for (auto it2 = mSideBySideLayers.begin(); it2 < mSideBySideLayers.end(); it2++)
|
|
{
|
|
if (*it2 == layer)
|
|
{
|
|
mSideBySideLayers.erase(it2);
|
|
updateSideBySideLayers();
|
|
}
|
|
}
|
|
|
|
if (it->second.get() == mFocusLayer)
|
|
setFocusLayer(nullptr);
|
|
|
|
mLayers.erase(it);
|
|
}
|
|
|
|
void VRGUIManager::removeWidget(MWGui::Layout* widget)
|
|
{
|
|
auto* layer = widget->mMainWidget->getLayer();
|
|
auto name = layer->getName();
|
|
|
|
auto it = mLayers.find(name);
|
|
if (it == mLayers.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
it->second->removeWidget(widget);
|
|
if (it->second->widgetCount() == 0)
|
|
{
|
|
removeLayer(name);
|
|
}
|
|
}
|
|
|
|
void VRGUIManager::setVisible(MWGui::Layout* widget, bool visible)
|
|
{
|
|
auto* layer = widget->mMainWidget->getLayer();
|
|
auto name = layer->getName();
|
|
|
|
if (layerBlacklist.find(name) != layerBlacklist.end())
|
|
{
|
|
// Never pick an invisible layer
|
|
setPick(widget, false);
|
|
return;
|
|
}
|
|
|
|
if (visible)
|
|
insertWidget(widget);
|
|
else
|
|
removeWidget(widget);
|
|
}
|
|
|
|
void VRGUIManager::updateTracking()
|
|
{
|
|
mUiTracking->resetStationaryPose();
|
|
}
|
|
|
|
bool VRGUIManager::updateFocus()
|
|
{
|
|
auto* world = MWBase::Environment::get().getWorld();
|
|
if (world)
|
|
{
|
|
auto& pointer = world->getUserPointer();
|
|
if (pointer.getPointerTarget().mHit)
|
|
{
|
|
std::shared_ptr<VRGUILayer> newFocusLayer = nullptr;
|
|
auto* node = pointer.getPointerTarget().mHitNode;
|
|
if (node->getName() == "VRGUILayer")
|
|
{
|
|
VRGUILayerUserData* userData = static_cast<VRGUILayerUserData*>(node->getUserData());
|
|
newFocusLayer = userData->mLayer.lock();
|
|
}
|
|
|
|
if (newFocusLayer && newFocusLayer->mLayerName != "Notification")
|
|
{
|
|
setFocusLayer(newFocusLayer.get());
|
|
computeGuiCursor(pointer.getPointerTarget().mHitPointLocal);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void VRGUIManager::update()
|
|
{
|
|
auto xr = MWVR::Environment::get().getManager();
|
|
if (xr)
|
|
if (!xr->appShouldRender())
|
|
updateTracking();
|
|
}
|
|
|
|
void VRGUIManager::setFocusLayer(VRGUILayer* layer)
|
|
{
|
|
if (layer == mFocusLayer)
|
|
return;
|
|
|
|
if (mFocusLayer)
|
|
{
|
|
if (!mFocusLayer->mWidgets.empty())
|
|
setPick(mFocusLayer->mWidgets.front(), false);
|
|
}
|
|
mFocusLayer = layer;
|
|
if (mFocusLayer)
|
|
{
|
|
if (!mFocusLayer->mWidgets.empty())
|
|
{
|
|
Log(Debug::Verbose) << "Set focus layer to " << mFocusLayer->mWidgets.front()->mMainWidget->getLayer()->getName();
|
|
setPick(mFocusLayer->mWidgets.front(), true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log(Debug::Verbose) << "Set focus layer to null";
|
|
}
|
|
}
|
|
|
|
void VRGUIManager::setFocusWidget(MyGUI::Widget* widget)
|
|
{
|
|
// TODO: This relies on MyGUI internal functions and may break on any future version.
|
|
if (widget == mFocusWidget)
|
|
return;
|
|
if (mFocusWidget)
|
|
mFocusWidget->_riseMouseLostFocus(widget);
|
|
if (widget)
|
|
widget->_riseMouseSetFocus(mFocusWidget);
|
|
mFocusWidget = widget;
|
|
}
|
|
|
|
void VRGUIManager::configUpdated(const std::string& layer)
|
|
{
|
|
auto it = mLayers.find(layer);
|
|
if (it != mLayers.end())
|
|
{
|
|
it->second->mConfig = mLayerConfigs[layer];
|
|
}
|
|
}
|
|
|
|
void VRGUIManager::notifyWidgetUnlinked(MyGUI::Widget* widget)
|
|
{
|
|
if (widget == mFocusWidget)
|
|
mFocusWidget = nullptr;
|
|
}
|
|
|
|
bool VRGUIManager::injectMouseClick(bool onPress)
|
|
{
|
|
// TODO: This relies on a MyGUI internal functions and may break un any future version.
|
|
if (mFocusWidget)
|
|
{
|
|
if(onPress)
|
|
mFocusWidget->_riseMouseButtonClick();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void VRGUIManager::processChangedSettings(const std::set<std::pair<std::string, std::string>>& changed)
|
|
{
|
|
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
|
|
{
|
|
if (it->first == "VR" && it->second == "left hand hud position")
|
|
{
|
|
std::string leftHudSetting = Settings::Manager::getString("left hand hud position", "VR");
|
|
if (Misc::StringUtils::ciEqual(leftHudSetting, "top"))
|
|
mLayerConfigs["StatusHUD"].offset = gLeftHudOffsetTop;
|
|
else
|
|
mLayerConfigs["StatusHUD"].offset = gLeftHudOffsetWrist;
|
|
mLayerConfigs["VirtualKeyboard"].offset = mLayerConfigs["StatusHUD"].offset + osg::Vec3(0,0.0001,0);
|
|
|
|
configUpdated("StatusHUD");
|
|
configUpdated("VirtualKeyboard");
|
|
}
|
|
}
|
|
}
|
|
|
|
class Pickable
|
|
{
|
|
public:
|
|
virtual void setPick(bool pick) = 0;
|
|
};
|
|
|
|
template <typename L>
|
|
class PickLayer : public L, public Pickable
|
|
{
|
|
public:
|
|
using L::L;
|
|
|
|
void setPick(bool pick) override
|
|
{
|
|
L::mIsPick = pick;
|
|
}
|
|
};
|
|
|
|
template <typename L>
|
|
class MyFactory
|
|
{
|
|
public:
|
|
using LayerType = L;
|
|
using PickLayerType = PickLayer<LayerType>;
|
|
using Delegate = MyGUI::delegates::CDelegate1<MyGUI::IObject*&>;
|
|
static typename Delegate::IDelegate* getFactory()
|
|
{
|
|
return MyGUI::newDelegate(createFromFactory);
|
|
}
|
|
|
|
static void registerFactory()
|
|
{
|
|
MyGUI::FactoryManager::getInstance().registerFactory("Layer", LayerType::getClassTypeName(), getFactory());
|
|
}
|
|
|
|
private:
|
|
static void createFromFactory(MyGUI::IObject*& _instance)
|
|
{
|
|
_instance = new PickLayerType();
|
|
}
|
|
};
|
|
|
|
void VRGUIManager::registerMyGUIFactories()
|
|
{
|
|
MyFactory< MyGUI::OverlappedLayer >::registerFactory();
|
|
MyFactory< MyGUI::SharedLayer >::registerFactory();
|
|
MyFactory< osgMyGUI::AdditiveLayer >::registerFactory();
|
|
MyFactory< osgMyGUI::AdditiveLayer >::registerFactory();
|
|
}
|
|
|
|
void VRGUIManager::setPick(MWGui::Layout* widget, bool pick)
|
|
{
|
|
auto* layer = widget->mMainWidget->getLayer();
|
|
auto* pickable = dynamic_cast<Pickable*>(layer);
|
|
if (pickable)
|
|
pickable->setPick(pick);
|
|
}
|
|
|
|
void VRGUIManager::computeGuiCursor(osg::Vec3 hitPoint)
|
|
{
|
|
float x = 0;
|
|
float y = 0;
|
|
if (mFocusLayer)
|
|
{
|
|
osg::Vec2 bottomLeft = mFocusLayer->mConfig.center - osg::Vec2(0.5f, 0.5f);
|
|
x = hitPoint.x() - bottomLeft.x();
|
|
y = hitPoint.z() - bottomLeft.y();
|
|
auto rect = mFocusLayer->mRealRect;
|
|
auto viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
|
float width = static_cast<float>(viewSize.width) * rect.width();
|
|
float height = static_cast<float>(viewSize.height) * rect.height();
|
|
float left = static_cast<float>(viewSize.width) * rect.left;
|
|
float bottom = static_cast<float>(viewSize.height) * rect.bottom;
|
|
x = width * x + left;
|
|
y = bottom - height * y;
|
|
}
|
|
|
|
mGuiCursor.x() = (int)x;
|
|
mGuiCursor.y() = (int)y;
|
|
|
|
MyGUI::InputManager::getInstance().injectMouseMove((int)x, (int)y, 0);
|
|
MWBase::Environment::get().getWindowManager()->setCursorActive(true);
|
|
|
|
// The virtual keyboard must be interactive regardless of modals
|
|
// This could be generalized with another config entry, but i don't think any other
|
|
// widgets/layers need it so i'm hardcoding it for the VirtualKeyboard for now.
|
|
if (
|
|
mFocusLayer
|
|
&& mFocusLayer->mLayerName == "VirtualKeyboard"
|
|
&& MyGUI::InputManager::getInstance().isModalAny())
|
|
{
|
|
auto* widget = MyGUI::LayerManager::getInstance().getWidgetFromPoint((int)x, (int)y);
|
|
setFocusWidget(widget);
|
|
}
|
|
else
|
|
setFocusWidget(nullptr);
|
|
|
|
}
|
|
|
|
VRGUITracking::VRGUITracking(const std::string& source)
|
|
: VRTrackingSource("uisource")
|
|
{
|
|
auto* tm = Environment::get().getTrackingManager();
|
|
mSource = tm->getSource(source);
|
|
mHeadPath = tm->stringToVRPath("/user/head/input/pose");
|
|
mStationaryPath = tm->stringToVRPath("/ui/input/stationary/pose");
|
|
}
|
|
|
|
VRTrackingPose VRGUITracking::getTrackingPoseImpl(DisplayTime predictedDisplayTime, VRPath path, VRPath reference)
|
|
{
|
|
if (path == mStationaryPath)
|
|
return mStationaryPose;
|
|
return mSource->getTrackingPose(predictedDisplayTime, path, reference);
|
|
}
|
|
|
|
std::vector<VRPath> VRGUITracking::listSupportedTrackingPosePaths() const
|
|
{
|
|
auto paths = mSource->listSupportedTrackingPosePaths();
|
|
paths.push_back(mStationaryPath);
|
|
return paths;
|
|
}
|
|
|
|
void VRGUITracking::updateTracking(DisplayTime predictedDisplayTime)
|
|
{
|
|
if (mSource->availablePosesChanged())
|
|
notifyAvailablePosesChanged();
|
|
|
|
if (mShouldUpdateStationaryPose)
|
|
{
|
|
auto tp = mSource->getTrackingPose(predictedDisplayTime, mHeadPath);
|
|
if (!!tp.status)
|
|
{
|
|
mShouldUpdateStationaryPose = false;
|
|
mStationaryPose = tp;
|
|
|
|
// Stationary UI elements should always be vertical
|
|
auto axis = osg::Z_AXIS;
|
|
osg::Quat vertical;
|
|
auto local = mStationaryPose.pose.orientation * axis;
|
|
vertical.makeRotate(local, axis);
|
|
mStationaryPose.pose.orientation = mStationaryPose.pose.orientation * vertical;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VRGUITracking::resetStationaryPose()
|
|
{
|
|
mShouldUpdateStationaryPose = true;
|
|
}
|
|
|
|
}
|