From c773ed9f9ad71c144cafbee40677a04bc1e3305c Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 4 Jul 2015 18:00:16 +0300 Subject: [PATCH 0001/2007] move checkWayIsClear to pathfinding; move shortcut logic to separate func (AiPackage::shortcutPath); rework AiPackage::pathTo --- apps/openmw/mwmechanics/aiactivate.cpp | 11 +- apps/openmw/mwmechanics/aicombat.cpp | 42 +------ apps/openmw/mwmechanics/aifollow.cpp | 14 +-- apps/openmw/mwmechanics/aipackage.cpp | 153 ++++++++++++++++++++++-- apps/openmw/mwmechanics/aipackage.hpp | 15 ++- apps/openmw/mwmechanics/aipursue.cpp | 8 +- apps/openmw/mwmechanics/aiwander.cpp | 4 +- apps/openmw/mwmechanics/pathfinding.cpp | 78 +++++++++--- apps/openmw/mwmechanics/pathfinding.hpp | 26 +++- 9 files changed, 248 insertions(+), 103 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 9e25084d3..aa937022d 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -34,20 +34,17 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state ) return true; //Target doesn't exist - //Set the target desition from the actor + //Set the target destination for the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + if (pathTo(actor, dest, duration, 200)) //Go to the destination + { + // activate when reached MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - MWBase::Environment::get().getWorld()->activate(target, actor); return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } return false; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f43c6280..fb47c1109 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -37,46 +37,6 @@ namespace Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, float duration, int weapType, float strength); - - float getZAngleToDir(const Ogre::Vector3& dir) - { - return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); - } - - float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) - { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return -Ogre::Math::ASin(dir.z / len).valueDegrees(); - } - - - const float PATHFIND_Z_REACH = 50.0f; - // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid - const float PATHFIND_CAUTION_DIST = 500.0f; - // distance after which actor (failed previously to shortcut) will try again - const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; - - // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; - // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) - { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) - { - Ogre::Vector3 dir = to - from; - dir.z = 0; - dir.normalise(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; - - // cast up-down ray and find height in world space of hit - float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); - - if(std::abs(from.z - h) <= PATHFIND_Z_REACH) - return true; - } - - return false; - } } namespace MWMechanics @@ -702,7 +662,7 @@ namespace MWMechanics if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) { ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); + mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell()); } } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ddfc14581..55414bfff 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -130,19 +130,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close - { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - - // turn towards target anyway - float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; - float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; - zTurn(actor, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5)); - } - else - { - pathTo(actor, dest, duration); //Go to the destination - } + pathTo(actor, dest, duration, followDistance); //Go to the destination //Check if you're far away if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 216bf7b09..425593624 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,12 +16,14 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mTimer(0.26f), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild - +MWMechanics::AiPackage::AiPackage() : + mTimer(0.26f), mStuckTimer(0), //mTimer starts at .26 to force initial pathbuild + mShortcutProhibited(false), mShortcutFailPos() +{ } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { //Update various Timers mTimer += duration; //Update timer @@ -63,13 +65,51 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //*********************** /// Checks if you can't get to the end position at all, adds end position to end of path - /// Rebuilds path every quarter of a second, in case the target has moved + /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved //*********************** - if(mTimer > 0.25) + + bool isStuck = distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 + && distance(dest, start) > 20; + + float distToNextWaypoint = distance(start, dest); + + bool isDestReached = (distToNextWaypoint <= destTolerance); + + if (!isDestReached && mTimer > AI_REACTION_TIME) { - if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved - mPrevDest = dest; + bool needPathRecalc = doesPathNeedRecalc(dest, cell); + + bool isWayClear = true; + + if (!needPathRecalc) // TODO: add check if actor is actually shortcutting + { + isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow + } + + if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut + { + bool destInLOS = false; + + if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS)) + { + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + // give priority to go directly on target if there is minimal opportunity + if (destInLOS && mPathFinder.getPath().size() > 1) + { + // get point just before dest + std::list::const_iterator pPointBeforeDest = mPathFinder.getPath().end(); + --pPointBeforeDest; + --pPointBeforeDest; + + // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target + if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(dest); + } + } + } } if(!mPathFinder.getPath().empty()) //Path has points in it @@ -86,13 +126,22 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //************************ /// Checks if you aren't moving; attempts to unstick you //************************ - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + { + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[2] = 0; + + // turn to destination point + zTurn(actor, Ogre::Degree(getZAngleToPoint(start, dest))); + smoothTurn(actor, Ogre::Degree(getXAngleToPoint(start, dest)), 0); return true; + } else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something { /// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason //if(mObstacleCheck.check(actor, duration)) { - if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + if (isStuck) { //Actually stuck, and far enough away from destination to care // first check if we're walking into a door MWWorld::Ptr door = getNearbyDoor(actor); if(door != MWWorld::Ptr()) // NOTE: checks interior cells only @@ -119,12 +168,94 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time } + // turn to next path point by X,Z axes zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + smoothTurn(actor, Ogre::Degree(mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2])), 0); return false; } +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +{ + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // don't use pathgrid when actor can move in 3 dimensions + bool isPathClear = actorCanMoveByZ; + + if (!isPathClear + && (!mShortcutProhibited || (PathFinder::MakeOgreVector3(mShortcutFailPos) - PathFinder::MakeOgreVector3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + { + // take the direct path only if there aren't any obstacles + isPathClear = !MWBase::Environment::get().getWorld()->castRay( + static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), + static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); + + if (destInLOS != NULL) *destInLOS = isPathClear; + + if (!isPathClear) + return false; + + // check if an actor can move along the shortcut path + isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); + } + + if (isPathClear) // can shortcut the path + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(endPoint); + return true; + } + + return false; +} + +bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) +{ + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if (actorCanMoveByZ) + return true; + + float actorSpeed = actor.getClass().getSpeed(actor); + float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability + Ogre::Real distToTarget = Ogre::Vector3(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + + float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + + bool isClear = checkWayIsClear(PathFinder::MakeOgreVector3(startPoint), PathFinder::MakeOgreVector3(endPoint), offsetXY); + + // update shortcut prohibit state + if (isClear) + { + if (mShortcutProhibited) + { + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); + } + } + if (!isClear) + { + if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) + { + mShortcutProhibited = true; + mShortcutFailPos = startPoint; + } + } + + return isClear; +} + bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { - return distance(mPrevDest, dest) > 10; + bool needRecalc = distance(mPrevDest, dest) > 10; + if (needRecalc) + mPrevDest = dest; + + return needRecalc; } \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 78a2bfd9f..a22ac9357 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -24,6 +24,7 @@ namespace ESM namespace MWMechanics { + const float AI_REACTION_TIME = 0.25f; /// \brief Base class for AI packages class AiPackage @@ -70,7 +71,16 @@ namespace MWMechanics protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + + /// Check if there aren't any obstacles along the path to make shortcut possible + /// If a shortcut is possible then path will be cleared and filled with the destination point. + /// \param destInLOS If not NULL function will return ray cast check result + /// \return If can shortcut the path + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + + /// Check if the way to the destination is clear, taking into account actor speed + bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); @@ -83,6 +93,9 @@ namespace MWMechanics ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; + + bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 8c31a10db..af007231b 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -55,14 +55,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + if (pathTo(actor, dest, duration, 100)) { + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1f4133c0a..4566740e3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -413,7 +413,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { @@ -520,7 +520,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index fea993e23..c32899dde 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -113,6 +113,49 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } + float getZAngleToDir(const Ogre::Vector3& dir) + { + return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); + } + + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + return -Ogre::Math::ASin(dir.z / len).valueDegrees(); + } + + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getZAngleToDir(dir); + } + + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getXAngleToDir(dir); + } + + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) + { + Ogre::Vector3 dir = to - from; + dir.z = 0; + dir.normalise(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + + if(std::abs(from.z - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } + PathFinder::PathFinder() : mPathgrid(NULL), mCell(NULL) @@ -165,23 +208,10 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { mPath.clear(); - if(allowShortcuts) - { - // if there's a ray cast hit, can't take a direct path - if (!MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) - { - mPath.push_back(endPoint); - return; - } - } - if(mCell != cell || !mPathgrid) { mCell = cell; @@ -270,6 +300,19 @@ namespace MWMechanics return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } + float PathFinder::getXAngleToNext(float x, float y, float z) const + { + // This should never happen (programmers should have an if statement checking + // isPathConstructed that prevents this call if otherwise). + if(mPath.empty()) + return 0.; + + const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); + Ogre::Vector3 dir = MakeOgreVector3(nextPoint) - Ogre::Vector3(x,y,z); + + return -Ogre::Math::ASin(dir.z / dir.length()).valueDegrees(); + } + bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { if(mPath.empty()) @@ -291,19 +334,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 644d79236..c978c9afa 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -15,8 +15,24 @@ namespace MWWorld namespace MWMechanics { - float distance(ESM::Pathgrid::Point point, float x, float y, float); + float distance(ESM::Pathgrid::Point point, float x, float y, float z); float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); + float getZAngleToDir(const Ogre::Vector3& dir); + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f); + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + + const float PATHFIND_Z_REACH = 50.0f; + //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem + // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid + const float PATHFIND_CAUTION_DIST = 500.0f; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY); + class PathFinder { public: @@ -39,13 +55,15 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); bool checkPathCompleted(float x, float y, float tolerance=32.f); ///< \Returns true if we are within \a tolerance units of the last path point. float getZAngleToNext(float x, float y) const; + float getXAngleToNext(float x, float y, float z) const; + bool isPathConstructed() const { return !mPath.empty(); @@ -69,9 +87,9 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); - void addPointToPath(ESM::Pathgrid::Point &point) + void addPointToPath(const ESM::Pathgrid::Point &point) { mPath.push_back(point); } From b960b0af9368643d30edc1394179efbbd65c4fc8 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 1 Jan 2016 16:41:45 +0300 Subject: [PATCH 0002/2007] rewrite pathTo for clearer logic; reapply Scrawl's aifollow threshold --- apps/openmw/mwmechanics/aifollow.cpp | 24 ++++---- apps/openmw/mwmechanics/aipackage.cpp | 88 ++++++++++----------------- apps/openmw/mwmechanics/aipackage.hpp | 3 +- 3 files changed, 45 insertions(+), 70 deletions(-) diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f0e67af08..342ee9714 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -127,21 +127,21 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); - //const float threshold = 10; - - //if (storage.mMoving) //Stop when you get close - // storage.mMoving = (dist > followDistance); - //else - // storage.mMoving = (dist > followDistance + threshold); + const float threshold = 10; // to avoid constant switching between moving/stopping + if (!storage.mMoving) followDistance += threshold; storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination - //Check if you're far away - if(dist > 450) - actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold - actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + if (storage.mMoving) + { + //Check if you're far away + float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + + if (dist > 450) + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + } return false; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 571bf0ac8..2999e8a19 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -22,6 +22,7 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mIsShortcutting(false), mShortcutProhibited(false), mShortcutFailPos() { } @@ -41,10 +42,8 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const return false; } - bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { - //Update various Timers mTimer += duration; //Update timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor @@ -59,39 +58,21 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr return false; } - //*********************** - /// Checks if you can't get to the end position at all, adds end position to end of path - /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved - //*********************** - + // handle path building and shortcutting ESM::Pathgrid::Point start = pos.pos; - float distToNextWaypoint = distance(start, dest); - bool isDestReached = (distToNextWaypoint <= destTolerance); + float distToTarget = distance(start, dest); + bool isDestReached = (distToTarget <= destTolerance); if (!isDestReached && mTimer > AI_REACTION_TIME) { - osg::Vec3f& lastActorPos = mLastActorPos; - bool isStuck = distance(start, lastActorPos.x(), lastActorPos.y(), lastActorPos.z()) < actor.getClass().getSpeed(actor)*mTimer - && distance(dest, start) > 20; - - lastActorPos = pos.asVec3(); + bool wasShortcutting = mIsShortcutting; + bool destInLOS = false; + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first - const ESM::Cell *cell = actor.getCell()->getCell(); - bool needPathRecalc = doesPathNeedRecalc(dest, cell); //Only rebuild path if dest point has changed - - bool isWayClear = true; - - if (!needPathRecalc) // TODO: add check if actor is actually shortcutting + if (!mIsShortcutting) { - isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow - } - - if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut - { - bool destInLOS = false; - - if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS)) + if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell()->getCell())) // only rebuild path if the target has moved { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -111,28 +92,21 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } } } - } - if(!mPathFinder.getPath().empty()) //Path has points in it - { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + if(!mPathFinder.getPath().empty()) //Path has points in it + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path - if(distance(dest, lastPos) > 100) //End of the path is far from the destination - mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + if(distance(dest, lastPos) > 100) //End of the path is far from the destination + mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + } } mTimer = 0; } - //************************ - /// Checks if you aren't moving; attempts to unstick you - //************************ if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? { - actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - actor.getClass().getMovementSettings(actor).mPosition[2] = 0; - // turn to destination point zTurn(actor, getZAngleToPoint(start, dest)); smoothTurn(actor, getXAngleToPoint(start, dest), 0); @@ -140,6 +114,8 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } else { + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target + // handle obstacles on the way evadeObstacles(actor, duration, pos); } @@ -155,24 +131,22 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (mObstacleCheck.check(actor, duration)) + + // check if stuck due to obstacles + if (!mObstacleCheck.check(actor, duration)) return; + + // first check if obstacle is a door + MWWorld::Ptr door = getNearbyDoor(actor); // NOTE: checks interior cells only + if (door != MWWorld::Ptr()) { - // first check if we're walking into a door - MWWorld::Ptr door = getNearbyDoor(actor); - if (door != MWWorld::Ptr()) // NOTE: checks interior cells only - { - if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() - && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { - MWBase::Environment::get().getWorld()->activateDoor(door, 1); - } - } - else // probably walking into another NPC - { - mObstacleCheck.takeEvasiveAction(movement); + if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() + && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { + MWBase::Environment::get().getWorld()->activateDoor(door, 1); } } - else { //Not stuck, so reset things - movement.mPosition[1] = 1; //Just run forward + else // any other obstacle (NPC, crate, etc.) + { + mObstacleCheck.takeEvasiveAction(movement); } } @@ -191,7 +165,7 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint if (!isPathClear && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { - // take the direct path only if there aren't any obstacles + // check if target is clearly visible isPathClear = !MWBase::Environment::get().getWorld()->castRay( static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 67c2b0c1b..79537e616 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -82,7 +82,7 @@ namespace MWMechanics bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: - /// Causes the actor to attempt to walk to the specified location + /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); @@ -108,6 +108,7 @@ namespace MWMechanics ESM::Pathgrid::Point mPrevDest; osg::Vec3f mLastActorPos; + bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail From bcb1f4ed056b6207c2e038247b3ef002c784b52d Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 3 Jan 2016 15:33:52 +0300 Subject: [PATCH 0003/2007] refactor AiCombat: remove all pathfinding code, adopt new version of AiPackage::pathTo; fix couple of warnings; --- apps/openmw/mwmechanics/aiactivate.cpp | 1 - apps/openmw/mwmechanics/aicombat.cpp | 255 +++++-------------------- apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwmechanics/aipackage.cpp | 10 +- apps/openmw/mwmechanics/aipackage.hpp | 3 +- apps/openmw/mwmechanics/aipursue.cpp | 1 - 6 files changed, 51 insertions(+), 228 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 348ddd692..b3c011d50 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -22,7 +22,6 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6251cff8d..abf4109ba 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -41,7 +41,7 @@ namespace MWMechanics float mTimerCombatMove; bool mReadyToAttack; bool mAttack; - bool mFollowTarget; + float mAttackRange; bool mCombatMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; @@ -50,8 +50,8 @@ namespace MWMechanics float mStrength; bool mForceNoShortcut; ESM::Position mShortcutFailPos; - osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; + bool mAdjustAiming; AiCombatStorage(): mAttackCooldown(0), @@ -59,7 +59,7 @@ namespace MWMechanics mTimerCombatMove(0), mReadyToAttack(false), mAttack(false), - mFollowTarget(false), + mAttackRange(200), // default attack range (same as in Creature::Hit) mCombatMove(false), mLastTargetPos(0,0,0), mCell(NULL), @@ -67,8 +67,9 @@ namespace MWMechanics mActionCooldown(0), mStrength(), mForceNoShortcut(false), - mLastActorPos(0,0,0), - mMovement(){} + mMovement(), + mAdjustAiming(false) + {} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); void updateCombatMove(float duration); @@ -139,6 +140,7 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ + bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage @@ -158,33 +160,33 @@ namespace MWMechanics return true; //Update every frame + storage.mReadyToAttack = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + storage.updateCombatMove(duration); - updateActorsMovement(actor, duration, storage.mMovement); + if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(characterController); storage.mActionCooldown -= duration; - + float& timerReact = storage.mTimerReact; - if(timerReact < AI_REACTION_TIME) + if (timerReact < AI_REACTION_TIME) { timerReact += duration; - return false; } else { timerReact = 0; - return reactionTimeActions(actor, characterController, storage, target); + attack(actor, target, storage, characterController); } + + return false; } - bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target) + void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { - MWMechanics::Movement& movement = storage.mMovement; - if (isTargetMagicallyHidden(target)) { storage.stopAttack(); - return false; // TODO: run away instead of doing nothing + return; // TODO: run away instead of doing nothing } const MWWorld::CellStore*& currentCell = storage.mCell; @@ -199,10 +201,9 @@ namespace MWMechanics float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) - return false; + return; - float rangeAttack = 0; - float rangeFollow = 0; + float &rangeAttack = storage.mAttackRange; boost::shared_ptr& currentAction = storage.mCurrentAction; if (characterController.readyToPrepareAttack()) { @@ -210,6 +211,7 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } + float rangeFollow; if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); @@ -243,7 +245,7 @@ namespace MWMechanics else //is creature { weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; - weapRange = 150.0f; //TODO: use true attack range (the same problem in Creature::hit) + weapRange = 200; //TODO: use true attack range (the same problem in Creature::hit) } bool distantCombat = false; @@ -253,55 +255,19 @@ namespace MWMechanics if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { rangeAttack = 1000; - rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { rangeAttack = weapRange; - rangeFollow = 300; } } else { distantCombat = (rangeAttack > 500); - weapRange = 150.f; + weapRange = 200; } - - bool& readyToAttack = storage.mReadyToAttack; - // start new attack - storage.startAttackIfReady(actor, characterController, weapon, distantCombat); - - /* - * Some notes on meanings of variables: - * - * rangeAttack: - * - * - Distance where attack using the actor's weapon is possible: - * longer for ranged weapons (obviously?) vs. melee weapons - * - Determined by weapon's reach parameter; hardcoded value - * for ranged weapon and for creatures - * - Once within this distance mFollowTarget is triggered - * - * rangeFollow: - * - * - Applies to melee weapons or hand to hand only (or creatures without - * weapons) - * - Distance a little further away than the actor's weapon reach - * i.e. rangeFollow > rangeAttack for melee weapons - * - Hardcoded value (0 for ranged weapons) - * - Once the target gets beyond this distance mFollowTarget is cleared - * and a path to the target needs to be found - * - * mFollowTarget: - * - * - Once triggered, the actor follows the target with LOS shortcut - * (the shortcut really only applies to cells where pathgrids are - * available, since the default path without pathgrids is direct to - * target even if LOS is not achieved) - */ - ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); @@ -309,153 +275,49 @@ namespace MWMechanics osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - osg::Vec3f& lastActorPos = storage.mLastActorPos; - bool& followTarget = storage.mFollowTarget; - - bool isStuck = false; - float speed = 0.0f; - if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * AI_REACTION_TIME / 2) - isStuck = true; - - lastActorPos = vActorPos; - - // check if actor can move along z-axis - bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) - || world->isFlying(actor); - // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. if (distToTarget >= rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? storage.stopAttack(); - return false; + return; } - // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack - bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - - // (within attack dist) || (not quite attack dist while following) - if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) + if (storage.mReadyToAttack) { - mPathFinder.clearPath(); - //Melee and Close-up combat - - // getXAngleToDir determines vertical angle to target: - // if actor can move along z-axis it will control movement dir - // if can't - it will control correct aiming. - // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate + storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); + // start new attack + storage.startAttackIfReady(actor, characterController, weapon, distantCombat); + if (distantCombat) { + // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, - storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, storage.mStrength); lastTargetPos = vTargetPos; - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir(vAimDir); - } - else - { - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated - } - - // (not quite attack dist while following) - if (followTarget && distToTarget > rangeAttack) - { - //Close-up combat: just run up on target - storage.stopCombatMove(); - movement.mPosition[1] = 1; - } - else // (within attack dist) - { - storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); - readyToAttack = true; - //only once got in melee combat, actor is allowed to use close-up shortcutting - followTarget = true; - } - } - else // remote pathfinding - { - bool preferShortcut = false; - if (!distantCombat) inLOS = world->getLOS(actor, target); - - // check if shortcut is available - bool& forceNoShortcut = storage.mForceNoShortcut; - ESM::Position& shortcutFailPos = storage.mShortcutFailPos; - - if(inLOS && (!isStuck || readyToAttack) - && (!forceNoShortcut || (shortcutFailPos.asVec3() - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) - { - if(speed == 0.0f) speed = actorClass.getSpeed(actor); - // maximum dist before pit/obstacle for actor to avoid them depending on his speed - float maxAvoidDist = AI_REACTION_TIME * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); - } - - // don't use pathgrid when actor can move in 3 dimensions - if (canMoveByZ) - { - preferShortcut = true; + MWMechanics::Movement& movement = storage.mMovement; movement.mRotation[0] = getXAngleToDir(vAimDir); + movement.mRotation[2] = getZAngleToDir(vAimDir); } - if(preferShortcut) - { - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); - forceNoShortcut = false; - shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; - mPathFinder.clearPath(); - } - else // if shortcut failed stick to path grid - { - if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) - { - forceNoShortcut = true; - shortcutFailPos = pos; - } - - followTarget = false; - - buildNewPath(actor, target); - - // should always return a path (even if it's just go straight on target.) - assert(mPathFinder.isPathConstructed()); - } - - if (readyToAttack) - { - // to stop possible sideway moving after target moved out of attack range - storage.stopCombatMove(); - readyToAttack = false; - } - movement.mPosition[1] = 1; + storage.mAdjustAiming = distantCombat; } - - return false; } - void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) { + // apply combat movement MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); - if (mPathFinder.isPathConstructed()) - { - const ESM::Position& pos = actor.getRefData().getPosition(); - if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) - { - actorMovementSettings.mPosition[1] = 0; - } - else - { - evadeObstacles(actor, duration, pos); - } - } - else + actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0]; + actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; + actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; + + if (storage.mAdjustAiming) { - actorMovementSettings = desiredMovement; - rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); + rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); } } @@ -474,35 +336,6 @@ namespace MWMechanics } } - bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) - { - if (!mPathFinder.getPath().empty()) - { - osg::Vec3f currPathTarget(PathFinder::MakeOsgVec3(mPathFinder.getPath().back())); - osg::Vec3f newPathTarget = PathFinder::MakeOsgVec3(dest); - float dist = (newPathTarget - currPathTarget).length(); - float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; - return dist > targetPosThreshold; - } - else - { - // necessarily construct a new path - return true; - } - } - - void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) - { - ESM::Pathgrid::Point newPathTarget = PathFinder::MakePathgridPoint(target.getRefData().getPosition()); - - //construct new path only if target has moved away more than on [targetPosThreshold] - if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) - { - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell()); - } - } - int AiCombat::getTypeId() const { return TypeIdCombat; @@ -542,13 +375,13 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } - // only NPCs are smart enough to use dodge movements + // dodge movements (for NPCs only) else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) { //apply sideway movement (kind of dodging) with some probability if (Misc::Rng::rollClosedProbability() < 0.25) { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 93d630529..ad683b505 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -53,19 +53,14 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; - protected: - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); - private: int mTargetActorId; - void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target); + void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); /// Transfer desired movement (from AiCombatStorage) to Actor - void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2999e8a19..4be464018 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -72,7 +72,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!mIsShortcutting) { - if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell()->getCell())) // only rebuild path if the target has moved + if (wasShortcutting || doesPathNeedRecalc(dest)) // only rebuild path if the target has moved { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -226,13 +226,11 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& return isClear; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest) { - bool needRecalc = distance(mPrevDest, dest) > 10; - if (needRecalc) - mPrevDest = dest; + if (mPathFinder.getPath().empty()) return true; - return needRecalc; + return (distance(mPathFinder.getPath().back(), newDest) > 10); } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 79537e616..2dc8bd3c7 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -95,7 +95,7 @@ namespace MWMechanics /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); @@ -105,7 +105,6 @@ namespace MWMechanics float mTimer; - ESM::Pathgrid::Point mPrevDest; osg::Vec3f mLastActorPos; bool mIsShortcutting; // if shortcutting at the moment diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 649a72cc0..7909b02c9 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -35,7 +35,6 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte if(actor.getClass().getCreatureStats(actor).isDead()) return true; - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered From 4be6b362c5fc045b54d7e140e120379fa813a77a Mon Sep 17 00:00:00 2001 From: Koncord Date: Mon, 4 Jan 2016 20:15:55 +0800 Subject: [PATCH 0004/2007] Add OpenMW-mp target --- CMakeLists.txt | 9 +++++++++ apps/opencs/{main.cpp => Networking.cpp} | 0 apps/openmw/mwmp/Player.cpp | 5 +++++ apps/openmw/mwmp/Player.hpp | 15 +++++++++++++++ 4 files changed, 29 insertions(+) rename apps/opencs/{main.cpp => Networking.cpp} (100%) create mode 100644 apps/openmw/mwmp/Player.cpp create mode 100644 apps/openmw/mwmp/Player.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index afe3ce4b7..8486666c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" # Apps and tools option(BUILD_OPENMW "build OpenMW" ON) +option(BUILD_OPENMW_MP "build OpenMW-MP" ON) option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) @@ -114,6 +115,10 @@ endif() # We probably support older versions than this. cmake_minimum_required(VERSION 2.6) + +find_package(RakNet REQUIRED) +include_directories(${RakNet_INCLUDES}) + # Sound setup unset(FFMPEG_LIBRARIES CACHE) @@ -568,6 +573,10 @@ add_subdirectory (components) #endif() # Apps and tools +if (BUILD_OPENMW_MP) + add_subdirectory( apps/openmw-mp ) +endif() + if (BUILD_OPENMW) add_subdirectory( apps/openmw ) endif() diff --git a/apps/opencs/main.cpp b/apps/opencs/Networking.cpp similarity index 100% rename from apps/opencs/main.cpp rename to apps/opencs/Networking.cpp diff --git a/apps/openmw/mwmp/Player.cpp b/apps/openmw/mwmp/Player.cpp new file mode 100644 index 000000000..59734df38 --- /dev/null +++ b/apps/openmw/mwmp/Player.cpp @@ -0,0 +1,5 @@ +// +// Created by koncord on 02.01.16. +// + +#include "Player.hpp" diff --git a/apps/openmw/mwmp/Player.hpp b/apps/openmw/mwmp/Player.hpp new file mode 100644 index 000000000..d702ac3e2 --- /dev/null +++ b/apps/openmw/mwmp/Player.hpp @@ -0,0 +1,15 @@ +// +// Created by koncord on 02.01.16. +// + +#ifndef OPENMW_PLAYER_HPP +#define OPENMW_PLAYER_HPP + + +class Player +{ + +}; + + +#endif //OPENMW_PLAYER_HPP From b304e98568c15f7e180eef68414cabfa076d9753 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 15 Jan 2016 21:49:27 +0300 Subject: [PATCH 0005/2007] implement ActionWeapon::getCombatRange (move logic from AiCombat) --- apps/openmw/mwmechanics/aicombat.cpp | 68 ++++------------------ apps/openmw/mwmechanics/aicombataction.cpp | 62 +++++++++++++++----- apps/openmw/mwmechanics/aicombataction.hpp | 13 +++-- 3 files changed, 64 insertions(+), 79 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index abf4109ba..acdbef299 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -211,61 +211,13 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } - float rangeFollow; - if (currentAction.get()) - currentAction->getCombatRange(rangeAttack, rangeFollow); - - // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype = WeapType_None; - float weapRange = 1.0f; - - // Get weapon characteristics - MWBase::World* world = MWBase::Environment::get().getWorld(); - if (actorClass.hasInventoryStore(actor)) - { - //Get weapon range - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); - - if (weaptype == WeapType_HandToHand) - { - static float fHandToHandReach = - world->getStore().get().find("fHandToHandReach")->getFloat(); - weapRange = fHandToHandReach; - } - else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) - { - // All other WeapTypes are actually weapons, so get is safe. - weapon = weaponSlot->get()->mBase; - weapRange = weapon->mData.mReach; - } - weapRange *= 100.0f; - } - else //is creature - { - weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; - weapRange = 200; //TODO: use true attack range (the same problem in Creature::hit) - } - - bool distantCombat = false; - if (weaptype != WeapType_Spell) - { - // TODO: move to ActionWeapon - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) - { - rangeAttack = 1000; - distantCombat = true; - } - else - { - rangeAttack = weapRange; - } - } - else + bool isRangedCombat = false; + if (currentAction.get()) { - distantCombat = (rangeAttack > 500); - weapRange = 200; + rangeAttack = currentAction->getCombatRange(isRangedCombat); + // Get weapon characteristics + weapon = currentAction->getWeapon(); } ESM::Position pos = actor.getRefData().getPosition(); @@ -286,15 +238,15 @@ namespace MWMechanics if (storage.mReadyToAttack) { - storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); + storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack); // start new attack - storage.startAttackIfReady(actor, characterController, weapon, distantCombat); + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); - if (distantCombat) + if (isRangedCombat) { // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, weaptype, storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); lastTargetPos = vTargetPos; MWMechanics::Movement& movement = storage.mMovement; @@ -302,7 +254,7 @@ namespace MWMechanics movement.mRotation[2] = getZAngleToDir(vAimDir); } - storage.mAdjustAiming = distantCombat; + storage.mAdjustAiming = isRangedCombat; } } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 60446e524..76d5ed952 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -40,23 +40,20 @@ int getRangeTypes (const ESM::EffectList& effects) return types; } -void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) +float suggestCombatRange(int rangeTypes) { if (rangeTypes & Touch) { - rangeAttack = 100.f; - rangeFollow = 300.f; + return 100.f; } else if (rangeTypes & Target) { - rangeAttack = 1000.f; - rangeFollow = 0.f; + return 1000.f; } else { // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } } @@ -394,11 +391,13 @@ namespace MWMechanics } } - void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionSpell::getCombatRange (bool& isRanged) const { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + + isRanged = (types & RangeTypes::Target); + return suggestCombatRange(types); } void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) @@ -408,18 +407,17 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); } - void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionEnchantedItem::getCombatRange(bool& isRanged) const { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + return suggestCombatRange(types); } - void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionPotion::getCombatRange(bool& isRanged) const { // distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } void ActionPotion::prepare(const MWWorld::Ptr &actor) @@ -430,6 +428,8 @@ namespace MWMechanics void ActionWeapon::prepare(const MWWorld::Ptr &actor) { + mIsNpc = actor.getClass().isNpc(); + if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) @@ -449,9 +449,39 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); } - void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionWeapon::getCombatRange(bool& isRanged) const + { + isRanged = false; + + if (mWeapon.isEmpty()) + { + if (!mIsNpc) + return 200.f; // TODO: use true attack range (the same problem in Creature::hit) + else + { + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + + return fHandToHandReach * 100.0f; + } + } + + const ESM::Weapon* weapon = mWeapon.get()->mBase; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { + isRanged = true; + return 1000.f; + } + else + return weapon->mData.mReach * 100.0f; + } + + const ESM::Weapon* ActionWeapon::getWeapon() const { - // Already done in AiCombat itself + if (mWeapon.isEmpty()) + return NULL; + return mWeapon.get()->mBase; } boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index a4a398d05..22f543418 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,8 +16,9 @@ namespace MWMechanics public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; - virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; + virtual float getCombatRange (bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } + virtual const ESM::Weapon* getWeapon() const { return NULL; }; }; class ActionSpell : public Action @@ -28,7 +29,7 @@ namespace MWMechanics /// Sets the given spell as selected on the actor's spell list. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; }; class ActionEnchantedItem : public Action @@ -38,7 +39,7 @@ namespace MWMechanics MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -51,7 +52,7 @@ namespace MWMechanics MWWorld::Ptr mPotion; /// Drinks the given potion. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -62,6 +63,7 @@ namespace MWMechanics private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; + bool mIsNpc; public: /// \a weapon may be empty for hand-to-hand combat @@ -69,7 +71,8 @@ namespace MWMechanics : mAmmunition(ammo), mWeapon(weapon) {} /// Equips the given weapon. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; + virtual const ESM::Weapon* getWeapon() const; }; float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); From a34a08c212074693e88289371f9702acff1eb959 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 7 Feb 2016 13:52:18 -0500 Subject: [PATCH 0006/2007] Render cell markers Adds rendering of cell markers. Markers are displayed at center of cell and contain cell's coordinates. --- apps/opencs/CMakeLists.txt | 5 ++- apps/opencs/view/render/cell.cpp | 7 +++ apps/opencs/view/render/cell.hpp | 5 +++ apps/opencs/view/render/cellmarker.cpp | 62 ++++++++++++++++++++++++++ apps/opencs/view/render/cellmarker.hpp | 48 ++++++++++++++++++++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/render/cellmarker.cpp create mode 100644 apps/opencs/view/render/cellmarker.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0bde541bf..c9245ca9f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -35,7 +35,7 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools reportmodel mergeoperation + tools reportmodel mergeoperation ) opencs_units_noqt (model/tools @@ -90,7 +90,7 @@ opencs_units (view/render opencs_units_noqt (view/render lighting lightingday lightingnight - lightingbright object cell terrainstorage tagbase cellarrow + lightingbright object cell terrainstorage tagbase cellarrow cellmarker ) opencs_hdrs_noqt (view/render @@ -192,6 +192,7 @@ endif(APPLE) target_link_libraries(openmw-cs ${OSG_LIBRARIES} ${OPENTHREADS_LIBRARIES} + ${OSGTEXT_LIBRARIES} ${OSGUTIL_LIBRARIES} ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index bd85c8a14..0030fd9b8 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -70,6 +70,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCellNode = new osg::Group; rootNode->addChild(mCellNode); + setCellMarker(); + if (!mDeleted) { CSMWorld::IdTable& references = dynamic_cast ( @@ -303,6 +305,11 @@ void CSVRender::Cell::setCellArrows (int mask) } } +void CSVRender::Cell::setCellMarker() +{ + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates)); +} + CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const { return mCoordinates; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 85b9bf21b..22f9872e3 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -15,6 +15,7 @@ #include "object.hpp" #include "cellarrow.hpp" +#include "cellmarker.hpp" class QModelIndex; @@ -42,6 +43,7 @@ namespace CSVRender std::auto_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::auto_ptr mCellArrows[4]; + std::auto_ptr mCellMarker; bool mDeleted; /// Ignored if cell does not have an object with the given ID. @@ -105,6 +107,9 @@ namespace CSVRender void setCellArrows (int mask); + /// \brief Set marker for this cell. + void setCellMarker(); + /// Returns 0, 0 in case of an unpaged cell. CSMWorld::CellCoordinates getCoordinates() const; diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp new file mode 100644 index 000000000..17bb05440 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.cpp @@ -0,0 +1,62 @@ +#include "cellmarker.hpp" + +#include + +#include +#include +#include +#include + +void CSVRender::CellMarker::buildMarker() +{ + const int characterSize = 20; + + // Set up marker text containing cell's coordinates. + osg::ref_ptr markerText (new osgText::Text); + markerText->setBackdropType(osgText::Text::OUTLINE); + markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); + markerText->setCharacterSize(characterSize); + std::string coordinatesText = + "#" + boost::lexical_cast(mCoordinates.getX()) + + " " + boost::lexical_cast(mCoordinates.getY()); + markerText->setText(coordinatesText); + + // Add text to marker node. + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(markerText); + mMarkerNode->addChild(geode); +} + +void CSVRender::CellMarker::positionMarker() +{ + const int cellSize = 8192; + const int markerHeight = 0; + + // Move marker to center of cell. + int x = (mCoordinates.getX() * cellSize) + (cellSize / 2); + int y = (mCoordinates.getY() * cellSize) + (cellSize / 2); + mMarkerNode->setPosition(osg::Vec3f(x, y, markerHeight)); +} + +CSVRender::CellMarker::CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates +) : mCellNode(cellNode), + mCoordinates(coordinates) +{ + // Set up node for cell marker. + mMarkerNode = new osg::AutoTransform(); + mMarkerNode->setAutoRotateMode(osg::AutoTransform::ROTATE_TO_SCREEN); + mMarkerNode->setAutoScaleToScreen(true); + mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + mCellNode->addChild(mMarkerNode); + + buildMarker(); + positionMarker(); +} + +CSVRender::CellMarker::~CellMarker() +{ + mCellNode->removeChild(mMarkerNode); +} diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp new file mode 100644 index 000000000..4ac9d86b0 --- /dev/null +++ b/apps/opencs/view/render/cellmarker.hpp @@ -0,0 +1,48 @@ +#ifndef OPENCS_VIEW_CELLMARKER_H +#define OPENCS_VIEW_CELLMARKER_H + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class AutoTransform; + class Group; +} + +namespace CSVRender +{ + /// \brief Marker to display cell coordinates. + class CellMarker + { + private: + + osg::Group* mCellNode; + osg::ref_ptr mMarkerNode; + CSMWorld::CellCoordinates mCoordinates; + + // Not implemented. + CellMarker(const CellMarker&); + CellMarker& operator=(const CellMarker&); + + /// \brief Build marker containing cell's coordinates. + void buildMarker(); + + /// \brief Position marker above center of cell. + void positionMarker(); + + public: + + /// \brief Constructor. + /// \param cellNode Cell to create marker for. + /// \param coordinates Coordinates of cell. + CellMarker( + osg::Group *cellNode, + const CSMWorld::CellCoordinates& coordinates); + + ~CellMarker(); + }; +} + +#endif From 61b6806a62baa89db93f5270e5f7907ffbb73764 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Tue, 9 Feb 2016 20:23:00 -0500 Subject: [PATCH 0007/2007] Allow toggling of cell markers --- apps/opencs/view/render/cellmarker.cpp | 14 ++++++++++++++ apps/opencs/view/render/cellmarker.hpp | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index 17bb05440..e8772a586 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -7,6 +7,17 @@ #include #include +#include "mask.hpp" + +CSVRender::CellMarkerTag::CellMarkerTag(CellMarker *marker) +: TagBase(Mask_CellMarker), mMarker(marker) +{} + +CSVRender::CellMarker *CSVRender::CellMarkerTag::getCellMarker() const +{ + return mMarker; +} + void CSVRender::CellMarker::buildMarker() { const int characterSize = 20; @@ -50,6 +61,9 @@ CSVRender::CellMarker::CellMarker( mMarkerNode->setAutoScaleToScreen(true); mMarkerNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mMarkerNode->setUserData(new CellMarkerTag(this)); + mMarkerNode->setNodeMask(Mask_CellMarker); + mCellNode->addChild(mMarkerNode); buildMarker(); diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp index 4ac9d86b0..08c7e0608 100644 --- a/apps/opencs/view/render/cellmarker.hpp +++ b/apps/opencs/view/render/cellmarker.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_VIEW_CELLMARKER_H #define OPENCS_VIEW_CELLMARKER_H +#include "tagbase.hpp" + #include #include "../../model/world/cellcoordinates.hpp" @@ -13,6 +15,21 @@ namespace osg namespace CSVRender { + class CellMarker; + + class CellMarkerTag : public TagBase + { + private: + + CellMarker *mMarker; + + public: + + CellMarkerTag(CellMarker *marker); + + CellMarker *getCellMarker() const; + }; + /// \brief Marker to display cell coordinates. class CellMarker { From fb219fea17533812d416e9617bdda2c66b4fbf4d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 7 Feb 2016 19:43:38 +0100 Subject: [PATCH 0008/2007] Fix respawning of NPCs/creatures when they were moved to a different cell --- apps/openmw/mwclass/creature.cpp | 10 +++++----- apps/openmw/mwclass/npc.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 6e9cfccb9..0f021b5a2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -772,18 +772,18 @@ namespace MWClass { if (isFlagBitSet(ptr, ESM::Creature::Respawn)) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 3519f9d83..474985f7b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1305,18 +1305,18 @@ namespace MWClass { if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) { - // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. - // This also means we cannot respawn dynamically placed references with no content file connection. if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); - // Reset to original position - ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); - MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(NULL); + + // Reset to original position + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], + ptr.getCellRef().getPosition().pos[2]); } } } From d3808580b032eb75232aaae02c121bb711e19e28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 8 Feb 2016 16:45:56 +0100 Subject: [PATCH 0009/2007] Rename lightRoot to sceneRoot --- apps/openmw/mwrender/renderingmanager.cpp | 40 +++++++++++------------ apps/openmw/mwrender/renderingmanager.hpp | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 177cee4ea..29b641f6b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -169,26 +169,26 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - osg::ref_ptr lightRoot = new SceneUtil::LightManager; - lightRoot->setLightingMask(Mask_Lighting); - mLightRoot = lightRoot; - lightRoot->setStartLight(1); + osg::ref_ptr sceneRoot = new SceneUtil::LightManager; + sceneRoot->setLightingMask(Mask_Lighting); + mSceneRoot = sceneRoot; + sceneRoot->setStartLight(1); - mRootNode->addChild(lightRoot); + mRootNode->addChild(sceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); - mObjects.reset(new Objects(mResourceSystem, lightRoot, mUnrefQueue.get())); + mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get())); mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); - mEffectManager.reset(new EffectManager(lightRoot, mResourceSystem)); + mEffectManager.reset(new EffectManager(sceneRoot, mResourceSystem)); - mWater.reset(new Water(mRootNode, lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); + mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); - mTerrain.reset(new Terrain::TerrainGrid(lightRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), + mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); @@ -203,21 +203,21 @@ namespace MWRender mSunLight->setAmbient(osg::Vec4f(0,0,0,1)); mSunLight->setSpecular(osg::Vec4f(0,0,0,0)); mSunLight->setConstantAttenuation(1.f); - lightRoot->addChild(source); + sceneRoot->addChild(source); - lightRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); - lightRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON); + sceneRoot->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); - lightRoot->setNodeMask(Mask_Scene); - lightRoot->setName("Scene Root"); + sceneRoot->setNodeMask(Mask_Scene); + sceneRoot->setName("Scene Root"); - mSky.reset(new SkyManager(lightRoot, resourceSystem->getSceneManager())); + mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); source->setStateSetModes(*mRootNode->getOrCreateStateSet(), osg::StateAttribute::ON); mStateUpdater = new StateUpdater; - lightRoot->addUpdateCallback(mStateUpdater); + sceneRoot->addUpdateCallback(mStateUpdater); osg::Camera::CullingMode cullingMode = osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING; @@ -291,7 +291,7 @@ namespace MWRender osg::Group* RenderingManager::getLightRoot() { - return mLightRoot.get(); + return mSceneRoot.get(); } void RenderingManager::setNightEyeFactor(float factor) @@ -589,7 +589,7 @@ namespace MWRender image->setPixelFormat(texture->getInternalFormat()); rttCamera->setUpdateCallback(new NoTraverseCallback); - rttCamera->addChild(mLightRoot); + rttCamera->addChild(mSceneRoot); rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); @@ -772,7 +772,7 @@ namespace MWRender { mPlayerNode = new SceneUtil::PositionAttitudeTransform; mPlayerNode->setNodeMask(Mask_Player); - mLightRoot->addChild(mPlayerNode); + mSceneRoot->addChild(mPlayerNode); } mPlayerNode->setUserDataContainer(new osg::DefaultUserDataContainer); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4dda6f273..59692f45a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -197,7 +197,7 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; - osg::ref_ptr mLightRoot; + osg::ref_ptr mSceneRoot; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From 6bfeb118d7ecae3bc528ff558656c42caaa8c6ec Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 10 Feb 2016 19:08:17 +0100 Subject: [PATCH 0010/2007] Fix cleanup issue --- apps/openmw/mwworld/cellpreloader.cpp | 7 +++++++ apps/openmw/mwworld/cellpreloader.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index ce9a87beb..6acb41dc3 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -169,6 +169,13 @@ namespace MWWorld { } + CellPreloader::~CellPreloader() + { + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();++it) + it->second.mWorkItem->waitTillDone(); + mPreloadCells.clear(); + } + void CellPreloader::preload(CellStore *cell, double timestamp) { if (!mWorkQueue) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 32ea194c6..437395397 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -24,6 +24,7 @@ namespace MWWorld { public: CellPreloader(Resource::ResourceSystem* resourceSystem, Resource::BulletShapeManager* bulletShapeManager, Terrain::World* terrain); + ~CellPreloader(); /// Ask a background thread to preload rendering meshes and collision shapes for objects in this cell. /// @note The cell itself must be in State_Loaded or State_Preloaded. From be6ea3d607724e89e917cc3d448333e9b2d688dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Feb 2016 16:22:54 +0100 Subject: [PATCH 0011/2007] Account for UV coordinate flip in UVController (Fixes #3203) --- components/nifosg/controller.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index d0abc9ead..aabe07c86 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -254,9 +254,15 @@ void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) float uScale = mUScale.interpKey(value); float vScale = mVScale.interpKey(value); + osg::Matrix flipMat; + flipMat.preMultTranslate(osg::Vec3f(0,1,0)); + flipMat.preMultScale(osg::Vec3f(1,-1,1)); + osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); mat.setTrans(uTrans, vTrans, 0); + mat = flipMat * mat * flipMat; + // setting once is enough because all other texture units share the same TexMat (see setDefaults). if (!mTextureUnits.empty()) { From 48ac0bef3ed17024957e4715b3d85396be30665f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 11 Feb 2016 16:34:02 +0100 Subject: [PATCH 0012/2007] Repair save games affected by bug #3080 (Fixes #3160) --- apps/openmw/mwmechanics/aisequence.cpp | 46 ++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 04ac96b11..71733d613 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -351,60 +351,64 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) for (std::vector::const_iterator it = sequence.mPackages.begin(); it != sequence.mPackages.end(); ++it) { + MWMechanics::AiPackage* package = NULL; switch (it->mType) { case ESM::AiSequence::Ai_Wander: { - MWMechanics::AiWander* wander = new AiWander( - static_cast(it->mPackage)); - mPackages.push_back(wander); + package = new AiWander(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Travel: { - MWMechanics::AiTravel* travel = new AiTravel( - static_cast(it->mPackage)); - mPackages.push_back(travel); + package = new AiTravel(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Escort: { - MWMechanics::AiEscort* escort = new AiEscort( - static_cast(it->mPackage)); - mPackages.push_back(escort); + package = new AiEscort(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Follow: { - MWMechanics::AiFollow* follow = new AiFollow( - static_cast(it->mPackage)); - mPackages.push_back(follow); + package = new AiFollow(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Activate: { - MWMechanics::AiActivate* activate = new AiActivate( - static_cast(it->mPackage)); - mPackages.push_back(activate); + package = new AiActivate(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Combat: { - MWMechanics::AiCombat* combat = new AiCombat( - static_cast(it->mPackage)); - mPackages.push_back(combat); + package = new AiCombat(static_cast(it->mPackage)); break; } case ESM::AiSequence::Ai_Pursue: { - MWMechanics::AiPursue* pursue = new AiPursue( - static_cast(it->mPackage)); - mPackages.push_back(pursue); + package = new AiPursue(static_cast(it->mPackage)); break; } default: break; } + + if (!package) + continue; + + // remove previous packages if required + if (package->shouldCancelPreviousAi()) + { + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if((*it)->canCancel()) + it = mPackages.erase(it); + else + ++it; + } + } + + mPackages.push_back(package); } } From 8b596dfcbe8a142a77b0af54f257343c16469cc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 14:46:45 +0100 Subject: [PATCH 0013/2007] Remove support for OSG 3.2 Since commit e8662bea3133ba9dbb09b86c3abb1af39425e90d, we're using OSG functionality that contains an unfixed crash bug in version 3.2. The bug is fixed in version 3.4 (OSG commit 6351e5020371b0b72b300088a5c6772f58379b84) --- CMakeLists.txt | 2 +- apps/openmw/mwrender/animation.cpp | 14 ----------- apps/openmw/mwrender/objects.cpp | 15 ----------- apps/openmw/mwrender/sky.cpp | 4 --- components/nifosg/nifloader.cpp | 32 ------------------------ components/resource/imagemanager.cpp | 7 ------ components/resource/scenemanager.cpp | 17 ------------- components/sceneutil/clone.cpp | 5 ---- components/sceneutil/controller.cpp | 8 ------ components/sceneutil/riggeometry.cpp | 2 -- components/sdlutil/sdlgraphicswindow.cpp | 8 ------ components/terrain/terraingrid.cpp | 6 ----- 12 files changed, 1 insertion(+), 119 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52970cfb5..df2ee6f49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,7 @@ if (USE_QT) set (OSG_QT osgQt) endif() -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.4.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 398fe5322..e3cc57bc4 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -201,17 +201,10 @@ namespace class RemoveDrawableVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &geode) - { - applyImpl(geode); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { @@ -239,17 +232,10 @@ namespace class RemoveTriBipVisitor : public RemoveVisitor { public: - virtual void apply(osg::Geode &node) - { - applyImpl(node); - } - -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { applyImpl(drw); } -#endif void applyImpl(osg::Node& node) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 516d10b95..d249a7602 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -43,26 +43,11 @@ namespace traverse(node); } - virtual void apply(osg::Geode& geode) - { - std::vector partsysVector; - for (unsigned int i=0; i(drw)) - partsysVector.push_back(partsys); - } - - for (std::vector::iterator it = partsysVector.begin(); it != partsysVector.end(); ++it) - geode.removeDrawable(*it); - } -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) virtual void apply(osg::Drawable& drw) { if (osgParticle::ParticleSystem* partsys = dynamic_cast(&drw)) mToRemove.push_back(partsys); } -#endif void remove() { diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 40283ba27..1757314fe 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1282,11 +1282,7 @@ public: if (stateset->getAttribute(osg::StateAttribute::MATERIAL)) { SceneUtil::CompositeStateSetUpdater* composite = NULL; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if ((composite = dynamic_cast(callback))) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6c6061063..61fbc9b96 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -934,13 +934,7 @@ namespace NifOsg updater->addParticleSystem(partsys); parentNode->addChild(updater); -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(partsys); - osg::Node* toAttach = geode.get(); -#else osg::Node* toAttach = partsys.get(); -#endif if (rf == osgParticle::ParticleProcessor::RELATIVE_RF) parentNode->addChild(toAttach); @@ -1017,11 +1011,6 @@ namespace NifOsg triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); } -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); -#endif - if (geometry->getDataVariance() == osg::Object::DYNAMIC) { // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch @@ -1029,24 +1018,14 @@ namespace NifOsg geometry->setDataVariance(osg::Object::STATIC); osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else osg::ref_ptr geom2 = static_cast(osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(geometry); frameswitch->addChild(geom2); -#endif parentNode->addChild(frameswitch); } else -#if OSG_VERSION_LESS_THAN(3,3,3) - parentNode->addChild(geode); -#else parentNode->addChild(geometry); -#endif } osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) @@ -1151,21 +1130,10 @@ namespace NifOsg osg::ref_ptr frameswitch = new FrameSwitch; -#if OSG_VERSION_LESS_THAN(3,3,3) - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(rig); - - osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| - osg::CopyOp::DEEP_COPY_DRAWABLES)); - - frameswitch->addChild(geode); - frameswitch->addChild(geode2); -#else SceneUtil::RigGeometry* rig2 = static_cast(osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES| osg::CopyOp::DEEP_COPY_DRAWABLES)); frameswitch->addChild(rig); frameswitch->addChild(rig2); -#endif parentNode->addChild(frameswitch); } diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 79da7d7ab..a4278eb0f 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -62,17 +62,10 @@ namespace Resource case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); if (exts && !exts->isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#else - osg::Texture::Extensions* exts = osg::Texture::getExtensions(0, false); - if (exts && !exts->isTextureCompressionS3TCSupported() - // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) -#endif { std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; return false; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 29f7076f0..52db2decc 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -52,23 +52,6 @@ namespace && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } - void apply(osg::Geode& geode) - { - for (unsigned int i=0;i(geode.getDrawable(i))) - { - if (isWorldSpaceParticleSystem(partsys)) - { - // HACK: Ignore the InverseWorldMatrix transform the geode is attached to - if (geode.getNumParents() && geode.getParent(0)->getNumParents()) - transformInitialParticles(partsys, geode.getParent(0)->getParent(0)); - } - geode.setNodeMask(mMask); - } - } - } - #if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. void apply(osg::Drawable& drw) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 784195e7e..26f3f5c7c 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -81,11 +81,6 @@ namespace SceneUtil #endif osg::Drawable* cloned = osg::clone(drawable, copyop); -#if OSG_VERSION_LESS_THAN(3,3,3) - // work around OSG 3.2 not respecting the DEEP_COPY_CALLBACK flag - if (cloned->getUpdateCallback()) - cloned->setUpdateCallback(osg::clone(cloned->getUpdateCallback(), *this)); -#endif return cloned; } if (dynamic_cast(drawable)) diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 7762b48d0..916d4f80b 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -65,11 +65,7 @@ namespace SceneUtil void ControllerVisitor::apply(osg::Node &node) { -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = node.getUpdateCallback(); -#else - osg::NodeCallback* callback = node.getUpdateCallback(); -#endif while (callback) { if (Controller* ctrl = dynamic_cast(callback)) @@ -96,11 +92,7 @@ namespace SceneUtil { osg::Drawable* drw = geode.getDrawable(i); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Callback* callback = drw->getUpdateCallback(); -#else - osg::Drawable::UpdateCallback* callback = drw->getUpdateCallback(); -#endif if (Controller* ctrl = dynamic_cast(callback)) visit(geode, *ctrl); diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index be8d97a4c..da7d42431 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -289,11 +289,9 @@ void RigGeometry::updateBounds(osg::NodeVisitor *nv) _boundingBox = box; _boundingBoxComputed = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3.3 and up Drawable inherits from Node, so has a bounding sphere as well. _boundingSphere = osg::BoundingSphere(_boundingBox); _boundingSphereComputed = true; -#endif for (unsigned int i=0; idirtyBound(); } diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index a0483e84d..e5bad1f00 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -112,11 +112,7 @@ void GraphicsWindowSDL2::init() mValid = true; -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif } @@ -133,11 +129,7 @@ bool GraphicsWindowSDL2::realizeImplementation() SDL_ShowWindow(mWindow); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,4) getEventQueue()->syncWindowRectangleWithGraphicsContext(); -#else - getEventQueue()->syncWindowRectangleWithGraphcisContext(); -#endif mRealized = true; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 4f0e464ce..96584172a 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -193,13 +193,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu transform->addChild(effect); -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) osg::Node* toAttach = geometry.get(); -#else - osg::ref_ptr geode (new osg::Geode); - geode->addDrawable(geometry); - osg::Node* toAttach = geode.get(); -#endif effect->addChild(toAttach); From f94722b271c83ffbbe55a46eff19619f0083d87c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 14:55:00 +0100 Subject: [PATCH 0014/2007] OSG 3.3.4 is the first release to include the DDS crash fix --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df2ee6f49..520279e50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,7 +233,7 @@ if (USE_QT) set (OSG_QT osgQt) endif() -find_package(OpenSceneGraph 3.4.0 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) From 5824619a95f5ad16dae84de41ab9a635bb16f56b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 19:28:10 +0100 Subject: [PATCH 0015/2007] Clean up includes --- apps/openmw/mwrender/animation.cpp | 1 - apps/openmw/mwrender/objects.cpp | 1 - apps/openmw/mwrender/sky.cpp | 1 - components/nifosg/nifloader.cpp | 1 - components/resource/imagemanager.cpp | 1 - components/resource/scenemanager.cpp | 3 --- components/sceneutil/controller.cpp | 1 - components/sceneutil/riggeometry.cpp | 1 - components/sdlutil/sdlgraphicswindow.cpp | 2 -- components/terrain/terraingrid.cpp | 1 - 10 files changed, 13 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e3cc57bc4..abca3b266 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index d249a7602..a13ee03ac 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 1757314fe..f10beca6c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 61fbc9b96..8f4076884 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include // resource diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index a4278eb0f..870fa370b 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -2,7 +2,6 @@ #include #include -#include #include diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 52db2decc..85a4afa30 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -52,7 +51,6 @@ namespace && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace"); } -#if OSG_VERSION_GREATER_OR_EQUAL(3,3,3) // in OSG 3.3 and up Drawables can be directly in the scene graph without a Geode decorating them. void apply(osg::Drawable& drw) { @@ -67,7 +65,6 @@ namespace partsys->setNodeMask(mMask); } } -#endif void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 916d4f80b..097899911 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace SceneUtil { diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index da7d42431..f915ee39c 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include "skeleton.hpp" diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index e5bad1f00..66d53b096 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -2,8 +2,6 @@ #include -#include - namespace SDLUtil { diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 96584172a..981a984e4 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include From d1375cd3a3ada3e8b9a84e64e1f5d1ab7b39cbaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 12 Feb 2016 23:40:50 +0100 Subject: [PATCH 0016/2007] Crashcatcher: limit backtrace to a sensible number of stack frames When a stack overflow occurs, trying to print the whole stack would cause the process to hang indefinitely. --- apps/openmw/crashcatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index cafd0e08a..4f0356259 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -149,7 +149,7 @@ static void gdb_info(pid_t pid) "info registers\n" "shell echo \"\"\n" "shell echo \"* Backtrace\"\n" - "thread apply all backtrace full\n" + "thread apply all backtrace full 1000\n" "detach\n" "quit\n", pid); fclose(f); From 383524c6881056dc3f84992d5ec8d51e2f102273 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 02:56:41 +0100 Subject: [PATCH 0017/2007] Run physics in fixed timesteps, use the remainder to interpolate between current and previous state Based on http://gafferongames.com/game-physics/fix-your-timestep/ --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwphysics/actor.cpp | 38 ++++++++-- apps/openmw/mwphysics/actor.hpp | 17 +++++ apps/openmw/mwphysics/physicssystem.cpp | 89 +++++++++++++---------- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 15 ++-- apps/openmw/mwworld/worldimp.hpp | 4 +- 8 files changed, 110 insertions(+), 59 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 946a9a5dd..52697d670 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -270,7 +270,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z, bool movePhysics=true) = 0; ///< @return an updated Ptr virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index c99754b5c..61022da28 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -45,8 +45,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr updateRotation(); updateScale(); - // already called by updateScale() - //updatePosition(); + updatePosition(); updateCollisionMask(); } @@ -86,19 +85,44 @@ void Actor::updatePosition() { osg::Vec3f position = mPtr.getRefData().getPosition().asVec3(); + mPosition = position; + mPreviousPosition = position; + + updateCollisionObjectPosition(); +} + +void Actor::updateCollisionObjectPosition() +{ btTransform tr = mCollisionObject->getWorldTransform(); osg::Vec3f scaledTranslation = mRotation * osg::componentMultiply(mMeshTranslation, mScale); - osg::Vec3f newPosition = scaledTranslation + position; - + osg::Vec3f newPosition = scaledTranslation + mPosition; tr.setOrigin(toBullet(newPosition)); mCollisionObject->setWorldTransform(tr); } -osg::Vec3f Actor::getPosition() const +osg::Vec3f Actor::getCollisionObjectPosition() const { return toOsg(mCollisionObject->getWorldTransform().getOrigin()); } +void Actor::setPosition(const osg::Vec3f &position) +{ + mPreviousPosition = mPosition; + + mPosition = position; + updateCollisionObjectPosition(); +} + +osg::Vec3f Actor::getPosition() const +{ + return mPosition; +} + +osg::Vec3f Actor::getPreviousPosition() const +{ + return mPreviousPosition; +} + void Actor::updateRotation () { btTransform tr = mCollisionObject->getWorldTransform(); @@ -106,7 +130,7 @@ void Actor::updateRotation () tr.setRotation(toBullet(mRotation)); mCollisionObject->setWorldTransform(tr); - updatePosition(); + updateCollisionObjectPosition(); } void Actor::updateScale() @@ -122,7 +146,7 @@ void Actor::updateScale() mPtr.getClass().adjustScale(mPtr, scaleVec, true); mRenderingScale = scaleVec; - updatePosition(); + updateCollisionObjectPosition(); } osg::Vec3f Actor::getHalfExtents() const diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index 5755def74..b238547e1 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -68,8 +68,15 @@ namespace MWPhysics void updateScale(); void updateRotation(); + + /** + * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used + * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. + */ void updatePosition(); + void updateCollisionObjectPosition(); + /** * Returns the half extents of the collision body (scaled according to collision scale) */ @@ -79,8 +86,17 @@ namespace MWPhysics * Returns the position of the collision body * @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. */ + osg::Vec3f getCollisionObjectPosition() const; + + /** + * Store the current position into mPreviousPosition, then move to this position. + */ + void setPosition(const osg::Vec3f& position); + osg::Vec3f getPosition() const; + osg::Vec3f getPreviousPosition() const; + /** * Returns the half extents of the collision body (scaled according to rendering scale) * @note The reason we need this extra method is because of an inconsistency in MW - NPC race scales aren't applied to the collision shape, @@ -138,6 +154,7 @@ namespace MWPhysics osg::Vec3f mScale; osg::Vec3f mRenderingScale; osg::Vec3f mPosition; + osg::Vec3f mPreviousPosition; osg::Vec3f mForce; bool mOnGround; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9f1cfc682..ddb3d0c85 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -234,13 +234,11 @@ namespace MWPhysics } } - static osg::Vec3f move(const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, + static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time, bool isFlying, float waterlevel, float slowFall, btCollisionWorld* collisionWorld, std::map& standingCollisionTracker) { const ESM::Position& refpos = ptr.getRefData().getPosition(); - osg::Vec3f position(refpos.asVec3()); - // Early-out for totally static creatures // (Not sure if gravity should still apply?) if (!ptr.getClass().isMobile(ptr)) @@ -944,8 +942,8 @@ namespace MWPhysics if (!physactor1 || !physactor2) return false; - osg::Vec3f pos1 (physactor1->getPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level - osg::Vec3f pos2 (physactor2->getPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); + osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.8)); // eye level + osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.8)); RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); @@ -1007,11 +1005,11 @@ namespace MWPhysics return osg::Vec3f(); } - osg::Vec3f PhysicsSystem::getPosition(const MWWorld::ConstPtr &actor) const + osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const { const Actor* physactor = getActor(actor); if (physactor) - return physactor->getPosition(); + return physactor->getCollisionObjectPosition(); else return osg::Vec3f(); } @@ -1296,54 +1294,65 @@ namespace MWPhysics mMovementResults.clear(); mTimeAccum += dt; - if(mTimeAccum >= 1.0f/60.0f) + const float physicsDt = 1.f/60.0f; + int numSteps = mTimeAccum / (physicsDt); + mTimeAccum -= numSteps * physicsDt; + + if (numSteps) { // Collision events should be available on every frame mStandingCollisions.clear(); + } - const MWBase::World *world = MWBase::Environment::get().getWorld(); - PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();++iter) - { - float waterlevel = -std::numeric_limits::max(); - const MWWorld::CellStore *cell = iter->first.getCell(); - if(cell->getCell()->hasWater()) - waterlevel = cell->getWaterLevel(); + const MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrVelocityList::iterator iter = mMovementQueue.begin(); + for(;iter != mMovementQueue.end();++iter) + { + float waterlevel = -std::numeric_limits::max(); + const MWWorld::CellStore *cell = iter->first.getCell(); + if(cell->getCell()->hasWater()) + waterlevel = cell->getWaterLevel(); - float oldHeight = iter->first.getRefData().getPosition().pos[2]; - const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); + const MWMechanics::MagicEffects& effects = iter->first.getClass().getCreatureStats(iter->first).getMagicEffects(); - bool waterCollision = false; - if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() - && cell->getCell()->hasWater() - && !world->isUnderwater(iter->first.getCell(), - osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) - waterCollision = true; + bool waterCollision = false; + if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() + && cell->getCell()->hasWater() + && !world->isUnderwater(iter->first.getCell(), + osg::Vec3f(iter->first.getRefData().getPosition().asVec3()))) + waterCollision = true; - ActorMap::iterator foundActor = mActors.find(iter->first); - if (foundActor == mActors.end()) // actor was already removed from the scene - continue; - Actor* physicActor = foundActor->second; - physicActor->setCanWaterWalk(waterCollision); + ActorMap::iterator foundActor = mActors.find(iter->first); + if (foundActor == mActors.end()) // actor was already removed from the scene + continue; + Actor* physicActor = foundActor->second; + physicActor->setCanWaterWalk(waterCollision); - // Slow fall reduces fall speed by a factor of (effect magnitude / 200) - float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); + // Slow fall reduces fall speed by a factor of (effect magnitude / 200) + float slowFall = 1.f - std::max(0.f, std::min(1.f, effects.get(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f)); - osg::Vec3f newpos = MovementSolver::move(physicActor->getPtr(), physicActor, iter->second, mTimeAccum, - world->isFlying(iter->first), - waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + osg::Vec3f position = physicActor->getPosition(); + float oldHeight = position.z(); + for (int i=0; igetPtr(), physicActor, iter->second, physicsDt, + world->isFlying(iter->first), + waterlevel, slowFall, mCollisionWorld, mStandingCollisions); + physicActor->setPosition(position); + } - float heightDiff = newpos.z() - oldHeight; + float interpolationFactor = mTimeAccum / physicsDt; + osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); - if (heightDiff < 0) - iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + float heightDiff = position.z() - oldHeight; - mMovementResults.push_back(std::make_pair(iter->first, newpos)); - } + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); - mTimeAccum = 0.0f; + mMovementResults.push_back(std::make_pair(iter->first, interpolated)); } + mMovementQueue.clear(); return mMovementResults; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 110b59268..62ebf4f0e 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -129,7 +129,7 @@ namespace MWPhysics /// Get the position of the collision shape for the actor. Use together with getHalfExtents() to get the collision bounds in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. - osg::Vec3f getPosition(const MWWorld::ConstPtr& actor) const; + osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to applyQueuedMovement. diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index b8150ce9c..881530c8a 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -117,7 +117,7 @@ namespace MWWorld const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) { - osg::Vec3f pos = mPhysics->getPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight + osg::Vec3f pos = mPhysics->getCollisionObjectPosition(caster) + osg::Vec3f(0,0,mPhysics->getHalfExtents(caster).z() * 0.5); // Spawn at 0.75 * ActorHeight if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible return; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 137dac42e..98517f543 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1113,7 +1113,7 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics) { ESM::Position pos = ptr.getRefData().getPosition(); @@ -1201,7 +1201,8 @@ namespace MWWorld if (haveToMove && newPtr.getRefData().getBaseNode()) { mRendering->moveObject(newPtr, vec); - mPhysics->updatePosition(newPtr); + if (movePhysics) + mPhysics->updatePosition(newPtr); } if (isPlayer) { @@ -1210,7 +1211,7 @@ namespace MWWorld return newPtr; } - MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics) { CellStore *cell = ptr.getCell(); @@ -1221,7 +1222,7 @@ namespace MWWorld cell = getExterior(cellX, cellY); } - return moveObject(ptr, cell, x, y, z); + return moveObject(ptr, cell, x, y, z, movePhysics); } MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) @@ -1373,10 +1374,10 @@ namespace MWWorld player = iter; continue; } - moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z()); + moveObjectImp(iter->first, iter->second.x(), iter->second.y(), iter->second.z(), false); } if(player != results.end()) - moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z()); + moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); mPhysics->debugDraw(); } @@ -3206,7 +3207,7 @@ namespace MWWorld osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target) { osg::Vec3f weaponPos = getActorHeadTransform(actor).getTrans(); - osg::Vec3f targetPos = mPhysics->getPosition(target); + osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target); return (targetPos - weaponPos); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index cf9321da5..6cc5cdc11 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -119,7 +119,7 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust); - Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const ConstPtr &ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); @@ -355,7 +355,7 @@ namespace MWWorld virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); + virtual MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z, bool movePhysics=true); ///< @return an updated Ptr virtual void scaleObject (const Ptr& ptr, float scale); From 796a4a795a95ad2ffa16de61368af9257b41b9c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 03:09:28 +0100 Subject: [PATCH 0018/2007] Avoid the 'spiral of death' --- apps/openmw/mwphysics/physicssystem.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ddb3d0c85..8d4c2c590 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1295,7 +1295,11 @@ namespace MWPhysics mTimeAccum += dt; const float physicsDt = 1.f/60.0f; + + const int maxAllowedSteps = 20; int numSteps = mTimeAccum / (physicsDt); + numSteps = std::min(numSteps, maxAllowedSteps); + mTimeAccum -= numSteps * physicsDt; if (numSteps) From 6fc6913424f07bda08e72c6253fe6e091f423222 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 03:34:00 +0100 Subject: [PATCH 0019/2007] Do not set the cursor when creating it --- components/sdlutil/sdlcursormanager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index afe240609..b372744f5 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -239,8 +239,6 @@ namespace SDLUtil //clean up SDL_FreeSurface(surf); - - _setGUICursor(name); } } From eaf3f5a82920e7b016a3a111de0de05d89cfc68b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 13 Feb 2016 04:14:05 +0100 Subject: [PATCH 0020/2007] Remove unused arguments --- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +--- components/sdlutil/sdlcursormanager.cpp | 6 +++--- components/sdlutil/sdlcursormanager.hpp | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 03fbc9238..f5d78aee6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2020,13 +2020,11 @@ namespace MWGui if(image.valid()) { //everything looks good, send it to the cursor manager - Uint8 size_x = imgSetPointer->getSize().width; - Uint8 size_y = imgSetPointer->getSize().height; Uint8 hotspot_x = imgSetPointer->getHotSpot().left; Uint8 hotspot_y = imgSetPointer->getHotSpot().top; int rotation = imgSetPointer->getRotation(); - mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, size_x, size_y, hotspot_x, hotspot_y); + mCursorManager->createCursor(imgSetPointer->getResourceName(), rotation, image, hotspot_x, hotspot_y); } } } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index b372744f5..ad03083de 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -211,12 +211,12 @@ namespace SDLUtil SDL_SetCursor(mCursorMap.find(name)->second); } - void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::createCursor(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { - _createCursorFromResource(name, rotDegrees, image, size_x, size_y, hotspot_x, hotspot_y); + _createCursorFromResource(name, rotDegrees, image, hotspot_x, hotspot_y); } - void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) + void SDLCursorManager::_createCursorFromResource(const std::string& name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y) { osg::ref_ptr decompressed; diff --git a/components/sdlutil/sdlcursormanager.hpp b/components/sdlutil/sdlcursormanager.hpp index 0db578039..f338778d1 100644 --- a/components/sdlutil/sdlcursormanager.hpp +++ b/components/sdlutil/sdlcursormanager.hpp @@ -29,10 +29,10 @@ namespace SDLUtil /// name of the cursor we changed to ("arrow", "ibeam", etc) virtual void cursorChanged(const std::string &name); - virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + virtual void createCursor(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); private: - void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); + void _createCursorFromResource(const std::string &name, int rotDegrees, osg::Image* image, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); From 25744aaaddd7a28d434e00ae2b426e12097d4e13 Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Sun, 14 Feb 2016 10:28:41 -0500 Subject: [PATCH 0021/2007] Update cell marker appearance - Added bounding box around marker text. Box is black when cell exists otherwise it is red. - Changed format of marker text. - Changed marker text's pivot point to be at center of text. --- apps/opencs/view/render/cell.cpp | 8 +++++++- apps/opencs/view/render/cellmarker.cpp | 27 ++++++++++++++++++++------ apps/opencs/view/render/cellmarker.hpp | 7 +++++-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 0030fd9b8..e7b135891 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -307,7 +307,13 @@ void CSVRender::Cell::setCellArrows (int mask) void CSVRender::Cell::setCellMarker() { - mCellMarker.reset(new CellMarker(mCellNode, mCoordinates)); + bool cellExists = false; + int cellIndex = mData.getCells().searchId(mId); + if (cellIndex > -1) + { + cellExists = !mData.getCells().getRecord(cellIndex).isDeleted(); + } + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const diff --git a/apps/opencs/view/render/cellmarker.cpp b/apps/opencs/view/render/cellmarker.cpp index e8772a586..e0d270f85 100644 --- a/apps/opencs/view/render/cellmarker.cpp +++ b/apps/opencs/view/render/cellmarker.cpp @@ -22,14 +22,27 @@ void CSVRender::CellMarker::buildMarker() { const int characterSize = 20; - // Set up marker text containing cell's coordinates. + // Set up attributes of marker text. osg::ref_ptr markerText (new osgText::Text); - markerText->setBackdropType(osgText::Text::OUTLINE); markerText->setLayout(osgText::Text::LEFT_TO_RIGHT); markerText->setCharacterSize(characterSize); + markerText->setAlignment(osgText::Text::CENTER_CENTER); + markerText->setDrawMode(osgText::Text::TEXT | osgText::Text::FILLEDBOUNDINGBOX); + + // If cell exists then show black bounding box otherwise show red. + if (mExists) + { + markerText->setBoundingBoxColor(osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f)); + } + else + { + markerText->setBoundingBoxColor(osg::Vec4f(1.0f, 0.0f, 0.0f, 1.0f)); + } + + // Add text containing cell's coordinates. std::string coordinatesText = - "#" + boost::lexical_cast(mCoordinates.getX()) + - " " + boost::lexical_cast(mCoordinates.getY()); + boost::lexical_cast(mCoordinates.getX()) + "," + + boost::lexical_cast(mCoordinates.getY()); markerText->setText(coordinatesText); // Add text to marker node. @@ -51,9 +64,11 @@ void CSVRender::CellMarker::positionMarker() CSVRender::CellMarker::CellMarker( osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists ) : mCellNode(cellNode), - mCoordinates(coordinates) + mCoordinates(coordinates), + mExists(cellExists) { // Set up node for cell marker. mMarkerNode = new osg::AutoTransform(); diff --git a/apps/opencs/view/render/cellmarker.hpp b/apps/opencs/view/render/cellmarker.hpp index 08c7e0608..4246b20b8 100644 --- a/apps/opencs/view/render/cellmarker.hpp +++ b/apps/opencs/view/render/cellmarker.hpp @@ -38,6 +38,7 @@ namespace CSVRender osg::Group* mCellNode; osg::ref_ptr mMarkerNode; CSMWorld::CellCoordinates mCoordinates; + bool mExists; // Not implemented. CellMarker(const CellMarker&); @@ -46,7 +47,7 @@ namespace CSVRender /// \brief Build marker containing cell's coordinates. void buildMarker(); - /// \brief Position marker above center of cell. + /// \brief Position marker at center of cell. void positionMarker(); public: @@ -54,9 +55,11 @@ namespace CSVRender /// \brief Constructor. /// \param cellNode Cell to create marker for. /// \param coordinates Coordinates of cell. + /// \param cellExists Whether or not cell exists. CellMarker( osg::Group *cellNode, - const CSMWorld::CellCoordinates& coordinates); + const CSMWorld::CellCoordinates& coordinates, + const bool cellExists); ~CellMarker(); }; From 83a9a164bcba46a32c3f5dfc4e408e9b2bf2c323 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 00:27:39 +0100 Subject: [PATCH 0022/2007] Raise the required bullet version to 2.83 2.82 appears to have a bug that causes the player to be able to phase through certain objects (bug #1587). --- apps/openmw/mwphysics/physicssystem.cpp | 16 ---------------- components/resource/bulletshape.cpp | 14 -------------- components/resource/bulletshapemanager.hpp | 6 ++++++ 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8d4c2c590..784910d51 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -765,18 +765,11 @@ namespace MWPhysics mLeastDistSqr(std::numeric_limits::max()) { } -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) { const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col1; -#endif if (collisionObject != mMe) { btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); @@ -1026,7 +1019,6 @@ namespace MWPhysics std::vector mResult; -#if BT_BULLET_VERSION >= 281 virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) @@ -1034,14 +1026,6 @@ namespace MWPhysics const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) collisionObject = col1Wrap->m_collisionObject; -#else - virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObject* col0, int partId0, int index0, - const btCollisionObject* col1, int partId1, int index1) - { - const btCollisionObject* collisionObject = col0; - if (collisionObject == mTestedAgainst) - collisionObject = col1; -#endif PtrHolder* holder = static_cast(collisionObject->getUserPointer()); if (holder) mResult.push_back(holder->getPtr()); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 6855429c3..dbdbf0c6e 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -65,21 +65,7 @@ btCollisionShape* BulletShape::duplicateCollisionShape(const btCollisionShape *s if(const btBvhTriangleMeshShape* trishape = dynamic_cast(shape)) { -#if BT_BULLET_VERSION >= 283 btScaledBvhTriangleMeshShape* newShape = new btScaledBvhTriangleMeshShape(const_cast(trishape), btVector3(1.f, 1.f, 1.f)); -#else - // work around btScaledBvhTriangleMeshShape bug ( https://code.google.com/p/bullet/issues/detail?id=371 ) in older bullet versions - const btTriangleMesh* oldMesh = static_cast(trishape->getMeshInterface()); - btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); - - // Do not build a new bvh (not needed, since it's the same as the original shape's bvh) - btOptimizedBvh* bvh = const_cast(trishape)->getOptimizedBvh(); - TriangleMeshShape* newShape = new TriangleMeshShape(newMesh, true, bvh == NULL); - // Set original shape's bvh via pointer - // The pointer is safe because the BulletShapeInstance keeps a ref_ptr to the original BulletShape - if (bvh) - newShape->setOptimizedBvh(bvh); -#endif return newShape; } diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 14b26962b..3fe351f90 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -6,9 +6,15 @@ #include +#include + #include "bulletshape.hpp" #include "resourcemanager.hpp" +#if BT_BULLET_VERSION < 283 +#error "OpenMW requires Bullet version 2.83 or later" +#endif + namespace Resource { class SceneManager; From 9eb96b9cb6af2643b21617e391d317c605cf7c7d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 14:34:59 +0100 Subject: [PATCH 0023/2007] Parse the bullet version in FindBullet.cmake --- CMakeLists.txt | 3 +++ cmake/FindBullet.cmake | 8 ++++++++ components/resource/bulletshapemanager.hpp | 6 ------ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 520279e50..f5c468114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,9 @@ find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(SDL2 REQUIRED) find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) +if (NOT BULLET_FOUND OR BULLET_VERSION VERSION_LESS 283) + message(FATAL_ERROR "OpenMW requires Bullet version 2.83 or later") +endif() include_directories("." SYSTEM diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 6d5c517af..d70815dd5 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -14,11 +14,14 @@ # # Copyright (c) 2009, Philip Lowman +# Modified for OpenMW to parse BT_BULLET_VERSION. # # Redistribution AND use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. +include(PreprocessorUtils) + set(BULLET_ROOT $ENV{BULLET_ROOT}) macro(_FIND_BULLET_LIBRARY _var) @@ -75,4 +78,9 @@ if(BULLET_FOUND) #_BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) + + find_file(BULLET_BTSCALAR_FILE NAMES btScalar.h PATHS "${BULLET_INCLUDE_DIR}/LinearMath") + file(READ ${BULLET_BTSCALAR_FILE} BULLET_BTSCALAR_CONTENT) + get_preprocessor_entry(BULLET_BTSCALAR_CONTENT BT_BULLET_VERSION BULLET_VERSION) + message(STATUS "Bullet version: ${BULLET_VERSION}") endif() diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 3fe351f90..14b26962b 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -6,15 +6,9 @@ #include -#include - #include "bulletshape.hpp" #include "resourcemanager.hpp" -#if BT_BULLET_VERSION < 283 -#error "OpenMW requires Bullet version 2.83 or later" -#endif - namespace Resource { class SceneManager; From 3c717a63603b53b301370bdf7cdef12441582748 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 14:38:58 +0100 Subject: [PATCH 0024/2007] Use Qt5 on travis to match the PPA's OSG build --- CI/before_install.linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 1c02bc8d9..b8d8b45e2 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -11,11 +11,11 @@ sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev -sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev +sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev qtbase5-dev if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 +sudo cmake .. -DBUILD_SHARED_LIBS=1 -DDESIRED_QT_VERSION=5 sudo make -j4 sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so From 647a5e091f4298ed390e511d7ce9fbfa64e0aa95 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:16:48 +0100 Subject: [PATCH 0025/2007] Add osgQt to the repository Ensures that it will be built against the correct Qt version. --- CMakeLists.txt | 7 +- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/scenewidget.cpp | 2 +- extern/osgQt/CMakeLists.txt | 21 + extern/osgQt/GraphicsWindowQt | 193 ++++ extern/osgQt/GraphicsWindowQt.cpp | 1063 +++++++++++++++++++++++ 6 files changed, 1281 insertions(+), 7 deletions(-) create mode 100644 extern/osgQt/CMakeLists.txt create mode 100644 extern/osgQt/GraphicsWindowQt create mode 100644 extern/osgQt/GraphicsWindowQt.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c468114..719df3e34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,11 +229,7 @@ IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) endif() -if (USE_QT) - set (OSG_QT osgQt) -endif() - -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle ${OSG_QT} osgUtil osgFX) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) @@ -570,6 +566,7 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) +add_subdirectory (extern/osgQt) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c9245ca9f..6401e4222 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -197,7 +197,7 @@ target_link_libraries(openmw-cs ${OSGVIEWER_LIBRARIES} ${OSGGA_LIBRARIES} ${OSGFX_LIBRARIES} - ${OSGQT_LIBRARIES} + ${EXTERN_OSGQT_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index e5b9171e0..ba31c9b8b 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt new file mode 100644 index 000000000..e8a456da9 --- /dev/null +++ b/extern/osgQt/CMakeLists.txt @@ -0,0 +1,21 @@ +set(OSGQT_LIBRARY "osgQt") + +# Sources + +set(OSGQT_SOURCE_FILES + GraphicsWindowQt.cpp +) + +include_directories(${FFMPEG_INCLUDE_DIRS}) +add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) + +if (DESIRED_QT_VERSION MATCHES 4) + include(${QT_USE_FILE}) + target_link_libraries(${OSGQT_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTOPENGL_LIBRARY}) +else() + qt5_use_modules(${OSGQT_LIBRARY} Core OpenGL) +endif() + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(EXTERN_OSGQT_LIBRARY ${OSGQT_LIBRARY} PARENT_SCOPE) diff --git a/extern/osgQt/GraphicsWindowQt b/extern/osgQt/GraphicsWindowQt new file mode 100644 index 000000000..54d069176 --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt @@ -0,0 +1,193 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGVIEWER_GRAPHICSWINDOWQT +#define OSGVIEWER_GRAPHICSWINDOWQT + +#include + +#include + +#include +#include +#include +#include +#include + +class QInputEvent; +class QGestureEvent; + +namespace osgViewer { + class ViewerBase; +} + +namespace osgQt +{ + +// forward declarations +class GraphicsWindowQt; + +/// The function sets the WindowingSystem to Qt. +void initQtWindowingSystem(); + +/** The function sets the viewer that will be used after entering + * the Qt main loop (QCoreApplication::exec()). + * + * The function also initializes internal structures required for proper + * scene rendering. + * + * The method must be called from main thread. */ +void setViewer( osgViewer::ViewerBase *viewer ); + + +class GLWidget : public QGLWidget +{ + typedef QGLWidget inherited; + +public: + + GLWidget( QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( QGLContext* context, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + GLWidget( const QGLFormat& format, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0, bool forwardKeyEvents = false ); + virtual ~GLWidget(); + + inline void setGraphicsWindow( GraphicsWindowQt* gw ) { _gw = gw; } + inline GraphicsWindowQt* getGraphicsWindow() { return _gw; } + inline const GraphicsWindowQt* getGraphicsWindow() const { return _gw; } + + inline bool getForwardKeyEvents() const { return _forwardKeyEvents; } + virtual void setForwardKeyEvents( bool f ) { _forwardKeyEvents = f; } + + inline bool getTouchEventsEnabled() const { return _touchEventsEnabled; } + void setTouchEventsEnabled( bool e ); + + void setKeyboardModifiers( QInputEvent* event ); + + virtual void keyPressEvent( QKeyEvent* event ); + virtual void keyReleaseEvent( QKeyEvent* event ); + virtual void mousePressEvent( QMouseEvent* event ); + virtual void mouseReleaseEvent( QMouseEvent* event ); + virtual void mouseDoubleClickEvent( QMouseEvent* event ); + virtual void mouseMoveEvent( QMouseEvent* event ); + virtual void wheelEvent( QWheelEvent* event ); + virtual bool gestureEvent( QGestureEvent* event ); + +protected: + + int getNumDeferredEvents() + { + QMutexLocker lock(&_deferredEventQueueMutex); + return _deferredEventQueue.count(); + } + void enqueueDeferredEvent(QEvent::Type eventType, QEvent::Type removeEventType = QEvent::None) + { + QMutexLocker lock(&_deferredEventQueueMutex); + + if (removeEventType != QEvent::None) + { + if (_deferredEventQueue.removeOne(removeEventType)) + _eventCompressor.remove(eventType); + } + + if (_eventCompressor.find(eventType) == _eventCompressor.end()) + { + _deferredEventQueue.enqueue(eventType); + _eventCompressor.insert(eventType); + } + } + void processDeferredEvents(); + + friend class GraphicsWindowQt; + GraphicsWindowQt* _gw; + + QMutex _deferredEventQueueMutex; + QQueue _deferredEventQueue; + QSet _eventCompressor; + + bool _touchEventsEnabled; + + bool _forwardKeyEvents; + qreal _devicePixelRatio; + + virtual void resizeEvent( QResizeEvent* event ); + virtual void moveEvent( QMoveEvent* event ); + virtual void glDraw(); + virtual bool event( QEvent* event ); +}; + +class GraphicsWindowQt : public osgViewer::GraphicsWindow +{ +public: + GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent = NULL, const QGLWidget* shareWidget = NULL, Qt::WindowFlags f = 0 ); + GraphicsWindowQt( GLWidget* widget ); + virtual ~GraphicsWindowQt(); + + inline GLWidget* getGLWidget() { return _widget; } + inline const GLWidget* getGLWidget() const { return _widget; } + + /// deprecated + inline GLWidget* getGraphWidget() { return _widget; } + /// deprecated + inline const GLWidget* getGraphWidget() const { return _widget; } + + struct WindowData : public osg::Referenced + { + WindowData( GLWidget* widget = NULL, QWidget* parent = NULL ): _widget(widget), _parent(parent) {} + GLWidget* _widget; + QWidget* _parent; + }; + + bool init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ); + + static QGLFormat traits2qglFormat( const osg::GraphicsContext::Traits* traits ); + static void qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ); + static osg::GraphicsContext::Traits* createTraits( const QGLWidget* widget ); + + virtual bool setWindowRectangleImplementation( int x, int y, int width, int height ); + virtual void getWindowRectangle( int& x, int& y, int& width, int& height ); + virtual bool setWindowDecorationImplementation( bool windowDecoration ); + virtual bool getWindowDecoration() const; + virtual void grabFocus(); + virtual void grabFocusIfPointerInWindow(); + virtual void raiseWindow(); + virtual void setWindowName( const std::string& name ); + virtual std::string getWindowName(); + virtual void useCursor( bool cursorOn ); + virtual void setCursor( MouseCursor cursor ); + inline bool getTouchEventsEnabled() const { return _widget->getTouchEventsEnabled(); } + virtual void setTouchEventsEnabled( bool e ) { _widget->setTouchEventsEnabled(e); } + + + virtual bool valid() const; + virtual bool realizeImplementation(); + virtual bool isRealizedImplementation() const; + virtual void closeImplementation(); + virtual bool makeCurrentImplementation(); + virtual bool releaseContextImplementation(); + virtual void swapBuffersImplementation(); + virtual void runOperations(); + + virtual void requestWarpPointer( float x, float y ); + +protected: + + friend class GLWidget; + GLWidget* _widget; + bool _ownsWidget; + QCursor _currentCursor; + bool _realized; +}; + +} + +#endif diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp new file mode 100644 index 000000000..2001c8b31 --- /dev/null +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -0,0 +1,1063 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 2009 Wang Rui + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. +*/ + +#include "GraphicsWindowQt" + +#include +#include +#include +#include + +#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0)) +# define USE_GESTURES +# include +# include +#endif + +using namespace osgQt; + + +class QtKeyboardMap +{ + +public: + QtKeyboardMap() + { + mKeyMap[Qt::Key_Escape ] = osgGA::GUIEventAdapter::KEY_Escape; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Home ] = osgGA::GUIEventAdapter::KEY_Home; + mKeyMap[Qt::Key_Enter ] = osgGA::GUIEventAdapter::KEY_KP_Enter; + mKeyMap[Qt::Key_End ] = osgGA::GUIEventAdapter::KEY_End; + mKeyMap[Qt::Key_Return ] = osgGA::GUIEventAdapter::KEY_Return; + mKeyMap[Qt::Key_PageUp ] = osgGA::GUIEventAdapter::KEY_Page_Up; + mKeyMap[Qt::Key_PageDown ] = osgGA::GUIEventAdapter::KEY_Page_Down; + mKeyMap[Qt::Key_Left ] = osgGA::GUIEventAdapter::KEY_Left; + mKeyMap[Qt::Key_Right ] = osgGA::GUIEventAdapter::KEY_Right; + mKeyMap[Qt::Key_Up ] = osgGA::GUIEventAdapter::KEY_Up; + mKeyMap[Qt::Key_Down ] = osgGA::GUIEventAdapter::KEY_Down; + mKeyMap[Qt::Key_Backspace ] = osgGA::GUIEventAdapter::KEY_BackSpace; + mKeyMap[Qt::Key_Tab ] = osgGA::GUIEventAdapter::KEY_Tab; + mKeyMap[Qt::Key_Space ] = osgGA::GUIEventAdapter::KEY_Space; + mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_Delete; + mKeyMap[Qt::Key_Alt ] = osgGA::GUIEventAdapter::KEY_Alt_L; + mKeyMap[Qt::Key_Shift ] = osgGA::GUIEventAdapter::KEY_Shift_L; + mKeyMap[Qt::Key_Control ] = osgGA::GUIEventAdapter::KEY_Control_L; + mKeyMap[Qt::Key_Meta ] = osgGA::GUIEventAdapter::KEY_Meta_L; + + mKeyMap[Qt::Key_F1 ] = osgGA::GUIEventAdapter::KEY_F1; + mKeyMap[Qt::Key_F2 ] = osgGA::GUIEventAdapter::KEY_F2; + mKeyMap[Qt::Key_F3 ] = osgGA::GUIEventAdapter::KEY_F3; + mKeyMap[Qt::Key_F4 ] = osgGA::GUIEventAdapter::KEY_F4; + mKeyMap[Qt::Key_F5 ] = osgGA::GUIEventAdapter::KEY_F5; + mKeyMap[Qt::Key_F6 ] = osgGA::GUIEventAdapter::KEY_F6; + mKeyMap[Qt::Key_F7 ] = osgGA::GUIEventAdapter::KEY_F7; + mKeyMap[Qt::Key_F8 ] = osgGA::GUIEventAdapter::KEY_F8; + mKeyMap[Qt::Key_F9 ] = osgGA::GUIEventAdapter::KEY_F9; + mKeyMap[Qt::Key_F10 ] = osgGA::GUIEventAdapter::KEY_F10; + mKeyMap[Qt::Key_F11 ] = osgGA::GUIEventAdapter::KEY_F11; + mKeyMap[Qt::Key_F12 ] = osgGA::GUIEventAdapter::KEY_F12; + mKeyMap[Qt::Key_F13 ] = osgGA::GUIEventAdapter::KEY_F13; + mKeyMap[Qt::Key_F14 ] = osgGA::GUIEventAdapter::KEY_F14; + mKeyMap[Qt::Key_F15 ] = osgGA::GUIEventAdapter::KEY_F15; + mKeyMap[Qt::Key_F16 ] = osgGA::GUIEventAdapter::KEY_F16; + mKeyMap[Qt::Key_F17 ] = osgGA::GUIEventAdapter::KEY_F17; + mKeyMap[Qt::Key_F18 ] = osgGA::GUIEventAdapter::KEY_F18; + mKeyMap[Qt::Key_F19 ] = osgGA::GUIEventAdapter::KEY_F19; + mKeyMap[Qt::Key_F20 ] = osgGA::GUIEventAdapter::KEY_F20; + + mKeyMap[Qt::Key_hyphen ] = '-'; + mKeyMap[Qt::Key_Equal ] = '='; + + mKeyMap[Qt::Key_division ] = osgGA::GUIEventAdapter::KEY_KP_Divide; + mKeyMap[Qt::Key_multiply ] = osgGA::GUIEventAdapter::KEY_KP_Multiply; + mKeyMap[Qt::Key_Minus ] = '-'; + mKeyMap[Qt::Key_Plus ] = '+'; + //mKeyMap[Qt::Key_H ] = osgGA::GUIEventAdapter::KEY_KP_Home; + //mKeyMap[Qt::Key_ ] = osgGA::GUIEventAdapter::KEY_KP_Up; + //mKeyMap[92 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Up; + //mKeyMap[86 ] = osgGA::GUIEventAdapter::KEY_KP_Left; + //mKeyMap[87 ] = osgGA::GUIEventAdapter::KEY_KP_Begin; + //mKeyMap[88 ] = osgGA::GUIEventAdapter::KEY_KP_Right; + //mKeyMap[83 ] = osgGA::GUIEventAdapter::KEY_KP_End; + //mKeyMap[84 ] = osgGA::GUIEventAdapter::KEY_KP_Down; + //mKeyMap[85 ] = osgGA::GUIEventAdapter::KEY_KP_Page_Down; + mKeyMap[Qt::Key_Insert ] = osgGA::GUIEventAdapter::KEY_KP_Insert; + //mKeyMap[Qt::Key_Delete ] = osgGA::GUIEventAdapter::KEY_KP_Delete; + } + + ~QtKeyboardMap() + { + } + + int remapKey(QKeyEvent* event) + { + KeyMap::iterator itr = mKeyMap.find(event->key()); + if (itr == mKeyMap.end()) + { + return int(*(event->text().toLatin1().data())); + } + else + return itr->second; + } + + private: + typedef std::map KeyMap; + KeyMap mKeyMap; +}; + +static QtKeyboardMap s_QtKeyboardMap; + + +/// The object responsible for the scene re-rendering. +class HeartBeat : public QObject { +public: + int _timerId; + osg::Timer _lastFrameStartTime; + osg::observer_ptr< osgViewer::ViewerBase > _viewer; + + virtual ~HeartBeat(); + + void init( osgViewer::ViewerBase *viewer ); + void stopTimer(); + void timerEvent( QTimerEvent *event ); + + static HeartBeat* instance(); +private: + HeartBeat(); + + static QPointer heartBeat; +}; + +QPointer HeartBeat::heartBeat; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0)) + #define GETDEVICEPIXELRATIO() 1.0 +#else + #define GETDEVICEPIXELRATIO() devicePixelRatio() +#endif + +GLWidget::GLWidget( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, bool forwardKeyEvents ) +: QGLWidget(parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( QGLContext* context, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(context, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::GLWidget( const QGLFormat& format, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f, + bool forwardKeyEvents ) +: QGLWidget(format, parent, shareWidget, f), +_gw( NULL ), +_touchEventsEnabled( false ), +_forwardKeyEvents( forwardKeyEvents ) +{ + _devicePixelRatio = GETDEVICEPIXELRATIO(); +} + +GLWidget::~GLWidget() +{ + // close GraphicsWindowQt and remove the reference to us + if( _gw ) + { + _gw->close(); + _gw->_widget = NULL; + _gw = NULL; + } +} + +void GLWidget::setTouchEventsEnabled(bool e) +{ +#ifdef USE_GESTURES + if (e==_touchEventsEnabled) + return; + + _touchEventsEnabled = e; + + if (_touchEventsEnabled) + { + grabGesture(Qt::PinchGesture); + } + else + { + ungrabGesture(Qt::PinchGesture); + } +#endif +} + +void GLWidget::processDeferredEvents() +{ + QQueue deferredEventQueueCopy; + { + QMutexLocker lock(&_deferredEventQueueMutex); + deferredEventQueueCopy = _deferredEventQueue; + _eventCompressor.clear(); + _deferredEventQueue.clear(); + } + + while (!deferredEventQueueCopy.isEmpty()) + { + QEvent event(deferredEventQueueCopy.dequeue()); + QGLWidget::event(&event); + } +} + +bool GLWidget::event( QEvent* event ) +{ +#ifdef USE_GESTURES + if ( event->type()==QEvent::Gesture ) + return gestureEvent(static_cast(event)); +#endif + + // QEvent::Hide + // + // workaround "Qt-workaround" that does glFinish before hiding the widget + // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0) + // + // Qt makes the context current, performs glFinish, and releases the context. + // This makes the problem in OSG multithreaded environment as the context + // is active in another thread, thus it can not be made current for the purpose + // of glFinish in this thread. + + // QEvent::ParentChange + // + // Reparenting GLWidget may create a new underlying window and a new GL context. + // Qt will then call doneCurrent on the GL context about to be deleted. The thread + // where old GL context was current has no longer current context to render to and + // we cannot make new GL context current in this thread. + + // We workaround above problems by deferring execution of problematic event requests. + // These events has to be enqueue and executed later in a main GUI thread (GUI operations + // outside the main thread are not allowed) just before makeCurrent is called from the + // right thread. The good place for doing that is right after swap in a swapBuffersImplementation. + + if (event->type() == QEvent::Hide) + { + // enqueue only the last of QEvent::Hide and QEvent::Show + enqueueDeferredEvent(QEvent::Hide, QEvent::Show); + return true; + } + else if (event->type() == QEvent::Show) + { + // enqueue only the last of QEvent::Show or QEvent::Hide + enqueueDeferredEvent(QEvent::Show, QEvent::Hide); + return true; + } + else if (event->type() == QEvent::ParentChange) + { + // enqueue only the last QEvent::ParentChange + enqueueDeferredEvent(QEvent::ParentChange); + return true; + } + + // perform regular event handling + return QGLWidget::event( event ); +} + +void GLWidget::setKeyboardModifiers( QInputEvent* event ) +{ + int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier); + unsigned int mask = 0; + if ( modkey & Qt::ShiftModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT; + if ( modkey & Qt::ControlModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL; + if ( modkey & Qt::AltModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_ALT; + _gw->getEventQueue()->getCurrentEventState()->setModKeyMask( mask ); +} + +void GLWidget::resizeEvent( QResizeEvent* event ) +{ + const QSize& size = event->size(); + + int scaled_width = static_cast(size.width()*_devicePixelRatio); + int scaled_height = static_cast(size.height()*_devicePixelRatio); + _gw->resized( x(), y(), scaled_width, scaled_height); + _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height ); + _gw->requestRedraw(); +} + +void GLWidget::moveEvent( QMoveEvent* event ) +{ + const QPoint& pos = event->pos(); + int scaled_width = static_cast(width()*_devicePixelRatio); + int scaled_height = static_cast(height()*_devicePixelRatio); + _gw->resized( pos.x(), pos.y(), scaled_width, scaled_height ); + _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width, scaled_height ); +} + +void GLWidget::glDraw() +{ + _gw->requestRedraw(); +} + +void GLWidget::keyPressEvent( QKeyEvent* event ) +{ + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyPress( value ); + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyPressEvent( event ); +} + +void GLWidget::keyReleaseEvent( QKeyEvent* event ) +{ + if( event->isAutoRepeat() ) + { + event->ignore(); + } + else + { + setKeyboardModifiers( event ); + int value = s_QtKeyboardMap.remapKey( event ); + _gw->getEventQueue()->keyRelease( value ); + } + + // this passes the event to the regular Qt key event processing, + // among others, it closes popup windows on ESC and forwards the event to the parent widgets + if( _forwardKeyEvents ) + inherited::keyReleaseEvent( event ); +} + +void GLWidget::mousePressEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseReleaseEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseButtonRelease( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseDoubleClickEvent( QMouseEvent* event ) +{ + int button = 0; + switch ( event->button() ) + { + case Qt::LeftButton: button = 1; break; + case Qt::MidButton: button = 2; break; + case Qt::RightButton: button = 3; break; + case Qt::NoButton: button = 0; break; + default: button = 0; break; + } + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseDoubleButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button ); +} + +void GLWidget::mouseMoveEvent( QMouseEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseMotion( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio ); +} + +void GLWidget::wheelEvent( QWheelEvent* event ) +{ + setKeyboardModifiers( event ); + _gw->getEventQueue()->mouseScroll( + event->orientation() == Qt::Vertical ? + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN) : + (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_LEFT : osgGA::GUIEventAdapter::SCROLL_RIGHT) ); +} + +#ifdef USE_GESTURES +static osgGA::GUIEventAdapter::TouchPhase translateQtGestureState( Qt::GestureState state ) +{ + osgGA::GUIEventAdapter::TouchPhase touchPhase; + switch ( state ) + { + case Qt::GestureStarted: + touchPhase = osgGA::GUIEventAdapter::TOUCH_BEGAN; + break; + case Qt::GestureUpdated: + touchPhase = osgGA::GUIEventAdapter::TOUCH_MOVED; + break; + case Qt::GestureFinished: + case Qt::GestureCanceled: + touchPhase = osgGA::GUIEventAdapter::TOUCH_ENDED; + break; + default: + touchPhase = osgGA::GUIEventAdapter::TOUCH_UNKNOWN; + }; + + return touchPhase; +} +#endif + + +bool GLWidget::gestureEvent( QGestureEvent* qevent ) +{ +#ifndef USE_GESTURES + return false; +#else + + bool accept = false; + + if ( QPinchGesture* pinch = static_cast(qevent->gesture(Qt::PinchGesture) ) ) + { + const QPointF qcenterf = pinch->centerPoint(); + const float angle = pinch->totalRotationAngle(); + const float scale = pinch->totalScaleFactor(); + + const QPoint pinchCenterQt = mapFromGlobal(qcenterf.toPoint()); + const osg::Vec2 pinchCenter( pinchCenterQt.x(), pinchCenterQt.y() ); + + //We don't have absolute positions of the two touches, only a scale and rotation + //Hence we create pseudo-coordinates which are reasonable, and centered around the + //real position + const float radius = (width()+height())/4; + const osg::Vec2 vector( scale*cos(angle)*radius, scale*sin(angle)*radius); + const osg::Vec2 p0 = pinchCenter+vector; + const osg::Vec2 p1 = pinchCenter-vector; + + osg::ref_ptr event = 0; + const osgGA::GUIEventAdapter::TouchPhase touchPhase = translateQtGestureState( pinch->state() ); + if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_BEGAN ) + { + event = _gw->getEventQueue()->touchBegan(0 , touchPhase, p0[0], p0[1] ); + } + else if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_MOVED ) + { + event = _gw->getEventQueue()->touchMoved( 0, touchPhase, p0[0], p0[1] ); + } + else + { + event = _gw->getEventQueue()->touchEnded( 0, touchPhase, p0[0], p0[1], 1 ); + } + + if ( event ) + { + event->addTouchPoint( 1, touchPhase, p1[0], p1[1] ); + accept = true; + } + } + + if ( accept ) + qevent->accept(); + + return accept; +#endif +} + + + +GraphicsWindowQt::GraphicsWindowQt( osg::GraphicsContext::Traits* traits, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +: _realized(false) +{ + + _widget = NULL; + _traits = traits; + init( parent, shareWidget, f ); +} + +GraphicsWindowQt::GraphicsWindowQt( GLWidget* widget ) +: _realized(false) +{ + _widget = widget; + _traits = _widget ? createTraits( _widget ) : new osg::GraphicsContext::Traits; + init( NULL, NULL, 0 ); +} + +GraphicsWindowQt::~GraphicsWindowQt() +{ + close(); + + // remove reference from GLWidget + if ( _widget ) + _widget->_gw = NULL; +} + +bool GraphicsWindowQt::init( QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f ) +{ + // update _widget and parent by WindowData + WindowData* windowData = _traits.get() ? dynamic_cast(_traits->inheritedWindowData.get()) : 0; + if ( !_widget ) + _widget = windowData ? windowData->_widget : NULL; + if ( !parent ) + parent = windowData ? windowData->_parent : NULL; + + // create widget if it does not exist + _ownsWidget = _widget == NULL; + if ( !_widget ) + { + // shareWidget + if ( !shareWidget ) { + GraphicsWindowQt* sharedContextQt = dynamic_cast(_traits->sharedContext.get()); + if ( sharedContextQt ) + shareWidget = sharedContextQt->getGLWidget(); + } + + // WindowFlags + Qt::WindowFlags flags = f | Qt::Window | Qt::CustomizeWindowHint; + if ( _traits->windowDecoration ) + flags |= Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowSystemMenuHint +#if (QT_VERSION_CHECK(4, 5, 0) <= QT_VERSION) + | Qt::WindowCloseButtonHint +#endif + ; + + // create widget + _widget = new GLWidget( traits2qglFormat( _traits.get() ), parent, shareWidget, flags ); + } + + // set widget name and position + // (do not set it when we inherited the widget) + if ( _ownsWidget ) + { + _widget->setWindowTitle( _traits->windowName.c_str() ); + _widget->move( _traits->x, _traits->y ); + if ( !_traits->supportsResize ) _widget->setFixedSize( _traits->width, _traits->height ); + else _widget->resize( _traits->width, _traits->height ); + } + + // initialize widget properties + _widget->setAutoBufferSwap( false ); + _widget->setMouseTracking( true ); + _widget->setFocusPolicy( Qt::WheelFocus ); + _widget->setGraphicsWindow( this ); + useCursor( _traits->useCursor ); + + // initialize State + setState( new osg::State ); + getState()->setGraphicsContext(this); + + // initialize contextID + if ( _traits.valid() && _traits->sharedContext.valid() ) + { + getState()->setContextID( _traits->sharedContext->getState()->getContextID() ); + incrementContextIDUsageCount( getState()->getContextID() ); + } + else + { + getState()->setContextID( osg::GraphicsContext::createNewContextID() ); + } + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + return true; +} + +QGLFormat GraphicsWindowQt::traits2qglFormat( const osg::GraphicsContext::Traits* traits ) +{ + QGLFormat format( QGLFormat::defaultFormat() ); + + format.setAlphaBufferSize( traits->alpha ); + format.setRedBufferSize( traits->red ); + format.setGreenBufferSize( traits->green ); + format.setBlueBufferSize( traits->blue ); + format.setDepthBufferSize( traits->depth ); + format.setStencilBufferSize( traits->stencil ); + format.setSampleBuffers( traits->sampleBuffers ); + format.setSamples( traits->samples ); + + format.setAlpha( traits->alpha>0 ); + format.setDepth( traits->depth>0 ); + format.setStencil( traits->stencil>0 ); + format.setDoubleBuffer( traits->doubleBuffer ); + format.setSwapInterval( traits->vsync ? 1 : 0 ); + format.setStereo( traits->quadBufferStereo ? 1 : 0 ); + + return format; +} + +void GraphicsWindowQt::qglFormat2traits( const QGLFormat& format, osg::GraphicsContext::Traits* traits ) +{ + traits->red = format.redBufferSize(); + traits->green = format.greenBufferSize(); + traits->blue = format.blueBufferSize(); + traits->alpha = format.alpha() ? format.alphaBufferSize() : 0; + traits->depth = format.depth() ? format.depthBufferSize() : 0; + traits->stencil = format.stencil() ? format.stencilBufferSize() : 0; + + traits->sampleBuffers = format.sampleBuffers() ? 1 : 0; + traits->samples = format.samples(); + + traits->quadBufferStereo = format.stereo(); + traits->doubleBuffer = format.doubleBuffer(); + + traits->vsync = format.swapInterval() >= 1; +} + +osg::GraphicsContext::Traits* GraphicsWindowQt::createTraits( const QGLWidget* widget ) +{ + osg::GraphicsContext::Traits *traits = new osg::GraphicsContext::Traits; + + qglFormat2traits( widget->format(), traits ); + + QRect r = widget->geometry(); + traits->x = r.x(); + traits->y = r.y(); + traits->width = r.width(); + traits->height = r.height(); + + traits->windowName = widget->windowTitle().toLocal8Bit().data(); + Qt::WindowFlags f = widget->windowFlags(); + traits->windowDecoration = ( f & Qt::WindowTitleHint ) && + ( f & Qt::WindowMinMaxButtonsHint ) && + ( f & Qt::WindowSystemMenuHint ); + QSizePolicy sp = widget->sizePolicy(); + traits->supportsResize = sp.horizontalPolicy() != QSizePolicy::Fixed || + sp.verticalPolicy() != QSizePolicy::Fixed; + + return traits; +} + +bool GraphicsWindowQt::setWindowRectangleImplementation( int x, int y, int width, int height ) +{ + if ( _widget == NULL ) + return false; + + _widget->setGeometry( x, y, width, height ); + return true; +} + +void GraphicsWindowQt::getWindowRectangle( int& x, int& y, int& width, int& height ) +{ + if ( _widget ) + { + const QRect& geom = _widget->geometry(); + x = geom.x(); + y = geom.y(); + width = geom.width(); + height = geom.height(); + } +} + +bool GraphicsWindowQt::setWindowDecorationImplementation( bool windowDecoration ) +{ + Qt::WindowFlags flags = Qt::Window|Qt::CustomizeWindowHint;//|Qt::WindowStaysOnTopHint; + if ( windowDecoration ) + flags |= Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowSystemMenuHint; + _traits->windowDecoration = windowDecoration; + + if ( _widget ) + { + _widget->setWindowFlags( flags ); + + return true; + } + + return false; +} + +bool GraphicsWindowQt::getWindowDecoration() const +{ + return _traits->windowDecoration; +} + +void GraphicsWindowQt::grabFocus() +{ + if ( _widget ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::grabFocusIfPointerInWindow() +{ + if ( _widget->underMouse() ) + _widget->setFocus( Qt::ActiveWindowFocusReason ); +} + +void GraphicsWindowQt::raiseWindow() +{ + if ( _widget ) + _widget->raise(); +} + +void GraphicsWindowQt::setWindowName( const std::string& name ) +{ + if ( _widget ) + _widget->setWindowTitle( name.c_str() ); +} + +std::string GraphicsWindowQt::getWindowName() +{ + return _widget ? _widget->windowTitle().toStdString() : ""; +} + +void GraphicsWindowQt::useCursor( bool cursorOn ) +{ + if ( _widget ) + { + _traits->useCursor = cursorOn; + if ( !cursorOn ) _widget->setCursor( Qt::BlankCursor ); + else _widget->setCursor( _currentCursor ); + } +} + +void GraphicsWindowQt::setCursor( MouseCursor cursor ) +{ + if ( cursor==InheritCursor && _widget ) + { + _widget->unsetCursor(); + } + + switch ( cursor ) + { + case NoCursor: _currentCursor = Qt::BlankCursor; break; + case RightArrowCursor: case LeftArrowCursor: _currentCursor = Qt::ArrowCursor; break; + case InfoCursor: _currentCursor = Qt::SizeAllCursor; break; + case DestroyCursor: _currentCursor = Qt::ForbiddenCursor; break; + case HelpCursor: _currentCursor = Qt::WhatsThisCursor; break; + case CycleCursor: _currentCursor = Qt::ForbiddenCursor; break; + case SprayCursor: _currentCursor = Qt::SizeAllCursor; break; + case WaitCursor: _currentCursor = Qt::WaitCursor; break; + case TextCursor: _currentCursor = Qt::IBeamCursor; break; + case CrosshairCursor: _currentCursor = Qt::CrossCursor; break; + case HandCursor: _currentCursor = Qt::OpenHandCursor; break; + case UpDownCursor: _currentCursor = Qt::SizeVerCursor; break; + case LeftRightCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopSideCursor: case BottomSideCursor: _currentCursor = Qt::UpArrowCursor; break; + case LeftSideCursor: case RightSideCursor: _currentCursor = Qt::SizeHorCursor; break; + case TopLeftCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case TopRightCorner: _currentCursor = Qt::SizeFDiagCursor; break; + case BottomRightCorner: _currentCursor = Qt::SizeBDiagCursor; break; + case BottomLeftCorner: _currentCursor = Qt::SizeFDiagCursor; break; + default: break; + }; + if ( _widget ) _widget->setCursor( _currentCursor ); +} + +bool GraphicsWindowQt::valid() const +{ + return _widget && _widget->isValid(); +} + +bool GraphicsWindowQt::realizeImplementation() +{ + // save the current context + // note: this will save only Qt-based contexts + const QGLContext *savedContext = QGLContext::currentContext(); + + // initialize GL context for the widget + if ( !valid() ) + _widget->glInit(); + + // make current + _realized = true; + bool result = makeCurrent(); + _realized = false; + + // fail if we do not have current context + if ( !result ) + { + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + OSG_WARN << "Window realize: Can make context current." << std::endl; + return false; + } + + _realized = true; + + // make sure the event queue has the correct window rectangle size and input range + getEventQueue()->syncWindowRectangleWithGraphicsContext(); + + // make this window's context not current + // note: this must be done as we will probably make the context current from another thread + // and it is not allowed to have one context current in two threads + if( !releaseContext() ) + OSG_WARN << "Window realize: Can not release context." << std::endl; + + // restore previous context + if ( savedContext ) + const_cast< QGLContext* >( savedContext )->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::isRealizedImplementation() const +{ + return _realized; +} + +void GraphicsWindowQt::closeImplementation() +{ + if ( _widget ) + _widget->close(); + _realized = false; +} + +void GraphicsWindowQt::runOperations() +{ + // While in graphics thread this is last chance to do something useful before + // graphics thread will execute its operations. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); + + GraphicsWindow::runOperations(); +} + +bool GraphicsWindowQt::makeCurrentImplementation() +{ + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + _widget->makeCurrent(); + + return true; +} + +bool GraphicsWindowQt::releaseContextImplementation() +{ + _widget->doneCurrent(); + return true; +} + +void GraphicsWindowQt::swapBuffersImplementation() +{ + _widget->swapBuffers(); + + // FIXME: the processDeferredEvents should really be executed in a GUI (main) thread context but + // I couln't find any reliable way to do this. For now, lets hope non of *GUI thread only operations* will + // be executed in a QGLWidget::event handler. On the other hand, calling GUI only operations in the + // QGLWidget event handler is an indication of a Qt bug. + if (_widget->getNumDeferredEvents() > 0) + _widget->processDeferredEvents(); + + // We need to call makeCurrent here to restore our previously current context + // which may be changed by the processDeferredEvents function. + if (QGLContext::currentContext() != _widget->context()) + _widget->makeCurrent(); +} + +void GraphicsWindowQt::requestWarpPointer( float x, float y ) +{ + if ( _widget ) + QCursor::setPos( _widget->mapToGlobal(QPoint((int)x,(int)y)) ); +} + + +class QtWindowingSystem : public osg::GraphicsContext::WindowingSystemInterface +{ +public: + + QtWindowingSystem() + { + OSG_INFO << "QtWindowingSystemInterface()" << std::endl; + } + + ~QtWindowingSystem() + { + if (osg::Referenced::getDeleteHandler()) + { + osg::Referenced::getDeleteHandler()->setNumFramesToRetainObjects(0); + osg::Referenced::getDeleteHandler()->flushAll(); + } + } + + // Access the Qt windowing system through this singleton class. + static QtWindowingSystem* getInterface() + { + static QtWindowingSystem* qtInterface = new QtWindowingSystem; + return qtInterface; + } + + // Return the number of screens present in the system + virtual unsigned int getNumScreens( const osg::GraphicsContext::ScreenIdentifier& /*si*/ ) + { + OSG_WARN << "osgQt: getNumScreens() not implemented yet." << std::endl; + return 0; + } + + // Return the resolution of specified screen + // (0,0) is returned if screen is unknown + virtual void getScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: getScreenSettings() not implemented yet." << std::endl; + } + + // Set the resolution for given screen + virtual bool setScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*si*/, const osg::GraphicsContext::ScreenSettings & /*resolution*/ ) + { + OSG_WARN << "osgQt: setScreenSettings() not implemented yet." << std::endl; + return false; + } + + // Enumerates available resolutions + virtual void enumerateScreenSettings( const osg::GraphicsContext::ScreenIdentifier& /*screenIdentifier*/, osg::GraphicsContext::ScreenSettingsList & /*resolution*/ ) + { + OSG_WARN << "osgQt: enumerateScreenSettings() not implemented yet." << std::endl; + } + + // Create a graphics context with given traits + virtual osg::GraphicsContext* createGraphicsContext( osg::GraphicsContext::Traits* traits ) + { + if (traits->pbuffer) + { + OSG_WARN << "osgQt: createGraphicsContext - pbuffer not implemented yet." << std::endl; + return NULL; + } + else + { + osg::ref_ptr< GraphicsWindowQt > window = new GraphicsWindowQt( traits ); + if (window->valid()) return window.release(); + else return NULL; + } + } + +private: + + // No implementation for these + QtWindowingSystem( const QtWindowingSystem& ); + QtWindowingSystem& operator=( const QtWindowingSystem& ); +}; + + +// declare C entry point for static compilation. +extern "C" void graphicswindow_Qt(void) +{ + osg::GraphicsContext::setWindowingSystemInterface(QtWindowingSystem::getInterface()); +} + + +void osgQt::initQtWindowingSystem() +{ + graphicswindow_Qt(); +} + + + +void osgQt::setViewer( osgViewer::ViewerBase *viewer ) +{ + HeartBeat::instance()->init( viewer ); +} + + +/// Constructor. Must be called from main thread. +HeartBeat::HeartBeat() : _timerId( 0 ) +{ +} + + +/// Destructor. Must be called from main thread. +HeartBeat::~HeartBeat() +{ + stopTimer(); +} + +HeartBeat* HeartBeat::instance() +{ + if (!heartBeat) + { + heartBeat = new HeartBeat(); + } + return heartBeat; +} + +void HeartBeat::stopTimer() +{ + if ( _timerId != 0 ) + { + killTimer( _timerId ); + _timerId = 0; + } +} + + +/// Initializes the loop for viewer. Must be called from main thread. +void HeartBeat::init( osgViewer::ViewerBase *viewer ) +{ + if( _viewer == viewer ) + return; + + stopTimer(); + + _viewer = viewer; + + if( viewer ) + { + _timerId = startTimer( 0 ); + _lastFrameStartTime.setStartTick( 0 ); + } +} + + +void HeartBeat::timerEvent( QTimerEvent */*event*/ ) +{ + osg::ref_ptr< osgViewer::ViewerBase > viewer; + if( !_viewer.lock( viewer ) ) + { + // viewer has been deleted -> stop timer + stopTimer(); + return; + } + + // limit the frame rate + if( viewer->getRunMaxFrameRate() > 0.0) + { + double dt = _lastFrameStartTime.time_s(); + double minFrameTime = 1.0 / viewer->getRunMaxFrameRate(); + if (dt < minFrameTime) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(minFrameTime-dt))); + } + else + { + // avoid excessive CPU loading when no frame is required in ON_DEMAND mode + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + double dt = _lastFrameStartTime.time_s(); + if (dt < 0.01) + OpenThreads::Thread::microSleep(static_cast(1000000.0*(0.01-dt))); + } + + // record start frame time + _lastFrameStartTime.setStartTick(); + + // make frame + if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND ) + { + if( viewer->checkNeedToDoFrame() ) + { + viewer->frame(); + } + } + else + { + viewer->frame(); + } + } +} From b77ed829b963179e4848035b55eb71ce06625040 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:17:23 +0100 Subject: [PATCH 0026/2007] Revert "Use Qt5 on travis to match the PPA's OSG build" This reverts commit 3c717a63603b53b301370bdf7cdef12441582748. --- CI/before_install.linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index b8d8b45e2..1c02bc8d9 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -11,11 +11,11 @@ sudo apt-get update -qq sudo apt-get install -qq libgtest-dev google-mock sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev -sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev qtbase5-dev +sudo apt-get install -qq libbullet-dev libopenscenegraph-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi sudo mkdir /usr/src/gtest/build cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 -DDESIRED_QT_VERSION=5 +sudo cmake .. -DBUILD_SHARED_LIBS=1 sudo make -j4 sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so From 80392775efe2c594005bd89ca1450dc934b9e19c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 15:18:06 +0100 Subject: [PATCH 0027/2007] Fix copy&paste error --- extern/osgQt/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/extern/osgQt/CMakeLists.txt b/extern/osgQt/CMakeLists.txt index e8a456da9..3bd08a390 100644 --- a/extern/osgQt/CMakeLists.txt +++ b/extern/osgQt/CMakeLists.txt @@ -6,7 +6,6 @@ set(OSGQT_SOURCE_FILES GraphicsWindowQt.cpp ) -include_directories(${FFMPEG_INCLUDE_DIRS}) add_library(${OSGQT_LIBRARY} STATIC ${OSGQT_SOURCE_FILES}) if (DESIRED_QT_VERSION MATCHES 4) From 3f403466360af2343e0f7527f7a91c70dcc5096d Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 15 Feb 2016 19:49:54 -0500 Subject: [PATCH 0028/2007] Implemented a wrapper for DialInfo::SelectStruct --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/infoselectwrapper.cpp | 855 ++++++++++++++++++ apps/opencs/model/world/infoselectwrapper.hpp | 238 +++++ 3 files changed, 1094 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/world/infoselectwrapper.cpp create mode 100644 apps/opencs/model/world/infoselectwrapper.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0bde541bf..7b825232b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata defaultgmsts + idcompletionmanager metadata defaultgmsts infoselectwrapper ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 000000000..42cbabf72 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,855 @@ +#include "infoselectwrapper.hpp" + +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Shorth Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "PC Level", + "PC Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Short || + mConstSelect.mValue.getType() == ESM::VT_Long || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // TODO these need to be checked + + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0,1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + return std::pair(IntMin, IntMax); + + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0,4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0,2); + + case Function_PcGender: + return std::pair(0,1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(IntMin, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0,0); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0,0); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + + switch (mRelationType) + { + case Relation_Equal: + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges are same, it will always be true + return rangesMatch(conditionRange, validRange); + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + case Relation_NotEqual: + // If the value is the only value withing the range, it will never be true + return rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + int functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 000000000..8ccae0efa --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,238 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif From ed57293e5488b6ff57c13e5cac147195ab965f5a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 14:55:13 +0100 Subject: [PATCH 0029/2007] Allow '^' escape characters in books http://forum.openmw.org/viewtopic.php?f=2&t=3373&p=37584&sid=1a0b015e6716b1bced37fd398ef876c7 --- components/interpreter/defines.cpp | 19 ++++++++++--------- components/interpreter/defines.hpp | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 2ceb857c4..a700253b6 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -26,13 +26,14 @@ namespace Interpreter{ return a.length() > b.length(); } - std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context) + std::string fixDefinesReal(std::string text, bool dialogue, Context& context) { unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++) { - if(text[i] == eschar) + char eschar = text[i]; + if(eschar == '%' || eschar == '^') { retval << text.substr(start, i - start); std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); @@ -113,7 +114,7 @@ namespace Interpreter{ retval << context.getCurrentCellName(); } - else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + else if(!dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } @@ -207,15 +208,15 @@ namespace Interpreter{ return retval.str (); } - std::string fixDefinesDialog(std::string text, Context& context){ - return fixDefinesReal(text, '%', false, context); + std::string fixDefinesDialog(const std::string& text, Context& context){ + return fixDefinesReal(text, true, context); } - std::string fixDefinesMsgBox(std::string text, Context& context){ - return fixDefinesReal(text, '^', false, context); + std::string fixDefinesMsgBox(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } - std::string fixDefinesBook(std::string text, Context& context){ - return fixDefinesReal(text, '%', true, context); + std::string fixDefinesBook(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } } diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp index 00c4386b8..3471b2030 100644 --- a/components/interpreter/defines.hpp +++ b/components/interpreter/defines.hpp @@ -5,9 +5,9 @@ #include "context.hpp" namespace Interpreter{ - std::string fixDefinesDialog(std::string text, Context& context); - std::string fixDefinesMsgBox(std::string text, Context& context); - std::string fixDefinesBook(std::string text, Context& context); + std::string fixDefinesDialog(const std::string& text, Context& context); + std::string fixDefinesMsgBox(const std::string& text, Context& context); + std::string fixDefinesBook(const std::string& text, Context& context); } #endif From dececf6c3878d555e94dd548c7a5f5e58c0e4efd Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 16 Feb 2016 16:02:29 +0100 Subject: [PATCH 0030/2007] instance moving via drag in 3D scenes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/cell.cpp | 13 ++ apps/opencs/view/render/cell.hpp | 2 + apps/opencs/view/render/instancemode.cpp | 160 +++++++++++++++++- apps/opencs/view/render/instancemode.hpp | 19 +++ apps/opencs/view/render/instancemovemode.cpp | 12 ++ apps/opencs/view/render/instancemovemode.hpp | 18 ++ apps/opencs/view/render/object.cpp | 137 ++++++++++++++- apps/opencs/view/render/object.hpp | 40 ++++- .../view/render/pagedworldspacewidget.cpp | 17 ++ .../view/render/pagedworldspacewidget.hpp | 3 + apps/opencs/view/render/scenewidget.cpp | 6 + apps/opencs/view/render/scenewidget.hpp | 3 + .../view/render/unpagedworldspacewidget.cpp | 6 + .../view/render/unpagedworldspacewidget.hpp | 3 + apps/opencs/view/render/worldspacewidget.hpp | 3 + 16 files changed, 428 insertions(+), 16 deletions(-) create mode 100644 apps/opencs/view/render/instancemovemode.cpp create mode 100644 apps/opencs/view/render/instancemovemode.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6401e4222..ce0216066 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -85,7 +85,7 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode instancemode instanceselectionmode + previewwidget editmode instancemode instanceselectionmode instancemovemode ) opencs_units_noqt (view/render diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index e7b135891..d666304fa 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -338,3 +338,16 @@ std::vector > CSVRender::Cell::getSelection (un return result; } + +std::vector > CSVRender::Cell::getEdited (unsigned int elementMask) const +{ + std::vector > result; + + if (elementMask & Mask_Reference) + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->isEdited()) + result.push_back (iter->second->getTag()); + + return result; +} diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 22f9872e3..6cdafbf36 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -116,6 +116,8 @@ namespace CSVRender bool isDeleted() const; std::vector > getSelection (unsigned int elementMask) const; + + std::vector > getEdited (unsigned int elementMask) const; }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4b6b2e41f..f2fb552b0 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -18,10 +18,11 @@ #include "worldspacewidget.hpp" #include "pagedworldspacewidget.hpp" #include "instanceselectionmode.hpp" +#include "instancemovemode.hpp" CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) : EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", - parent), mSubMode (0), mSelectionMode (0) + parent), mSubMode (0), mSelectionMode (0), mDragMode (DragMode_None) { } @@ -30,12 +31,7 @@ void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) if (!mSubMode) { mSubMode = new CSVWidget::SceneToolMode (toolbar, "Edit Sub-Mode"); - mSubMode->addButton (":placeholder", "move", - "Move selected instances" - "
  • Use primary edit to move instances around freely
  • " - "
  • Use secondary edit to move instances around within the grid
  • " - "
" - "Not implemented yet"); + mSubMode->addButton (new InstanceMoveMode (this), "move"); mSubMode->addButton (":placeholder", "rotate", "Rotate selected instances" "