diff --git a/CHANGELOG.md b/CHANGELOG.md index dfea001f0c..3a59aca8ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Bug #4453: Quick keys behaviour is invalid for equipment Bug #4454: AI opens doors too slow Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #4222: 360° screenshots Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 78e368cfcc..5052a13ccd 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -499,7 +499,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); @@ -656,8 +656,11 @@ void OMW::Engine::go() settingspath = loadSettings (settings); - mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), - Settings::Manager::getString("screenshot format", "General"))); + mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), + Settings::Manager::getString("screenshot format", "General")); + + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); + mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 2fff91c7e5..e42a5c94f1 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -7,7 +7,7 @@ #include #include - +#include #include "mwbase/environment.hpp" @@ -82,6 +82,7 @@ namespace OMW boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; bool mSkipMenu; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fb1450aa6a..23353cbfe3 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,6 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; + virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 2c12547fda..99fe777aa7 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -153,7 +153,7 @@ namespace MWGui virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); } }; - void LoadingScreen::loadingOn() + void LoadingScreen::loadingOn(bool visible) { mLoadingOnTime = mTimer.time_m(); // Early-out if already on @@ -169,7 +169,10 @@ namespace MWGui // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); - mShowWallpaper = (MWBase::Environment::get().getStateManager()->getState() + mVisible = visible; + mLoadingBox->setVisible(mVisible); + + mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); setVisible(true); @@ -180,10 +183,15 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); + + if (!mVisible) + draw(); } void LoadingScreen::loadingOff() { + mLoadingBox->setVisible(true); // restore + if (mLastRenderTime < mLoadingOnTime) { // the loading was so fast that we didn't show loading screen at all @@ -306,7 +314,7 @@ namespace MWGui void LoadingScreen::draw() { - if (!needToDrawLoadingScreen()) + if (mVisible && !needToDrawLoadingScreen()) return; if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 8536972a37..bdd210d00d 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -35,7 +35,7 @@ namespace MWGui /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details virtual void setLabel (const std::string& label, bool important); - virtual void loadingOn(); + virtual void loadingOn(bool visible=true); virtual void loadingOff(); virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); @@ -63,6 +63,8 @@ namespace MWGui bool mImportantLabel; + bool mVisible; + size_t mProgress; bool mShowWallpaper; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 507e97a6ac..9fe9026ca5 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -36,12 +36,14 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) + , mScreenCaptureOperation(screenCaptureOperation) , mJoystickLastUsed(false) , mPlayer(NULL) , mInputManager(NULL) @@ -1033,8 +1035,34 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); + bool regularScreenshot = true; + bool screenshotTaken = false; + + std::string settingStr; + + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; + + if (regularScreenshot) + { + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + screenshotTaken = true; + } + else + { + osg::ref_ptr screenshot (new osg::Image); + + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) + { + (*mScreenCaptureOperation) (*(screenshot.get()),0); + // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + screenshotTaken = true; + } + } + + if (screenshotTaken) + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); } void InputManager::toggleInventory() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index cba7fc7431..bc62ef7dc9 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -4,6 +4,7 @@ #include "../mwgui/mode.hpp" #include +#include #include #include @@ -14,7 +15,6 @@ #include "../mwbase/inputmanager.hpp" - namespace MWWorld { class Player; @@ -74,6 +74,7 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab); @@ -158,6 +159,7 @@ namespace MWInput bool mWindowVisible; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; bool mJoystickLastUsed; MWWorld::Player* mPlayer; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6f0ddba3ab..ff93e3d3de 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -12,16 +12,21 @@ #include #include #include +#include +#include #include #include +#include + #include #include #include #include #include +#include #include @@ -39,7 +44,12 @@ #include #include +#include + #include "../mwworld/cellstore.hpp" +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -212,7 +222,7 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(sceneRoot); + mRootNode->addChild(mSceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -668,7 +678,203 @@ namespace MWRender mutable bool mDone; }; - void RenderingManager::screenshot(osg::Image *image, int w, int h) + bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) + { + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + int screenshotMapping = 0; + int cubeSize = screenshotMapping == 2 ? + screenshotW: // planet mapping needs higher resolution + screenshotW / 2; + + std::vector settingArgs; + boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = i; + found = true; + break; + } + + if (!found) + { + std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; + return false; + } + } + + if (settingArgs.size() > 1) + screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); + + if (mCamera->isVanityOrPreviewModeEnabled()) + { + std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl; + return false; + } + + bool rawCubemap = screenshotMapping == 3; + + if (rawCubemap) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == 2) + screenshotH = screenshotW; // use square resolution for planet mapping + + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); + + osg::Vec3 directions[6] = { + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0}; + + double fovBackup = mFieldOfView; + mFieldOfView = 90.0; // each cubemap side sees 90 degrees + + int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); + + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(0); + + for (int i = 0; i < 6; ++i) // for each cubemap side + { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); + screenshot(sideImage,cubeSize,cubeSize,transform); + + if (!rawCubemap) + sideImage->flipHorizontal(); + } + + mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); + mFieldOfView = fovBackup; + + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + + return true; + } + + // run on GPU now: + + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,images[i].get()); + + osg::ref_ptr screenshotCamera (new osg::Camera); + osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); + + std::map defineMap; + + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program (new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap",0)); + stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); + stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(NULL); + + screenshotCamera->addChild(quad); + + mRootNode->addChild(screenshotCamera); + + renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); + + screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); + mRootNode->removeChild(screenshotCamera); + + return true; + } + + void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback); + camera->setFinalDrawCallback(callback); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + callback->waitTillDone(); + + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + } + + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -677,7 +883,8 @@ namespace MWRender rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); @@ -693,25 +900,17 @@ namespace MWRender rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - mRootNode->addChild(rttCamera); + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback); - rttCamera->setFinalDrawCallback(callback); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); - // at the time this function is called we are in the middle of a frame, - // so out of order calls are necessary to get a correct frameNumber for the next frame. - // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); + mRootNode->addChild(rttCamera); - callback->waitTillDone(); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + renderCameraToImage(rttCamera.get(),image,w,h); rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1c689d29fa..6511036cf5 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -125,7 +126,8 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h); + void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); + bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult { @@ -213,6 +215,8 @@ namespace MWRender void reportStats() const; + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 6e1fef716f..3c617f794e 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -470,6 +470,16 @@ void Water::updateWaterMaterial() updateVisible(); } +osg::Camera *Water::getReflectionCamera() +{ + return mReflection; +} + +osg::Camera *Water::getRefractionCamera() +{ + return mRefraction; +} + void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index a4fd1ed367..e2413cfa05 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -112,6 +113,9 @@ namespace MWRender void update(float dt); + osg::Camera *getReflectionCamera(); + osg::Camera *getRefractionCamera(); + void processChangedSettings(const Settings::CategorySettingVector& settings); osg::Uniform *getRainIntensityUniform(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cbd5b93406..000bdfa1aa 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2300,6 +2300,11 @@ namespace MWWorld { mRendering->screenshot(image, w, h); } + + bool World::screenshot360(osg::Image* image, std::string settingStr) + { + return mRendering->screenshot360(image,settingStr); + } void World::activateDoor(const MWWorld::Ptr& door) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1d22b96bcd..120397d28c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -561,6 +561,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; + bool screenshot360 (osg::Image* image, std::string settingStr) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 1d48cce0b9..6c7a3b090b 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -20,7 +20,7 @@ namespace Loading /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, /// so that the loading is exception safe. - virtual void loadingOn() {} + virtual void loadingOn(bool visible=true) {} virtual void loadingOff() {} /// Set the total range of progress (e.g. the number of objects to load). diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 09283bfc91..1390bf8c6d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -402,6 +402,10 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 +# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) +screenshot type = regular + [Water] # Enable water shader with reflections and optionally refraction. diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5833a592fe..7baca78ef1 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,8 @@ set(SHADER_FILES terrain_fragment.glsl lighting.glsl parallax.glsl + s360_fragment.glsl + s360_vertex.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl new file mode 100644 index 0000000000..f52e1478ee --- /dev/null +++ b/files/shaders/s360_fragment.glsl @@ -0,0 +1,52 @@ +#version 120 + +varying vec2 uv; +uniform samplerCube cubeMap; +uniform int mapping; + +#define PI 3.1415926535 + +vec3 sphericalCoords(vec2 coords) +{ + coords.x = -1 * coords.x * 2 * PI; + coords.y = (coords.y - 0.5) * PI; + + vec3 result = vec3(0.0,cos(coords.y),sin(coords.y)); + result = vec3(cos(coords.x) * result.y,sin(coords.x) * result.y,result.z); + + return result; +} + +vec3 cylindricalCoords(vec2 coords) +{ + return normalize(vec3(cos(-1 * coords.x * 2 * PI),sin(-1 * coords.x * 2 * PI),coords.y * 2.0 - 1.0)); +} + +vec3 planetCoords(vec2 coords) +{ + vec2 fromCenter = coords - vec2(0.5,0.5); + + float magnitude = length(fromCenter); + + fromCenter = normalize(fromCenter); + + float dotProduct = dot(fromCenter,vec2(0.0,1.0)); + + coords.x = coords.x > 0.5 ? 0.5 - (dotProduct + 1.0) / 4.0 : 0.5 + (dotProduct + 1.0) / 4.0; + coords.y = max(0.0,1.0 - pow(magnitude / 0.5,0.5)); + return sphericalCoords(coords); +} + +void main(void) +{ + vec3 c; + + if (mapping == 0) + c = sphericalCoords(uv); + else if (mapping == 1) + c = cylindricalCoords(uv); + else + c = planetCoords(uv); + + gl_FragData[0] = textureCube(cubeMap,c); +} diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl new file mode 100644 index 0000000000..ad96620c3f --- /dev/null +++ b/files/shaders/s360_vertex.glsl @@ -0,0 +1,9 @@ +#version 120 + +varying vec2 uv; + +void main(void) +{ + gl_Position = gl_Vertex; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2; +}