From 7feba7e49816485c43fb7bb46c2895317eb56c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 13:07:11 +0100 Subject: [PATCH 01/47] basic setup for 360 screenshots --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwinput/inputmanagerimp.cpp | 33 +- apps/openmw/mwmechanics/character.cpp.orig | 2464 ++++++++++++++++++++ apps/openmw/mwmechanics/character.cpp.rej | 46 + apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/worldimp.cpp | 5 + apps/openmw/mwworld/worldimp.hpp | 1 + 9 files changed, 2553 insertions(+), 4 deletions(-) create mode 100644 apps/openmw/mwmechanics/character.cpp.orig create mode 100644 apps/openmw/mwmechanics/character.cpp.rej diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 038535939..80a9e11a1 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 void screenshot360 (osg::Image* image, int w) = 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/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index a5bb93b6c..192eadbde 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,6 +1,7 @@ #include "inputmanagerimp.hpp" #include +#include #include #include @@ -1014,10 +1015,36 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); + // MOVE THIS ELSEWHERE LATER! + int screenshotW = 512; + osg::ref_ptr screenshot (new osg::Image); + MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); - MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); + + if (!readerwriter) + { + std::cerr << "Error: Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + return; + } + + std::ofstream outfile; + outfile.open("test.jpg"); + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, outfile); + + if (!result.success()) + { + outfile << "Error: Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; + return; + } + + outfile.close(); + +// mScreenCaptureHandler->setFramesToCapture(1); +// mScreenCaptureHandler->captureNextFrame(*mViewer); + +// MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } void InputManager::toggleInventory() diff --git a/apps/openmw/mwmechanics/character.cpp.orig b/apps/openmw/mwmechanics/character.cpp.orig new file mode 100644 index 000000000..f262850a4 --- /dev/null +++ b/apps/openmw/mwmechanics/character.cpp.orig @@ -0,0 +1,2464 @@ +/* + * OpenMW - The completely unofficial reimplementation of Morrowind + * + * This file (character.cpp) is part of the OpenMW package. + * + * OpenMW is distributed as free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 3 along with this program. If not, see + * http://www.gnu.org/licenses/ . + */ + +#include "character.hpp" + +#include + +#include + +#include + +#include + +#include "../mwrender/animation.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" + +#include "movement.hpp" +#include "npcstats.hpp" +#include "creaturestats.hpp" +#include "security.hpp" +#include "actorutil.hpp" +#include "spellcasting.hpp" + +namespace +{ + +// Wraps a value to (-PI, PI] +void wrap(float& rad) +{ + if (rad>0) + rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; + else + rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; +} + +std::string toString(int num) +{ + std::ostringstream stream; + stream << num; + return stream.str(); +} + +std::string getBestAttack (const ESM::Weapon* weapon) +{ + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + if (slash == chop && slash == thrust) + return "slash"; + else if (thrust >= chop && thrust >= slash) + return "thrust"; + else if (slash >= chop && slash >= thrust) + return "slash"; + else + return "chop"; +} + +// Converts a movement Run state to its equivalent Walk state. +MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + CharacterState ret = state; + switch (state) + { + case CharState_RunForward: + ret = CharState_WalkForward; + break; + case CharState_RunBack: + ret = CharState_WalkBack; + break; + case CharState_RunLeft: + ret = CharState_WalkLeft; + break; + case CharState_RunRight: + ret = CharState_WalkRight; + break; + case CharState_SwimRunForward: + ret = CharState_SwimWalkForward; + break; + case CharState_SwimRunBack: + ret = CharState_SwimWalkBack; + break; + case CharState_SwimRunLeft: + ret = CharState_SwimWalkLeft; + break; + case CharState_SwimRunRight: + ret = CharState_SwimWalkRight; + break; + default: + break; + } + return ret; +} + +float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); + + if (fallHeight >= fallDistanceMin) + { + const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); + const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); + const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); + + float x = fallHeight - fallDistanceMin; + x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; + x = std::max(0.0f, x); + + float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); + x = fallDistanceBase + fallDistanceMult * x; + x *= a; + + return x; + } + return 0.f; +} + +} + +namespace MWMechanics +{ + +struct StateInfo { + CharacterState state; + const char groupname[32]; +}; + +static const StateInfo sMovementList[] = { + { CharState_WalkForward, "walkforward" }, + { CharState_WalkBack, "walkback" }, + { CharState_WalkLeft, "walkleft" }, + { CharState_WalkRight, "walkright" }, + + { CharState_SwimWalkForward, "swimwalkforward" }, + { CharState_SwimWalkBack, "swimwalkback" }, + { CharState_SwimWalkLeft, "swimwalkleft" }, + { CharState_SwimWalkRight, "swimwalkright" }, + + { CharState_RunForward, "runforward" }, + { CharState_RunBack, "runback" }, + { CharState_RunLeft, "runleft" }, + { CharState_RunRight, "runright" }, + + { CharState_SwimRunForward, "swimrunforward" }, + { CharState_SwimRunBack, "swimrunback" }, + { CharState_SwimRunLeft, "swimrunleft" }, + { CharState_SwimRunRight, "swimrunright" }, + + { CharState_SneakForward, "sneakforward" }, + { CharState_SneakBack, "sneakback" }, + { CharState_SneakLeft, "sneakleft" }, + { CharState_SneakRight, "sneakright" }, + + { CharState_Jump, "jump" }, + + { CharState_TurnLeft, "turnleft" }, + { CharState_TurnRight, "turnright" }, + { CharState_SwimTurnLeft, "swimturnleft" }, + { CharState_SwimTurnRight, "swimturnright" }, +}; +static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; + + +class FindCharState { + CharacterState state; + +public: + FindCharState(CharacterState _state) : state(_state) { } + + bool operator()(const StateInfo &info) const + { return info.state == state; } +}; + + +static const struct WeaponInfo { + WeaponType type; + const char shortgroup[16]; + const char longgroup[16]; +} sWeaponTypeList[] = { + { WeapType_HandToHand, "hh", "handtohand" }, + { WeapType_OneHand, "1h", "weapononehand" }, + { WeapType_TwoHand, "2c", "weapontwohand" }, + { WeapType_TwoWide, "2w", "weapontwowide" }, + { WeapType_BowAndArrow, "1h", "bowandarrow" }, + { WeapType_Crossbow, "crossbow", "crossbow" }, + { WeapType_Thrown, "1h", "throwweapon" }, + { WeapType_PickProbe, "1h", "pickprobe" }, + { WeapType_Spell, "spell", "spellcast" }, +}; +static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; + +class FindWeaponType { + WeaponType type; + +public: + FindWeaponType(WeaponType _type) : type(_type) { } + + bool operator()(const WeaponInfo &weap) const + { return weap.type == type; } +}; + +std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const +{ + int numAnims=0; + while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) + ++numAnims; + + int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + if (num) + *num = roll; + return prefix + toString(roll); +} + +void CharacterController::refreshHitRecoilAnims() +{ + bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); + bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); + bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + if(mHitState == CharState_None) + { + if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 + || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) + && mAnimation->hasAnimation("knockout")) + { + if (isSwimming && mAnimation->hasAnimation("swimknockout")) + { + mHitState = CharState_SwimKnockOut; + mCurrentHit = "swimknockout"; + } + else + { + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + } + + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + } + else if(knockdown && mAnimation->hasAnimation("knockdown")) + { + if (isSwimming && mAnimation->hasAnimation("swimknockdown")) + { + mHitState = CharState_SwimKnockDown; + mCurrentHit = "swimknockdown"; + } + else + { + mHitState = CharState_KnockDown; + mCurrentHit = "knockdown"; + } + + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else if (recovery) + { + std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); + if (isSwimming && mAnimation->hasAnimation(anim)) + { + mHitState = CharState_SwimHit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else + { + anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + } + } + else if (block && mAnimation->hasAnimation("shield")) + { + mHitState = CharState_Block; + mCurrentHit = "shield"; + MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); + priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); + } + + // Cancel upper body animations + if (isKnockedOut() || isKnockedDown()) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; + } + } + } + else if(!mAnimation->isPlaying(mCurrentHit)) + { + mCurrentHit.erase(); + if (knockdown) + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + if (recovery) + mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + if (block) + mPtr.getClass().getCreatureStats(mPtr).setBlock(false); + mHitState = CharState_None; + } + else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + { + mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); + } + if (mHitState != CharState_None) + mIdleState = CharState_None; +} + +void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) +{ + if(force || jump != mJumpState) + { + mIdleState = CharState_None; + bool startAtLoop = (jump == mJumpState); + mJumpState = jump; + + std::string jumpAnimName; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + if(mJumpState != JumpState_None) + { + jumpAnimName = "jump"; + if(weap != sWeaponTypeListEnd) + { + jumpAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(jumpAnimName)) + { + jumpmask = MWRender::Animation::BlendMask_LowerBody; + jumpAnimName = "jump"; + } + } + } + + if(mJumpState == JumpState_InAir) + { + mAnimation->disable(mCurrentJump); + mCurrentJump = jumpAnimName; + if (mAnimation->hasAnimation("jump")) + mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); + } + else if (mJumpState == JumpState_Landing) + { + if (startAtLoop) + mAnimation->disable(mCurrentJump); + + if (mAnimation->hasAnimation("jump")) + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + else // JumpState_None + { + if (mCurrentJump.length() > 0) + { + mAnimation->disable(mCurrentJump); + mCurrentJump.clear(); + } + } + } +} + +void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) +{ + if(force || movement != mMovementState) + { + mMovementState = movement; + + if (movement != CharState_None) + mIdleState = CharState_None; + + std::string movementAnimName; + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); + if(movestate != sMovementListEnd) + { + movementAnimName = movestate->groupname; + if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) + { + movementAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(movementAnimName)) + { + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = movestate->groupname; + } + } + + if(!mAnimation->hasAnimation(movementAnimName)) + { + std::string::size_type swimpos = movementAnimName.find("swim"); + if(swimpos == std::string::npos) + { + std::string::size_type runpos = movementAnimName.find("run"); + if (runpos != std::string::npos) + { + movementAnimName.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); + } + else + movementAnimName.clear(); + } + else + { + movementAnimName.erase(swimpos, 4); + if (weap != sWeaponTypeListEnd) + { + std::string weapMovementAnimName = movementAnimName + weap->shortgroup; + if(mAnimation->hasAnimation(weapMovementAnimName)) + movementAnimName = weapMovementAnimName; + else + movemask = MWRender::Animation::BlendMask_LowerBody; + } + + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); + } + } + } + + /* If we're playing the same animation, restart from the loop start instead of the + * beginning. */ + int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); + + mMovementAnimationControlled = true; + + mAnimation->disable(mCurrentMovement); + mCurrentMovement = movementAnimName; + if(!mCurrentMovement.empty()) + { + bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && !MWBase::Environment::get().getWorld()->isFlying(mPtr); + + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + std::string anim = mCurrentMovement; + mAdjustMovementAnimSpeed = true; + if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() + && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) + { + CharacterState walkState = runStateToWalkState(mMovementState); + const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); + anim = stateinfo->groupname; + + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) + { + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; + } + } + else + { + mMovementAnimSpeed = mAnimation->getVelocity(anim); + + if (mMovementAnimSpeed <= 1.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } + } + + mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, + 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul, true); + } + } +} + +void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) +{ + if(force || idle != mIdleState || + ((idle == mIdleState) && !mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + { + mIdleState = idle; + size_t numLoops = ~0ul; + + std::string idleGroup; + MWRender::Animation::AnimPriority idlePriority (Priority_Default); + // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to + // "idle"+weapon or "idle". + if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + { + idleGroup = "idleswim"; + idlePriority = Priority_SwimIdle; + } + else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + { + idleGroup = "idlesneak"; + idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + } + else if(mIdleState != CharState_None) + { + idleGroup = "idle"; + if(weap != sWeaponTypeListEnd) + { + idleGroup += weap->shortgroup; + if(!mAnimation->hasAnimation(idleGroup)) + idleGroup = "idle"; + + // play until the Loop Stop key 2 to 5 times, then play until the Stop key + // this replicates original engine behavior for the "Idle1h" 1st-person animation + numLoops = 1 + Misc::Rng::rollDice(4); + } + } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idleGroup; + if(!mCurrentIdle.empty()) + mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, numLoops, true); + } +} + +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) +{ + if (mPtr.getClass().isActor()) + refreshHitRecoilAnims(); + + const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); + if (!mPtr.getClass().isBipedal(mPtr)) + weap = sWeaponTypeListEnd; + + refreshJumpAnims(weap, jump, force); + refreshMovementAnims(weap, movement, force); + + // idle handled last as it can depend on the other states + // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), + // the idle animation should be displayed + if ((mUpperBodyState != UpperCharState_Nothing + || (mMovementState != CharState_None && !isTurning()) + || mHitState != CharState_None) + && !mPtr.getClass().isBipedal(mPtr)) + idle = CharState_None; + + refreshIdleAnims(weap, idle, force); +} + + +void getWeaponGroup(WeaponType weaptype, std::string &group) +{ + const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); + if(info != sWeaponTypeListEnd) + group = info->longgroup; + else + group.clear(); +} + + +MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) +{ + if(stats.getDrawState() == DrawState_Spell) + { + *weaptype = WeapType_Spell; + return inv.end(); + } + + if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + { + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + *weaptype = WeapType_HandToHand; + else + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + *weaptype = WeapType_PickProbe; + else if(type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + switch(weaponType) + { + case ESM::Weapon::ShortBladeOneHand: + case ESM::Weapon::LongBladeOneHand: + case ESM::Weapon::BluntOneHand: + case ESM::Weapon::AxeOneHand: + case ESM::Weapon::Arrow: + case ESM::Weapon::Bolt: + *weaptype = WeapType_OneHand; + break; + case ESM::Weapon::LongBladeTwoHand: + case ESM::Weapon::BluntTwoClose: + case ESM::Weapon::AxeTwoHand: + *weaptype = WeapType_TwoHand; + break; + case ESM::Weapon::BluntTwoWide: + case ESM::Weapon::SpearTwoWide: + *weaptype = WeapType_TwoWide; + break; + case ESM::Weapon::MarksmanBow: + *weaptype = WeapType_BowAndArrow; + break; + case ESM::Weapon::MarksmanCrossbow: + *weaptype = WeapType_Crossbow; + break; + case ESM::Weapon::MarksmanThrown: + *weaptype = WeapType_Thrown; + break; + } + } + } + + return weapon; + } + + return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); +} + +void CharacterController::playDeath(float startpoint, CharacterState death) +{ + switch (death) + { + case CharState_SwimDeath: + mCurrentDeath = "swimdeath"; + break; + case CharState_SwimDeathKnockDown: + mCurrentDeath = "swimdeathknockdown"; + break; + case CharState_SwimDeathKnockOut: + mCurrentDeath = "swimdeathknockout"; + break; + case CharState_DeathKnockDown: + mCurrentDeath = "deathknockdown"; + break; + case CharState_DeathKnockOut: + mCurrentDeath = "deathknockout"; + break; + default: + mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); + } + mDeathState = death; + + mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); + + // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). + // However, they could still trigger text keys, such as Hit events, or sounds. + mMovementState = CharState_None; + mAnimation->disable(mCurrentMovement); + mCurrentMovement = ""; + mUpperBodyState = UpperCharState_Nothing; + mAnimation->disable(mCurrentWeapon); + mCurrentWeapon = ""; + mHitState = CharState_None; + mAnimation->disable(mCurrentHit); + mCurrentHit = ""; + mIdleState = CharState_None; + mAnimation->disable(mCurrentIdle); + mCurrentIdle = ""; + mJumpState = JumpState_None; + mAnimation->disable(mCurrentJump); + mCurrentJump = ""; + mMovementAnimationControlled = true; + + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", startpoint, 0); +} + +CharacterState CharacterController::chooseRandomDeathState() const +{ + int selected=0; + chooseRandomGroup("death", &selected); + return static_cast(CharState_Death1 + (selected-1)); +} + +void CharacterController::playRandomDeath(float startpoint) +{ + if (mPtr == getPlayer()) + { + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); + } + + if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) + { + mDeathState = CharState_SwimDeathKnockDown; + } + else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) + { + mDeathState = CharState_SwimDeathKnockOut; + } + else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) + { + mDeathState = CharState_SwimDeath; + } + else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) + { + mDeathState = CharState_DeathKnockDown; + } + else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) + { + mDeathState = CharState_DeathKnockOut; + } + else + { + mDeathState = chooseRandomDeathState(); + } + playDeath(startpoint, mDeathState); +} + +CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) + : mPtr(ptr) + , mAnimation(anim) + , mIdleState(CharState_None) + , mMovementState(CharState_None) + , mMovementAnimSpeed(0.f) + , mAdjustMovementAnimSpeed(false) + , mHasMovedInXY(false) + , mMovementAnimationControlled(true) + , mDeathState(CharState_None) + , mFloatToSurface(true) + , mHitState(CharState_None) + , mUpperBodyState(UpperCharState_Nothing) + , mJumpState(JumpState_None) + , mWeaponType(WeapType_None) + , mAttackStrength(0.f) + , mSkipAnim(false) + , mSecondsOfSwimming(0) + , mSecondsOfRunning(0) + , mTurnAnimationThreshold(0) + , mAttackingOrSpell(false) +{ + if(!mAnimation) + return; + + mAnimation->setTextKeyListener(this); + + const MWWorld::Class &cls = mPtr.getClass(); + if(cls.isActor()) + { + /* Accumulate along X/Y only for now, until we can figure out how we should + * handle knockout and death which moves the character down. */ + mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); + + if (cls.hasInventoryStore(mPtr)) + { + getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + if (mWeaponType != WeapType_None) + { + mUpperBodyState = UpperCharState_WeapEquiped; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + { + mAnimation->showWeapons(true); + mAnimation->setWeaponGroup(mCurrentWeapon); + } + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); + } + + if(!cls.getCreatureStats(mPtr).isDead()) + mIdleState = CharState_Idle; + else + { + const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (cStats.isDeathAnimationFinished()) + { + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + signed char deathanim = cStats.getDeathAnimation(); + if (deathanim == -1) + mDeathState = chooseRandomDeathState(); + else + mDeathState = static_cast(CharState_Death1 + deathanim); + + mFloatToSurface = false; + } + // else: nothing to do, will detect death in the next frame and start playing death animation + } + } + else + { + /* Don't accumulate with non-actors. */ + mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); + + mIdleState = CharState_Idle; + } + + + if(mDeathState == CharState_None) + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + + mAnimation->runAnimation(0.f); + + unpersistAnimationState(); +} + +CharacterController::~CharacterController() +{ + if (mAnimation) + { + persistAnimationState(); + mAnimation->setTextKeyListener(NULL); + } +} + +void split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + +void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) +{ + const std::string &evt = key->second; + + if(evt.compare(0, 7, "sound: ") == 0) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->stopSound3D(mPtr, evt.substr(7)); + sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); + return; + } + if(evt.compare(0, 10, "soundgen: ") == 0) + { + std::string soundgen = evt.substr(10); + + // The event can optionally contain volume and pitch modifiers + float volume=1.f, pitch=1.f; + if (soundgen.find(" ") != std::string::npos) + { + std::vector tokens; + split(soundgen, ' ', tokens); + soundgen = tokens[0]; + if (tokens.size() >= 2) + { + std::stringstream stream; + stream << tokens[1]; + stream >> volume; + } + if (tokens.size() >= 3) + { + std::stringstream stream; + stream << tokens[2]; + stream >> pitch; + } + } + + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); + if(!sound.empty()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) + { + // Don't make foot sounds local for the player, it makes sense to keep them + // positioned on the ground. + sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, + MWSound::PlayMode::NoPlayerLocal); + } + else + { + sndMgr->stopSound3D(mPtr, sound); + sndMgr->playSound3D(mPtr, sound, volume, pitch); + } + } + return; + } + + if(evt.compare(0, groupname.size(), groupname) != 0 || + evt.compare(groupname.size(), 2, ": ") != 0) + { + // Not ours, skip it + return; + } + size_t off = groupname.size()+2; + size_t len = evt.size() - off; + + if(evt.compare(off, len, "equip attach") == 0) + mAnimation->showWeapons(true); + else if(evt.compare(off, len, "unequip detach") == 0) + mAnimation->showWeapons(false); + else if(evt.compare(off, len, "chop hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if(evt.compare(off, len, "slash hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if(evt.compare(off, len, "thrust hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else if(evt.compare(off, len, "hit") == 0) + { + if (groupname == "attack1" || groupname == "swimattack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2" || groupname == "swimattack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3" || groupname == "swimattack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else + mPtr.getClass().hit(mPtr, mAttackStrength); + } + else if (!groupname.empty() + && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) + && evt.compare(off, len, "start") == 0) + { + std::multimap::const_iterator hitKey = key; + + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != map.end()) + { + if (hitKey->second == groupname + ": hit") + { + hasHitKey = true; + break; + } + if (hitKey->second == groupname + ": stop") + break; + ++hitKey; + } + if (!hasHitKey) + { + if (groupname == "attack1" || groupname == "swimattack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2" || groupname == "swimattack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3" || groupname == "swimattack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + } + } + else if (evt.compare(off, len, "shoot attach") == 0) + mAnimation->attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + mAnimation->releaseArrow(mAttackStrength); + else if (evt.compare(off, len, "shoot follow attach") == 0) + mAnimation->attachArrow(); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" + // Make sure this key is actually for the RangeType we are casting. The flame atronach has + // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. + && evt.compare(off, len, mAttackType + " release") == 0) + { + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + + else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) + mPtr.getClass().block(mPtr); +} + +void CharacterController::updatePtr(const MWWorld::Ptr &ptr) +{ + mPtr = ptr; +} + +void CharacterController::updateIdleStormState(bool inwater) +{ + bool inStormDirection = false; + if (MWBase::Environment::get().getWorld()->isInStorm()) + { + osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) + > osg::DegreesToRadians(120.f); + } + if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + { + float complete = 0; + mAnimation->getInfo("idlestorm", &complete); + + if (complete == 0) + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, + 1.0f, "start", "loop start", 0.0f, 0); + else if (complete == 1) + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, + 1.0f, "loop start", "loop stop", 0.0f, ~0ul); + } + else + { + if (mUpperBodyState == UpperCharState_Nothing) + { + if (mAnimation->isPlaying("idlestorm")) + { + if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) + { + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + } + } + else + mAnimation->disable("idlestorm"); + } +} + +bool CharacterController::updateCreatureState() +{ + const MWWorld::Class &cls = mPtr.getClass(); + CreatureStats &stats = cls.getCreatureStats(mPtr); + + WeaponType weapType = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weapType = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weapType = WeapType_Spell; + + if (weapType != mWeaponType) + { + mWeaponType = weapType; + if (mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->disable(mCurrentWeapon); + } + + if(mAttackingOrSpell) + { + if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) + { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + + std::string startKey = "start"; + std::string stopKey = "stop"; + if (weapType == WeapType_Spell) + { + const std::string spellid = stats.getSpells().getSelectedSpell(); + if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); + + if (!mAnimation->hasAnimation("spellcast")) + MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately + else + { + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + switch(effectentry.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + startKey = mAttackType + " " + startKey; + stopKey = mAttackType + " " + stopKey; + mCurrentWeapon = "spellcast"; + } + } + else + mCurrentWeapon = ""; + } + if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + { + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + int roll = Misc::Rng::rollDice(3); // [0, 2] + if (roll == 0) + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; + else if (roll == 1) + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; + else + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; + } + + if (!mCurrentWeapon.empty()) + { + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::BlendMask_All, true, + 1, startKey, stopKey, + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + + if (weapType == WeapType_HandToHand) + playSwishSound(0.0f); + } + } + + mAttackingOrSpell = false; + } + + bool animPlaying = mAnimation->getInfo(mCurrentWeapon); + if (!animPlaying) + mUpperBodyState = UpperCharState_Nothing; + return false; +} + +bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +{ + // Shields/torches shouldn't be visible during any operation involving two hands + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + switch (weaptype) + { + case WeapType_Spell: + case WeapType_BowAndArrow: + case WeapType_Crossbow: + case WeapType_HandToHand: + case WeapType_TwoHand: + case WeapType_TwoWide: + return false; + default: + return true; + } +} + +bool CharacterController::updateWeaponState() +{ + const MWWorld::Class &cls = mPtr.getClass(); + CreatureStats &stats = cls.getCreatureStats(mPtr); + WeaponType weaptype = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weaptype = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weaptype = WeapType_Spell; + + const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); + + std::string soundid; + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) + { + soundid = (weaptype == WeapType_None) ? + weapon->getClass().getDownSoundId(*weapon) : + weapon->getClass().getUpSoundId(*weapon); + } + } + + MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + + bool forcestateupdate = false; + + // We should not play equipping animation and sound during weapon->weapon transition + bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && + mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + + if(weaptype != mWeaponType && !isKnockedOut() && + !isKnockedDown() && !isRecovery()) + { + forcestateupdate = true; + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + + std::string weapgroup; + if(weaptype == WeapType_None) + { + if ((!isWerewolf || mWeaponType != WeapType_Spell)) + { + getWeaponGroup(mWeaponType, weapgroup); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_UnEquipingWeap; + } + } + else + { + getWeaponGroup(weaptype, weapgroup); + mAnimation->setWeaponGroup(weapgroup); + + if (!isStillWeapon) + { + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; + } + + if(isWerewolf) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); + if(sound) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + } + } + + if(!soundid.empty() && !isStillWeapon) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); + } + + mWeaponType = weaptype; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + if(isWerewolf) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && mHasMovedInXY + && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) + && mWeaponType == WeapType_None) + { + if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) + sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, + MWSound::PlayMode::Loop); + } + else + sndMgr->stopSound3D(mPtr, "WolfRun"); + } + + // Cancel attack if we no longer have ammunition + bool ammunition = true; + bool isWeapon = false; + float weapSpeed = 1.f; + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); + if(isWeapon) + weapSpeed = weapon->get()->mBase->mData.mSpeed; + + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (mWeaponType == WeapType_Crossbow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); + else if (mWeaponType == WeapType_BowAndArrow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); + if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + } + + float complete; + bool animPlaying; + if(mAttackingOrSpell) + { + mIdleState = CharState_None; + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) + { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + mAttackStrength = 0; + if(mWeaponType == WeapType_Spell) + { + // Unset casting flag, otherwise pressing the mouse button down would + // continue casting every frame if there is no animation + mAttackingOrSpell = false; + if (mPtr == getPlayer()) + { + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + } + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) + if (mPtr == getPlayer()) + { + std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); + stats.getSpells().setSelectedSpell(selectedSpell); + } + std::string spellid = stats.getSpells().getSelectedSpell(); + + if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); + + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); + const ESM::MagicEffect *effect; + + effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands + + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + + for (size_t iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect + { + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + } + + const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation + + switch(firstEffect.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + weapSpeed, mAttackType+" start", mAttackType+" stop", + 0.0f, 0); + mUpperBodyState = UpperCharState_CastingSpell; + } + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + if (inv.getSelectedEnchantItem() != inv.end()) + { + // Enchanted items cast immediately (no animation) + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + } + + } + else if(mWeaponType == WeapType_PickProbe) + { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::Ptr item = *weapon; + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); + std::string resultMessage, resultSound; + + if(!target.isEmpty()) + { + if(item.getTypeName() == typeid(ESM::Lockpick).name()) + Security(mPtr).pickLock(target, item, resultMessage, resultSound); + else if(item.getTypeName() == typeid(ESM::Probe).name()) + Security(mPtr).probeTrap(target, item, resultMessage, resultSound); + } + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "start", "stop", 0.0, 0); + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + + if(!resultMessage.empty()) + MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); + if(!resultSound.empty()) + MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, + 1.0f, 1.0f); + } + else if (ammunition) + { + if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || + mWeaponType == WeapType_Thrown) + mAttackType = "shoot"; + else + { + if(mPtr == getPlayer()) + { + if (isWeapon) + { + if (Settings::Manager::getBool("best attack", "Game")) + { + MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + mAttackType = getBestAttack(weapon->get()->mBase); + } + else + setAttackTypeBasedOnMovement(); + } + else + setAttackTypeRandomly(mAttackType); + } + // else if (mPtr != getPlayer()) use mAttackType set by AiCombat + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, mAttackType+" start", mAttackType+" min attack", + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + } + } + + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + mAttackStrength = complete; + } + else + { + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + { + float attackStrength = complete; + if (!mPtr.getClass().isNpc()) + { + // most creatures don't actually have an attack wind-up animation, so use a uniform random value + // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) + // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + } + + if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + + if(isWerewolf) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); + if(sound) + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + else + { + playSwishSound(attackStrength); + } + } + mAttackStrength = attackStrength; + + mAnimation->disable(mCurrentWeapon); + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, mAttackType+" max attack", mAttackType+" min hit", + 1.0f-complete, 0); + + complete = 0.f; + mUpperBodyState = UpperCharState_MaxAttackToMinHit; + } + else if (isKnockedDown()) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + mUpperBodyState = UpperCharState_WeapEquiped; + mAnimation->disable(mCurrentWeapon); + } + } + + mAnimation->setPitchFactor(0.f); + if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { + switch (mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + mAnimation->setPitchFactor(1.f); + break; + case UpperCharState_FollowStartToFollowStop: + if (animPlaying) + mAnimation->setPitchFactor(1.f-complete); + break; + default: + break; + } + } + else if (mWeaponType == WeapType_Crossbow) + { + switch (mUpperBodyState) + { + case UpperCharState_EquipingWeap: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_UnEquipingWeap: + mAnimation->setPitchFactor(1.f-complete); + break; + case UpperCharState_WeapEquiped: + case UpperCharState_StartToMinAttack: + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + case UpperCharState_FollowStartToFollowStop: + mAnimation->setPitchFactor(1.f); + break; + default: + break; + } + } + + if(!animPlaying) + { + if(mUpperBodyState == UpperCharState_EquipingWeap || + mUpperBodyState == UpperCharState_FollowStartToFollowStop || + mUpperBodyState == UpperCharState_CastingSpell) + { + if (ammunition && mWeaponType == WeapType_Crossbow) + mAnimation->attachArrow(); + + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if(mUpperBodyState == UpperCharState_UnEquipingWeap) + mUpperBodyState = UpperCharState_Nothing; + } + else if(complete >= 1.0f) + { + std::string start, stop; + switch(mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + start = mAttackType+" min attack"; + stop = mAttackType+" max attack"; + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + break; + case UpperCharState_MinAttackToMaxAttack: + //hack to avoid body pos desync when jumping/sneaking in 'max attack' state + if(!mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); + break; + case UpperCharState_MaxAttackToMinHit: + if(mAttackType == "shoot") + { + start = mAttackType+" min hit"; + stop = mAttackType+" release"; + } + else + { + start = mAttackType+" min hit"; + stop = mAttackType+" hit"; + } + mUpperBodyState = UpperCharState_MinHitToHit; + break; + case UpperCharState_MinHitToHit: + if(mAttackType == "shoot") + { + start = mAttackType+" follow start"; + stop = mAttackType+" follow stop"; + } + else + { + float str = mAttackStrength; + start = mAttackType+((str < 0.5f) ? " small follow start" + : (str < 1.0f) ? " medium follow start" + : " large follow start"); + stop = mAttackType+((str < 0.5f) ? " small follow stop" + : (str < 1.0f) ? " medium follow stop" + : " large follow stop"); + } + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + break; + default: + break; + } + + if(!start.empty()) + { + mAnimation->disable(mCurrentWeapon); + if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + weapSpeed, start, stop, 0.0f, 0); + else + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, start, stop, 0.0f, 0); + } + } + + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + && updateCarriedLeftVisible(mWeaponType)) + + { + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, + false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); + } + } + + mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + + return forcestateupdate; +} + +void CharacterController::updateAnimQueue() +{ + if(mAnimQueue.size() > 1) + { + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, + MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + } + } + + if(!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); +} + +void CharacterController::update(float duration) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Class &cls = mPtr.getClass(); + osg::Vec3f movement(0.f, 0.f, 0.f); + float speed = 0.f; + + updateMagicEffects(); + + bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + + if(!cls.isActor()) + updateAnimQueue(); + else if(!cls.getCreatureStats(mPtr).isDead()) + { + bool onground = world->isOnGround(mPtr); + bool inwater = world->isSwimming(mPtr); + bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool flying = world->isFlying(mPtr); + // Can't run while flying (see speed formula in Npc/Creature::getSpeed) + bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; + CreatureStats &stats = cls.getCreatureStats(mPtr); + + //Force Jump Logic + + bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + if(!inwater && !flying) + { + //Force Jump + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) + { + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + //Force Move Jump, only jump if they're otherwise moving + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) + { + + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + } + + osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); + vec.normalize(); + + if(mHitState != CharState_None && mJumpState == JumpState_None) + vec = osg::Vec3f(0.f, 0.f, 0.f); + osg::Vec3f rot = cls.getRotationVector(mPtr); + + speed = cls.getSpeed(mPtr); + + vec.x() *= speed; + vec.y() *= speed; + + CharacterState movestate = CharState_None; + CharacterState idlestate = CharState_SpecialIdle; + JumpingState jumpstate = JumpState_None; + + bool forcestateupdate = false; + + mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; + isrunning = isrunning && mHasMovedInXY; + + // advance athletics + if(mHasMovedInXY && mPtr == getPlayer()) + { + if(inwater) + { + mSecondsOfSwimming += duration; + while(mSecondsOfSwimming > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + mSecondsOfSwimming -= 1; + } + } + else if(isrunning && !sneak) + { + mSecondsOfRunning += duration; + while(mSecondsOfRunning > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + mSecondsOfRunning -= 1; + } + } + } + + // reduce fatigue + const MWWorld::Store &gmst = world->getStore().get(); + float fatigueLoss = 0; + static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); + static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); + static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); + static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); + static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); + static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); + static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); + static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); + + if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) + { + const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); + + if (sneak) + fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; + else + { + if (inwater) + { + if (!isrunning) + fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; + else + fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; + } + else if (isrunning) + fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; + } + } + fatigueLoss *= duration; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + + if (!godmode) + { + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + } + + if(sneak || inwater || flying) + vec.z() = 0.0f; + + bool inJump = true; + if(!onground && !flying && !inwater) + { + // In the air (either getting up —ascending part of jump— or falling). + + forcestateupdate = (mJumpState != JumpState_InAir); + jumpstate = JumpState_InAir; + + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; + factor = std::min(1.f, factor); + vec.x() *= factor; + vec.y() *= factor; + vec.z() = 0.0f; + } + else if(vec.z() > 0.0f && mJumpState == JumpState_None) + { + // Started a jump. + float z = cls.getJump(mPtr); + if (z > 0) + { + if(vec.x() == 0 && vec.y() == 0) + vec = osg::Vec3f(0.0f, 0.0f, z); + else + { + osg::Vec3f lat (vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; + } + + // advance acrobatics + if (mPtr == getPlayer()) + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + + // decrease fatigue + const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); + const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); + float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; + const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult; + + if (!godmode) + { + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + } + } + } + else if(mJumpState == JumpState_InAir && !inwater && !flying) + { + forcestateupdate = true; + jumpstate = JumpState_Landing; + vec.z() = 0.0f; + + float height = cls.getCreatureStats(mPtr).land(); + float healthLost = getFallDamage(mPtr, height); + + if (healthLost > 0.0f) + { + const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); + + // inflict fall damages + if (!godmode) + { + DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); + float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); + health.setCurrent(health.getCurrent() - realHealthLost); + cls.getCreatureStats(mPtr).setHealth(health); + cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); + } + + const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); + if (healthLost > (acrobaticsSkill * fatigueTerm)) + { + cls.getCreatureStats(mPtr).setKnockedDown(true); + } + else + { + // report acrobatics progression + if (mPtr == getPlayer()) + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + } + } + } + else + { + jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; + + vec.z() = 0.0f; + + inJump = false; + + if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) + { + if(vec.x() > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight + : (isrunning ? CharState_RunRight : CharState_WalkRight))); + else if(vec.x() < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft + : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + } + else if(vec.y() != 0.0f) + { + if(vec.y() > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward + : (isrunning ? CharState_RunForward : CharState_WalkForward))); + else if(vec.y() < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack + : (isrunning ? CharState_RunBack : CharState_WalkBack))); + } + else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) + { + if(rot.z() > 0.0f) + { + movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + mAnimation->disable(mCurrentJump); + } + else if(rot.z() < 0.0f) + { + movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + mAnimation->disable(mCurrentJump); + } + } + } + + mTurnAnimationThreshold -= duration; + if (isTurning()) + mTurnAnimationThreshold = 0.05f; + else if (movestate == CharState_None && isTurning() + && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } + + if(movestate != CharState_None && !isTurning()) + clearAnimQueue(); + + if(mAnimQueue.empty() || inwater || sneak) + { + idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); + } + else + updateAnimQueue(); + + if (!mSkipAnim) + { + // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. + if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) + forcestateupdate = updateWeaponState() || forcestateupdate; + else + forcestateupdate = updateCreatureState() || forcestateupdate; + + refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); + updateIdleStormState(inwater); + } + + if (inJump) + mMovementAnimationControlled = false; + + if (isTurning()) + { + if (duration > 0) + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); + } + else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) + { + float speedmult = speed / mMovementAnimSpeed; + mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); + } + + if (!mSkipAnim) + { + if(!isKnockedDown() && !isKnockedOut()) + { + if (rot != osg::Vec3f()) + world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); + } + else //avoid z-rotating for knockdown + { + if (rot.x() != 0 && rot.y() != 0) + world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); + } + + if (!mMovementAnimationControlled) + world->queueMovement(mPtr, vec); + } + else + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + + movement = vec; + cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame + // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. + + if (!mSkipAnim) + updateHeadTracking(duration); + } + else if(cls.getCreatureStats(mPtr).isDead()) + { + // initial start of death animation for actors that started the game as dead + // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + { + playDeath(1.f, mDeathState); + } + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + } + + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + if(duration > 0.0f) + moved /= duration; + else + moved = osg::Vec3f(0.f, 0.f, 0.f); + + // Ensure we're moving in generally the right direction... + if(speed > 0.f) + { + float l = moved.length(); + + if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || + (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) + moved.x() = movement.x(); + if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || + (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) + moved.y() = movement.y(); + if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || + (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) + moved.z() = movement.z(); + // but keep the original speed + float newLength = moved.length(); + if (newLength > 0) + moved *= (l / newLength); + } + + if (mSkipAnim) + mAnimation->updateEffects(duration); + + if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) + moved.z() = 1.0; + + // Update movement + if(mMovementAnimationControlled && mPtr.getClass().isActor()) + world->queueMovement(mPtr, moved); + + mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); +} + +void CharacterController::persistAnimationState() +{ + ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + state.mScriptedAnims.clear(); + for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) + { + if (!iter->mPersist) + continue; + + ESM::AnimationState::ScriptedAnimation anim; + anim.mGroup = iter->mGroup; + + if (iter == mAnimQueue.begin()) + { + anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); + float complete; + mAnimation->getInfo(anim.mGroup, &complete, NULL); + anim.mTime = complete; + } + else + { + anim.mLoopCount = iter->mLoopCount; + anim.mTime = 0.f; + } + + state.mScriptedAnims.push_back(anim); + } +} + +void CharacterController::unpersistAnimationState() +{ + const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + if (!state.mScriptedAnims.empty()) + { + clearAnimQueue(); + for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) + { + AnimationQueueEntry entry; + entry.mGroup = iter->mGroup; + entry.mLoopCount = iter->mLoopCount; + entry.mPersist = true; + + mAnimQueue.push_back(entry); + } + + const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); + float complete = anim.mTime; + if (anim.mAbsolute) + { + float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); + float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); + float time = std::max(start, std::min(stop, anim.mTime)); + complete = (time - start) / (stop - start); + } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + mIdleState = CharState_SpecialIdle; + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(anim.mGroup, + Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, + "start", "stop", complete, anim.mLoopCount, loopfallback); + } +} + +bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) +{ + if(!mAnimation || !mAnimation->hasAnimation(groupname)) + return false; + + // If this animation is a looped animation (has a "loop start" key) that is already playing + // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count + // and remove any other animations that were queued. + // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && + mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && + mAnimation->isPlaying(groupname)) + { + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); + + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); + + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimQueue.resize(1); + return true; + } + } + + count = std::max(count, 1); + + AnimationQueueEntry entry; + entry.mGroup = groupname; + entry.mLoopCount = count-1; + entry.mPersist = persist; + + if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) + { + clearAnimQueue(); + mAnimQueue.push_back(entry); + + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + + mIdleState = CharState_SpecialIdle; + bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); + mAnimation->play(groupname, Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, + ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); + } + else if(mode == 0) + { + mAnimQueue.resize(1); + mAnimQueue.push_back(entry); + } + return true; +} + +void CharacterController::skipAnim() +{ + mSkipAnim = true; +} + +bool CharacterController::isAnimPlaying(const std::string &groupName) +{ + if(mAnimation == NULL) + return false; + return mAnimation->isPlaying(groupName); +} + + +void CharacterController::clearAnimQueue() +{ + if(!mAnimQueue.empty()) + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.clear(); +} + +void CharacterController::forceStateUpdate() +{ + if(!mAnimation) + return; + clearAnimQueue(); + + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + if(mDeathState != CharState_None) + { + playRandomDeath(); + } + + mAnimation->runAnimation(0.f); +} + +CharacterController::KillResult CharacterController::kill() +{ + if (mDeathState == CharState_None) + { + playRandomDeath(); + + mAnimation->disable(mCurrentIdle); + + mIdleState = CharState_None; + mCurrentIdle.clear(); + return Result_DeathAnimStarted; + } + + MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (isAnimPlaying(mCurrentDeath)) + return Result_DeathAnimPlaying; + if (!cStats.isDeathAnimationFinished()) + { + cStats.setDeathAnimationFinished(true); + return Result_DeathAnimJustFinished; + } + return Result_DeathAnimFinished; +} + +void CharacterController::resurrect() +{ + if(mDeathState == CharState_None) + return; + + if(mAnimation) + mAnimation->disable(mCurrentDeath); + mCurrentDeath.clear(); + mDeathState = CharState_None; +} + +void CharacterController::updateContinuousVfx() +{ + // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, + // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. + + // Stop any effects that are no longer active + std::vector effects; + mAnimation->getLoopingEffects(effects); + + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + { + if (mPtr.getClass().getCreatureStats(mPtr).isDead() + || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) + mAnimation->removeEffect(*it); + } +} + +void CharacterController::updateMagicEffects() +{ + if (!mPtr.getClass().isActor()) + return; + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). + { + if (mPtr == getPlayer()) + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + mAnimation->setAlpha(alpha); + + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; + mAnimation->setVampire(vampire); + + float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); + mAnimation->setLightEffect(light); +} + +void CharacterController::setAttackTypeBasedOnMovement() +{ + float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; + + if (move[1] && !move[0]) // forward-backward + mAttackType = "thrust"; + else if (move[0] && !move[1]) //sideway + mAttackType = "slash"; + else + mAttackType = "chop"; +} + +bool CharacterController::isAttackPrepairing() const +{ + return mUpperBodyState == UpperCharState_StartToMinAttack || + mUpperBodyState == UpperCharState_MinAttackToMaxAttack; +} + +bool CharacterController::isReadyToBlock() const +{ + return updateCarriedLeftVisible(mWeaponType); +} + +bool CharacterController::isKnockedDown() const +{ + return mHitState == CharState_KnockDown || + mHitState == CharState_SwimKnockDown; +} + +bool CharacterController::isKnockedOut() const +{ + return mHitState == CharState_KnockOut || + mHitState == CharState_SwimKnockOut; +} + +bool CharacterController::isTurning() const +{ + return mMovementState == CharState_TurnLeft || + mMovementState == CharState_TurnRight || + mMovementState == CharState_SwimTurnLeft || + mMovementState == CharState_SwimTurnRight; +} + +bool CharacterController::isRecovery() const +{ + return mHitState == CharState_Hit || + mHitState == CharState_SwimHit; +} + +bool CharacterController::isAttackingOrSpell() const +{ + return mUpperBodyState != UpperCharState_Nothing && + mUpperBodyState != UpperCharState_WeapEquiped; +} + +bool CharacterController::isSneaking() const +{ + return mIdleState == CharState_IdleSneak || + mMovementState == CharState_SneakForward || + mMovementState == CharState_SneakBack || + mMovementState == CharState_SneakLeft || + mMovementState == CharState_SneakRight; +} + +bool CharacterController::isRunning() const +{ + return mMovementState == CharState_RunForward || + mMovementState == CharState_RunBack || + mMovementState == CharState_RunLeft || + mMovementState == CharState_RunRight || + mMovementState == CharState_SwimRunForward || + mMovementState == CharState_SwimRunBack || + mMovementState == CharState_SwimRunLeft || + mMovementState == CharState_SwimRunRight; +} + +void CharacterController::setAttackingOrSpell(bool attackingOrSpell) +{ + mAttackingOrSpell = attackingOrSpell; +} + +void CharacterController::setAIAttackType(const std::string& attackType) +{ + mAttackType = attackType; +} + +void CharacterController::setAttackTypeRandomly(std::string& attackType) +{ + float random = Misc::Rng::rollProbability(); + if (random >= 2/3.f) + attackType = "thrust"; + else if (random >= 1/3.f) + attackType = "slash"; + else + attackType = "chop"; +} + +bool CharacterController::readyToPrepareAttack() const +{ + return (mHitState == CharState_None || mHitState == CharState_Block) + && mUpperBodyState <= UpperCharState_WeapEquiped; +} + +bool CharacterController::readyToStartAttack() const +{ + if (mHitState != CharState_None && mHitState != CharState_Block) + return false; + + if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) + return mUpperBodyState == UpperCharState_WeapEquiped; + else + return mUpperBodyState == UpperCharState_Nothing; +} + +float CharacterController::getAttackStrength() const +{ + return mAttackStrength; +} + +void CharacterController::setActive(bool active) +{ + mAnimation->setActive(active); +} + +void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) +{ + mHeadTrackTarget = target; +} + +void CharacterController::playSwishSound(float attackStrength) +{ + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + + std::string sound = "Weapon Swish"; + if(attackStrength < 0.5f) + sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack + else if(attackStrength < 1.0f) + sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack + else + sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack +} + +void CharacterController::updateHeadTracking(float duration) +{ + const osg::Node* head = mAnimation->getNode("Bip01 Head"); + if (!head) + return; + + float zAngleRadians = 0.f; + float xAngleRadians = 0.f; + + if (!mHeadTrackTarget.isEmpty()) + { + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); + osg::Vec3f headPos = mat.getTrans(); + + osg::Vec3f direction; + if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + { + const osg::Node* node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if (node != NULL) + { + nodepaths = node->getParentalNodePaths(); + if (!nodepaths.empty()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; + } + else + // no head node to look at, fall back to look at center of collision box + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); + } + direction.normalize(); + + if (!mPtr.getRefData().getBaseNode()) + return; + const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + + zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); + xAngleRadians = -std::asin(direction.z()); + + wrap(zAngleRadians); + wrap(xAngleRadians); + + xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); + xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); + zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); + zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); + } + + float factor = duration*5; + factor = std::min(factor, 1.f); + xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians); + zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians); + + mAnimation->setHeadPitch(xAngleRadians); + mAnimation->setHeadYaw(zAngleRadians); +} + +} diff --git a/apps/openmw/mwmechanics/character.cpp.rej b/apps/openmw/mwmechanics/character.cpp.rej new file mode 100644 index 000000000..f04f72a90 --- /dev/null +++ b/apps/openmw/mwmechanics/character.cpp.rej @@ -0,0 +1,46 @@ +--- apps/openmw/mwmechanics/character.cpp ++++ apps/openmw/mwmechanics/character.cpp +@@ -372,29 +372,28 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState + } + } + +- if(mJumpState == JumpState_InAir) ++ if (!mCurrentJump.empty()) + { + mAnimation->disable(mCurrentJump); +- mCurrentJump = jumpAnimName; +- if (mAnimation->hasAnimation("jump")) +- mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, ++ mCurrentJump.clear(); ++ } ++ ++ if(mJumpState == JumpState_InAir) ++ { ++ if (mAnimation->hasAnimation(jumpAnimName)) ++ { ++ mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); ++ mCurrentJump = jumpAnimName; ++ } + } + else if (mJumpState == JumpState_Landing) + { +- if (startAtLoop) +- mAnimation->disable(mCurrentJump); +- +- if (mAnimation->hasAnimation("jump")) ++ if (mAnimation->hasAnimation(jumpAnimName)) ++ { + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, + 1.0f, "loop stop", "stop", 0.0f, 0); +- } +- else // JumpState_None +- { +- if (mCurrentJump.length() > 0) +- { +- mAnimation->disable(mCurrentJump); +- mCurrentJump.clear(); ++ mCurrentJump = jumpAnimName; + } + } + } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4fbcdc4d5..9d580a12a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -620,6 +620,11 @@ namespace MWRender mutable bool mDone; }; + void RenderingManager::screenshot360(osg::Image* image, int w) + { + screenshot(image,w,w); + } + void RenderingManager::screenshot(osg::Image *image, int w, int h) { osg::ref_ptr rttCamera (new osg::Camera); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f0087e43d..d0ac9a500 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,6 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h); + void screenshot360(osg::Image* image, int w); struct RayResult { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 14ee5adee..c643480a9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -655,5 +655,4 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); - } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9c7fba9fa..f9a030a80 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2280,6 +2280,11 @@ namespace MWWorld { mRendering->screenshot(image, w, h); } + + void World::screenshot360 (osg::Image* image, int w) + { + mRendering->screenshot360(image, w); + } void World::activateDoor(const MWWorld::Ptr& door) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7af7b2968..1882b88b9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,6 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; + void screenshot360 (osg::Image* image, int w) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From ce55d7c2f584bd9d80cb362451755c01055e0f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 15:02:01 +0100 Subject: [PATCH 02/47] basic cubemap rendering --- apps/openmw/mwrender/renderingmanager.cpp | 38 +++++++++++++++++++++-- apps/openmw/mwrender/renderingmanager.hpp | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9d580a12a..eaee3a803 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include @@ -622,10 +624,37 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image, int w) { - screenshot(image,w,w); + osg::Vec3 directions[6] = { + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + osg::Vec3(0,0,1), + osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0), + }; + + double fovBackup = mFieldOfView; + mFieldOfView = 90.0; // each side sees 90 degrees + + for (int i = 0; i < 6; i++) // for each cube side + { + osg::ref_ptr sideImage (new osg::Image); + screenshot(sideImage.get(),w,w,directions[i]); + + if (i == 0) + { + image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + std::cout << image->s() << " " << image->t() << std::endl; + } + + osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), + image,w * i,0,0); + } + + mFieldOfView = fovBackup; } - void RenderingManager::screenshot(osg::Image *image, int w, int h) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -634,7 +663,10 @@ 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() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) + ); + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d0ac9a500..add7aae4d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ 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::Vec3 direction=osg::Vec3(0,0,-1)); void screenshot360(osg::Image* image, int w); struct RayResult From 5afe02505b6816e3f44c819047508047902b7bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 16:12:31 +0100 Subject: [PATCH 03/47] hide player in first person 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 192eadbde..61434521e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1016,7 +1016,7 @@ namespace MWInput void InputManager::screenshot() { // MOVE THIS ELSEWHERE LATER! - int screenshotW = 512; + int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index eaee3a803..4901bc176 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -636,21 +636,24 @@ namespace MWRender double fovBackup = mFieldOfView; mFieldOfView = 90.0; // each side sees 90 degrees + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(0); + for (int i = 0; i < 6; i++) // for each cube side { osg::ref_ptr sideImage (new osg::Image); screenshot(sideImage.get(),w,w,directions[i]); if (i == 0) - { image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - std::cout << image->s() << " " << image->t() << std::endl; - } osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), image,w * i,0,0); } + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(1); + mFieldOfView = fovBackup; } @@ -667,6 +670,8 @@ namespace MWRender mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) ); +// TODO: water reflections have to be transformed as well!!!!! + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); From 3be9e2ee9548ce4795fc3f9eb5149681d9f11f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 19:47:36 +0100 Subject: [PATCH 04/47] make spherical screenshot class --- apps/openmw/mwrender/renderingmanager.cpp | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4901bc176..f25f28bab 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -622,8 +622,34 @@ namespace MWRender mutable bool mDone; }; + class SphericalScreenshot + { + public: + SphericalScreenshot(int size) + { + mSize = size; + + for (int i = 0; i < 6; ++i) + mImages.push_back(new osg::Image); + } + + osg::Image *getImage(int index) + { + return mImages[index].get(); + } + + protected: + std::vector> mImages; + int mSize; + }; + void RenderingManager::screenshot360(osg::Image* image, int w) { + int resultW = 1024; + int resultH = 768; + + SphericalScreenshot s(w); + osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), osg::Vec3(-1,0,0), @@ -641,14 +667,14 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { - osg::ref_ptr sideImage (new osg::Image); - screenshot(sideImage.get(),w,w,directions[i]); + osg::Image *sideImage = s.getImage(i); + screenshot(sideImage,w,w,directions[i]); if (i == 0) - image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), - image,w * i,0,0); + osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) From 4761a3d98b4d50dd041631490aa5c654f4c847a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 22:13:05 +0100 Subject: [PATCH 05/47] dirty cylindrical projection --- apps/openmw/mwrender/renderingmanager.cpp | 85 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f25f28bab..f22a0778d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -638,6 +638,80 @@ namespace MWRender return mImages[index].get(); } + void create(osg::Image *dest, int w, int h) + { + dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); + + for (int j = 0; j < h; ++j) + for (int i = 0; i < w; ++i) + dest->setColor(getColorByDirection(cylindricalCoords(i / ((float) w), j / ((float) h))),i,j); + } + + osg::Vec3d cylindricalCoords(double x, double y) + { + osg::Vec3 result = osg::Vec3d(sin(x * 2 * osg::PI),cos(x * 2 * osg::PI),y * 2.0 - 1.0); + result.normalize(); + return result; + } + + osg::Vec4 getColorByDirection(osg::Vec3d d) + { + double x, y; + double ma; // see OpenGL 4.4 specification page 241 + int side; + + double ax, ay, az; + ax = d.x() > 0 ? d.x() : -d.x(); + ay = d.y() > 0 ? d.y() : -d.y(); + az = d.z() > 0 ? d.z() : -d.z(); + + + if (ax > ay) + { + if (ax > az) + { + side = d.x() > 0 ? 1 : 3; + ma = ax; + } + else + { + side = d.z() > 0 ? 5 : 4; + ma = az; + } + } + else + { + if (ay > az) + { + side = d.y() > 0 ? 0 : 2; + ma = ay; + } + else + { + side = d.z() > 0 ? 5 : 4; + ma = az; + } + } + + switch (side) + { + case 0: x = d.x(); y = d.z(); break; + case 1: x = -d.y(); y = d.z(); break; + case 2: x = -d.x(); y = d.z(); break; + case 3: x = d.y(); y = d.z(); break; + case 4: x = d.x(); y = d.y(); break; + case 5: x = d.x(); y = -d.y(); break; + default: break; + } + + x = 0.5 * (x / ma + 1); + y = 0.5 * (y / ma + 1); + + return mImages[side]->getColor( + std::min(std::max(int(x * mSize),0),mSize - 1), + std::min(std::max(int(y * mSize),0),mSize - 1)); //osg::Vec4(d.x(),d.y(),d.z(),1); + } + protected: std::vector> mImages; int mSize; @@ -645,9 +719,6 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image, int w) { - int resultW = 1024; - int resultH = 768; - SphericalScreenshot s(w); osg::Vec3 directions[6] = { @@ -670,16 +741,18 @@ namespace MWRender osg::Image *sideImage = s.getImage(i); screenshot(sideImage,w,w,directions[i]); - if (i == 0) + // if (i == 0) //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + //image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); + // osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); + s.create(image,1600,768); + mFieldOfView = fovBackup; } From 5f365181813c34505782a0efac62c940bf3814aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 8 Nov 2017 09:15:45 +0100 Subject: [PATCH 06/47] spherical mapping --- apps/openmw/mwrender/renderingmanager.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f22a0778d..73b761ff2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,7 +644,7 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(cylindricalCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) @@ -654,6 +654,17 @@ namespace MWRender return result; } + osg::Vec3d sphericalCoords(double x, double y) + { + x = x * 2 * osg::PI; + y = (y - 0.5) * osg::PI; + + osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); + result = osg::Vec3(cos(x) * result.y(),sin(x) * result.y(),result.z()); + + return result; + } + osg::Vec4 getColorByDirection(osg::Vec3d d) { double x, y; From 5698d70806c52d7912e3d303851818ce3233c589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 8 Nov 2017 12:58:27 +0100 Subject: [PATCH 07/47] small planet mapping --- apps/openmw/mwrender/renderingmanager.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 73b761ff2..9e2773870 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,12 +644,12 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(smallPlanetCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) { - osg::Vec3 result = osg::Vec3d(sin(x * 2 * osg::PI),cos(x * 2 * osg::PI),y * 2.0 - 1.0); + osg::Vec3 result = osg::Vec3d(cos(x * 2 * osg::PI),sin(x * 2 * osg::PI),y * 2.0 - 1.0); result.normalize(); return result; } @@ -665,6 +665,21 @@ namespace MWRender return result; } + osg::Vec3d smallPlanetCoords(double x, double y) + { + osg::Vec2d fromCenter = osg::Vec2d(x,y) - osg::Vec2d(0.5,0.5); + + double magnitude = fromCenter.length(); + + fromCenter.normalize(); + double dot = fromCenter * osg::Vec2d(0.0,1.0); + + x = x > 0.5 ? 0.5 - (dot + 1) / 4.0 : 0.5 + (dot + 1) / 4.0; + y = pow(std::min(1.0,magnitude / 0.5),0.5); + + return sphericalCoords(x,y); + } + osg::Vec4 getColorByDirection(osg::Vec3d d) { double x, y; @@ -720,7 +735,7 @@ namespace MWRender return mImages[side]->getColor( std::min(std::max(int(x * mSize),0),mSize - 1), - std::min(std::max(int(y * mSize),0),mSize - 1)); //osg::Vec4(d.x(),d.y(),d.z(),1); + std::min(std::max(int(y * mSize),0),mSize - 1)); } protected: @@ -762,7 +777,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,1600,768); + s.create(image,1600,1600); mFieldOfView = fovBackup; } From f60840754fcf7da27bb41640ee9c5ab3cb91cfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 14:44:42 +0100 Subject: [PATCH 08/47] disable water effects for spherical screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 14 ++++------- apps/openmw/mwrender/water.cpp | 29 +++++++++++++++++++---- apps/openmw/mwrender/water.hpp | 1 + 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9e2773870..b1f1888d0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,7 +644,7 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(smallPlanetCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) @@ -766,12 +766,6 @@ namespace MWRender { osg::Image *sideImage = s.getImage(i); screenshot(sideImage,w,w,directions[i]); - - // if (i == 0) - //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - //image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - - // osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) @@ -795,8 +789,6 @@ namespace MWRender mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) ); -// TODO: water reflections have to be transformed as well!!!!! - rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); @@ -823,10 +815,14 @@ namespace MWRender // 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() + mWater->setEffectsEnabled(false); + mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); + mWater->setEffectsEnabled(true); + callback->waitTillDone(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c4dffb7a4..70e7b36ad 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -207,7 +207,6 @@ osg::ref_ptr readPngImage (const std::string& file) return result.getImage(); } - class Refraction : public osg::Camera { public: @@ -221,7 +220,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setupCullMask(true); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -262,6 +261,12 @@ public: attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } + void setupCullMask(bool enabled) + { + setCullMask(!enabled ? 0 : + Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + } + void setScene(osg::Node* scene) { if (mScene) @@ -304,9 +309,9 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("ReflectionCamera"); - bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); + mReflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); + setupCullMask(true); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -334,6 +339,12 @@ public: addChild(mClipCullNode); } + void setupCullMask(bool enabled) + { + setCullMask(!enabled ? 0 : + Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(mReflectActors ? Mask_Actor : 0)); + } + void setWaterLevel(float waterLevel) { setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); @@ -357,6 +368,7 @@ private: osg::ref_ptr mReflectionTexture; osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; + bool mReflectActors; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. @@ -697,4 +709,13 @@ void Water::clearRipples() mSimulation->clear(); } +void Water::setEffectsEnabled(bool enabled) +{ + if (mReflection) + mReflection->setupCullMask(enabled); + + if (mRefraction) + mRefraction->setupCullMask(enabled); +} + } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index a4fd1ed36..ed6e40f1a 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -106,6 +106,7 @@ namespace MWRender void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); + void setEffectsEnabled(bool enabled); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); From 1b97a541f4e6ed5fafb41ff8213641c93e5c831e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 16:06:29 +0100 Subject: [PATCH 09/47] make a new action for 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 22 ++++++++++++++++------ apps/openmw/mwinput/inputmanagerimp.hpp | 3 +++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 61434521e..5d1c236ca 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -247,6 +247,9 @@ namespace MWInput case A_Screenshot: screenshot(); break; + case A_Screenshot360: + screenshot360(); + break; case A_Inventory: toggleInventory (); break; @@ -1015,7 +1018,13 @@ namespace MWInput void InputManager::screenshot() { - // MOVE THIS ELSEWHERE LATER! + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + } + + void InputManager::screenshot360() + { int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); @@ -1040,11 +1049,6 @@ namespace MWInput } outfile.close(); - -// mScreenCaptureHandler->setFramesToCapture(1); -// mScreenCaptureHandler->captureNextFrame(*mViewer); - -// MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } void InputManager::toggleInventory() @@ -1230,6 +1234,7 @@ namespace MWInput defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; + defaultKeyBindings[A_Screenshot360] = SDL_SCANCODE_F8; defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; defaultKeyBindings[A_ToggleDebug] = SDL_SCANCODE_F10; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; @@ -1365,6 +1370,9 @@ namespace MWInput if (action == A_Screenshot) return "Screenshot"; + if (action == A_Screenshot360) + return "Screenshot 360"; + descriptions[A_Use] = "sUse"; descriptions[A_Activate] = "sActivate"; descriptions[A_MoveBackward] = "sBack"; @@ -1526,6 +1534,7 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); + ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); @@ -1557,6 +1566,7 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); + ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index cba7fc743..07b43d0ac 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -227,6 +227,7 @@ namespace MWInput void toggleInventory(); void toggleConsole(); void screenshot(); + void screenshot360(); void toggleJournal(); void activate(); void toggleWalking(); @@ -257,6 +258,8 @@ namespace MWInput A_Screenshot, // Take a screenshot + A_Screenshot360, // Take a 360 degree screenshot + A_Inventory, // Toggle inventory screen A_Console, // Toggle console screen From d4fd08a63fc696e415957a15d9d06774654059fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 16:49:46 +0100 Subject: [PATCH 10/47] save 360 screenshots in the configured directory --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 11 ++++++----- apps/openmw/mwrender/renderingmanager.cpp | 3 ++- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 80a9e11a1..6d39229f0 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual void screenshot360 (osg::Image* image, int w) = 0; + virtual void screenshot360 (osg::Image* image) = 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/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5d1c236ca..cd2e8408b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1020,20 +1020,21 @@ namespace MWInput { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); } void InputManager::screenshot360() { - int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); - MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); + MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); +(*mScreenCaptureHandler->getCaptureOperation()) (*(screenshot.get()),0); +/* osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { - std::cerr << "Error: Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + std::cerr << "Error: Unable to write 360 degree screenshot, can't find a jpg ReaderWriter" << std::endl; return; } @@ -1048,7 +1049,7 @@ namespace MWInput return; } - outfile.close(); + outfile.close(); */ } void InputManager::toggleInventory() diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b1f1888d0..4ea602f3f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -743,8 +743,9 @@ namespace MWRender int mSize; }; - void RenderingManager::screenshot360(osg::Image* image, int w) + void RenderingManager::screenshot360(osg::Image* image) { + int w = 1024; SphericalScreenshot s(w); osg::Vec3 directions[6] = { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index add7aae4d..e6e2e1c0a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); - void screenshot360(osg::Image* image, int w); + void screenshot360(osg::Image* image); struct RayResult { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f9a030a80..f51f9af3d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - void World::screenshot360 (osg::Image* image, int w) + void World::screenshot360 (osg::Image* image) { - mRendering->screenshot360(image, w); + mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1882b88b9..a68982412 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - void screenshot360 (osg::Image* image, int w) override; + void screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From 8f321140256d835adb0d0779eedef732b4cda1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 18:26:27 +0100 Subject: [PATCH 11/47] segfault fix --- apps/openmw/engine.cpp | 9 ++-- apps/openmw/engine.hpp | 3 +- apps/openmw/mwinput/inputmanagerimp.cpp | 26 ++-------- apps/openmw/mwinput/inputmanagerimp.hpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 62 +++++++++++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3e5ab7ce6..499158fd4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -484,7 +484,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(); @@ -641,8 +641,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 bf144bfed..3cbd4b6e7 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/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cd2e8408b..e1c85744e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -37,12 +37,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) @@ -1027,29 +1029,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); -(*mScreenCaptureHandler->getCaptureOperation()) (*(screenshot.get()),0); - -/* - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); - - if (!readerwriter) - { - std::cerr << "Error: Unable to write 360 degree screenshot, can't find a jpg ReaderWriter" << std::endl; - return; - } - - std::ofstream outfile; - outfile.open("test.jpg"); - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, outfile); - - if (!result.success()) - { - outfile << "Error: Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; - return; - } - - outfile.close(); */ + (*mScreenCaptureOperation) (*(screenshot.get()),0); } void InputManager::toggleInventory() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 07b43d0ac..5d3e88eab 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 4ea602f3f..a08e7a0a2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -625,6 +625,13 @@ namespace MWRender class SphericalScreenshot { public: + typedef enum + { + MAPPING_CYLINDRICAL = 0, + MAPPING_SPHERICAL, + MAPPING_SMALL_PLANET + } SphericalScreenshotMapping; + SphericalScreenshot(int size) { mSize = size; @@ -638,13 +645,26 @@ namespace MWRender return mImages[index].get(); } - void create(osg::Image *dest, int w, int h) + void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) { dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); + { + osg::Vec3d coords; + osg::Vec2d normalizedXY = osg::Vec2d(i / ((float) w), j / ((float) h)); + + switch (mapping) + { + case MAPPING_CYLINDRICAL: coords = cylindricalCoords(normalizedXY.x(),normalizedXY.y()); break; + case MAPPING_SPHERICAL: coords = sphericalCoords(normalizedXY.x(),normalizedXY.y()); break; + case MAPPING_SMALL_PLANET: coords = smallPlanetCoords(normalizedXY.x(),normalizedXY.y()); break; + default: break; + } + + dest->setColor(getColorByDirection(coords),i,j); + } } osg::Vec3d cylindricalCoords(double x, double y) @@ -682,18 +702,18 @@ namespace MWRender osg::Vec4 getColorByDirection(osg::Vec3d d) { + // for details see OpenGL 4.4 specification page 241 + double x, y; - double ma; // see OpenGL 4.4 specification page 241 + double ma; int side; double ax, ay, az; - ax = d.x() > 0 ? d.x() : -d.x(); + ax = d.x() > 0 ? d.x() : -d.x(); // abs behaves weirdly for some reason ay = d.y() > 0 ? d.y() : -d.y(); az = d.z() > 0 ? d.z() : -d.z(); - if (ax > ay) - { if (ax > az) { side = d.x() > 0 ? 1 : 3; @@ -704,9 +724,7 @@ namespace MWRender side = d.z() > 0 ? 5 : 4; ma = az; } - } else - { if (ay > az) { side = d.y() > 0 ? 0 : 2; @@ -717,12 +735,11 @@ namespace MWRender side = d.z() > 0 ? 5 : 4; ma = az; } - } switch (side) { - case 0: x = d.x(); y = d.z(); break; - case 1: x = -d.y(); y = d.z(); break; + case 0: x = d.x(); y = d.z(); break; + case 1: x = -d.y(); y = d.z(); break; case 2: x = -d.x(); y = d.z(); break; case 3: x = d.y(); y = d.z(); break; case 4: x = d.x(); y = d.y(); break; @@ -745,8 +762,10 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image) { - int w = 1024; - SphericalScreenshot s(w); + int cubeWidth = 1024; + int screenshotWidth = 1600; + int screenshotHeight = 1280; + SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), @@ -766,18 +785,18 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,w,w,directions[i]); + screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); } if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,1600,1600); + s.create(image,screenshotWidth,screenshotHeight,SphericalScreenshot::MAPPING_SPHERICAL); mFieldOfView = fovBackup; } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -786,9 +805,7 @@ 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() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) - ); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); rttCamera->setViewport(0, 0, w, h); @@ -816,13 +833,16 @@ namespace MWRender // 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() - mWater->setEffectsEnabled(false); + + if (disableWaterEffects) + mWater->setEffectsEnabled(false); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - mWater->setEffectsEnabled(true); + if (disableWaterEffects) + mWater->setEffectsEnabled(true); callback->waitTillDone(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e6e2e1c0a..cb0723397 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ 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, osg::Vec3 direction=osg::Vec3(0,0,-1)); + void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); void screenshot360(osg::Image* image); struct RayResult From d763e9fe46be5189e4531e074be6a90309d6f77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 20:25:29 +0100 Subject: [PATCH 12/47] add settings for spherical screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 15 +++++++++------ components/settings/settings.cpp | 12 ++++++++++++ components/settings/settings.hpp | 1 + 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e1c85744e..9ea1f8e16 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1029,6 +1029,8 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); + + // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason (*mScreenCaptureOperation) (*(screenshot.get()),0); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a08e7a0a2..bac150459 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -627,8 +627,8 @@ namespace MWRender public: typedef enum { - MAPPING_CYLINDRICAL = 0, - MAPPING_SPHERICAL, + MAPPING_SPHERICAL = 0, + MAPPING_CYLINDRICAL, MAPPING_SMALL_PLANET } SphericalScreenshotMapping; @@ -762,9 +762,12 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image) { - int cubeWidth = 1024; - int screenshotWidth = 1600; - int screenshotHeight = 1280; + int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); + int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); + SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( + Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); + + int cubeWidth = screenshotWidth / 2; SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { @@ -791,7 +794,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,screenshotWidth,screenshotHeight,SphericalScreenshot::MAPPING_SPHERICAL); + s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); mFieldOfView = fovBackup; } diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index e93642ee2..4e250974a 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -401,6 +401,18 @@ int Manager::getInt (const std::string& setting, const std::string& category) return parseInt( getString(setting, category) ); } +int Manager::tryGetInt (const std::string &setting, const std::string& category, int defaultValue) +{ + try + { + return getInt(setting,category); + } + catch (std::runtime_error) + { + return defaultValue; + } +} + bool Manager::getBool (const std::string& setting, const std::string& category) { return parseBool( getString(setting, category) ); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 7adcb9b39..9e80c21a6 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -39,6 +39,7 @@ namespace Settings ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); + static int tryGetInt (const std::string &setting, const std::string& category, int defaultValue); static float getFloat (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); From e54c0a90fadda522a25fdc8fe5ce581ee75780a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 21:14:02 +0100 Subject: [PATCH 13/47] fix mirrored spherical screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bac150459..c018c1707 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -669,14 +669,14 @@ namespace MWRender osg::Vec3d cylindricalCoords(double x, double y) { - osg::Vec3 result = osg::Vec3d(cos(x * 2 * osg::PI),sin(x * 2 * osg::PI),y * 2.0 - 1.0); + osg::Vec3 result = osg::Vec3d(cos(-1 * x * 2 * osg::PI),sin(-1 * x * 2 * osg::PI),y * 2.0 - 1.0); result.normalize(); return result; } osg::Vec3d sphericalCoords(double x, double y) { - x = x * 2 * osg::PI; + x = -1 * x * 2 * osg::PI; y = (y - 0.5) * osg::PI; osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); From 1b184d871646f6ec8954dca838c4ec4ee6194015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 23:09:13 +0100 Subject: [PATCH 14/47] correct player mask --- apps/openmw/mwrender/renderingmanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c018c1707..9c6eac3c0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -782,6 +782,8 @@ namespace MWRender double fovBackup = mFieldOfView; mFieldOfView = 90.0; // each side sees 90 degrees + int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); + if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); @@ -791,11 +793,9 @@ namespace MWRender screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); } - if (mCamera->isFirstPerson()) - mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); + mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; } From 43c49e2f3135f13297badb42d752c35ed4625a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 23:12:23 +0100 Subject: [PATCH 15/47] delete accidentally commited files --- apps/openmw/mwmechanics/character.cpp.orig | 2464 -------------------- apps/openmw/mwmechanics/character.cpp.rej | 46 - 2 files changed, 2510 deletions(-) delete mode 100644 apps/openmw/mwmechanics/character.cpp.orig delete mode 100644 apps/openmw/mwmechanics/character.cpp.rej diff --git a/apps/openmw/mwmechanics/character.cpp.orig b/apps/openmw/mwmechanics/character.cpp.orig deleted file mode 100644 index f262850a4..000000000 --- a/apps/openmw/mwmechanics/character.cpp.orig +++ /dev/null @@ -1,2464 +0,0 @@ -/* - * OpenMW - The completely unofficial reimplementation of Morrowind - * - * This file (character.cpp) is part of the OpenMW package. - * - * OpenMW is distributed as free software: you can redistribute it - * and/or modify it under the terms of the GNU General Public License - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * version 3 along with this program. If not, see - * http://www.gnu.org/licenses/ . - */ - -#include "character.hpp" - -#include - -#include - -#include - -#include - -#include "../mwrender/animation.hpp" - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/player.hpp" - -#include "movement.hpp" -#include "npcstats.hpp" -#include "creaturestats.hpp" -#include "security.hpp" -#include "actorutil.hpp" -#include "spellcasting.hpp" - -namespace -{ - -// Wraps a value to (-PI, PI] -void wrap(float& rad) -{ - if (rad>0) - rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; - else - rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; -} - -std::string toString(int num) -{ - std::ostringstream stream; - stream << num; - return stream.str(); -} - -std::string getBestAttack (const ESM::Weapon* weapon) -{ - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - if (slash == chop && slash == thrust) - return "slash"; - else if (thrust >= chop && thrust >= slash) - return "thrust"; - else if (slash >= chop && slash >= thrust) - return "slash"; - else - return "chop"; -} - -// Converts a movement Run state to its equivalent Walk state. -MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) -{ - using namespace MWMechanics; - CharacterState ret = state; - switch (state) - { - case CharState_RunForward: - ret = CharState_WalkForward; - break; - case CharState_RunBack: - ret = CharState_WalkBack; - break; - case CharState_RunLeft: - ret = CharState_WalkLeft; - break; - case CharState_RunRight: - ret = CharState_WalkRight; - break; - case CharState_SwimRunForward: - ret = CharState_SwimWalkForward; - break; - case CharState_SwimRunBack: - ret = CharState_SwimWalkBack; - break; - case CharState_SwimRunLeft: - ret = CharState_SwimWalkLeft; - break; - case CharState_SwimRunRight: - ret = CharState_SwimWalkRight; - break; - default: - break; - } - return ret; -} - -float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); - - const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); - - if (fallHeight >= fallDistanceMin) - { - const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); - const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); - const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); - - float x = fallHeight - fallDistanceMin; - x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; - x = std::max(0.0f, x); - - float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); - x = fallDistanceBase + fallDistanceMult * x; - x *= a; - - return x; - } - return 0.f; -} - -} - -namespace MWMechanics -{ - -struct StateInfo { - CharacterState state; - const char groupname[32]; -}; - -static const StateInfo sMovementList[] = { - { CharState_WalkForward, "walkforward" }, - { CharState_WalkBack, "walkback" }, - { CharState_WalkLeft, "walkleft" }, - { CharState_WalkRight, "walkright" }, - - { CharState_SwimWalkForward, "swimwalkforward" }, - { CharState_SwimWalkBack, "swimwalkback" }, - { CharState_SwimWalkLeft, "swimwalkleft" }, - { CharState_SwimWalkRight, "swimwalkright" }, - - { CharState_RunForward, "runforward" }, - { CharState_RunBack, "runback" }, - { CharState_RunLeft, "runleft" }, - { CharState_RunRight, "runright" }, - - { CharState_SwimRunForward, "swimrunforward" }, - { CharState_SwimRunBack, "swimrunback" }, - { CharState_SwimRunLeft, "swimrunleft" }, - { CharState_SwimRunRight, "swimrunright" }, - - { CharState_SneakForward, "sneakforward" }, - { CharState_SneakBack, "sneakback" }, - { CharState_SneakLeft, "sneakleft" }, - { CharState_SneakRight, "sneakright" }, - - { CharState_Jump, "jump" }, - - { CharState_TurnLeft, "turnleft" }, - { CharState_TurnRight, "turnright" }, - { CharState_SwimTurnLeft, "swimturnleft" }, - { CharState_SwimTurnRight, "swimturnright" }, -}; -static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; - - -class FindCharState { - CharacterState state; - -public: - FindCharState(CharacterState _state) : state(_state) { } - - bool operator()(const StateInfo &info) const - { return info.state == state; } -}; - - -static const struct WeaponInfo { - WeaponType type; - const char shortgroup[16]; - const char longgroup[16]; -} sWeaponTypeList[] = { - { WeapType_HandToHand, "hh", "handtohand" }, - { WeapType_OneHand, "1h", "weapononehand" }, - { WeapType_TwoHand, "2c", "weapontwohand" }, - { WeapType_TwoWide, "2w", "weapontwowide" }, - { WeapType_BowAndArrow, "1h", "bowandarrow" }, - { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_Thrown, "1h", "throwweapon" }, - { WeapType_PickProbe, "1h", "pickprobe" }, - { WeapType_Spell, "spell", "spellcast" }, -}; -static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - -class FindWeaponType { - WeaponType type; - -public: - FindWeaponType(WeaponType _type) : type(_type) { } - - bool operator()(const WeaponInfo &weap) const - { return weap.type == type; } -}; - -std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const -{ - int numAnims=0; - while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) - ++numAnims; - - int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] - if (num) - *num = roll; - return prefix + toString(roll); -} - -void CharacterController::refreshHitRecoilAnims() -{ - bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); - bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); - bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - if(mHitState == CharState_None) - { - if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) - && mAnimation->hasAnimation("knockout")) - { - if (isSwimming && mAnimation->hasAnimation("swimknockout")) - { - mHitState = CharState_SwimKnockOut; - mCurrentHit = "swimknockout"; - } - else - { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; - } - - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); - } - else if(knockdown && mAnimation->hasAnimation("knockdown")) - { - if (isSwimming && mAnimation->hasAnimation("swimknockdown")) - { - mHitState = CharState_SwimKnockDown; - mCurrentHit = "swimknockdown"; - } - else - { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; - } - - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else if (recovery) - { - std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); - if (isSwimming && mAnimation->hasAnimation(anim)) - { - mHitState = CharState_SwimHit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) - { - mHitState = CharState_Hit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - } - } - else if (block && mAnimation->hasAnimation("shield")) - { - mHitState = CharState_Block; - mCurrentHit = "shield"; - MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); - priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); - } - - // Cancel upper body animations - if (isKnockedOut() || isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; - } - } - } - else if(!mAnimation->isPlaying(mCurrentHit)) - { - mCurrentHit.erase(); - if (knockdown) - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); - if (recovery) - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); - if (block) - mPtr.getClass().getCreatureStats(mPtr).setBlock(false); - mHitState = CharState_None; - } - else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) - { - mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); - } - if (mHitState != CharState_None) - mIdleState = CharState_None; -} - -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) -{ - if(force || jump != mJumpState) - { - mIdleState = CharState_None; - bool startAtLoop = (jump == mJumpState); - mJumpState = jump; - - std::string jumpAnimName; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; - if(mJumpState != JumpState_None) - { - jumpAnimName = "jump"; - if(weap != sWeaponTypeListEnd) - { - jumpAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(jumpAnimName)) - { - jumpmask = MWRender::Animation::BlendMask_LowerBody; - jumpAnimName = "jump"; - } - } - } - - if(mJumpState == JumpState_InAir) - { - mAnimation->disable(mCurrentJump); - mCurrentJump = jumpAnimName; - if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, - 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); - } - else if (mJumpState == JumpState_Landing) - { - if (startAtLoop) - mAnimation->disable(mCurrentJump); - - if (mAnimation->hasAnimation("jump")) - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - } - else // JumpState_None - { - if (mCurrentJump.length() > 0) - { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - } - } - } -} - -void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) -{ - if(force || movement != mMovementState) - { - mMovementState = movement; - - if (movement != CharState_None) - mIdleState = CharState_None; - - std::string movementAnimName; - MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; - const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); - if(movestate != sMovementListEnd) - { - movementAnimName = movestate->groupname; - if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) - { - movementAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(movementAnimName)) - { - movemask = MWRender::Animation::BlendMask_LowerBody; - movementAnimName = movestate->groupname; - } - } - - if(!mAnimation->hasAnimation(movementAnimName)) - { - std::string::size_type swimpos = movementAnimName.find("swim"); - if(swimpos == std::string::npos) - { - std::string::size_type runpos = movementAnimName.find("run"); - if (runpos != std::string::npos) - { - movementAnimName.replace(runpos, runpos+3, "walk"); - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - else - movementAnimName.clear(); - } - else - { - movementAnimName.erase(swimpos, 4); - if (weap != sWeaponTypeListEnd) - { - std::string weapMovementAnimName = movementAnimName + weap->shortgroup; - if(mAnimation->hasAnimation(weapMovementAnimName)) - movementAnimName = weapMovementAnimName; - else - movemask = MWRender::Animation::BlendMask_LowerBody; - } - - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - } - } - - /* If we're playing the same animation, restart from the loop start instead of the - * beginning. */ - int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); - - mMovementAnimationControlled = true; - - mAnimation->disable(mCurrentMovement); - mCurrentMovement = movementAnimName; - if(!mCurrentMovement.empty()) - { - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && !MWBase::Environment::get().getWorld()->isFlying(mPtr); - - // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity - // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. - std::string anim = mCurrentMovement; - mAdjustMovementAnimSpeed = true; - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() - && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) - { - CharacterState walkState = runStateToWalkState(mMovementState); - const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); - anim = stateinfo->groupname; - - mMovementAnimSpeed = mAnimation->getVelocity(anim); - if (mMovementAnimSpeed <= 1.0f) - { - // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), - // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist - // we will play without any scaling. - // Makes the speed attribute of most water creatures totally useless. - // And again, this can not be fixed without patching game data. - mAdjustMovementAnimSpeed = false; - mMovementAnimSpeed = 1.f; - } - } - else - { - mMovementAnimSpeed = mAnimation->getVelocity(anim); - - if (mMovementAnimSpeed <= 1.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); - mMovementAnimationControlled = false; - } - } - - mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, - 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul, true); - } - } -} - -void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) -{ - if(force || idle != mIdleState || - ((idle == mIdleState) && !mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) - { - mIdleState = idle; - size_t numLoops = ~0ul; - - std::string idleGroup; - MWRender::Animation::AnimPriority idlePriority (Priority_Default); - // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to - // "idle"+weapon or "idle". - if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) - { - idleGroup = "idleswim"; - idlePriority = Priority_SwimIdle; - } - else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) - { - idleGroup = "idlesneak"; - idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; - } - else if(mIdleState != CharState_None) - { - idleGroup = "idle"; - if(weap != sWeaponTypeListEnd) - { - idleGroup += weap->shortgroup; - if(!mAnimation->hasAnimation(idleGroup)) - idleGroup = "idle"; - - // play until the Loop Stop key 2 to 5 times, then play until the Stop key - // this replicates original engine behavior for the "Idle1h" 1st-person animation - numLoops = 1 + Misc::Rng::rollDice(4); - } - } - - mAnimation->disable(mCurrentIdle); - mCurrentIdle = idleGroup; - if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, numLoops, true); - } -} - -void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) -{ - if (mPtr.getClass().isActor()) - refreshHitRecoilAnims(); - - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().isBipedal(mPtr)) - weap = sWeaponTypeListEnd; - - refreshJumpAnims(weap, jump, force); - refreshMovementAnims(weap, movement, force); - - // idle handled last as it can depend on the other states - // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), - // the idle animation should be displayed - if ((mUpperBodyState != UpperCharState_Nothing - || (mMovementState != CharState_None && !isTurning()) - || mHitState != CharState_None) - && !mPtr.getClass().isBipedal(mPtr)) - idle = CharState_None; - - refreshIdleAnims(weap, idle, force); -} - - -void getWeaponGroup(WeaponType weaptype, std::string &group) -{ - const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); - if(info != sWeaponTypeListEnd) - group = info->longgroup; - else - group.clear(); -} - - -MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) -{ - if(stats.getDrawState() == DrawState_Spell) - { - *weaptype = WeapType_Spell; - return inv.end(); - } - - if(stats.getDrawState() == MWMechanics::DrawState_Weapon) - { - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - *weaptype = WeapType_HandToHand; - else - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - *weaptype = WeapType_PickProbe; - else if(type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - case ESM::Weapon::LongBladeOneHand: - case ESM::Weapon::BluntOneHand: - case ESM::Weapon::AxeOneHand: - case ESM::Weapon::Arrow: - case ESM::Weapon::Bolt: - *weaptype = WeapType_OneHand; - break; - case ESM::Weapon::LongBladeTwoHand: - case ESM::Weapon::BluntTwoClose: - case ESM::Weapon::AxeTwoHand: - *weaptype = WeapType_TwoHand; - break; - case ESM::Weapon::BluntTwoWide: - case ESM::Weapon::SpearTwoWide: - *weaptype = WeapType_TwoWide; - break; - case ESM::Weapon::MarksmanBow: - *weaptype = WeapType_BowAndArrow; - break; - case ESM::Weapon::MarksmanCrossbow: - *weaptype = WeapType_Crossbow; - break; - case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_Thrown; - break; - } - } - } - - return weapon; - } - - return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); -} - -void CharacterController::playDeath(float startpoint, CharacterState death) -{ - switch (death) - { - case CharState_SwimDeath: - mCurrentDeath = "swimdeath"; - break; - case CharState_SwimDeathKnockDown: - mCurrentDeath = "swimdeathknockdown"; - break; - case CharState_SwimDeathKnockOut: - mCurrentDeath = "swimdeathknockout"; - break; - case CharState_DeathKnockDown: - mCurrentDeath = "deathknockdown"; - break; - case CharState_DeathKnockOut: - mCurrentDeath = "deathknockout"; - break; - default: - mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); - } - mDeathState = death; - - mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); - - // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. - // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). - // However, they could still trigger text keys, such as Hit events, or sounds. - mMovementState = CharState_None; - mAnimation->disable(mCurrentMovement); - mCurrentMovement = ""; - mUpperBodyState = UpperCharState_Nothing; - mAnimation->disable(mCurrentWeapon); - mCurrentWeapon = ""; - mHitState = CharState_None; - mAnimation->disable(mCurrentHit); - mCurrentHit = ""; - mIdleState = CharState_None; - mAnimation->disable(mCurrentIdle); - mCurrentIdle = ""; - mJumpState = JumpState_None; - mAnimation->disable(mCurrentJump); - mCurrentJump = ""; - mMovementAnimationControlled = true; - - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", startpoint, 0); -} - -CharacterState CharacterController::chooseRandomDeathState() const -{ - int selected=0; - chooseRandomGroup("death", &selected); - return static_cast(CharState_Death1 + (selected-1)); -} - -void CharacterController::playRandomDeath(float startpoint) -{ - if (mPtr == getPlayer()) - { - // The first-person animations do not include death, so we need to - // force-switch to third person before playing the death animation. - MWBase::Environment::get().getWorld()->useDeathCamera(); - } - - if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) - { - mDeathState = CharState_SwimDeathKnockDown; - } - else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) - { - mDeathState = CharState_SwimDeathKnockOut; - } - else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) - { - mDeathState = CharState_SwimDeath; - } - else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) - { - mDeathState = CharState_DeathKnockDown; - } - else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) - { - mDeathState = CharState_DeathKnockOut; - } - else - { - mDeathState = chooseRandomDeathState(); - } - playDeath(startpoint, mDeathState); -} - -CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) - : mPtr(ptr) - , mAnimation(anim) - , mIdleState(CharState_None) - , mMovementState(CharState_None) - , mMovementAnimSpeed(0.f) - , mAdjustMovementAnimSpeed(false) - , mHasMovedInXY(false) - , mMovementAnimationControlled(true) - , mDeathState(CharState_None) - , mFloatToSurface(true) - , mHitState(CharState_None) - , mUpperBodyState(UpperCharState_Nothing) - , mJumpState(JumpState_None) - , mWeaponType(WeapType_None) - , mAttackStrength(0.f) - , mSkipAnim(false) - , mSecondsOfSwimming(0) - , mSecondsOfRunning(0) - , mTurnAnimationThreshold(0) - , mAttackingOrSpell(false) -{ - if(!mAnimation) - return; - - mAnimation->setTextKeyListener(this); - - const MWWorld::Class &cls = mPtr.getClass(); - if(cls.isActor()) - { - /* Accumulate along X/Y only for now, until we can figure out how we should - * handle knockout and death which moves the character down. */ - mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); - - if (cls.hasInventoryStore(mPtr)) - { - getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if (mWeaponType != WeapType_None) - { - mUpperBodyState = UpperCharState_WeapEquiped; - getWeaponGroup(mWeaponType, mCurrentWeapon); - } - - if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) - { - mAnimation->showWeapons(true); - mAnimation->setWeaponGroup(mCurrentWeapon); - } - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); - } - - if(!cls.getCreatureStats(mPtr).isDead()) - mIdleState = CharState_Idle; - else - { - const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); - if (cStats.isDeathAnimationFinished()) - { - // Set the death state, but don't play it yet - // We will play it in the first frame, but only if no script set the skipAnim flag - signed char deathanim = cStats.getDeathAnimation(); - if (deathanim == -1) - mDeathState = chooseRandomDeathState(); - else - mDeathState = static_cast(CharState_Death1 + deathanim); - - mFloatToSurface = false; - } - // else: nothing to do, will detect death in the next frame and start playing death animation - } - } - else - { - /* Don't accumulate with non-actors. */ - mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); - - mIdleState = CharState_Idle; - } - - - if(mDeathState == CharState_None) - refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); - - mAnimation->runAnimation(0.f); - - unpersistAnimationState(); -} - -CharacterController::~CharacterController() -{ - if (mAnimation) - { - persistAnimationState(); - mAnimation->setTextKeyListener(NULL); - } -} - -void split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } -} - -void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) -{ - const std::string &evt = key->second; - - if(evt.compare(0, 7, "sound: ") == 0) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->stopSound3D(mPtr, evt.substr(7)); - sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); - return; - } - if(evt.compare(0, 10, "soundgen: ") == 0) - { - std::string soundgen = evt.substr(10); - - // The event can optionally contain volume and pitch modifiers - float volume=1.f, pitch=1.f; - if (soundgen.find(" ") != std::string::npos) - { - std::vector tokens; - split(soundgen, ' ', tokens); - soundgen = tokens[0]; - if (tokens.size() >= 2) - { - std::stringstream stream; - stream << tokens[1]; - stream >> volume; - } - if (tokens.size() >= 3) - { - std::stringstream stream; - stream << tokens[2]; - stream >> pitch; - } - } - - std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); - if(!sound.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) - { - // Don't make foot sounds local for the player, it makes sense to keep them - // positioned on the ground. - sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, - MWSound::PlayMode::NoPlayerLocal); - } - else - { - sndMgr->stopSound3D(mPtr, sound); - sndMgr->playSound3D(mPtr, sound, volume, pitch); - } - } - return; - } - - if(evt.compare(0, groupname.size(), groupname) != 0 || - evt.compare(groupname.size(), 2, ": ") != 0) - { - // Not ours, skip it - return; - } - size_t off = groupname.size()+2; - size_t len = evt.size() - off; - - if(evt.compare(off, len, "equip attach") == 0) - mAnimation->showWeapons(true); - else if(evt.compare(off, len, "unequip detach") == 0) - mAnimation->showWeapons(false); - else if(evt.compare(off, len, "chop hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if(evt.compare(off, len, "slash hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if(evt.compare(off, len, "thrust hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - else if(evt.compare(off, len, "hit") == 0) - { - if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - else - mPtr.getClass().hit(mPtr, mAttackStrength); - } - else if (!groupname.empty() - && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) - && evt.compare(off, len, "start") == 0) - { - std::multimap::const_iterator hitKey = key; - - // Not all animations have a hit key defined. If there is none, the hit happens with the start key. - bool hasHitKey = false; - while (hitKey != map.end()) - { - if (hitKey->second == groupname + ": hit") - { - hasHitKey = true; - break; - } - if (hitKey->second == groupname + ": stop") - break; - ++hitKey; - } - if (!hasHitKey) - { - if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - } - } - else if (evt.compare(off, len, "shoot attach") == 0) - mAnimation->attachArrow(); - else if (evt.compare(off, len, "shoot release") == 0) - mAnimation->releaseArrow(mAttackStrength); - else if (evt.compare(off, len, "shoot follow attach") == 0) - mAnimation->attachArrow(); - - else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" - // Make sure this key is actually for the RangeType we are casting. The flame atronach has - // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. - && evt.compare(off, len, mAttackType + " release") == 0) - { - MWBase::Environment::get().getWorld()->castSpell(mPtr); - } - - else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) - mPtr.getClass().block(mPtr); -} - -void CharacterController::updatePtr(const MWWorld::Ptr &ptr) -{ - mPtr = ptr; -} - -void CharacterController::updateIdleStormState(bool inwater) -{ - bool inStormDirection = false; - if (MWBase::Environment::get().getWorld()->isInStorm()) - { - osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); - osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) - > osg::DegreesToRadians(120.f); - } - if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) - { - float complete = 0; - mAnimation->getInfo("idlestorm", &complete); - - if (complete == 0) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "start", "loop start", 0.0f, 0); - else if (complete == 1) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "loop start", "loop stop", 0.0f, ~0ul); - } - else - { - if (mUpperBodyState == UpperCharState_Nothing) - { - if (mAnimation->isPlaying("idlestorm")) - { - if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) - { - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - } - } - } - else - mAnimation->disable("idlestorm"); - } -} - -bool CharacterController::updateCreatureState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - - WeaponType weapType = WeapType_None; - if(stats.getDrawState() == DrawState_Weapon) - weapType = WeapType_HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weapType = WeapType_Spell; - - if (weapType != mWeaponType) - { - mWeaponType = weapType; - if (mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->disable(mCurrentWeapon); - } - - if(mAttackingOrSpell) - { - if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - - std::string startKey = "start"; - std::string stopKey = "stop"; - if (weapType == WeapType_Spell) - { - const std::string spellid = stats.getSpells().getSelectedSpell(); - if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) - { - MWMechanics::CastSpell cast(mPtr, NULL); - cast.playSpellCastingEffects(spellid); - - if (!mAnimation->hasAnimation("spellcast")) - MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately - else - { - const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - switch(effectentry.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - startKey = mAttackType + " " + startKey; - stopKey = mAttackType + " " + stopKey; - mCurrentWeapon = "spellcast"; - } - } - else - mCurrentWeapon = ""; - } - if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation - { - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - int roll = Misc::Rng::rollDice(3); // [0, 2] - if (roll == 0) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; - else if (roll == 1) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; - else - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; - } - - if (!mCurrentWeapon.empty()) - { - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - - if (weapType == WeapType_HandToHand) - playSwishSound(0.0f); - } - } - - mAttackingOrSpell = false; - } - - bool animPlaying = mAnimation->getInfo(mCurrentWeapon); - if (!animPlaying) - mUpperBodyState = UpperCharState_Nothing; - return false; -} - -bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const -{ - // Shields/torches shouldn't be visible during any operation involving two hands - // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", - // but they are also present in weapon drawing animation. - switch (weaptype) - { - case WeapType_Spell: - case WeapType_BowAndArrow: - case WeapType_Crossbow: - case WeapType_HandToHand: - case WeapType_TwoHand: - case WeapType_TwoWide: - return false; - default: - return true; - } -} - -bool CharacterController::updateWeaponState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weaptype = WeapType_None; - if(stats.getDrawState() == DrawState_Weapon) - weaptype = WeapType_HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weaptype = WeapType_Spell; - - const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); - - std::string soundid; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) - { - soundid = (weaptype == WeapType_None) ? - weapon->getClass().getDownSoundId(*weapon) : - weapon->getClass().getUpSoundId(*weapon); - } - } - - MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; - - bool forcestateupdate = false; - - // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && - mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; - - if(weaptype != mWeaponType && !isKnockedOut() && - !isKnockedDown() && !isRecovery()) - { - forcestateupdate = true; - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - - std::string weapgroup; - if(weaptype == WeapType_None) - { - if ((!isWerewolf || mWeaponType != WeapType_Spell)) - { - getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; - } - } - else - { - getWeaponGroup(weaptype, weapgroup); - mAnimation->setWeaponGroup(weapgroup); - - if (!isStillWeapon) - { - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; - } - - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - } - } - - if(!soundid.empty() && !isStillWeapon) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); - } - - mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); - } - - if(isWerewolf) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && mHasMovedInXY - && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) - && mWeaponType == WeapType_None) - { - if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) - sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, - MWSound::PlayMode::Loop); - } - else - sndMgr->stopSound3D(mPtr, "WolfRun"); - } - - // Cancel attack if we no longer have ammunition - bool ammunition = true; - bool isWeapon = false; - float weapSpeed = 1.f; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - if(isWeapon) - weapSpeed = weapon->get()->mBase->mData.mSpeed; - - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (mWeaponType == WeapType_Crossbow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); - else if (mWeaponType == WeapType_BowAndArrow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - } - - float complete; - bool animPlaying; - if(mAttackingOrSpell) - { - mIdleState = CharState_None; - if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - mAttackStrength = 0; - if(mWeaponType == WeapType_Spell) - { - // Unset casting flag, otherwise pressing the mouse button down would - // continue casting every frame if there is no animation - mAttackingOrSpell = false; - if (mPtr == getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - - // For the player, set the spell we want to cast - // This has to be done at the start of the casting animation, - // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == getPlayer()) - { - std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); - stats.getSpells().setSelectedSpell(selectedSpell); - } - std::string spellid = stats.getSpells().getSelectedSpell(); - - if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) - { - MWMechanics::CastSpell cast(mPtr, NULL); - cast.playSpellCastingEffects(spellid); - - const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); - const ESM::MagicEffect *effect; - - effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands - - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - - for (size_t iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect - { - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); - - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - } - - const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation - - switch(firstEffect.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - weapSpeed, mAttackType+" start", mAttackType+" stop", - 0.0f, 0); - mUpperBodyState = UpperCharState_CastingSpell; - } - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - if (inv.getSelectedEnchantItem() != inv.end()) - { - // Enchanted items cast immediately (no animation) - MWBase::Environment::get().getWorld()->castSpell(mPtr); - } - } - - } - else if(mWeaponType == WeapType_PickProbe) - { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr item = *weapon; - // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); - std::string resultMessage, resultSound; - - if(!target.isEmpty()) - { - if(item.getTypeName() == typeid(ESM::Lockpick).name()) - Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getTypeName() == typeid(ESM::Probe).name()) - Security(mPtr).probeTrap(target, item, resultMessage, resultSound); - } - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - - if(!resultMessage.empty()) - MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); - if(!resultSound.empty()) - MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, - 1.0f, 1.0f); - } - else if (ammunition) - { - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown) - mAttackType = "shoot"; - else - { - if(mPtr == getPlayer()) - { - if (isWeapon) - { - if (Settings::Manager::getBool("best attack", "Game")) - { - MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); - } - else - setAttackTypeBasedOnMovement(); - } - else - setAttackTypeRandomly(mAttackType); - } - // else if (mPtr != getPlayer()) use mAttackType set by AiCombat - } - - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" start", mAttackType+" min attack", - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - } - } - - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) - mAttackStrength = complete; - } - else - { - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) - { - float attackStrength = complete; - if (!mPtr.getClass().isNpc()) - { - // most creatures don't actually have an attack wind-up animation, so use a uniform random value - // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) - // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - } - - if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); - if(sound) - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - else - { - playSwishSound(attackStrength); - } - } - mAttackStrength = attackStrength; - - mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" max attack", mAttackType+" min hit", - 1.0f-complete, 0); - - complete = 0.f; - mUpperBodyState = UpperCharState_MaxAttackToMinHit; - } - else if (isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - mUpperBodyState = UpperCharState_WeapEquiped; - mAnimation->disable(mCurrentWeapon); - } - } - - mAnimation->setPitchFactor(0.f); - if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) - { - switch (mUpperBodyState) - { - case UpperCharState_StartToMinAttack: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: - mAnimation->setPitchFactor(1.f); - break; - case UpperCharState_FollowStartToFollowStop: - if (animPlaying) - mAnimation->setPitchFactor(1.f-complete); - break; - default: - break; - } - } - else if (mWeaponType == WeapType_Crossbow) - { - switch (mUpperBodyState) - { - case UpperCharState_EquipingWeap: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_UnEquipingWeap: - mAnimation->setPitchFactor(1.f-complete); - break; - case UpperCharState_WeapEquiped: - case UpperCharState_StartToMinAttack: - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: - case UpperCharState_FollowStartToFollowStop: - mAnimation->setPitchFactor(1.f); - break; - default: - break; - } - } - - if(!animPlaying) - { - if(mUpperBodyState == UpperCharState_EquipingWeap || - mUpperBodyState == UpperCharState_FollowStartToFollowStop || - mUpperBodyState == UpperCharState_CastingSpell) - { - if (ammunition && mWeaponType == WeapType_Crossbow) - mAnimation->attachArrow(); - - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if(mUpperBodyState == UpperCharState_UnEquipingWeap) - mUpperBodyState = UpperCharState_Nothing; - } - else if(complete >= 1.0f) - { - std::string start, stop; - switch(mUpperBodyState) - { - case UpperCharState_StartToMinAttack: - start = mAttackType+" min attack"; - stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; - break; - case UpperCharState_MinAttackToMaxAttack: - //hack to avoid body pos desync when jumping/sneaking in 'max attack' state - if(!mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); - break; - case UpperCharState_MaxAttackToMinHit: - if(mAttackType == "shoot") - { - start = mAttackType+" min hit"; - stop = mAttackType+" release"; - } - else - { - start = mAttackType+" min hit"; - stop = mAttackType+" hit"; - } - mUpperBodyState = UpperCharState_MinHitToHit; - break; - case UpperCharState_MinHitToHit: - if(mAttackType == "shoot") - { - start = mAttackType+" follow start"; - stop = mAttackType+" follow stop"; - } - else - { - float str = mAttackStrength; - start = mAttackType+((str < 0.5f) ? " small follow start" - : (str < 1.0f) ? " medium follow start" - : " large follow start"); - stop = mAttackType+((str < 0.5f) ? " small follow stop" - : (str < 1.0f) ? " medium follow stop" - : " large follow stop"); - } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - break; - default: - break; - } - - if(!start.empty()) - { - mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - weapSpeed, start, stop, 0.0f, 0); - else - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, start, stop, 0.0f, 0); - } - } - - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() - && updateCarriedLeftVisible(mWeaponType)) - - { - mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, - false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); - } - } - - mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); - - return forcestateupdate; -} - -void CharacterController::updateAnimQueue() -{ - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } - - if(!mAnimQueue.empty()) - mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); -} - -void CharacterController::update(float duration) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Class &cls = mPtr.getClass(); - osg::Vec3f movement(0.f, 0.f, 0.f); - float speed = 0.f; - - updateMagicEffects(); - - bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - if(!cls.isActor()) - updateAnimQueue(); - else if(!cls.getCreatureStats(mPtr).isDead()) - { - bool onground = world->isOnGround(mPtr); - bool inwater = world->isSwimming(mPtr); - bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool flying = world->isFlying(mPtr); - // Can't run while flying (see speed formula in Npc/Creature::getSpeed) - bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; - CreatureStats &stats = cls.getCreatureStats(mPtr); - - //Force Jump Logic - - bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); - if(!inwater && !flying) - { - //Force Jump - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) - { - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } - //Force Move Jump, only jump if they're otherwise moving - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) - { - - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } - } - - osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); - vec.normalize(); - - if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = osg::Vec3f(0.f, 0.f, 0.f); - osg::Vec3f rot = cls.getRotationVector(mPtr); - - speed = cls.getSpeed(mPtr); - - vec.x() *= speed; - vec.y() *= speed; - - CharacterState movestate = CharState_None; - CharacterState idlestate = CharState_SpecialIdle; - JumpingState jumpstate = JumpState_None; - - bool forcestateupdate = false; - - mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; - isrunning = isrunning && mHasMovedInXY; - - // advance athletics - if(mHasMovedInXY && mPtr == getPlayer()) - { - if(inwater) - { - mSecondsOfSwimming += duration; - while(mSecondsOfSwimming > 1) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); - mSecondsOfSwimming -= 1; - } - } - else if(isrunning && !sneak) - { - mSecondsOfRunning += duration; - while(mSecondsOfRunning > 1) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); - mSecondsOfRunning -= 1; - } - } - } - - // reduce fatigue - const MWWorld::Store &gmst = world->getStore().get(); - float fatigueLoss = 0; - static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); - static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); - static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); - static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); - static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); - static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); - static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); - static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); - - if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) - { - const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); - - if (sneak) - fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; - else - { - if (inwater) - { - if (!isrunning) - fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; - else - fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; - } - else if (isrunning) - fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; - } - } - fatigueLoss *= duration; - DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); - - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } - - if(sneak || inwater || flying) - vec.z() = 0.0f; - - bool inJump = true; - if(!onground && !flying && !inwater) - { - // In the air (either getting up —ascending part of jump— or falling). - - forcestateupdate = (mJumpState != JumpState_InAir); - jumpstate = JumpState_InAir; - - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); - float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; - factor = std::min(1.f, factor); - vec.x() *= factor; - vec.y() *= factor; - vec.z() = 0.0f; - } - else if(vec.z() > 0.0f && mJumpState == JumpState_None) - { - // Started a jump. - float z = cls.getJump(mPtr); - if (z > 0) - { - if(vec.x() == 0 && vec.y() == 0) - vec = osg::Vec3f(0.0f, 0.0f, z); - else - { - osg::Vec3f lat (vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } - - // advance acrobatics - if (mPtr == getPlayer()) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); - - // decrease fatigue - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); - float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult; - - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } - } - } - else if(mJumpState == JumpState_InAir && !inwater && !flying) - { - forcestateupdate = true; - jumpstate = JumpState_Landing; - vec.z() = 0.0f; - - float height = cls.getCreatureStats(mPtr).land(); - float healthLost = getFallDamage(mPtr, height); - - if (healthLost > 0.0f) - { - const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); - - // inflict fall damages - if (!godmode) - { - DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); - float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); - health.setCurrent(health.getCurrent() - realHealthLost); - cls.getCreatureStats(mPtr).setHealth(health); - cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); - } - - const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); - if (healthLost > (acrobaticsSkill * fatigueTerm)) - { - cls.getCreatureStats(mPtr).setKnockedDown(true); - } - else - { - // report acrobatics progression - if (mPtr == getPlayer()) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); - } - } - } - else - { - jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; - - vec.z() = 0.0f; - - inJump = false; - - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) - { - if(vec.x() > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) - : (sneak ? CharState_SneakRight - : (isrunning ? CharState_RunRight : CharState_WalkRight))); - else if(vec.x() < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) - : (sneak ? CharState_SneakLeft - : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); - } - else if(vec.y() != 0.0f) - { - if(vec.y() > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) - : (sneak ? CharState_SneakForward - : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y() < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) - : (sneak ? CharState_SneakBack - : (isrunning ? CharState_RunBack : CharState_WalkBack))); - } - else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) - { - if(rot.z() > 0.0f) - { - movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - mAnimation->disable(mCurrentJump); - } - else if(rot.z() < 0.0f) - { - movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; - mAnimation->disable(mCurrentJump); - } - } - } - - mTurnAnimationThreshold -= duration; - if (isTurning()) - mTurnAnimationThreshold = 0.05f; - else if (movestate == CharState_None && isTurning() - && mTurnAnimationThreshold > 0) - { - movestate = mMovementState; - } - - if(movestate != CharState_None && !isTurning()) - clearAnimQueue(); - - if(mAnimQueue.empty() || inwater || sneak) - { - idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); - } - else - updateAnimQueue(); - - if (!mSkipAnim) - { - // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. - if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState() || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; - - refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); - updateIdleStormState(inwater); - } - - if (inJump) - mMovementAnimationControlled = false; - - if (isTurning()) - { - if (duration > 0) - mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); - } - else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) - { - float speedmult = speed / mMovementAnimSpeed; - mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); - } - - if (!mSkipAnim) - { - if(!isKnockedDown() && !isKnockedOut()) - { - if (rot != osg::Vec3f()) - world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); - } - else //avoid z-rotating for knockdown - { - if (rot.x() != 0 && rot.y() != 0) - world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); - } - - if (!mMovementAnimationControlled) - world->queueMovement(mPtr, vec); - } - else - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); - - movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; - // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame - // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); - } - else if(cls.getCreatureStats(mPtr).isDead()) - { - // initial start of death animation for actors that started the game as dead - // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag - if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) - { - playDeath(1.f, mDeathState); - } - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); - } - - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); - if(duration > 0.0f) - moved /= duration; - else - moved = osg::Vec3f(0.f, 0.f, 0.f); - - // Ensure we're moving in generally the right direction... - if(speed > 0.f) - { - float l = moved.length(); - - if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || - (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) - moved.x() = movement.x(); - if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || - (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) - moved.y() = movement.y(); - if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || - (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) - moved.z() = movement.z(); - // but keep the original speed - float newLength = moved.length(); - if (newLength > 0) - moved *= (l / newLength); - } - - if (mSkipAnim) - mAnimation->updateEffects(duration); - - if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) - moved.z() = 1.0; - - // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) - world->queueMovement(mPtr, moved); - - mSkipAnim = false; - - mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); -} - -void CharacterController::persistAnimationState() -{ - ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); - - state.mScriptedAnims.clear(); - for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) - { - if (!iter->mPersist) - continue; - - ESM::AnimationState::ScriptedAnimation anim; - anim.mGroup = iter->mGroup; - - if (iter == mAnimQueue.begin()) - { - anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); - float complete; - mAnimation->getInfo(anim.mGroup, &complete, NULL); - anim.mTime = complete; - } - else - { - anim.mLoopCount = iter->mLoopCount; - anim.mTime = 0.f; - } - - state.mScriptedAnims.push_back(anim); - } -} - -void CharacterController::unpersistAnimationState() -{ - const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); - - if (!state.mScriptedAnims.empty()) - { - clearAnimQueue(); - for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) - { - AnimationQueueEntry entry; - entry.mGroup = iter->mGroup; - entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; - - mAnimQueue.push_back(entry); - } - - const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); - float complete = anim.mTime; - if (anim.mAbsolute) - { - float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); - float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); - float time = std::max(start, std::min(stop, anim.mTime)); - complete = (time - start) / (stop - start); - } - - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - mIdleState = CharState_SpecialIdle; - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(anim.mGroup, - Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", complete, anim.mLoopCount, loopfallback); - } -} - -bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) -{ - if(!mAnimation || !mAnimation->hasAnimation(groupname)) - return false; - - // If this animation is a looped animation (has a "loop start" key) that is already playing - // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count - // and remove any other animations that were queued. - // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && - mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && - mAnimation->isPlaying(groupname)) - { - float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); - - if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key - endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); - - if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) - { - mAnimQueue.resize(1); - return true; - } - } - - count = std::max(count, 1); - - AnimationQueueEntry entry; - entry.mGroup = groupname; - entry.mLoopCount = count-1; - entry.mPersist = persist; - - if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) - { - clearAnimQueue(); - mAnimQueue.push_back(entry); - - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - - mIdleState = CharState_SpecialIdle; - bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, - ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); - } - else if(mode == 0) - { - mAnimQueue.resize(1); - mAnimQueue.push_back(entry); - } - return true; -} - -void CharacterController::skipAnim() -{ - mSkipAnim = true; -} - -bool CharacterController::isAnimPlaying(const std::string &groupName) -{ - if(mAnimation == NULL) - return false; - return mAnimation->isPlaying(groupName); -} - - -void CharacterController::clearAnimQueue() -{ - if(!mAnimQueue.empty()) - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.clear(); -} - -void CharacterController::forceStateUpdate() -{ - if(!mAnimation) - return; - clearAnimQueue(); - - refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); - if(mDeathState != CharState_None) - { - playRandomDeath(); - } - - mAnimation->runAnimation(0.f); -} - -CharacterController::KillResult CharacterController::kill() -{ - if (mDeathState == CharState_None) - { - playRandomDeath(); - - mAnimation->disable(mCurrentIdle); - - mIdleState = CharState_None; - mCurrentIdle.clear(); - return Result_DeathAnimStarted; - } - - MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); - if (isAnimPlaying(mCurrentDeath)) - return Result_DeathAnimPlaying; - if (!cStats.isDeathAnimationFinished()) - { - cStats.setDeathAnimationFinished(true); - return Result_DeathAnimJustFinished; - } - return Result_DeathAnimFinished; -} - -void CharacterController::resurrect() -{ - if(mDeathState == CharState_None) - return; - - if(mAnimation) - mAnimation->disable(mCurrentDeath); - mCurrentDeath.clear(); - mDeathState = CharState_None; -} - -void CharacterController::updateContinuousVfx() -{ - // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, - // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. - - // Stop any effects that are no longer active - std::vector effects; - mAnimation->getLoopingEffects(effects); - - for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) - { - if (mPtr.getClass().getCreatureStats(mPtr).isDead() - || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) - mAnimation->removeEffect(*it); - } -} - -void CharacterController::updateMagicEffects() -{ - if (!mPtr.getClass().isActor()) - return; - float alpha = 1.f; - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). - { - if (mPtr == getPlayer()) - alpha = 0.4f; - else - alpha = 0.f; - } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - if (chameleon) - { - alpha *= std::max(0.2f, (100.f - chameleon)/100.f); - } - mAnimation->setAlpha(alpha); - - bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; - mAnimation->setVampire(vampire); - - float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); - mAnimation->setLightEffect(light); -} - -void CharacterController::setAttackTypeBasedOnMovement() -{ - float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; - - if (move[1] && !move[0]) // forward-backward - mAttackType = "thrust"; - else if (move[0] && !move[1]) //sideway - mAttackType = "slash"; - else - mAttackType = "chop"; -} - -bool CharacterController::isAttackPrepairing() const -{ - return mUpperBodyState == UpperCharState_StartToMinAttack || - mUpperBodyState == UpperCharState_MinAttackToMaxAttack; -} - -bool CharacterController::isReadyToBlock() const -{ - return updateCarriedLeftVisible(mWeaponType); -} - -bool CharacterController::isKnockedDown() const -{ - return mHitState == CharState_KnockDown || - mHitState == CharState_SwimKnockDown; -} - -bool CharacterController::isKnockedOut() const -{ - return mHitState == CharState_KnockOut || - mHitState == CharState_SwimKnockOut; -} - -bool CharacterController::isTurning() const -{ - return mMovementState == CharState_TurnLeft || - mMovementState == CharState_TurnRight || - mMovementState == CharState_SwimTurnLeft || - mMovementState == CharState_SwimTurnRight; -} - -bool CharacterController::isRecovery() const -{ - return mHitState == CharState_Hit || - mHitState == CharState_SwimHit; -} - -bool CharacterController::isAttackingOrSpell() const -{ - return mUpperBodyState != UpperCharState_Nothing && - mUpperBodyState != UpperCharState_WeapEquiped; -} - -bool CharacterController::isSneaking() const -{ - return mIdleState == CharState_IdleSneak || - mMovementState == CharState_SneakForward || - mMovementState == CharState_SneakBack || - mMovementState == CharState_SneakLeft || - mMovementState == CharState_SneakRight; -} - -bool CharacterController::isRunning() const -{ - return mMovementState == CharState_RunForward || - mMovementState == CharState_RunBack || - mMovementState == CharState_RunLeft || - mMovementState == CharState_RunRight || - mMovementState == CharState_SwimRunForward || - mMovementState == CharState_SwimRunBack || - mMovementState == CharState_SwimRunLeft || - mMovementState == CharState_SwimRunRight; -} - -void CharacterController::setAttackingOrSpell(bool attackingOrSpell) -{ - mAttackingOrSpell = attackingOrSpell; -} - -void CharacterController::setAIAttackType(const std::string& attackType) -{ - mAttackType = attackType; -} - -void CharacterController::setAttackTypeRandomly(std::string& attackType) -{ - float random = Misc::Rng::rollProbability(); - if (random >= 2/3.f) - attackType = "thrust"; - else if (random >= 1/3.f) - attackType = "slash"; - else - attackType = "chop"; -} - -bool CharacterController::readyToPrepareAttack() const -{ - return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; -} - -bool CharacterController::readyToStartAttack() const -{ - if (mHitState != CharState_None && mHitState != CharState_Block) - return false; - - if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) - return mUpperBodyState == UpperCharState_WeapEquiped; - else - return mUpperBodyState == UpperCharState_Nothing; -} - -float CharacterController::getAttackStrength() const -{ - return mAttackStrength; -} - -void CharacterController::setActive(bool active) -{ - mAnimation->setActive(active); -} - -void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) -{ - mHeadTrackTarget = target; -} - -void CharacterController::playSwishSound(float attackStrength) -{ - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - - std::string sound = "Weapon Swish"; - if(attackStrength < 0.5f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack - else if(attackStrength < 1.0f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack - else - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack -} - -void CharacterController::updateHeadTracking(float duration) -{ - const osg::Node* head = mAnimation->getNode("Bip01 Head"); - if (!head) - return; - - float zAngleRadians = 0.f; - float xAngleRadians = 0.f; - - if (!mHeadTrackTarget.isEmpty()) - { - osg::NodePathList nodepaths = head->getParentalNodePaths(); - if (nodepaths.empty()) - return; - osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); - osg::Vec3f headPos = mat.getTrans(); - - osg::Vec3f direction; - if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) - { - const osg::Node* node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if (node != NULL) - { - nodepaths = node->getParentalNodePaths(); - if (!nodepaths.empty()) - direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; - } - else - // no head node to look at, fall back to look at center of collision box - direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); - } - direction.normalize(); - - if (!mPtr.getRefData().getBaseNode()) - return; - const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - - zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); - xAngleRadians = -std::asin(direction.z()); - - wrap(zAngleRadians); - wrap(xAngleRadians); - - xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); - xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); - zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); - zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); - } - - float factor = duration*5; - factor = std::min(factor, 1.f); - xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians); - zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians); - - mAnimation->setHeadPitch(xAngleRadians); - mAnimation->setHeadYaw(zAngleRadians); -} - -} diff --git a/apps/openmw/mwmechanics/character.cpp.rej b/apps/openmw/mwmechanics/character.cpp.rej deleted file mode 100644 index f04f72a90..000000000 --- a/apps/openmw/mwmechanics/character.cpp.rej +++ /dev/null @@ -1,46 +0,0 @@ ---- apps/openmw/mwmechanics/character.cpp -+++ apps/openmw/mwmechanics/character.cpp -@@ -372,29 +372,28 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState - } - } - -- if(mJumpState == JumpState_InAir) -+ if (!mCurrentJump.empty()) - { - mAnimation->disable(mCurrentJump); -- mCurrentJump = jumpAnimName; -- if (mAnimation->hasAnimation("jump")) -- mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, -+ mCurrentJump.clear(); -+ } -+ -+ if(mJumpState == JumpState_InAir) -+ { -+ if (mAnimation->hasAnimation(jumpAnimName)) -+ { -+ mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, - 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); -+ mCurrentJump = jumpAnimName; -+ } - } - else if (mJumpState == JumpState_Landing) - { -- if (startAtLoop) -- mAnimation->disable(mCurrentJump); -- -- if (mAnimation->hasAnimation("jump")) -+ if (mAnimation->hasAnimation(jumpAnimName)) -+ { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); -- } -- else // JumpState_None -- { -- if (mCurrentJump.length() > 0) -- { -- mAnimation->disable(mCurrentJump); -- mCurrentJump.clear(); -+ mCurrentJump = jumpAnimName; - } - } - } From 5a07d135ae356837e64b050550ade27990bd9d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 10:34:46 +0100 Subject: [PATCH 16/47] add settings for cubemap size --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9c6eac3c0..3416d1333 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -766,8 +766,8 @@ namespace MWRender int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); + int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); - int cubeWidth = screenshotWidth / 2; SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { From 5baff05bace7598a653b18eb3b8bf2489c35761f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 10:50:28 +0100 Subject: [PATCH 17/47] add cubemap mapping --- apps/openmw/mwrender/renderingmanager.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3416d1333..a19af4822 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -629,7 +629,8 @@ namespace MWRender { MAPPING_SPHERICAL = 0, MAPPING_CYLINDRICAL, - MAPPING_SMALL_PLANET + MAPPING_SMALL_PLANET, + MAPPING_CUBEMAP } SphericalScreenshotMapping; SphericalScreenshot(int size) @@ -647,6 +648,16 @@ namespace MWRender void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) { + if (mapping == MAPPING_CUBEMAP) + { + dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); + + for (int i = 0; i < 6; i++) + osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); + + return; + } + dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); for (int j = 0; j < h; ++j) @@ -768,6 +779,9 @@ namespace MWRender Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); + if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) + cubeWidth = screenshotWidth / 6; // the image will consist of 6 cube sides in a row + SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { From 9ab3a0c44b9068e477db9cb4fdfabcf1323b4b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 11:16:25 +0100 Subject: [PATCH 18/47] set cubemap width differently --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a19af4822..dbf62a784 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -780,7 +780,7 @@ namespace MWRender int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) - cubeWidth = screenshotWidth / 6; // the image will consist of 6 cube sides in a row + screenshotWidth = cubeWidth * 6; // the image will consist of 6 cube sides in a row SphericalScreenshot s(cubeWidth); From 497b33e4037ad9b296fc6bb757b2d9655735b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 14:28:09 +0100 Subject: [PATCH 19/47] small corrections --- apps/openmw/mwrender/renderingmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dbf62a784..6b94c72f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -713,16 +713,16 @@ namespace MWRender osg::Vec4 getColorByDirection(osg::Vec3d d) { - // for details see OpenGL 4.4 specification page 241 + // for details see OpenGL 4.4 specification page 225 double x, y; double ma; int side; double ax, ay, az; - ax = d.x() > 0 ? d.x() : -d.x(); // abs behaves weirdly for some reason - ay = d.y() > 0 ? d.y() : -d.y(); - az = d.z() > 0 ? d.z() : -d.z(); + ax = fabs(d.x()); + ay = fabs(d.y()); + az = fabs(d.z()); if (ax > ay) if (ax > az) From 319ed2f9b86372fa604535cde1f1117e0fdd6793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 15:23:44 +0100 Subject: [PATCH 20/47] disable 360 screenshots in vanity/preview mode --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 7 +++---- apps/openmw/mwrender/renderingmanager.cpp | 7 ++++++- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6d39229f0..f91d1bc2a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual void screenshot360 (osg::Image* image) = 0; + virtual bool screenshot360 (osg::Image* image) = 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/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 9ea1f8e16..bbd6feb66 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1028,10 +1028,9 @@ namespace MWInput void InputManager::screenshot360() { osg::ref_ptr screenshot (new osg::Image); - MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); - - // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason - (*mScreenCaptureOperation) (*(screenshot.get()),0); + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + (*mScreenCaptureOperation) (*(screenshot.get()),0); + // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason } void InputManager::toggleInventory() diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6b94c72f8..aefc55c39 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -771,8 +771,11 @@ namespace MWRender int mSize; }; - void RenderingManager::screenshot360(osg::Image* image) + bool RenderingManager::screenshot360(osg::Image* image) { + if (mCamera->isVanityOrPreviewModeEnabled()) + return false; + int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( @@ -811,6 +814,8 @@ namespace MWRender mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; + + return true; } void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index cb0723397..a4cb9bf8f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); - void screenshot360(osg::Image* image); + bool screenshot360(osg::Image* image); struct RayResult { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f51f9af3d..0d0079677 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - void World::screenshot360 (osg::Image* image) + bool World::screenshot360(osg::Image* image) { - mRendering->screenshot360(image); + return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a68982412..3a54d28ae 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - void screenshot360 (osg::Image* image) override; + bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From 1f49612ca38aa8e7bc546d84e664644f5009645a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 19:18:16 +0100 Subject: [PATCH 21/47] enable water effects for 360 screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 14 ++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwrender/water.cpp | 10 ++++++++++ apps/openmw/mwrender/water.hpp | 4 ++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index aefc55c39..ff54ab1dc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -807,7 +807,7 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); + screenshot(sideImage,cubeWidth,cubeWidth,directions[i]); } s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); @@ -818,7 +818,7 @@ namespace MWRender return true; } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -844,6 +844,10 @@ namespace MWRender rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); + + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); @@ -856,16 +860,10 @@ namespace MWRender // 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() - if (disableWaterEffects) - mWater->setEffectsEnabled(false); - mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - if (disableWaterEffects) - mWater->setEffectsEnabled(true); - callback->waitTillDone(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a4cb9bf8f..9e0394593 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ 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, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); + void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); bool screenshot360(osg::Image* image); struct RayResult diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 70e7b36ad..8b047eb84 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -476,6 +476,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 ed6e40f1a..6985ddca6 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -113,6 +114,9 @@ namespace MWRender void update(float dt); + osg::Camera *getReflectionCamera(); + osg::Camera *getRefractionCamera(); + void processChangedSettings(const Settings::CategorySettingVector& settings); osg::Uniform *getRainIntensityUniform(); From 525f8b4d8e4b0544ff4202519723fab65ad25565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 13:51:42 +0100 Subject: [PATCH 22/47] get rid of special key for 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 43 ++++++++------ apps/openmw/mwinput/inputmanagerimp.hpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 71 ++++++++++++++++++----- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index bbd6feb66..10288bbc0 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -249,9 +249,6 @@ namespace MWInput case A_Screenshot: screenshot(); break; - case A_Screenshot360: - screenshot360(); - break; case A_Inventory: toggleInventory (); break; @@ -1020,17 +1017,31 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); - } + bool regularScreenshot = true; - void InputManager::screenshot360() - { - osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) - (*mScreenCaptureOperation) (*(screenshot.get()),0); - // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason + try + { + // FIXME: the same string "screenshot type" is queried here AND in renderingmanager.cpp + std::string s = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = s.size() == 0; + } + catch (std::runtime_error) + { + } + + if (regularScreenshot) + { + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); + } + else + { + osg::ref_ptr screenshot (new osg::Image); + + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + (*mScreenCaptureOperation) (*(screenshot.get()),0); + } } void InputManager::toggleInventory() @@ -1216,7 +1227,6 @@ namespace MWInput defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; - defaultKeyBindings[A_Screenshot360] = SDL_SCANCODE_F8; defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; defaultKeyBindings[A_ToggleDebug] = SDL_SCANCODE_F10; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; @@ -1352,9 +1362,6 @@ namespace MWInput if (action == A_Screenshot) return "Screenshot"; - if (action == A_Screenshot360) - return "Screenshot 360"; - descriptions[A_Use] = "sUse"; descriptions[A_Activate] = "sActivate"; descriptions[A_MoveBackward] = "sBack"; @@ -1516,7 +1523,6 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); - ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); @@ -1548,7 +1554,6 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); - ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 5d3e88eab..82b62e79b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -229,7 +229,6 @@ namespace MWInput void toggleInventory(); void toggleConsole(); void screenshot(); - void screenshot360(); void toggleJournal(); void activate(); void toggleWalking(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ff54ab1dc..682f35cc6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -41,6 +41,8 @@ #include #include +#include + #include "../mwworld/cellstore.hpp" #include "sky.hpp" @@ -773,19 +775,62 @@ namespace MWRender bool RenderingManager::screenshot360(osg::Image* image) { - if (mCamera->isVanityOrPreviewModeEnabled()) + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; + int cubeSize = screenshotW / 2; + + try + { + std::string settingStr = Settings::Manager::getString("screenshot type","Video"); + 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 = (SphericalScreenshot::SphericalScreenshotMapping) 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())); + } + catch (std::runtime_error) + { + std::cerr << "Wrong parameters for screenshot type." << std::endl; return false; + } - int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); - int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); - SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( - Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); - int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); + if (mCamera->isVanityOrPreviewModeEnabled()) + { + std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl; + return false; + } - if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) - screenshotWidth = cubeWidth * 6; // the image will consist of 6 cube sides in a row + if (screenshotMapping == SphericalScreenshot::MAPPING_CUBEMAP) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - SphericalScreenshot s(cubeWidth); + SphericalScreenshot s(cubeSize); osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), @@ -797,20 +842,20 @@ namespace MWRender }; double fovBackup = mFieldOfView; - mFieldOfView = 90.0; // each side sees 90 degrees + 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 cube side + for (int i = 0; i < 6; i++) // for each cubemap side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeWidth,cubeWidth,directions[i]); + screenshot(sideImage,cubeSize,cubeSize,directions[i]); } - s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); + s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; From e804c4a011f1e6a1a1d14a39ddb4e48f981e3c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 13:54:14 +0100 Subject: [PATCH 23/47] remove no longer used method --- components/settings/settings.cpp | 12 ------------ components/settings/settings.hpp | 1 - 2 files changed, 13 deletions(-) diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 4e250974a..e93642ee2 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -401,18 +401,6 @@ int Manager::getInt (const std::string& setting, const std::string& category) return parseInt( getString(setting, category) ); } -int Manager::tryGetInt (const std::string &setting, const std::string& category, int defaultValue) -{ - try - { - return getInt(setting,category); - } - catch (std::runtime_error) - { - return defaultValue; - } -} - bool Manager::getBool (const std::string& setting, const std::string& category) { return parseBool( getString(setting, category) ); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 9e80c21a6..7adcb9b39 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -39,7 +39,6 @@ namespace Settings ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); - static int tryGetInt (const std::string &setting, const std::string& category, int defaultValue); static float getFloat (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); From d71d984cfae6b476eed0a404c0d8a97c81083af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 14:14:24 +0100 Subject: [PATCH 24/47] more unused stuff cleanup --- apps/openmw/mwinput/inputmanagerimp.hpp | 2 -- apps/openmw/mwrender/water.cpp | 28 ++++--------------------- apps/openmw/mwrender/water.hpp | 1 - apps/openmw/mwstate/statemanagerimp.cpp | 1 + 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 82b62e79b..bc62ef7dc 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -259,8 +259,6 @@ namespace MWInput A_Screenshot, // Take a screenshot - A_Screenshot360, // Take a 360 degree screenshot - A_Inventory, // Toggle inventory screen A_Console, // Toggle console screen diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 8b047eb84..ee5b0d599 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -207,6 +207,7 @@ osg::ref_ptr readPngImage (const std::string& file) return result.getImage(); } + class Refraction : public osg::Camera { public: @@ -220,7 +221,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setupCullMask(true); + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -261,12 +262,6 @@ public: attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } - void setupCullMask(bool enabled) - { - setCullMask(!enabled ? 0 : - Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); - } - void setScene(osg::Node* scene) { if (mScene) @@ -309,9 +304,9 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("ReflectionCamera"); - mReflectActors = Settings::Manager::getBool("reflect actors", "Water"); + bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setupCullMask(true); + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -339,12 +334,6 @@ public: addChild(mClipCullNode); } - void setupCullMask(bool enabled) - { - setCullMask(!enabled ? 0 : - Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(mReflectActors ? Mask_Actor : 0)); - } - void setWaterLevel(float waterLevel) { setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); @@ -719,13 +708,4 @@ void Water::clearRipples() mSimulation->clear(); } -void Water::setEffectsEnabled(bool enabled) -{ - if (mReflection) - mReflection->setupCullMask(enabled); - - if (mRefraction) - mRefraction->setupCullMask(enabled); -} - } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 6985ddca6..e2413cfa0 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -107,7 +107,6 @@ namespace MWRender void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); - void setEffectsEnabled(bool enabled); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c643480a9..14ee5adee 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -655,4 +655,5 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); + } From 4fc532d873f163c9d582c9d5354aa59baea7f2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 14:32:28 +0100 Subject: [PATCH 25/47] reference screenshot settings only from one place --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 9 ++-- apps/openmw/mwrender/renderingmanager.cpp | 59 ++++++++++------------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwrender/water.cpp | 1 - apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 35 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f91d1bc2a..f72f74c53 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +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) = 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/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 10288bbc0..0215e43df 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1019,11 +1019,12 @@ namespace MWInput { bool regularScreenshot = true; + std::string settingStr; + try { - // FIXME: the same string "screenshot type" is queried here AND in renderingmanager.cpp - std::string s = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = s.size() == 0; + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0; } catch (std::runtime_error) { @@ -1039,7 +1040,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) (*mScreenCaptureOperation) (*(screenshot.get()),0); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 682f35cc6..5fbc5d397 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -773,53 +773,44 @@ namespace MWRender int mSize; }; - bool RenderingManager::screenshot360(osg::Image* image) + bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) { int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; int cubeSize = screenshotW / 2; - try - { - std::string settingStr = Settings::Manager::getString("screenshot type","Video"); - 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; + std::vector settingArgs; + boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); - for (int i = 0; i < 4; ++i) - if (settingArgs[0].compare(typeStrings[i]) == 0) - { - screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; - found = true; - break; - } + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; + bool found = false; - if (!found) + for (int i = 0; i < 4; ++i) + if (settingArgs[0].compare(typeStrings[i]) == 0) { - std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; - return false; + screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) 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() > 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() > 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())); - } - catch (std::runtime_error) - { - std::cerr << "Wrong parameters for screenshot type." << std::endl; - return false; - } + if (settingArgs.size() > 3) + cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); if (mCamera->isVanityOrPreviewModeEnabled()) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 9e0394593..01630c408 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); - bool screenshot360(osg::Image* image); + bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult { diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index ee5b0d599..bfd671971 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -357,7 +357,6 @@ private: osg::ref_ptr mReflectionTexture; osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; - bool mReflectActors; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0d0079677..1f8b01eac 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - bool World::screenshot360(osg::Image* image) + bool World::screenshot360(osg::Image* image, std::string settingStr) { - return mRendering->screenshot360(image); + 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 3a54d28ae..e432e77d3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - bool screenshot360 (osg::Image* image) 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 From 511a5686dad417e8c5827a99dc29f85aa4487cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 15:10:54 +0100 Subject: [PATCH 26/47] planet mapping adjustment --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 0215e43df..cfae8e872 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1042,6 +1042,7 @@ namespace MWInput if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) (*mScreenCaptureOperation) (*(screenshot.get()),0); + // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5fbc5d397..b2d97e81d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -654,7 +654,7 @@ namespace MWRender { dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 6; ++i) osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); return; @@ -778,7 +778,9 @@ namespace MWRender int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; - int cubeSize = screenshotW / 2; + int cubeSize = screenshotMapping == SphericalScreenshot::SphericalScreenshotMapping::MAPPING_SMALL_PLANET ? + screenshotW: // planet mapping needs higher resolution + screenshotW / 2; std::vector settingArgs; boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); From 1ab854446c4098bf34cee03d829428dcab34eef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 17:21:01 +0100 Subject: [PATCH 27/47] remove unused include --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cfae8e872..b491fb996 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,7 +1,6 @@ #include "inputmanagerimp.hpp" #include -#include #include #include From 823218bb611adbbe07a1a4727a6f381cc7b896b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 14 Nov 2017 18:23:12 +0100 Subject: [PATCH 28/47] freeze screen during screenshot taking --- apps/openmw/mwrender/renderingmanager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b2d97e81d..a1b508f01 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -890,6 +890,13 @@ namespace MWRender mRootNode->addChild(rttCamera); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); + double clearDepthBackup = mViewer->getCamera()->getClearDepth(); + mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); + mViewer->getCamera()->setClearDepth(0); + // The draw needs to complete before we can copy back our image. osg::ref_ptr callback (new NotifyDrawCompletedCallback); rttCamera->setFinalDrawCallback(callback); @@ -907,6 +914,9 @@ namespace MWRender // 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()); + mViewer->getCamera()->setClearMask(maskBackup); + mViewer->getCamera()->setClearDepth(clearDepthBackup); + rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); } From 1c3d45f641cc6e5b01b29b33ec6caf158cccde4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 15:20:59 +0100 Subject: [PATCH 29/47] dirty 360 screenshot GPU setup --- apps/openmw/mwrender/renderingmanager.cpp | 101 ++++++++++++++++++---- apps/openmw/mwrender/renderingmanager.hpp | 3 + files/shaders/s360_fragment.glsl | 9 ++ files/shaders/s360_vertex.glsl | 9 ++ 4 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 files/shaders/s360_fragment.glsl create mode 100644 files/shaders/s360_vertex.glsl diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a1b508f01..878056955 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -24,6 +26,7 @@ #include #include #include +#include #include @@ -853,12 +856,93 @@ namespace MWRender mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; + +osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + +for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,s.getImage(i)); + +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->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + +quad->setStateSet(stateset); +quad->setUpdateCallback(NULL); + +screenshotCamera->addChild(quad); + +mRootNode->addChild(screenshotCamera); + +renderCameraToImage(screenshotCamera,image,1000,640); + + +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); + + // 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(); + + callback->waitTillDone(); + + // 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::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); + + rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); + rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->attach(osg::Camera::COLOR_BUFFER, image); rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); @@ -897,22 +981,7 @@ namespace MWRender mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); mViewer->getCamera()->setClearDepth(0); - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback); - rttCamera->setFinalDrawCallback(callback); - - // 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(); - - callback->waitTillDone(); - - // 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); mViewer->getCamera()->setClearMask(maskBackup); mViewer->getCamera()->setClearDepth(clearDepthBackup); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 01630c408..e6dfd3999 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -214,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/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl new file mode 100644 index 000000000..4bf91abb3 --- /dev/null +++ b/files/shaders/s360_fragment.glsl @@ -0,0 +1,9 @@ +#version 120 + +varying vec2 uv; +uniform samplerCube cubeMap; + +void main(void) +{ + gl_FragData[0] = textureCube(cubeMap,vec3(uv.x * 2.0 - 1.0,uv.y * 2.0 - 1.0,1)); +} diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl new file mode 100644 index 000000000..84cecfdaf --- /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)) / 2.0; +} From 226fb9c26bdbbcb7a406f3ca874319d0eb4a7119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 16:07:01 +0100 Subject: [PATCH 30/47] render cubemaps in OGL coordinates --- apps/openmw/mwrender/renderingmanager.cpp | 85 ++++++++++++----------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- files/shaders/s360_fragment.glsl | 14 +++- files/shaders/s360_vertex.glsl | 2 +- 4 files changed, 61 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 878056955..bb7fbd5de 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -829,13 +829,20 @@ namespace MWRender SphericalScreenshot s(cubeSize); osg::Vec3 directions[6] = { - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - osg::Vec3(0,0,1), - osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0), - }; + osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + 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 @@ -847,51 +854,55 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cubemap side { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]) * osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeSize,cubeSize,directions[i]); + screenshot(sideImage,cubeSize,cubeSize,transform); + sideImage->flipHorizontal(); } - s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); +// s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; -osg::ref_ptr cubeTexture (new osg::TextureCubeMap); -for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,s.getImage(i)); -osg::ref_ptr screenshotCamera (new osg::Camera); -osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); -std::map defineMap; + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,s.getImage(i)); -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 screenshotCamera (new osg::Camera); + osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); -osg::ref_ptr program (new osg::Program); -program->addShader(fragmentShader); -program->addShader(vertexShader); -stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + std::map defineMap; -stateset->addUniform(new osg::Uniform("cubeMap",0)); -stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - -quad->setStateSet(stateset); -quad->setUpdateCallback(NULL); + 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->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(NULL); -screenshotCamera->addChild(quad); + screenshotCamera->addChild(quad); -mRootNode->addChild(screenshotCamera); + mRootNode->addChild(screenshotCamera); -renderCameraToImage(screenshotCamera,image,1000,640); + renderCameraToImage(screenshotCamera,image,1000,640); -screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); -mRootNode->removeChild(screenshotCamera); + screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); + mRootNode->removeChild(screenshotCamera); return true; @@ -936,20 +947,16 @@ mRootNode->removeChild(screenshotCamera); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); - - rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); - rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->attach(osg::Camera::COLOR_BUFFER, image); rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); 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() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setViewport(0, 0, w, h); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e6dfd3999..8fd96cff9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ 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, osg::Vec3 direction=osg::Vec3(0,0,-1)); + void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index 4bf91abb3..3ff8e93c1 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -3,7 +3,19 @@ varying vec2 uv; uniform samplerCube cubeMap; +#define PI 3.1415926535 + +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)); +} + void main(void) { - gl_FragData[0] = textureCube(cubeMap,vec3(uv.x * 2.0 - 1.0,uv.y * 2.0 - 1.0,1)); + vec3 c; + c.x = uv.x * 2.0 - 1.0; + c.y = uv.y * 2.0 - 1.0; + c.z = 1.0; + + gl_FragData[0] = textureCube(cubeMap,vec3(cylindricalCoords(uv))); } diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index 84cecfdaf..b7fbf28a4 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -5,5 +5,5 @@ varying vec2 uv; void main(void) { gl_Position = gl_Vertex; - uv = (gl_Vertex.xy + vec2(1.0)) / 2.0; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2.0; } From af38d3a47d46a8d78f6f41564714026c7cdca6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 17:01:16 +0100 Subject: [PATCH 31/47] get rid of sphericalscreenshot class --- apps/openmw/mwrender/renderingmanager.cpp | 203 ++++------------------ files/shaders/s360_fragment.glsl | 40 ++++- files/shaders/s360_vertex.glsl | 2 +- 3 files changed, 75 insertions(+), 170 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bb7fbd5de..3b82b549d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -627,161 +627,12 @@ namespace MWRender mutable bool mDone; }; - class SphericalScreenshot - { - public: - typedef enum - { - MAPPING_SPHERICAL = 0, - MAPPING_CYLINDRICAL, - MAPPING_SMALL_PLANET, - MAPPING_CUBEMAP - } SphericalScreenshotMapping; - - SphericalScreenshot(int size) - { - mSize = size; - - for (int i = 0; i < 6; ++i) - mImages.push_back(new osg::Image); - } - - osg::Image *getImage(int index) - { - return mImages[index].get(); - } - - void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) - { - if (mapping == MAPPING_CUBEMAP) - { - dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - - for (int i = 0; i < 6; ++i) - osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); - - return; - } - - dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - - for (int j = 0; j < h; ++j) - for (int i = 0; i < w; ++i) - { - osg::Vec3d coords; - osg::Vec2d normalizedXY = osg::Vec2d(i / ((float) w), j / ((float) h)); - - switch (mapping) - { - case MAPPING_CYLINDRICAL: coords = cylindricalCoords(normalizedXY.x(),normalizedXY.y()); break; - case MAPPING_SPHERICAL: coords = sphericalCoords(normalizedXY.x(),normalizedXY.y()); break; - case MAPPING_SMALL_PLANET: coords = smallPlanetCoords(normalizedXY.x(),normalizedXY.y()); break; - default: break; - } - - dest->setColor(getColorByDirection(coords),i,j); - } - } - - osg::Vec3d cylindricalCoords(double x, double y) - { - osg::Vec3 result = osg::Vec3d(cos(-1 * x * 2 * osg::PI),sin(-1 * x * 2 * osg::PI),y * 2.0 - 1.0); - result.normalize(); - return result; - } - - osg::Vec3d sphericalCoords(double x, double y) - { - x = -1 * x * 2 * osg::PI; - y = (y - 0.5) * osg::PI; - - osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); - result = osg::Vec3(cos(x) * result.y(),sin(x) * result.y(),result.z()); - - return result; - } - - osg::Vec3d smallPlanetCoords(double x, double y) - { - osg::Vec2d fromCenter = osg::Vec2d(x,y) - osg::Vec2d(0.5,0.5); - - double magnitude = fromCenter.length(); - - fromCenter.normalize(); - double dot = fromCenter * osg::Vec2d(0.0,1.0); - - x = x > 0.5 ? 0.5 - (dot + 1) / 4.0 : 0.5 + (dot + 1) / 4.0; - y = pow(std::min(1.0,magnitude / 0.5),0.5); - - return sphericalCoords(x,y); - } - - osg::Vec4 getColorByDirection(osg::Vec3d d) - { - // for details see OpenGL 4.4 specification page 225 - - double x, y; - double ma; - int side; - - double ax, ay, az; - ax = fabs(d.x()); - ay = fabs(d.y()); - az = fabs(d.z()); - - if (ax > ay) - if (ax > az) - { - side = d.x() > 0 ? 1 : 3; - ma = ax; - } - else - { - side = d.z() > 0 ? 5 : 4; - ma = az; - } - else - if (ay > az) - { - side = d.y() > 0 ? 0 : 2; - ma = ay; - } - else - { - side = d.z() > 0 ? 5 : 4; - ma = az; - } - - switch (side) - { - case 0: x = d.x(); y = d.z(); break; - case 1: x = -d.y(); y = d.z(); break; - case 2: x = -d.x(); y = d.z(); break; - case 3: x = d.y(); y = d.z(); break; - case 4: x = d.x(); y = d.y(); break; - case 5: x = d.x(); y = -d.y(); break; - default: break; - } - - x = 0.5 * (x / ma + 1); - y = 0.5 * (y / ma + 1); - - return mImages[side]->getColor( - std::min(std::max(int(x * mSize),0),mSize - 1), - std::min(std::max(int(y * mSize),0),mSize - 1)); - } - - protected: - std::vector> mImages; - int mSize; - }; - bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) { int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); - SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; - int cubeSize = screenshotMapping == SphericalScreenshot::SphericalScreenshotMapping::MAPPING_SMALL_PLANET ? + int screenshotMapping = 0; + int cubeSize = screenshotMapping == 2 ? screenshotW: // planet mapping needs higher resolution screenshotW / 2; @@ -796,7 +647,7 @@ namespace MWRender for (int i = 0; i < 4; ++i) if (settingArgs[0].compare(typeStrings[i]) == 0) { - screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; + screenshotMapping = i; found = true; break; } @@ -823,16 +674,23 @@ namespace MWRender return false; } - if (screenshotMapping == SphericalScreenshot::MAPPING_CUBEMAP) + 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 - SphericalScreenshot s(cubeSize); + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); osg::Vec3 directions[6] = { - osg::Vec3(0,0,1), + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), osg::Vec3(0,0,-1), osg::Vec3(-1,0,0), - 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)}; @@ -854,24 +712,40 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cubemap side { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]) * osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); - osg::Image *sideImage = s.getImage(i); + 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); - sideImage->flipHorizontal(); - } -// s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); + 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->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,s.getImage(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))); @@ -889,6 +763,7 @@ namespace MWRender 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); @@ -898,13 +773,11 @@ namespace MWRender mRootNode->addChild(screenshotCamera); - renderCameraToImage(screenshotCamera,image,1000,640); - + renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); mRootNode->removeChild(screenshotCamera); - return true; } diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index 3ff8e93c1..e2a6e2c99 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -2,20 +2,52 @@ 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; - c.x = uv.x * 2.0 - 1.0; - c.y = uv.y * 2.0 - 1.0; - c.z = 1.0; - gl_FragData[0] = textureCube(cubeMap,vec3(cylindricalCoords(uv))); + switch (mapping) + { + case 0: c = sphericalCoords(uv); break; + case 1: c = cylindricalCoords(uv); break; + case 2: c = planetCoords(uv); break; + default: c = sphericalCoords(uv); break; + } + + gl_FragData[0] = textureCube(cubeMap,c); } diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index b7fbf28a4..ad96620c3 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -5,5 +5,5 @@ varying vec2 uv; void main(void) { gl_Position = gl_Vertex; - uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2.0; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2; } From 1a4f351e3d9fafbb60c0baa1d0485376b55c65aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 12:17:52 +0100 Subject: [PATCH 32/47] move camera freeze code --- apps/openmw/mwrender/renderingmanager.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b82b549d..c571d516f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -806,6 +806,11 @@ namespace MWRender osg::ref_ptr callback (new NotifyDrawCompletedCallback); camera->setFinalDrawCallback(callback); + GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); + double clearDepthBackup = mViewer->getCamera()->getClearDepth(); + mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); // don't render the main camera + mViewer->getCamera()->setClearDepth(0); + // 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() @@ -816,6 +821,9 @@ namespace MWRender callback->waitTillDone(); + mViewer->getCamera()->setClearMask(maskBackup); + mViewer->getCamera()->setClearDepth(clearDepthBackup); + // 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()); } @@ -856,16 +864,8 @@ namespace MWRender rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); - double clearDepthBackup = mViewer->getCamera()->getClearDepth(); - mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); - mViewer->getCamera()->setClearDepth(0); - renderCameraToImage(rttCamera.get(),image,w,h); - mViewer->getCamera()->setClearMask(maskBackup); - mViewer->getCamera()->setClearDepth(clearDepthBackup); - rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); } From 56c74fb96fcf59a43919d8f303ffb222817d4857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 12:46:46 +0100 Subject: [PATCH 33/47] add scene switch node --- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c571d516f..bc16a5ea2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -205,7 +205,9 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(sceneRoot); + mSceneSwitch = new osg::Switch; + mSceneSwitch->addChild(sceneRoot); + mRootNode->addChild(mSceneSwitch); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -814,13 +816,16 @@ namespace MWRender // 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() - + + mSceneSwitch->setAllChildrenOff(); // don't render the scene for main camera + mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - callback->waitTillDone(); + mSceneSwitch->setAllChildrenOn(); + mViewer->getCamera()->setClearMask(maskBackup); mViewer->getCamera()->setClearDepth(clearDepthBackup); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8fd96cff9..4a271228e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -224,6 +224,7 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; + osg::ref_ptr mSceneSwitch; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From bccff768ffa8f890b4a7d335883730f9957187f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 13:41:09 +0100 Subject: [PATCH 34/47] select closest power of 2 resolution for the cubemap --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bc16a5ea2..18d2dea2a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -683,6 +683,9 @@ namespace MWRender else if (screenshotMapping == 2) screenshotH = screenshotW; // use square resolution for planet mapping + if (!rawCubemap) + cubeSize = pow(2,round(log2(cubeSize))); // select closest power of 2 for GPU + std::vector> images; for (int i = 0; i < 6; ++i) From 902862aa8bc79b98e373cfd715026a41e21bcdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 13:50:10 +0100 Subject: [PATCH 35/47] display confirming message for all screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b491fb996..5aa98b6de 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1017,6 +1017,7 @@ namespace MWInput void InputManager::screenshot() { bool regularScreenshot = true; + bool screenshotTaken = false; std::string settingStr; @@ -1033,16 +1034,22 @@ namespace MWInput { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); + screenshotTaken = true; } else { osg::ref_ptr screenshot (new osg::Image); if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) + { (*mScreenCaptureOperation) (*(screenshot.get()),0); // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + screenshotTaken = true; + } } + + if (screenshotTaken) + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); } void InputManager::toggleInventory() From 2b5f14754577c351e44cc8e18875b4abdb04c7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 20:06:34 +0100 Subject: [PATCH 36/47] allow non-power-of-2 sized cubemaps --- apps/openmw/mwrender/renderingmanager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 18d2dea2a..92480f45b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -683,9 +683,6 @@ namespace MWRender else if (screenshotMapping == 2) screenshotH = screenshotW; // use square resolution for planet mapping - if (!rawCubemap) - cubeSize = pow(2,round(log2(cubeSize))); // select closest power of 2 for GPU - std::vector> images; for (int i = 0; i < 6; ++i) @@ -745,6 +742,7 @@ namespace MWRender // run on GPU now: osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From 3ae53105679b9b5dba9cf84b3f37f043fb3c48e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:08:33 +0100 Subject: [PATCH 37/47] use loading screen to freeze the screen --- apps/openmw/mwgui/loadingscreen.cpp | 14 +++++++++++--- apps/openmw/mwgui/loadingscreen.hpp | 4 +++- apps/openmw/mwrender/renderingmanager.cpp | 19 +++++-------------- .../loadinglistener/loadinglistener.hpp | 2 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 4753c39f2..023a693c9 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 @@ -170,7 +170,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); @@ -181,10 +184,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 @@ -307,7 +315,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 8536972a3..bdd210d00 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/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 92480f45b..037cf9403 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -47,6 +47,9 @@ #include #include "../mwworld/cellstore.hpp" +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -809,26 +812,14 @@ namespace MWRender osg::ref_ptr callback (new NotifyDrawCompletedCallback); camera->setFinalDrawCallback(callback); - GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); - double clearDepthBackup = mViewer->getCamera()->getClearDepth(); - mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); // don't render the main camera - mViewer->getCamera()->setClearDepth(0); - - // 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() - - mSceneSwitch->setAllChildrenOff(); // don't render the scene for main camera + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); callback->waitTillDone(); - mSceneSwitch->setAllChildrenOn(); - - mViewer->getCamera()->setClearMask(maskBackup); - mViewer->getCamera()->setClearDepth(clearDepthBackup); + 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()); diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 1d48cce0b..6c7a3b090 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). From fc507c66f7d289760134c204571978cda65ff343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:17:00 +0100 Subject: [PATCH 38/47] remove no longer needed stuff --- apps/openmw/mwrender/renderingmanager.cpp | 4 +--- apps/openmw/mwrender/renderingmanager.hpp | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 037cf9403..fd352f694 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -208,9 +208,7 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mSceneSwitch = new osg::Switch; - mSceneSwitch->addChild(sceneRoot); - mRootNode->addChild(mSceneSwitch); + mRootNode->addChild(mSceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4a271228e..8fd96cff9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -224,7 +224,6 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; - osg::ref_ptr mSceneSwitch; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From ddbf6c162fa47896047c3a1d4c53225e1a44d752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:43:41 +0100 Subject: [PATCH 39/47] use linear filtering for the cubemap --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fd352f694..55bf2f2c4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -744,6 +744,9 @@ namespace MWRender osg::ref_ptr cubeTexture (new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From df61a302592fe40c28eb839a50861f311e8c21db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 23:20:24 +0100 Subject: [PATCH 40/47] preincrement --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 55bf2f2c4..7b734f6ce 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -713,7 +713,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; i++) // for each cubemap side + for (int i = 0; i < 6; ++i) // for each cubemap side { osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); From ea5e078526b8d8d26f50bf5f7a7cf70ac791f656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 19:54:48 +0100 Subject: [PATCH 41/47] Update shaders in CMakeLists --- files/shaders/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5833a592f..7baca78ef 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}") From 390838e084c9fa2ab4a66151af3fb9dc512037b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 21:03:29 +0100 Subject: [PATCH 42/47] Replace switch with ifs in shader --- files/shaders/s360_fragment.glsl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index e2a6e2c99..f52e1478e 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -41,13 +41,12 @@ void main(void) { vec3 c; - switch (mapping) - { - case 0: c = sphericalCoords(uv); break; - case 1: c = cylindricalCoords(uv); break; - case 2: c = planetCoords(uv); break; - default: c = sphericalCoords(uv); break; - } + if (mapping == 0) + c = sphericalCoords(uv); + else if (mapping == 1) + c = cylindricalCoords(uv); + else + c = planetCoords(uv); gl_FragData[0] = textureCube(cubeMap,c); } From 37aa7612831864e3c24db9c4e3689ce0bb93ca27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 21:47:26 +0100 Subject: [PATCH 43/47] Change spherical screenshot filtering to nearest --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b734f6ce..3b9100126 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -745,8 +745,8 @@ namespace MWRender osg::ref_ptr cubeTexture (new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR); + 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); From a142a67972f835d914c88a20528bf636630b2691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 6 Dec 2017 21:16:30 +0100 Subject: [PATCH 44/47] Add default settings for 360 screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- files/settings-default.cfg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5aa98b6de..e01deb669 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1024,7 +1024,7 @@ namespace MWInput try { settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0; + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; } catch (std::runtime_error) { diff --git a/files/settings-default.cfg b/files/settings-default.cfg index aec667a9c..c0f9e85ce 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -359,6 +359,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. From dcfbd554bbd1ac264a31f4c5afdf2261d0e3f579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 11:12:46 +0200 Subject: [PATCH 45/47] Remove try catch block --- apps/openmw/mwinput/inputmanagerimp.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2a101e58f..1e8a12e2c 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1041,14 +1041,8 @@ namespace MWInput std::string settingStr; - try - { - settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; - } - catch (std::runtime_error) - { - } + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { From 5387e3ba8f5475a030cd98948441d39dbfd7f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 21:15:22 +0200 Subject: [PATCH 46/47] Update CHANGELOG --- CHANGELOG.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2a5472e..3a59aca8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +0.45.0 +------ + + Bug #1990: Sunrise/sunset not set correct + Bug #2222: Fatigue's effect on selling price is backwards + Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2835: Player able to slowly move when overencumbered + Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y + Bug #3374: Touch spells not hitting kwama foragers + Bug #3591: Angled hit distance too low + Bug #3629: DB assassin attack never triggers creature spawning + Bug #3876: Landscape texture painting is misaligned + Bug #3897: Have Goodbye give all choices the effects of Goodbye + Bug #3993: Terrain texture blending map is not upscaled + Bug #3997: Almalexia doesn't pace + Bug #4036: Weird behaviour of AI packages if package target has non-unique ID + Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully + Bug #4215: OpenMW shows book text after last
tag + Bug #4221: Characters get stuck in V-shaped terrain + Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4327: Missing animations during spell/weapon stance switching + Bug #4368: Settings window ok button doesn't have key focus by default + Bug #4393: NPCs walk back to where they were after using ResetActors + Bug #4419: MRK NiStringExtraData is handled incorrectly + Bug #4426: RotateWorld behavior is incorrect + Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 + Bug #4432: Guards behaviour is incorrect if they do not have AI packages + Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax + 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 + +0.44.0 +------ + + Bug #1428: Daedra summoning scripts aren't executed when the item is taken through the inventory + Bug #1987: Some glyphs are not supported + Bug #2254: Magic related visual effects are not rendered when loading a saved game + Bug #2485: Journal alphabetical index doesn't match "Morrowind content language" setting + Bug #2703: OnPCHitMe is not handled correctly + Bug #2829: Incorrect order for content list consisting of a game file and an esp without dependencies + Bug #2841: "Total eclipse" happens if weather settings are not defined. + Bug #2897: Editor: Rename "Original creature" field + Bug #3278: Editor: Unchecking "Auto Calc" flag changes certain values + Bug #3343: Editor: ID sorting is case-sensitive in certain tables + Bug #3557: Resource priority confusion when using the local data path as installation root + Bug #3587: Pathgrid and Flying Creatures wrong behaviour – abotWhereAreAllBirdsGoing + Bug #3603: SetPos should not skip weather transitions + Bug #3618: Myar Aranath total conversion can't be started due to capital-case extension of the master file + Bug #3638: Fast forwarding can move NPC inside objects + Bug #3664: Combat music does not start in dialogue + Bug #3696: Newlines are accompanied by empty rectangle glyph in dialogs + Bug #3708: Controllers broken on macOS + Bug #3726: Items with suppressed activation can be picked up via the inventory menu + Bug #3783: [Mod] Abot's Silt Striders 1.16 - silt strider "falls" to ground and glides on floor during travel + Bug #3863: Can be forced to not resist arrest if you cast Calm Humanoid on aggroed death warrant guards + Bug #3884: Incorrect enemy behavior when exhausted + Bug #3926: Installation Wizard places Morrowind.esm after Tribunal/Bloodmoon if it has a later file creation date + Bug #4061: Scripts error on special token included in name + Bug #4111: Crash when mouse over soulgem with a now-missing soul + Bug #4122: Swim animation should not be interrupted during underwater attack + Bug #4134: Battle music behaves different than vanilla + Bug #4135: Reflecting an absorb spell different from vanilla + Bug #4136: Enchanted weapons without "ignore normal weapons" flag don't bypass creature "ignore normal weapons" effect + Bug #4143: Antialiasing produces graphical artifacts when used with shader lighting + Bug #4159: NPCs' base skeleton files should not be optimized + Bug #4177: Jumping/landing animation interference/flickering + Bug #4179: NPCs do not face target + Bug #4180: Weapon switch sound playing even though no weapon is switched + Bug #4184: Guards can initiate dialogue even though you are far above them + Bug #4190: Enchanted clothes changes visibility with Chameleon on equip/unequip + Bug #4191: "screenshot saved" message also appears in the screenshot image + Bug #4192: Archers in OpenMW have shorter attack range than archers in Morrowind + Bug #4210: Some dialogue topics are not highlighted on first encounter + Bug #4211: FPS drops after minimizing the game during rainy weather + Bug #4216: Thrown weapon projectile doesn't rotate + Bug #4223: Displayed spell casting chance must be 0 if player doesn't have enough magicka to cast it + Bug #4225: Double "Activate" key presses with Mouse and Gamepad. + Bug #4226: The current player's class should be default value in the class select menu + Bug #4229: Tribunal/Bloodmoon summoned creatures fight other summons + Bug #4233: W and A keys override S and D Keys + Bug #4235: Wireframe mode affects local map + Bug #4239: Quick load from container screen causes crash + Bug #4242: Crime greetings display in Journal + Bug #4245: Merchant NPCs sell ingredients growing on potted plants they own + Bug #4246: Take armor condition into account when calcuting armor rating + Bug #4250: Jumping is not as fluid as it was pre-0.43.0 + Bug #4252: "Error in frame: FFmpeg exception: Failed to allocate input stream" message spam if OpenMW encounter non-music file in the Music folder + Bug #4261: Magic effects from eaten ingredients always have 1 sec duration + Bug #4263: Arrow position is incorrect in 3rd person view during attack for beast races + Bug #4264: Player in god mode can be affected by some negative spell effects + Bug #4269: Crash when hovering the faction section and the 'sAnd' GMST is missing (as in MW 1.0) + Bug #4272: Root note transformations are discarded again + Bug #4279: Sometimes cells are not marked as explored on the map + Bug #4298: Problem with MessageBox and chargen menu interaction order + Bug #4301: Optimizer breaks LOD nodes + Bug #4308: PlaceAtMe doesn't inherit scale of calling object + Bug #4309: Only harmful effects with resistance effect set are resistable + Bug #4313: Non-humanoid creatures are capable of opening doors + Bug #4314: Rainy weather slows down the game when changing from indoors/outdoors + Bug #4319: Collisions for certain meshes are incorrectly ignored + Bug #4320: Using mouse 1 to move forward causes selection dialogues to jump selections forward. + Bug #4322: NPC disposition: negative faction reaction modifier doesn't take PC rank into account + Bug #4328: Ownership by dead actors is not cleared from picked items + Bug #4334: Torch and shield usage inconsistent with original game + Bug #4336: Wizard: Incorrect Morrowind assets path autodetection + Bug #4343: Error message for coc and starting cell shouldn't imply that it only works for interior cells + Bug #4346: Count formatting does not work well with very high numbers + Bug #4351: Using AddSoulgem fills all soul gems of the specified type + Bug #4391: No visual indication is provided when an unavailable spell fails to be chosen via a quick key + Bug #4392: Inventory filter breaks after loading a game + Bug #4405: No default terrain in empty cells when distant terrain is enabled + Bug #4410: [Mod] Arktwend: OpenMW does not use default marker definitions + Bug #4412: openmw-iniimporter ignores data paths from config + Bug #4413: Moving with 0 strength uses all of your fatigue + Bug #4420: Camera flickering when I open up and close menus while sneaking + Bug #4435: Item health is considered a signed integer + Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game + Feature #1786: Round up encumbrance value in the encumbrance bar + Feature #2694: Editor: rename "model" column to make its purpose clear + Feature #3870: Editor: Terrain Texture Brush Button + Feature #3872: Editor: Edit functions in terrain texture editing mode + Feature #4054: Launcher: Create menu for settings.cfg options + Feature #4064: Option for fast travel services to charge for the first companion + Feature #4142: Implement fWereWolfHealth GMST + Feature #4174: Multiple quicksaves + Feature #4407: Support NiLookAtController + Feature #4423: Rebalance soul gem values + Task #4015: Use AppVeyor build artifact features to make continuous builds available + Editor: New (and more complete) icon set + 0.43.0 ------ From 7178ee3a6e94b1001042a8bbbc292e023709f3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 21:34:43 +0200 Subject: [PATCH 47/47] Add FIXME comment --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 1e8a12e2c..c440de455 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1057,7 +1057,7 @@ namespace MWInput if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) { (*mScreenCaptureOperation) (*(screenshot.get()),0); - // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason screenshotTaken = true; } }