From 3f403466360af2343e0f7527f7a91c70dcc5096d Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 15 Feb 2016 19:49:54 -0500 Subject: [PATCH 01/99] 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 02/99] 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 63665104bfdd458c7a791f579aafccebdf2b1592 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 18:19:17 +0100 Subject: [PATCH 03/99] Remove optimization build flags on Travis-CI to speed up the build --- CI/before_script.linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 17667ad28..93be1cb48 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -5,4 +5,4 @@ mkdir build cd build export CODE_COVERAGE=1 if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE +${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE From c4d38bb42df4c734b20c2567ab5b79808bf8728b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 19:17:04 +0100 Subject: [PATCH 04/99] Fix clang analyzer warnings --- apps/opencs/model/prefs/state.hpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 18 +++++++++--------- apps/openmw/mwgui/settingswindow.cpp | 7 ------- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aiescort.cpp | 2 +- apps/openmw/mwmechanics/aiescort.hpp | 2 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aifollow.hpp | 4 ++-- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 8 +++----- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 7 ------- components/resource/scenemanager.cpp | 18 +++++++++--------- components/sceneutil/lightutil.hpp | 2 +- components/vfs/bsaarchive.hpp | 2 +- 16 files changed, 33 insertions(+), 49 deletions(-) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index bcd76c671..fffadee5e 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -1,4 +1,4 @@ -#ifndef CSV_PREFS_STATE_H +#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 039624c84..7885bf595 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -108,7 +108,7 @@ void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* colu ESM::Ingredient ingredient = record.get(); ingredient.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (ingredient); } @@ -120,11 +120,11 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTabl static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1129,7 +1129,7 @@ void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1141,10 +1141,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nest static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1235,7 +1235,7 @@ void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* co // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1247,10 +1247,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTa static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 86ecd9dfb..b410204e9 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -27,13 +27,6 @@ namespace { - std::string fpsLevelToStr(int level) - { - if (level == 0) - return "#{sOff}"; - else //if (level == 1) - return "#{sOn}"; - } std::string textureMipmappingToStr(const std::string& val) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 70aab95df..4a689d964 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1377,7 +1377,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); + MWWorld::Ptr followTarget = (*it)->getTarget(); if (followTarget.isEmpty()) continue; if (followTarget == actor) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index fffab8d77..1fc6c52a4 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -119,7 +119,7 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() + MWWorld::Ptr AiEscort::getTarget() const { return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index cdb0f7936..677cf6f43 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -37,7 +37,7 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 1430d42f2..02daec19e 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -200,7 +200,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const sequence.mPackages.push_back(package); } -MWWorld::Ptr AiFollow::getTarget() +MWWorld::Ptr AiFollow::getTarget() const { if (mActorId == -2) return MWWorld::Ptr(); diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 017ea7122..0f955879a 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -31,7 +31,7 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } @@ -60,7 +60,7 @@ namespace MWMechanics float mY; float mZ; std::string mActorRefId; - int mActorId; + mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 58ba7dfe8..863fe05ef 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -20,7 +20,7 @@ MWMechanics::AiPackage::~AiPackage() {} -MWWorld::Ptr MWMechanics::AiPackage::getTarget() +MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { return MWWorld::Ptr(); } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 72bb4487c..f938a34a1 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -72,7 +72,7 @@ namespace MWMechanics virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) - virtual MWWorld::Ptr getTarget(); + virtual MWWorld::Ptr getTarget() const; /// Return true if having this AiPackage makes the actor side with the target in fights (default false) virtual bool sideWithTarget() const; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 71733d613..5cfc1264a 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -68,9 +68,8 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { if (getTypeId() != AiPackage::TypeIdCombat) return false; - const AiCombat *combat = static_cast(mPackages.front()); - targetActor = combat->getTarget(); + targetActor = mPackages.front()->getTarget(); return !targetActor.isEmpty(); } @@ -114,8 +113,7 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - const AiCombat *combat = static_cast(*it); - if (combat->getTarget() == actor) + if ((*it)->getTarget() == actor) return true; } } @@ -255,7 +253,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) { if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + && (*iter)->getTarget() == (&package)->getTarget()) { return; // already in combat with this actor } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 84da270eb..7fbc3a853 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1330,7 +1330,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - MWWorld::Ptr target = static_cast(*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); if (!target.isEmpty() && target.getClass().isNpc()) isFightingNpc = true; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4c5138835..736d2a616 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -62,13 +62,6 @@ namespace } } - void applyDynamicStatsEffect(int attribute, const MWWorld::Ptr& target, float magnitude) - { - MWMechanics::DynamicStat value = target.getClass().getCreatureStats(target).getDynamic(attribute); - value.setCurrent(value.getCurrent()+magnitude, attribute == 2); - target.getClass().getCreatureStats(target).setDynamic(attribute, value); - } - } namespace MWMechanics diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 85a4afa30..ffe8dd881 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -139,16 +139,16 @@ namespace Resource virtual void apply(osg::Node& node) { if (osgFX::Effect* effect = dynamic_cast(&node)) - apply(*effect); + applyEffect(*effect); osg::StateSet* stateset = node.getStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); traverse(node); } - void apply(osgFX::Effect& effect) + void applyEffect(osgFX::Effect& effect) { for (int i =0; igetNumPasses(); ++pass) { if (tech->getPassStateSet(pass)) - apply(tech->getPassStateSet(pass)); + applyStateSet(tech->getPassStateSet(pass)); } } } @@ -165,29 +165,29 @@ namespace Resource { osg::StateSet* stateset = geode.getStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); for (unsigned int i=0; igetStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); } } - void apply(osg::StateSet* stateset) + void applyStateSet(osg::StateSet* stateset) { const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (texture) - apply(texture); + applyStateAttribute(texture); } } - void apply(osg::StateAttribute* attr) + void applyStateAttribute(osg::StateAttribute* attr) { osg::Texture* tex = attr->asTexture(); if (tex) diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 810ffa318..d6c970340 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -8,7 +8,7 @@ namespace osg namespace ESM { - class Light; + struct Light; } namespace SceneUtil diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 25ad60e0a..a6e274037 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -34,4 +34,4 @@ namespace VFS } -#endif \ No newline at end of file +#endif From e89609e5b0d8258ddf1064df9b4c143133979a0c Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Wed, 17 Feb 2016 14:15:57 -0500 Subject: [PATCH 05/99] Changes to how Info Conditions are edited --- apps/opencs/model/world/columns.cpp | 25 +- .../model/world/nestedcoladapterimp.cpp | 271 ++++-------------- 2 files changed, 59 insertions(+), 237 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d0d3a1671..aa453ced8 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,7 @@ #include #include "universalid.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -273,9 +274,9 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Func/Variable" }, - { ColumnId_InfoCondComp, "Comp" }, - { ColumnId_InfoCondValue, "Values" }, + { ColumnId_InfoCondVar, "Name" }, + { ColumnId_InfoCondComp, "Relation" }, + { ColumnId_InfoCondValue, "Value" }, { ColumnId_OriginalCell, "Original Cell" }, { ColumnId_NpcAttributes, "NPC Attributes" }, @@ -546,18 +547,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sInfoCondFunc[] = - { - " ", "Function", "Global", "Local", "Journal", - "Item", "Dead", "Not ID", "Not Faction", "Not Class", - "Not Race", "Not Cell", "Not Local", 0 - }; - - static const char *sInfoCondComp[] = - { - "!=", "<", "<=", "=", ">", ">=", 0 - }; - const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -585,10 +574,8 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; - // FIXME: don't have dynamic value enum delegate, use Display_String for now - //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; - case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; default: return 0; } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 92b4b9e62..a8f1c229a 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -529,16 +530,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - // 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 - // InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const @@ -547,11 +538,11 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - // blank row + // default row ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "00000"; + condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); // default to ints + condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); @@ -589,89 +580,6 @@ namespace CSMWorld return new NestedTableWrapper >(record.get().mSelects); } - // See the mappings in MWDialogue::SelectWrapper::getArgument - // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) - static std::map populateEncToInfoFunc() - { - std::map funcMap; - funcMap["00"] = "Rank Low"; - funcMap["01"] = "Rank High"; - funcMap["02"] = "Rank Requirement"; - funcMap["03"] = "Reputation"; - funcMap["04"] = "Health Percent"; - funcMap["05"] = "PC Reputation"; - funcMap["06"] = "PC Level"; - funcMap["07"] = "PC Health Percent"; - funcMap["08"] = "PC Magicka"; - funcMap["09"] = "PC Fatigue"; - funcMap["10"] = "PC Strength"; - funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armorer"; - funcMap["13"] = "PC Medium Armor"; - funcMap["14"] = "PC Heavy Armor"; - funcMap["15"] = "PC Blunt Weapon"; - funcMap["16"] = "PC Long Blade"; - funcMap["17"] = "PC Axe"; - funcMap["18"] = "PC Spear"; - funcMap["19"] = "PC Athletics"; - funcMap["20"] = "PC Enchant"; - funcMap["21"] = "PC Destruction"; - funcMap["22"] = "PC Alteration"; - funcMap["23"] = "PC Illusion"; - funcMap["24"] = "PC Conjuration"; - funcMap["25"] = "PC Mysticism"; - funcMap["26"] = "PC Restoration"; - funcMap["27"] = "PC Alchemy"; - funcMap["28"] = "PC Unarmored"; - funcMap["29"] = "PC Security"; - funcMap["30"] = "PC Sneak"; - funcMap["31"] = "PC Acrobatics"; - funcMap["32"] = "PC Light Armor"; - funcMap["33"] = "PC Short Blade"; - funcMap["34"] = "PC Marksman"; - funcMap["35"] = "PC Merchantile"; - funcMap["36"] = "PC Speechcraft"; - funcMap["37"] = "PC Hand To Hand"; - funcMap["38"] = "PC Sex"; - funcMap["39"] = "PC Expelled"; - funcMap["40"] = "PC Common Disease"; - funcMap["41"] = "PC Blight Disease"; - funcMap["42"] = "PC Clothing Modifier"; - funcMap["43"] = "PC Crime Level"; - funcMap["44"] = "Same Sex"; - funcMap["45"] = "Same Race"; - funcMap["46"] = "Same Faction"; - funcMap["47"] = "Faction Rank Difference"; - funcMap["48"] = "Detected"; - funcMap["49"] = "Alarmed"; - funcMap["50"] = "Choice"; - funcMap["51"] = "PC Intelligence"; - funcMap["52"] = "PC Willpower"; - funcMap["53"] = "PC Agility"; - funcMap["54"] = "PC Speed"; - funcMap["55"] = "PC Endurance"; - funcMap["56"] = "PC Personality"; - funcMap["57"] = "PC Luck"; - funcMap["58"] = "PC Corpus"; - funcMap["59"] = "Weather"; - funcMap["60"] = "PC Vampire"; - funcMap["61"] = "Level"; - funcMap["62"] = "Attacked"; - funcMap["63"] = "Talked To PC"; - funcMap["64"] = "PC Health"; - funcMap["65"] = "Creature Target"; - funcMap["66"] = "Friend Hit"; - funcMap["67"] = "Fight"; - funcMap["68"] = "Hello"; - funcMap["69"] = "Alarm"; - funcMap["70"] = "Flee"; - funcMap["71"] = "Should Attack"; - funcMap["72"] = "Werewolf"; - funcMap["73"] = "PC Werewolf Kills"; - return funcMap; - } - static const std::map sEncToInfoFunc = populateEncToInfoFunc(); - QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { @@ -682,70 +590,36 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + switch (subColIndex) { case 0: { - char condType = conditions[subRowIndex].mSelectRule[1]; - switch (condType) - { - case '0': return 0; // blank space - case '1': return 1; // Function - case '2': return 2; // Global - case '3': return 3; // Local - case '4': return 4; // Journal - case '5': return 5; // Item - case '6': return 6; // Dead - case '7': return 7; // Not ID - case '8': return 8; // Not Factio - case '9': return 9; // Not Class - case 'A': return 10; // Not Race - case 'B': return 11; // Not Cell - case 'C': return 12; // Not Local - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getFunctionName(); } case 1: { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - // throws an exception if the encoding is not found - return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); - } + if (infoSelectWrapper.hasVariable()) + return QString(infoSelectWrapper.getVariableName().c_str()); else - return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + return ""; } case 2: { - char compType = conditions[subRowIndex].mSelectRule[4]; - switch (compType) - { - case '0': return 3; // = - case '1': return 0; // != - case '2': return 4; // > - case '3': return 5; // >= - case '4': return 1; // < - case '5': return 2; // <= - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getRelationType(); } case 3: { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getVariant().getType()) { - case ESM::VT_String: - { - return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); - } case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: { - return conditions[subRowIndex].mValue.getInteger(); + return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { - return conditions[subRowIndex].mValue.getFloat(); + return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } @@ -764,101 +638,62 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + bool conversionResult = false; + switch (subColIndex) { - case 0: + case 0: // Function { - // See sInfoCondFunc in columns.cpp for the enum values - switch (value.toInt()) - { - // FIXME: when these change the values of the other columns need to change - // correspondingly (and automatically) - case 1: - { - conditions[subRowIndex].mSelectRule[1] = '1'; // Function - // default to "Rank Low" - conditions[subRowIndex].mSelectRule[2] = '0'; - conditions[subRowIndex].mSelectRule[3] = '0'; - break; - } - case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global - case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local - case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal - case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item - case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead - case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID - case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction - case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class - case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race - case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell - case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local - default: return; // return without saving - } - break; - } - case 1: - { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - std::map::const_iterator it = sEncToInfoFunc.begin(); - for (;it != sEncToInfoFunc.end(); ++it) - { - if (it->second == value.toString().toUtf8().constData()) - { - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); - rule.append(it->first); - // leave old values for undo (NOTE: may not be vanilla's behaviour) - rule.append(conditions[subRowIndex].mSelectRule.substr(4)); - conditions[subRowIndex].mSelectRule = rule; - break; - } - } + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - if (it == sEncToInfoFunc.end()) - return; // return without saving; TODO: maybe log an error here - } - else + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && + infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { - // FIXME: validate the string values before saving, based on the current function - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); - conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); + infoSelectWrapper.getVariant().setType(ESM::VT_Int); } + + infoSelectWrapper.update(); break; } - case 2: + case 1: // Variable { - // See sInfoCondComp in columns.cpp for the enum values - switch (value.toInt()) - { - case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != - case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < - case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= - case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = - case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > - case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= - default: return; // return without saving - } + infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); + infoSelectWrapper.update(); break; } - case 3: + case 2: // Relation { - switch (conditions[subRowIndex].mValue.getType()) + infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.update(); + break; + } + case 3: // Value + { + switch (infoSelectWrapper.getComparisonType()) { - case ESM::VT_String: + case ConstInfoSelectWrapper::Comparison_Numeric: { - conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); + if (value.toInt(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } + else if (value.toFloat(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Float); + infoSelectWrapper.getVariant().setFloat(value.toFloat()); + } break; } - case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: + case ConstInfoSelectWrapper::Comparison_Boolean: + case ConstInfoSelectWrapper::Comparison_Integer: { - conditions[subRowIndex].mValue.setInteger (value.toInt()); - break; - } - case ESM::VT_Float: - { - conditions[subRowIndex].mValue.setFloat (value.toFloat()); + if (value.toInt(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } break; } default: break; From 8668eccd0bec7e565934d8d65519c05303ef621c Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Wed, 17 Feb 2016 15:38:30 -0500 Subject: [PATCH 06/99] Topic Info verifier with fixes to InfoSelectWrapper class --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/tools/tools.cpp | 16 +- apps/opencs/model/tools/topicinfocheck.cpp | 441 ++++++++++++++++++ apps/opencs/model/tools/topicinfocheck.hpp | 95 ++++ apps/opencs/model/world/infoselectwrapper.cpp | 41 +- apps/opencs/model/world/infoselectwrapper.hpp | 2 + 6 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 apps/opencs/model/tools/topicinfocheck.cpp create mode 100644 apps/opencs/model/tools/topicinfocheck.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7b825232b..a657bade2 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -42,7 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck + mergestages gmstcheck topicinfocheck ) opencs_hdrs_noqt (model/tools diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e750092b9..b6a04a236 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -30,6 +30,7 @@ #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" #include "gmstcheck.hpp" +#include "topicinfocheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -111,9 +112,22 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), + mData.getCells(), + mData.getClasses(), + mData.getFactions(), + mData.getGmsts(), + mData.getGlobals(), + mData.getJournals(), + mData.getRaces(), + mData.getRegions(), + mData.getTopics(), + mData.getReferenceables().getDataSet(), + mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp new file mode 100644 index 000000000..f6af2a945 --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -0,0 +1,441 @@ +#include "topicinfocheck.hpp" + +#include + +#include "../world/infoselectwrapper.hpp" + +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection &topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles) + : mTopicInfos(topicInfos), + mCells(cells), + mClasses(classes), + mFactions(factions), + mGameSettings(gmsts), + mGlobals(globals), + mJournals(journals), + mRaces(races), + mRegions(regions), + mTopics(topics), + mReferencables(referencables), + mSoundFiles(soundFiles) +{} + +int CSMTools::TopicInfoCheckStage::setup() +{ + // Generate list of cell names for reference checking + + mCellNames.clear(); + for (int i = 0; i < mCells.getSize(); ++i) + { + const CSMWorld::Record& cellRecord = mCells.getRecord(i); + + if (cellRecord.isDeleted()) + continue; + + mCellNames.insert(cellRecord.get().mName); + } + // Cell names can also include region names + for (int i = 0; i < mRegions.getSize(); ++i) + { + const CSMWorld::Record& regionRecord = mRegions.getRecord(i); + + if (regionRecord.isDeleted()) + continue; + + mCellNames.insert(regionRecord.get().mName); + } + // Default cell name + int index = mGameSettings.searchId("sDefaultCellname"); + if (index != -1) + { + const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); + + if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) + { + mCellNames.insert(gmstRecord.get().mValue.getString()); + } + } + + return mTopicInfos.getSize(); +} + +void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); + + if (infoRecord.isDeleted()) + return; + + const CSMWorld::Info& topicInfo = infoRecord.get(); + + // There should always be a topic that matches + int topicIndex = mTopics.searchId(topicInfo.mTopicId); + + const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); + + if (topicRecord.isDeleted()) + return; + + const ESM::Dialogue& topic = topicRecord.get(); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); + + // Check fields + + if (!topicInfo.mActor.empty()) + { + verifyActor(topicInfo.mActor, id, messages); + } + + if (!topicInfo.mClass.empty()) + { + verifyId(topicInfo.mClass, mClasses, id, messages); + } + + if (!topicInfo.mCell.empty()) + { + verifyCell(topicInfo.mCell, id, messages); + } + + if (!topicInfo.mFaction.empty()) + { + if (verifyId(topicInfo.mFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); + } + } + + if (!topicInfo.mPcFaction.empty()) + { + if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); + } + } + + if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) + { + std::ostringstream stream; + messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + } + + if (!topicInfo.mRace.empty()) + { + verifyId(topicInfo.mRace, mRaces, id, messages); + } + + if (!topicInfo.mSound.empty()) + { + verifySound(topicInfo.mSound, id, messages); + } + + if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) + { + messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); + } + + // Check info conditions + + for (std::vector::const_iterator it = topicInfo.mSelects.begin(); + it != topicInfo.mSelects.end(); ++it) + { + verifySelectStruct((*it), id, messages); + } +} + +// Verification functions + +bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Actor"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); + + if (index.first == -1) + { + writeMissingIdError(specifier, actor, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, actor, id, messages); + return false; + } + else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) + { + writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Cell"; + + if (mCellNames.find(cell) == mCellNames.end()) + { + writeMissingIdError(specifier, cell, id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + if (rank < -1) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + int index = mFactions.searchId(factionName); + + const ESM::Faction &faction = mFactions.getRecord(index).get(); + + int limit = 0; + for (; limit < 10; ++limit) + { + if (faction.mRanks[limit].empty()) + break; + } + + if (rank >= limit) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the " << factionName << " faction"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Item"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); + + if (index.first == -1) + { + writeMissingIdError(specifier, item, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, item, id, messages); + return false; + } + else + { + switch (index.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + + default: + writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + return false; + } + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + CSMWorld::ConstInfoSelectWrapper infoCondition(select); + + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + { + messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (!infoCondition.variantTypeIsValid()) + { + std::ostringstream stream; + stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + + switch (select.mValue.getType()) + { + case ESM::VT_None: stream << "None"; break; + case ESM::VT_Short: stream << "Short"; break; + case ESM::VT_Int: stream << "Int"; break; + case ESM::VT_Long: stream << "Long"; break; + case ESM::VT_Float: stream << "Float"; break; + case ESM::VT_String: stream << "String"; break; + default: stream << "Unknown"; break; + } + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (infoCondition.conditionIsAlwaysTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is always true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + else if (infoCondition.conditionIsNeverTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is never true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + + // Id checks + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && + !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && + !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && + !verifyItem(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && + !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && + !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && + !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && + !verifyCell(infoCondition.getVariableName(), id, messages)) + { + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Sound File"; + + if (mSoundFiles.searchId(sound) == -1) + { + writeMissingIdError(specifier, sound, id, messages); + return false; + } + + return true; +} + +template +bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + int index = collection.searchId(name); + + if (index == -1) + { + writeMissingIdError(T::getRecordType(), name, id, messages); + return false; + } + else if (collection.getRecord(index).isDeleted()) + { + writeDeletedRecordError(T::getRecordType(), name, id, messages); + return false; + } + + return true; +} + +// Error functions + +void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + CSMWorld::UniversalId tempId(invalidType, invalidId); + + std::ostringstream stream; + stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" + << invalidId << "\" (can be of type " << expectedType << ")"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp new file mode 100644 index 000000000..510901dac --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP +#define CSM_TOOLS_TOPICINFOCHECK_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../world/cell.hpp" +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/refiddata.hpp" +#include "../world/resources.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: check topics + class TopicInfoCheckStage : public CSMDoc::Stage + { + public: + + TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int step, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::InfoCollection& mTopicInfos; + + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mGameSettings; + const CSMWorld::IdCollection& mGlobals; + const CSMWorld::IdCollection& mJournals; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mRegions; + const CSMWorld::IdCollection& mTopics; + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::Resources& mSoundFiles; + + std::set mCellNames; + + // These return false when not successful and write an error + bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + template + bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + // Common error messages + void writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + }; +} + +#endif diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index 42cbabf72..0bc1ec22b 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -226,8 +226,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const 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); + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); } const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const @@ -235,6 +234,40 @@ const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const return mConstSelect.mValue; } +std::string CSMWorld::ConstInfoSelectWrapper::toString() const +{ + std::ostringstream stream; + stream << convertToString(mFunctionName) << " "; + + if (mHasVariable) + stream << mVariableName << " "; + + stream << convertToString(mRelationType) << " "; + + switch (mConstSelect.mValue.getType()) + { + case ESM::VT_Short: + case ESM::VT_Long: + case ESM::VT_Int: + stream << mConstSelect.mValue.getInteger(); + break; + + case ESM::VT_Float: + stream << mConstSelect.mValue.getFloat(); + break; + + case ESM::VT_String: + stream << mConstSelect.mValue.getString(); + break; + + default: + stream << "(Invalid value type)"; + break; + } + + return stream.str(); +} + void CSMWorld::ConstInfoSelectWrapper::readRule() { if (mConstSelect.mSelectRule.size() < RuleMinSize) @@ -554,9 +587,9 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const case Function_RankHigh: case Function_Reputation: case Function_PcReputation: + case Function_Journal: return std::pair(IntMin, IntMax); - case Function_Journal: case Function_Item: case Function_Dead: case Function_PcLevel: @@ -736,7 +769,7 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair con case Relation_NotEqual: // If the value is the only value withing the range, it will never be true - return rangesOverlap(conditionRange, validRange); + return rangesMatch(conditionRange, validRange); default: throw std::logic_error("InfoCondition: operator can not be used to compare"); diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index 8ccae0efa..1aa86aeca 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -164,6 +164,8 @@ namespace CSMWorld bool variantTypeIsValid() const; const ESM::Variant& getVariant() const; + + std::string toString() const; protected: From bdc99048bba21b309b7fde55d0fc7406d7b05b8b Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Wed, 17 Feb 2016 19:01:22 -0500 Subject: [PATCH 07/99] Missing header --- apps/opencs/model/world/infoselectwrapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index 0bc1ec22b..5271430f5 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -2,6 +2,7 @@ #include #include +#include const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; From a94029267f4067d761c42f80a667a60ff3e177b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 01:25:52 +0100 Subject: [PATCH 08/99] Fix an inverted condition (Bug #3209) --- components/interpreter/defines.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index a700253b6..fafaf1fef 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -114,7 +114,7 @@ namespace Interpreter{ retval << context.getCurrentCellName(); } - else if(!dialogue) { // In Dialogue, not messagebox + else if(dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } From 6fb0022b351a25b426336d68ec3e798c4d082dd0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 17:47:10 +0100 Subject: [PATCH 09/99] Update preloading settings Disable 'preload fast travel' by default. Add 'min cache size' and 'max cache size' settings. Split the 'cache expiry delay' into 'preload cell expiry delay' and 'cache expiry delay'. --- apps/openmw/mwworld/cellpreloader.cpp | 33 ++++++++++++++++++++++++--- apps/openmw/mwworld/cellpreloader.hpp | 8 +++++++ apps/openmw/mwworld/scene.cpp | 8 ++++--- files/settings-default.cfg | 18 +++++++++++---- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 6acb41dc3..f2881d68f 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -166,6 +166,8 @@ namespace MWWorld , mBulletShapeManager(bulletShapeManager) , mTerrain(terrain) , mExpiryDelay(0.0) + , mMinCacheSize(0) + , mMaxCacheSize(0) { } @@ -197,6 +199,23 @@ namespace MWWorld return; } + while (mPreloadCells.size() >= mMaxCacheSize) + { + // throw out oldest cell to make room + PreloadMap::iterator oldestCell = mPreloadCells.begin(); + double oldestTimestamp = DBL_MAX; + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) + { + if (it->second.mTimeStamp < oldestTimestamp) + { + oldestTimestamp = it->second.mTimeStamp; + oldestCell = it; + } + } + + mPreloadCells.erase(oldestCell); + } + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain)); mWorkQueue->addWorkItem(item); @@ -210,11 +229,9 @@ namespace MWWorld void CellPreloader::updateCache(double timestamp) { - // TODO: add settings for a minimum/maximum size of the cache - for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { - if (it->second.mTimeStamp < timestamp - mExpiryDelay) + if (mPreloadCells.size() >= mMinCacheSize && it->second.mTimeStamp < timestamp - mExpiryDelay) mPreloadCells.erase(it++); else ++it; @@ -229,6 +246,16 @@ namespace MWWorld mExpiryDelay = expiryDelay; } + void CellPreloader::setMinCacheSize(unsigned int num) + { + mMinCacheSize = num; + } + + void CellPreloader::setMaxCacheSize(unsigned int num) + { + mMaxCacheSize = num; + } + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) { mWorkQueue = workQueue; diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 437395397..f78e66e75 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -38,6 +38,12 @@ namespace MWWorld /// How long to keep a preloaded cell in cache after it's no longer requested. void setExpiryDelay(double expiryDelay); + /// The minimum number of preloaded cells before unused cells get thrown out. + void setMinCacheSize(unsigned int num); + + /// The maximum number of preloaded cells. + void setMaxCacheSize(unsigned int num); + void setWorkQueue(osg::ref_ptr workQueue); private: @@ -46,6 +52,8 @@ namespace MWWorld Terrain::World* mTerrain; osg::ref_ptr mWorkQueue; double mExpiryDelay; + unsigned int mMinCacheSize; + unsigned int mMaxCacheSize; struct PreloadEntry { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 308809bd2..cd0954260 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -463,9 +463,11 @@ namespace MWWorld mPhysics->setUnrefQueue(rendering.getUnrefQueue()); - float cacheExpiryDelay = Settings::Manager::getFloat("cache expiry delay", "Cells"); - rendering.getResourceSystem()->setExpiryDelay(cacheExpiryDelay); - mPreloader->setExpiryDelay(cacheExpiryDelay); + rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); + + mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells")); + mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells")); + mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells")); } Scene::~Scene() diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a9a5dd65..c6c97546e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -44,7 +44,7 @@ preload enabled = true preload exterior grid = true # Preload possible fast travel destinations. -preload fast travel = true +preload fast travel = false # Preload the locations that doors lead to. preload doors = true @@ -52,9 +52,19 @@ preload doors = true # Preloading distance threshold preload distance = 1000 -# How long to keep preloaded cells and cached models/textures/collision shapes in cache -# after they're no longer referenced/required (in seconds) -cache expiry delay = 300 +# The minimum amount of cells in the preload cache before unused cells start to get thrown out (see "preload cell expiry delay"). +# This value should be lower or equal to 'preload cell cache max'. +preload cell cache min = 12 + +# The maximum amount of cells in the preload cache. A too high value could cause you to run out of memory. +# You may need to reduce this setting when running lots of mods or high-res texture replacers. +preload cell cache max = 20 + +# How long to keep preloaded cells in cache after they're no longer referenced/required (in seconds) +preload cell expiry delay = 5 + +# How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds) +cache expiry delay = 5 [Map] From 7f89bb273a1d8d0c807863d955acc6b1618353bd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 19:31:25 +0100 Subject: [PATCH 10/99] Add 'reflect actors' setting for the water shader, default off --- apps/openmw/mwrender/water.cpp | 4 +++- files/mygui/openmw_settings_window.layout | 10 ++++++++++ files/settings-default.cfg | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index dba85aeb7..e20590ecb 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -375,7 +375,9 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting); + bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); + + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index cf7fe1be7..5d6d817d6 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -396,6 +396,16 @@ + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c6c97546e..a5e750d4d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -274,6 +274,9 @@ rtt size = 512 # Enable refraction which affects visibility through water plane. refraction = false +# Draw NPCs and creatures on water reflections. +reflect actors = false + [Objects] # Enable shaders for objects other than water. Unused. From e05d97502095d71506eb9595a42ed07f47b728f0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Feb 2016 23:14:52 +0100 Subject: [PATCH 11/99] Change the way that texture filtering settings are applied at runtime --- apps/openmw/engine.cpp | 3 +-- apps/openmw/mwrender/renderingmanager.cpp | 10 ++++++---- apps/openmw/mwrender/water.cpp | 3 --- components/resource/objectcache.cpp | 16 +++++++++++++++ components/resource/objectcache.hpp | 4 ++++ components/resource/scenemanager.cpp | 24 +++-------------------- components/resource/scenemanager.hpp | 10 ++-------- components/terrain/terraingrid.cpp | 8 +++++++- components/terrain/terraingrid.hpp | 4 ++++ components/terrain/world.hpp | 2 ++ 10 files changed, 45 insertions(+), 39 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b3a58d18e..5fd328022 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -453,8 +453,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - NULL + Settings::Manager::getInt("anisotropy", "General") ); // Create input and UI first to set up a bootstrapping environment for diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 29b641f6b..7569cea1f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -836,16 +836,18 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { - if (mTerrain.get()) - mTerrain->updateCache(); + mViewer->stopThreading(); mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - mViewer + Settings::Manager::getInt("anisotropy", "General") ); + + mTerrain->updateTextureFiltering(); + + mViewer->startThreading(); } void RenderingManager::updateAmbient() diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e20590ecb..e6acbb500 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -318,7 +318,6 @@ public: mRefractionTexture->setInternalFormat(GL_RGB); mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRefractionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); @@ -330,7 +329,6 @@ public: mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRefractionDepthTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } @@ -393,7 +391,6 @@ public: mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mReflectionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index a0a3cf3a5..5fec6730d 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -14,6 +14,7 @@ #include "objectcache.hpp" #include +#include namespace Resource { @@ -119,4 +120,19 @@ void ObjectCache::releaseGLObjects(osg::State* state) } } +void ObjectCache::accept(osg::NodeVisitor &nv) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.first.get(); + osg::Node* node = object->asNode(); + if (node) + node->accept(nv); + } +} + } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 79ebabd5b..82aca76f0 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -27,6 +27,7 @@ namespace osg { class Object; class State; + class NodeVisitor; } namespace Resource { @@ -66,6 +67,9 @@ class ObjectCache : public osg::Referenced /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); + /** call node->accept(nv); for all nodes in the objectCache. */ + void accept(osg::NodeVisitor& nv); + protected: virtual ~ObjectCache(); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ffe8dd881..e78cc7cb2 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -10,8 +10,6 @@ #include -#include - #include #include @@ -192,13 +190,6 @@ namespace Resource osg::Texture* tex = attr->asTexture(); if (tex) { - if (tex->getUserDataContainer()) - { - const std::vector& descriptions = tex->getUserDataContainer()->getDescriptions(); - if (std::find(descriptions.begin(), descriptions.end(), "dont_override_filter") != descriptions.end()) - return; - } - tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); @@ -425,8 +416,7 @@ namespace Resource } void SceneManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer) + const std::string &mipmap, int maxAnisotropy) { osg::Texture::FilterMode min = osg::Texture::LINEAR; osg::Texture::FilterMode mag = osg::Texture::LINEAR; @@ -458,23 +448,15 @@ namespace Resource min = osg::Texture::LINEAR_MIPMAP_LINEAR; } - if(viewer) viewer->stopThreading(); - mMinFilter = min; mMagFilter = mag; mMaxAnisotropy = std::max(1, maxAnisotropy); - mCache->clear(); - SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); SetFilterSettingsVisitor setFilterSettingsVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); - if (viewer && viewer->getSceneData()) - { - viewer->getSceneData()->accept(setFilterSettingsControllerVisitor); - viewer->getSceneData()->accept(setFilterSettingsVisitor); - } - if(viewer) viewer->startThreading(); + mCache->accept(setFilterSettingsVisitor); + mCache->accept(setFilterSettingsControllerVisitor); } void SceneManager::applyFilterSettings(osg::Texture *tex) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 8357e63cd..987b7898e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -21,11 +21,6 @@ namespace osgUtil class IncrementalCompileOperation; } -namespace osgViewer -{ - class Viewer; -} - namespace Resource { @@ -83,10 +78,9 @@ namespace Resource /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); - /// @param viewer used to apply the new filter settings to the existing scene graph. If there is no scene yet, you can pass a NULL viewer. + /// @warning It is unsafe to call this method while the draw thread is using textures! call Viewer::stopThreading first. void setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer); + const std::string &mipmap, int maxAnisotropy); /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate or createInstance) /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 981a984e4..fefb0b8bc 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -175,7 +175,6 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setResizeNonPowerOfTwoHint(false); - texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); @@ -278,4 +277,11 @@ void TerrainGrid::updateCache() } } +void TerrainGrid::updateTextureFiltering() +{ + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end(); ++it) + mResourceSystem->getSceneManager()->applyFilterSettings(it->second); +} + } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 46a95e817..d2276a0af 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -36,6 +36,10 @@ namespace Terrain /// @note Thread safe. void updateCache(); + /// Apply the scene manager's texture filtering settings to all cached textures. + /// @note Thread safe. + void updateTextureFiltering(); + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 992438a6a..f5638750c 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,6 +39,8 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); + virtual void updateTextureFiltering() {} + virtual void updateCache() {} float getHeightAt (const osg::Vec3f& worldPos); From acf44fd9e5621d4eff375932e71766f0d9852bd7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Feb 2016 23:19:26 +0100 Subject: [PATCH 12/99] NifLoader: handleTextureControllers outside of the NumTextures loop --- components/nifosg/nifloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8f4076884..27ce24240 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1390,8 +1390,8 @@ namespace NifOsg boundTextures.push_back(tex.uvSet); } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); break; } // unused by mw From 50e92c8136cdebeae95c6a9fc74adb734dd07210 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Feb 2016 23:21:50 +0100 Subject: [PATCH 13/99] NifLoader: move texture property handling to a separate function --- components/nifosg/nifloader.cpp | 222 ++++++++++++++++---------------- 1 file changed, 113 insertions(+), 109 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 27ce24240..dbb1e7d43 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1210,6 +1210,118 @@ namespace NifOsg } } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + { + if (boundTextures.size()) + { + // overriding a parent NiTexturingProperty, so remove what was previously bound + for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + for (int i=0; itextures[i].inUse) + { + switch(i) + { + //These are handled later on + case Nif::NiTexturingProperty::BaseTexture: + case Nif::NiTexturingProperty::GlowTexture: + case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::DetailTexture: + break; + case Nif::NiTexturingProperty::GlossTexture: + { + std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::BumpTexture: + { + std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::DecalTexture: + { + std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + default: + { + std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + continue; + } + } + + const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; + if(tex.texture.empty()) + { + std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + continue; + } + const Nif::NiSourceTexture *st = tex.texture.getPtr(); + if (!st->external) + { + std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; + continue; + } + + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + + unsigned int clamp = static_cast(tex.clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + + // create a new texture, will later attempt to share using the SharedStateManager + osg::ref_ptr texture2d (new osg::Texture2D(imageManager->getImage(filename))); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + int texUnit = boundTextures.size(); + + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + + if (i == Nif::NiTexturingProperty::GlowTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DarkTexture) + { + osg::TexEnv* texEnv = new osg::TexEnv; + texEnv->setMode(osg::TexEnv::MODULATE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DetailTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setScale_RGB(2.f); + texEnv->setCombine_Alpha(GL_MODULATE); + texEnv->setOperand0_Alpha(GL_SRC_ALPHA); + texEnv->setOperand1_Alpha(GL_SRC_ALPHA); + texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); + texEnv->setSource1_Alpha(GL_TEXTURE); + texEnv->setCombine_RGB(GL_MODULATE); + texEnv->setOperand0_RGB(GL_SRC_COLOR); + texEnv->setOperand1_RGB(GL_SRC_COLOR); + texEnv->setSource0_RGB(GL_PREVIOUS_ARB); + texEnv->setSource1_RGB(GL_TEXTURE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + + boundTextures.push_back(tex.uvSet); + } + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + } + void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { @@ -1283,115 +1395,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - - if (boundTextures.size()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - - for (int i=0; itextures[i].inUse) - { - switch(i) - { - //These are handled later on - case Nif::NiTexturingProperty::BaseTexture: - case Nif::NiTexturingProperty::GlowTexture: - case Nif::NiTexturingProperty::DarkTexture: - case Nif::NiTexturingProperty::DetailTexture: - break; - case Nif::NiTexturingProperty::GlossTexture: - { - std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::BumpTexture: - { - std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::DecalTexture: - { - std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - default: - { - std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; - continue; - } - } - - const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; - if(tex.texture.empty()) - { - std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; - continue; - } - const Nif::NiSourceTexture *st = tex.texture.getPtr(); - if (!st->external) - { - std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; - continue; - } - - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); - - unsigned int clamp = static_cast(tex.clamp); - int wrapT = (clamp) & 0x1; - int wrapS = (clamp >> 1) & 0x1; - - // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d (new osg::Texture2D(imageManager->getImage(filename))); - texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); - texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); - - int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - - if (i == Nif::NiTexturingProperty::GlowTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DarkTexture) - { - osg::TexEnv* texEnv = new osg::TexEnv; - texEnv->setMode(osg::TexEnv::MODULATE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DetailTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setScale_RGB(2.f); - texEnv->setCombine_Alpha(GL_MODULATE); - texEnv->setOperand0_Alpha(GL_SRC_ALPHA); - texEnv->setOperand1_Alpha(GL_SRC_ALPHA); - texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); - texEnv->setSource1_Alpha(GL_TEXTURE); - texEnv->setCombine_RGB(GL_MODULATE); - texEnv->setOperand0_RGB(GL_SRC_COLOR); - texEnv->setOperand1_RGB(GL_SRC_COLOR); - texEnv->setSource0_RGB(GL_PREVIOUS_ARB); - texEnv->setSource1_RGB(GL_TEXTURE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - - boundTextures.push_back(tex.uvSet); - } - } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureProperty(texprop, stateset, composite, imageManager, boundTextures, animflags); break; } // unused by mw From 4cd4131da9ce0b09bf11d4d28b81d111a91833a6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Feb 2016 23:24:54 +0100 Subject: [PATCH 14/99] NifLoader: assign a name to created textures This name will tell the shader visitor how to handle that texture. --- components/nifosg/nifloader.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index dbb1e7d43..f8ee15062 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1316,6 +1316,27 @@ namespace NifOsg stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } + switch (i) + { + case Nif::NiTexturingProperty::BaseTexture: + texture2d->setName("diffuseMap"); + break; + case Nif::NiTexturingProperty::BumpTexture: + texture2d->setName("normalMap"); + break; + case Nif::NiTexturingProperty::GlowTexture: + texture2d->setName("emissiveMap"); + break; + case Nif::NiTexturingProperty::DarkTexture: + texture2d->setName("darkMap"); + break; + case Nif::NiTexturingProperty::DetailTexture: + texture2d->setName("detailMap"); + break; + default: + break; + } + boundTextures.push_back(tex.uvSet); } } From 606758d72f4e32ba771a604adea4c3ca1b31fc20 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 14 Feb 2016 23:29:00 +0100 Subject: [PATCH 15/99] NifLoader: create the BumpTexture slot, but don't use it yet --- components/nifosg/nifloader.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f8ee15062..9fbee3d4d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1230,6 +1230,7 @@ namespace NifOsg case Nif::NiTexturingProperty::BaseTexture: case Nif::NiTexturingProperty::GlowTexture: case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::BumpTexture: case Nif::NiTexturingProperty::DetailTexture: break; case Nif::NiTexturingProperty::GlossTexture: @@ -1237,11 +1238,6 @@ namespace NifOsg std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; continue; } - case Nif::NiTexturingProperty::BumpTexture: - { - std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; - continue; - } case Nif::NiTexturingProperty::DecalTexture: { std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; @@ -1315,6 +1311,11 @@ namespace NifOsg texEnv->setSource1_RGB(GL_TEXTURE); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } + else if (i == Nif::NiTexturingProperty::BumpTexture) + { + // Set this texture to Off by default since we can't render it with the fixed-function pipeline + stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); + } switch (i) { From a9ac10838221f0ccbc9c5afe863b08f8d1de43c2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 16:49:58 +0100 Subject: [PATCH 16/99] Fully read NiPixelData --- components/nif/data.cpp | 18 +++++++++++------- components/nif/data.hpp | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 08c268dde..751b15097 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -154,7 +154,7 @@ void NiFloatData::read(NIFStream *nif) void NiPixelData::read(NIFStream *nif) { - nif->getInt(); // always 0 or 1 + fmt = (Format)nif->getUInt(); rmask = nif->getInt(); // usually 0xff gmask = nif->getInt(); // usually 0xff00 @@ -169,19 +169,23 @@ void NiPixelData::read(NIFStream *nif) mips = nif->getInt(); // Bytes per pixel, should be bpp * 8 - /*int bytes =*/ nif->getInt(); + /* int bytes = */ nif->getInt(); for(int i=0; igetInt(); - /*int y =*/ nif->getInt(); - /*int offset =*/ nif->getInt(); + Mipmap m; + m.width = nif->getInt(); + m.height = nif->getInt(); + m.dataOffset = nif->getInt(); + mipmaps.push_back(m); } - // Skip the data + // Read the data unsigned int dataSize = nif->getInt(); - nif->skip(dataSize); + data.reserve(dataSize); + for (unsigned i=0; igetChar()); } void NiColorData::read(NIFStream *nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 95f244129..89886b66f 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,9 +105,30 @@ public: class NiPixelData : public Record { public: + enum Format + { + NIPXFMT_RGB8, + NIPXFMT_RGBA8, + NIPXFMT_PAL8, + NIPXFMT_DXT1, + NIPXFMT_DXT3, + NIPXFMT_DXT5, + NIPXFMT_DXT5_ALT + }; + Format fmt; + unsigned int rmask, gmask, bmask, amask; int bpp, mips; + struct Mipmap + { + int width, height; + int dataOffset; + }; + std::vector mipmaps; + + std::vector data; + void read(NIFStream *nif); }; From e647ee5424a4f1c8bb506d262ab09ccd94e2e3b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 15 Feb 2016 18:00:13 +0100 Subject: [PATCH 17/99] Support RGB and RGBA embedded textures in NIF files (Fixes #2295) --- components/nif/data.cpp | 6 +-- components/nifosg/nifloader.cpp | 77 ++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 751b15097..1de7d0966 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -175,9 +175,9 @@ void NiPixelData::read(NIFStream *nif) { // Image size and offset in the following data field Mipmap m; - m.width = nif->getInt(); - m.height = nif->getInt(); - m.dataOffset = nif->getInt(); + m.width = nif->getUInt(); + m.height = nif->getUInt(); + m.dataOffset = nif->getUInt(); mipmaps.push_back(m); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9fbee3d4d..0b49419ff 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1210,6 +1210,68 @@ namespace NifOsg } } + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + { + osg::ref_ptr image (new osg::Image); + + GLenum pixelformat = 0; + switch (pixelData->fmt) + { + case Nif::NiPixelData::NIPXFMT_RGB8: + pixelformat = GL_RGB; + break; + case Nif::NiPixelData::NIPXFMT_RGBA8: + pixelformat = GL_RGBA; + break; + default: + std::cerr << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename << std::endl; + return NULL; + } + + if (!pixelData->mipmaps.size()) + return NULL; + + unsigned char* data = new unsigned char[pixelData->data.size()]; + memcpy(data, &pixelData->data[0], pixelData->data.size()); + unsigned int width = 0; + unsigned int height = 0; + + std::vector mipmapVector; + for (unsigned int i=0; imipmaps.size()-3; ++i) + { + const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; + + size_t mipSize = mip.height * mip.width * pixelData->bpp / 8; + if (mipSize + mip.dataOffset > pixelData->data.size()) + { + std::cerr << "Internal texture's mipmap data out of bounds" << std::endl; + delete data; + return NULL; + } + + if (i != 0) + mipmapVector.push_back(mip.dataOffset); + else + { + width = mip.width; + height = mip.height; + } + } + + if (width <= 0 || height <= 0) + { + std::cerr << "Width and height must be non zero " << std::endl; + delete data; + return NULL; + } + + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + image->setMipmapLevels(mipmapVector); + image->flipVertical(); + + return image; + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { if (boundTextures.size()) @@ -1256,21 +1318,24 @@ namespace NifOsg std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; continue; } + osg::ref_ptr image; const Nif::NiSourceTexture *st = tex.texture.getPtr(); - if (!st->external) + if (!st->external && !st->data.empty()) { - std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; - continue; + image = handleInternalTexture(st->data.getPtr()); + } + else + { + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + image = imageManager->getImage(filename); } - - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); unsigned int clamp = static_cast(tex.clamp); int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d (new osg::Texture2D(imageManager->getImage(filename))); + osg::ref_ptr texture2d (new osg::Texture2D(image)); texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); From a9ad1b09e2f267e502c30db902e108fabdf8956e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 18:18:48 +0100 Subject: [PATCH 18/99] Introduce ShaderManager & ShaderVisitor Actual shaders still to be written. --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/CMakeLists.txt | 4 + components/resource/scenemanager.cpp | 12 ++ components/resource/scenemanager.hpp | 9 + components/shader/shadermanager.cpp | 81 +++++++++ components/shader/shadermanager.hpp | 49 ++++++ components/shader/shadervisitor.cpp | 195 ++++++++++++++++++++++ components/shader/shadervisitor.hpp | 54 ++++++ files/shaders/CMakeLists.txt | 2 + files/shaders/objects_fragment.glsl | 15 ++ files/shaders/objects_vertex.glsl | 14 ++ 11 files changed, 436 insertions(+) create mode 100644 components/shader/shadermanager.cpp create mode 100644 components/shader/shadermanager.hpp create mode 100644 components/shader/shadervisitor.cpp create mode 100644 components/shader/shadervisitor.hpp create mode 100644 files/shaders/objects_fragment.glsl create mode 100644 files/shaders/objects_vertex.glsl diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7569cea1f..6b5cf460c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -168,6 +168,7 @@ namespace MWRender , mFieldOfViewOverridden(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0ac32f65e..2508cdc82 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,6 +44,10 @@ add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager ) +add_component_dir (shader + shadermanager shadervisitor + ) + add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e78cc7cb2..f4846a8a0 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include + #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" @@ -205,6 +208,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) + , mShaderManager(new Shader::ShaderManager) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -221,6 +225,11 @@ namespace Resource // this has to be defined in the .cpp file as we can't delete incomplete types } + void SceneManager::setShaderPath(const std::string &path) + { + mShaderManager->setShaderPath(path); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { @@ -329,6 +338,9 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + loaded->accept(shaderVisitor); + // share state mSharedStateMutex.lock(); osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 987b7898e..d86c746d7 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,6 +22,11 @@ namespace osgUtil class IncrementalCompileOperation; } +namespace Shader +{ + class ShaderManager; +} + namespace Resource { @@ -34,6 +40,8 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + void setShaderPath(const std::string& path); + /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. @@ -97,6 +105,7 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); + std::auto_ptr mShaderManager; osg::ref_ptr mInstanceCache; OpenThreads::Mutex mSharedStateMutex; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp new file mode 100644 index 000000000..fc9a1f722 --- /dev/null +++ b/components/shader/shadermanager.cpp @@ -0,0 +1,81 @@ +#include "shadermanager.hpp" + +#include +#include + +#include +#include +#include + +#include + +namespace Shader +{ + + void ShaderManager::setShaderPath(const std::string &path) + { + mPath = path; + } + + osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + { + OpenThreads::ScopedLock lock(mMutex); + + // read the template if we haven't already + TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate); + if (templateIt == mShaderTemplates.end()) + { + boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate); + boost::filesystem::ifstream stream; + stream.open(p); + if (stream.fail()) + { + std::cerr << "Failed to open " << p.string() << std::endl; + return NULL; + } + std::stringstream buffer; + buffer << stream.rdbuf(); + + templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, buffer.str())).first; + } + + ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); + if (shaderIt == mShaders.end()) + { + std::string shaderSource = templateIt->second; + + const char escapeCharacter = '@'; + size_t foundPos = 0; + while ((foundPos = shaderSource.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = shaderSource.find_first_of(" \n\r()[].;", foundPos); + if (endPos == std::string::npos) + { + std::cerr << "Unexpected EOF" << std::endl; + return NULL; + } + std::string define = shaderSource.substr(foundPos+1, endPos - (foundPos+1)); + DefineMap::const_iterator defineFound = defines.find(define); + if (defineFound == defines.end()) + { + std::cerr << "Undefined " << define << " in shader " << shaderTemplate << std::endl; + return NULL; + } + else + { + shaderSource.replace(foundPos, endPos-foundPos, defineFound->second); + } + } + + osg::ref_ptr shader (new osg::Shader(shaderType)); + shader->setShaderSource(shaderSource); + // Assign a unique name to allow the SharedStateManager to compare shaders efficiently + static unsigned int counter = 0; + shader->setName(boost::lexical_cast(counter++)); + + shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first; + } + return shaderIt->second; + } + +} diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp new file mode 100644 index 000000000..e0ec3fe5f --- /dev/null +++ b/components/shader/shadermanager.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SHADERMANAGER_H + +#include +#include + +#include + +#include + +#include + +namespace Shader +{ + + /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. + /// @par Shader templates can get the value of a define with the syntax @define. + class ShaderManager + { + public: + void setShaderPath(const std::string& path); + + typedef std::map DefineMap; + + /// Create or retrieve a shader instance. + /// @param shaderTemplate The filename of the shader template. + /// @param defines Define values that can be retrieved by the shader template. + /// @param shaderType The type of shader (usually vertex or fragment shader). + /// @note May return NULL on failure. + /// @note Thread safe. + osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + + private: + std::string mPath; + + // + typedef std::map TemplateMap; + TemplateMap mShaderTemplates; + + typedef std::pair MapKey; + typedef std::map > ShaderMap; + ShaderMap mShaders; + + OpenThreads::Mutex mMutex; + }; + +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp new file mode 100644 index 000000000..235e63d95 --- /dev/null +++ b/components/shader/shadervisitor.cpp @@ -0,0 +1,195 @@ +#include "shadervisitor.hpp" + +#include + +#include +#include +#include + +#include + +#include + +#include "shadermanager.hpp" + +namespace Shader +{ + + ShaderVisitor::ShaderRequirements::ShaderRequirements() + : mColorMaterial(false) + , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mTexStageRequiringTangents(-1) + { + } + + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mShaderManager(shaderManager) + , mDefaultVsTemplate(defaultVsTemplate) + , mDefaultFsTemplate(defaultFsTemplate) + { + mRequirements.push_back(ShaderRequirements()); + } + + void ShaderVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + pushRequirements(); + applyStateSet(node.getStateSet()); + traverse(node); + popRequirements(); + } + else + traverse(node); + } + + void ShaderVisitor::applyStateSet(osg::StateSet* stateset) + { + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (attr) + { + const osg::Texture* texture = attr->asTexture(); + if (texture) + { + if (!texture->getName().empty()) + { + mRequirements.back().mTextures[unit] = texture->getName(); + if (texture->getName() == "normalMap") + { + mRequirements.back().mTexStageRequiringTangents = unit; + // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On + stateset->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + } + } + else + std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + } + } + // remove state that has no effect when rendering with shaders + stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); + } + + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); + for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + { + if (it->first.first == osg::StateAttribute::MATERIAL) + { + const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mVertexColorMode = mat->getColorMode(); + } + } + } + + void ShaderVisitor::pushRequirements() + { + mRequirements.push_back(mRequirements.back()); + } + + void ShaderVisitor::popRequirements() + { + mRequirements.pop_back(); + } + + void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::StateSet *stateset) + { + ShaderManager::DefineMap defineMap; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap" }; + for (unsigned int i=0; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + defineMap[texIt->second] = "1"; + defineMap[texIt->second + std::string("UV")] = boost::lexical_cast(texIt->first); + } + + if (!reqs.mColorMaterial) + defineMap["colorMode"] = "0"; + else + { + switch (reqs.mVertexColorMode) + { + default: + case GL_AMBIENT_AND_DIFFUSE: + defineMap["colorMode"] = "2"; + break; + case GL_AMBIENT: + defineMap["colorMode"] = "1"; + break; + } + } + + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + + if (vertexShader && fragmentShader) + { + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + stateset->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + } + } + } + + void ShaderVisitor::apply(osg::Geometry& geometry) + { + bool needPop = (geometry.getStateSet() != NULL); + if (geometry.getStateSet()) + { + pushRequirements(); + applyStateSet(geometry.getStateSet()); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + if (reqs.mTexStageRequiringTangents != -1) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(&geometry, reqs.mTexStageRequiringTangents); + + geometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + } + + // TODO: find a better place for the stateset + createProgram(reqs, geometry.getOrCreateStateSet()); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::apply(osg::Drawable& drawable) + { + // non-Geometry drawable (e.g. particle system) + bool needPop = (drawable.getStateSet() != NULL); + + if (drawable.getStateSet()) + { + pushRequirements(); + applyStateSet(drawable.getStateSet()); + } + + if (!mRequirements.empty()) + { + // TODO: find a better place for the stateset + createProgram(mRequirements.back(), drawable.getOrCreateStateSet()); + } + + if (needPop) + popRequirements(); + } + +} diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp new file mode 100644 index 000000000..507f1417e --- /dev/null +++ b/components/shader/shadervisitor.hpp @@ -0,0 +1,54 @@ +#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H +#define OPENMW_COMPONENTS_SHADERVISITOR_H + +#include + +namespace Shader +{ + + class ShaderManager; + + /// @brief Adjusts the given subgraph to render using shaders. + class ShaderVisitor : public osg::NodeVisitor + { + public: + ShaderVisitor(ShaderManager& shaderManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Drawable& drawable); + virtual void apply(osg::Geometry& geometry); + + void applyStateSet(osg::StateSet* stateset); + + void pushRequirements(); + void popRequirements(); + + private: + ShaderManager& mShaderManager; + + struct ShaderRequirements + { + ShaderRequirements(); + + // + std::map mTextures; + + bool mColorMaterial; + // osg::Material::ColorMode + int mVertexColorMode; + + // -1 == no tangents required + int mTexStageRequiringTangents; + }; + std::vector mRequirements; + + std::string mDefaultVsTemplate; + std::string mDefaultFsTemplate; + + void createProgram(const ShaderRequirements& reqs, osg::StateSet* stateset); + }; + +} + +#endif diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index fc4706c1f..edb5b51f2 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -6,6 +6,8 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + objects_vertex.glsl + objects_fragment.glsl ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl new file mode 100644 index 000000000..9b42c30a7 --- /dev/null +++ b/files/shaders/objects_fragment.glsl @@ -0,0 +1,15 @@ +#version 120 + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1,1,1,1); +#endif +} diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl new file mode 100644 index 000000000..17e8f3fc4 --- /dev/null +++ b/files/shaders/objects_vertex.glsl @@ -0,0 +1,14 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + +#if @diffuseMap + diffuseMapUV = gl_MultiTexCoord@diffuseMapUV.xy; +#endif +} From 41e1fd407d3e0b2fb5e167b1ec5ff7f2ef7bdd51 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 22:28:06 +0100 Subject: [PATCH 19/99] Disable shaders for the sky --- apps/openmw/mwrender/sky.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f10beca6c..95204d5eb 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1125,6 +1125,9 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mSunEnabled(true) { osg::ref_ptr skyroot (new CameraRelativeTransform); + // Assign empty program to specify we don't want shaders + // The shaders generated by the SceneManager can't handle everything we need + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); From 044e0a829a812748934893d941d2bc3e09ecc553 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 22:32:59 +0100 Subject: [PATCH 20/99] Add fog --- apps/openmw/mwrender/characterpreview.cpp | 7 +++++++ apps/openmw/mwrender/localmap.cpp | 7 ++++++- files/shaders/objects_fragment.glsl | 7 ++++++- files/shaders/objects_vertex.glsl | 3 +++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 258854054..1c9c3be4b 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -102,6 +103,12 @@ namespace MWRender stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.25, 0.25, 0.25, 1.0)); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index fd224ba41..be477735f 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -180,7 +180,12 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - stateset->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 9b42c30a7..288760229 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -5,11 +5,16 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif +varying float depth; + void main() { #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else - gl_FragData[0] = vec4(1,1,1,1); + gl_FragData[0] = vec4(1.0, 1.0, 1.0, 1.0); #endif + + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); } diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 17e8f3fc4..47ae20572 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -3,10 +3,13 @@ #if @diffuseMap varying vec2 diffuseMapUV; #endif + +varying float depth; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + depth = gl_Position.z; #if @diffuseMap diffuseMapUV = gl_MultiTexCoord@diffuseMapUV.xy; From 937681121348b8ed38b20225f62951fd7d568b0c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 16 Feb 2016 23:30:23 +0100 Subject: [PATCH 21/99] Vertex lighting shader --- apps/openmw/mwrender/animation.cpp | 5 ++++ components/sceneutil/lightmanager.cpp | 12 ++++++++ components/shader/shadervisitor.cpp | 1 + files/shaders/objects_fragment.glsl | 4 +++ files/shaders/objects_vertex.glsl | 42 ++++++++++++++++++++++++++- 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index abca3b266..65ac7d877 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1063,6 +1063,9 @@ namespace MWRender osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); node->addUpdateCallback(glowupdater); + + // TODO: regenerate shader + // allowedToModifyStatesets = false } // TODO: Should not be here @@ -1243,6 +1246,8 @@ namespace MWRender material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // FIXME: regenerate shader (mVertexColorMode) + mObjectRoot->setStateSet(stateset); } else diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 64448e73e..ca1fc7eb8 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -56,6 +56,18 @@ namespace SceneUtil } state.applyModelViewMatrix(modelViewMatrix); + + /* + for (int i=0; i<8; ++i) + { + osg::ref_ptr defaultLight (new osg::Light(i)); + defaultLight->setAmbient(osg::Vec4()); + defaultLight->setDiffuse(osg::Vec4()); + defaultLight->setSpecular(osg::Vec4()); + defaultLight->setConstantAttenuation(0.f); + state.setGlobalDefaultAttribute(defaultLight); + } + */ } private: diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 235e63d95..02ece3296 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -79,6 +79,7 @@ namespace Shader if (it->first.first == osg::StateAttribute::MATERIAL) { const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); mRequirements.back().mVertexColorMode = mat->getColorMode(); } } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 288760229..07a524ba1 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -7,6 +7,8 @@ varying vec2 diffuseMapUV; varying float depth; +varying vec3 lighting; + void main() { #if @diffuseMap @@ -15,6 +17,8 @@ void main() gl_FragData[0] = vec4(1.0, 1.0, 1.0, 1.0); #endif + gl_FragData[0].xyz *= lighting; + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); } diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 47ae20572..679abda1d 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -5,7 +5,44 @@ varying vec2 diffuseMapUV; #endif varying float depth; - + +varying vec3 lighting; + +#define MAX_LIGHTS 8 + +vec3 doLighting(vec4 vertex, vec3 normal, vec3 vertexColor) +{ + vec3 viewPos = (gl_ModelViewMatrix * vertex).xyz; + vec3 viewNormal = normalize((gl_NormalMatrix * normal).xyz); + vec3 lightDir; + float d; + +#if @colorMode == 2 + vec3 diffuse = vertexColor; + vec3 ambient = vertexColor; +#else + vec3 diffuse = gl_FrontMaterial.diffuse.xyz; + vec3 ambient = gl_FrontMaterial.ambient.xyz; +#endif + + vec3 lightResult = vec3(0.0, 0.0, 0.0); + for (int i=0; i Date: Wed, 17 Feb 2016 00:33:20 +0100 Subject: [PATCH 22/99] Fix light state issue GLSL does not respect gl_Disable(GL_LIGHTX), so we have to set unused lights to zero. Sadly, this change makes the applying of the modelView matrix less efficient. So far I couldn't find a better solution, osg's state tracker keeps getting in the way. :( --- components/sceneutil/lightmanager.cpp | 43 +++++++++++++-------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index ca1fc7eb8..4d8edec42 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -56,18 +56,6 @@ namespace SceneUtil } state.applyModelViewMatrix(modelViewMatrix); - - /* - for (int i=0; i<8; ++i) - { - osg::ref_ptr defaultLight (new osg::Light(i)); - defaultLight->setAmbient(osg::Vec4()); - defaultLight->setDiffuse(osg::Vec4()); - defaultLight->setSpecular(osg::Vec4()); - defaultLight->setConstantAttenuation(0.f); - state.setGlobalDefaultAttribute(defaultLight); - } - */ } private: @@ -204,18 +192,18 @@ namespace SceneUtil return found->second; else { - - std::vector > lights; - for (unsigned int i=0; imLightSource->getLight(frameNum)); - - osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); - osg::ref_ptr stateset = new osg::StateSet; - // don't use setAttributeAndModes, that does not support light indices! - stateset->setAttribute(attr, osg::StateAttribute::ON); - stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + for (unsigned int i=0; i > lights; + lights.push_back(lightList[i]->mLightSource->getLight(frameNum)); + osg::ref_ptr attr = new LightStateAttribute(mStartLight+i, lights); + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(attr, osg::StateAttribute::ON); + stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + + } stateSetCache.insert(std::make_pair(hash, stateset)); return stateset; @@ -254,6 +242,17 @@ namespace SceneUtil void LightManager::setStartLight(int start) { mStartLight = start; + + // Set default light state to zero + for (int i=start; i<8; ++i) + { + osg::ref_ptr defaultLight (new osg::Light(i)); + defaultLight->setAmbient(osg::Vec4()); + defaultLight->setDiffuse(osg::Vec4()); + defaultLight->setSpecular(osg::Vec4()); + defaultLight->setConstantAttenuation(0.f); + getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); + } } int LightManager::getStartLight() const From bd279c63f7c99557e6bcb19973a769d1efee9c56 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 01:56:41 +0100 Subject: [PATCH 23/99] Fix fog on water --- apps/openmw/mwrender/water.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index e6acbb500..8e9a7630c 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -306,7 +306,12 @@ public: setUpdateCallback(new NoTraverseCallback); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog - getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; addChild(mClipCullNode); From 553408949af813d7716d07bcdf443328d8f44815 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 01:56:47 +0100 Subject: [PATCH 24/99] Fix character preview --- apps/openmw/mwrender/characterpreview.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 1c9c3be4b..b9f20f4ea 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -99,7 +99,7 @@ namespace MWRender osg::ref_ptr lightManager = new SceneUtil::LightManager; lightManager->setStartLight(1); - osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); @@ -130,7 +130,6 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON); - lightManager->setStateSet(stateset); lightManager->addChild(lightSource); mCamera->addChild(lightManager); From 3859c58a8a0c31111c4be4a1a7612c6fccabcab1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 02:04:30 +0100 Subject: [PATCH 25/99] Assign gl_ClipVertex in the vertex shader to make clip planes work --- files/shaders/objects_vertex.glsl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 679abda1d..fadbd9898 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -10,10 +10,8 @@ varying vec3 lighting; #define MAX_LIGHTS 8 -vec3 doLighting(vec4 vertex, vec3 normal, vec3 vertexColor) +vec3 doLighting(vec3 viewPos, vec3 viewNormal, vec3 vertexColor) { - vec3 viewPos = (gl_ModelViewMatrix * vertex).xyz; - vec3 viewNormal = normalize((gl_NormalMatrix * normal).xyz); vec3 lightDir; float d; @@ -28,7 +26,7 @@ vec3 doLighting(vec4 vertex, vec3 normal, vec3 vertexColor) vec3 lightResult = vec3(0.0, 0.0, 0.0); for (int i=0; i Date: Wed, 17 Feb 2016 02:27:40 +0100 Subject: [PATCH 26/99] Slightly more efficient applying of light state --- components/sceneutil/lightmanager.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 4d8edec42..3950d0aea 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -45,6 +45,8 @@ namespace SceneUtil virtual void apply(osg::State& state) const { + if (mLights.empty()) + return; osg::Matrix modelViewMatrix = state.getModelViewMatrix(); state.applyModelViewMatrix(state.getInitialViewMatrix()); @@ -193,16 +195,25 @@ namespace SceneUtil else { osg::ref_ptr stateset = new osg::StateSet; - + std::vector > lights; for (unsigned int i=0; i > lights; lights.push_back(lightList[i]->mLightSource->getLight(frameNum)); - osg::ref_ptr attr = new LightStateAttribute(mStartLight+i, lights); - // don't use setAttributeAndModes, that does not support light indices! - stateset->setAttribute(attr, osg::StateAttribute::ON); - stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + } + // the first light state attribute handles the actual state setting for all lights + // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary + osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); + // don't use setAttributeAndModes, that does not support light indices! + stateset->setAttribute(attr, osg::StateAttribute::ON); + stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + + // need to push some dummy attributes to ensure proper state tracking + // lights need to reset to their default when the StateSet is popped + for (unsigned int i=1; i dummy = new LightStateAttribute(mStartLight+i, std::vector >()); + stateset->setAttribute(dummy, osg::StateAttribute::ON); } stateSetCache.insert(std::make_pair(hash, stateset)); From e845c576d4c9993c7e5cae8932dc2818f50eaec0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 02:46:47 +0100 Subject: [PATCH 27/99] Use the texture matrix --- files/shaders/objects_vertex.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index fadbd9898..7ef2f6aa3 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -51,7 +51,7 @@ void main(void) vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); #if @diffuseMap - diffuseMapUV = gl_MultiTexCoord@diffuseMapUV.xy; + diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; #endif lighting = doLighting(viewPos.xyz, viewNormal, gl_Color.xyz); From 5fd84074c59e967c45f06c45f0ccf4d21b455c17 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 02:52:44 +0100 Subject: [PATCH 28/99] Reimplement detailMap, darkMap and emissiveMap --- files/shaders/objects_fragment.glsl | 27 +++++++++++++++++++++++++++ files/shaders/objects_vertex.glsl | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 07a524ba1..c3f004e02 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -5,6 +5,21 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif +#if @darkMap +uniform sampler2D darkMap; +varying vec2 darkMapUV; +#endif + +#if @detailMap +uniform sampler2D detailMap; +varying vec2 detailMapUV; +#endif + +#if @emissiveMap +uniform sampler2D emissiveMap; +varying vec2 emissiveMapUV; +#endif + varying float depth; varying vec3 lighting; @@ -17,8 +32,20 @@ void main() gl_FragData[0] = vec4(1.0, 1.0, 1.0, 1.0); #endif +#if @detailMap + gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; +#endif + +#if @darkMap + gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; +#endif + gl_FragData[0].xyz *= lighting; +#if @emissiveMap + gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; +#endif + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); } diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 7ef2f6aa3..50d6eb490 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -4,6 +4,18 @@ varying vec2 diffuseMapUV; #endif +#if @darkMap +varying vec2 darkMapUV; +#endif + +#if @detailMap +varying vec2 detailMapUV; +#endif + +#if @emissiveMap +varying vec2 emissiveMapUV; +#endif + varying float depth; varying vec3 lighting; @@ -54,6 +66,18 @@ void main(void) diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; #endif +#if @darkMap + darkMapUV = (gl_TextureMatrix[@darkMapUV] * gl_MultiTexCoord@darkMapUV).xy; +#endif + +#if @detailMap + detailMapUV = (gl_TextureMatrix[@detailMap] * gl_MultiTexCoord@detailMap).xy; +#endif + +#if @emissiveMap + emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; +#endif + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color.xyz); lighting = clamp(lighting, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0)); } From 456816f707c36b482edb7385033bc5d4e282b412 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 03:07:18 +0100 Subject: [PATCH 29/99] Use diffuse.a / vertex.a, use material emission --- components/shader/shadervisitor.cpp | 2 +- files/shaders/objects_fragment.glsl | 4 ++-- files/shaders/objects_vertex.glsl | 29 +++++++++++++++++++---------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 02ece3296..96357ea9d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -120,7 +120,7 @@ namespace Shader case GL_AMBIENT_AND_DIFFUSE: defineMap["colorMode"] = "2"; break; - case GL_AMBIENT: + case GL_EMISSION: defineMap["colorMode"] = "1"; break; } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c3f004e02..016cae13f 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -22,7 +22,7 @@ varying vec2 emissiveMapUV; varying float depth; -varying vec3 lighting; +varying vec4 lighting; void main() { @@ -40,7 +40,7 @@ void main() gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; #endif - gl_FragData[0].xyz *= lighting; + gl_FragData[0] *= lighting; #if @emissiveMap gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 50d6eb490..1805f31de 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -18,24 +18,24 @@ varying vec2 emissiveMapUV; varying float depth; -varying vec3 lighting; +varying vec4 lighting; #define MAX_LIGHTS 8 -vec3 doLighting(vec3 viewPos, vec3 viewNormal, vec3 vertexColor) +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) { vec3 lightDir; float d; #if @colorMode == 2 - vec3 diffuse = vertexColor; - vec3 ambient = vertexColor; + vec4 diffuse = vertexColor; + vec3 ambient = vertexColor.xyz; #else - vec3 diffuse = gl_FrontMaterial.diffuse.xyz; + vec4 diffuse = gl_FrontMaterial.diffuse; vec3 ambient = gl_FrontMaterial.ambient.xyz; #endif + vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); - vec3 lightResult = vec3(0.0, 0.0, 0.0); for (int i=0; i Date: Wed, 17 Feb 2016 15:04:05 +0100 Subject: [PATCH 30/99] Cache the Program --- components/shader/shadermanager.cpp | 16 ++++++++++++++++ components/shader/shadermanager.hpp | 6 ++++++ components/shader/shadervisitor.cpp | 6 +----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index fc9a1f722..9c0e70d48 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -78,4 +80,18 @@ namespace Shader return shaderIt->second; } + osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader) + { + OpenThreads::ScopedLock lock(mMutex); + ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); + if (found == mPrograms.end()) + { + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; + } + return found->second; + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index e0ec3fe5f..5196dbe80 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -30,6 +30,9 @@ namespace Shader /// @note Thread safe. osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + + private: std::string mPath; @@ -41,6 +44,9 @@ namespace Shader typedef std::map > ShaderMap; ShaderMap mShaders; + typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; + ProgramMap mPrograms; + OpenThreads::Mutex mMutex; }; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 96357ea9d..a077cb09e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -131,11 +131,7 @@ namespace Shader if (vertexShader && fragmentShader) { - osg::ref_ptr program (new osg::Program); - program->addShader(vertexShader); - program->addShader(fragmentShader); - - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { From 21c4dffed25424f8a0ef2d48a896c81197202cdc Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 23:29:07 +0100 Subject: [PATCH 31/99] Add #include support in shaders --- components/shader/shadermanager.cpp | 99 ++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 9c0e70d48..286642e57 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -19,6 +19,73 @@ namespace Shader mPath = path; } + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) + { + std::set includedFiles; + size_t foundPos = 0; + while ((foundPos = source.find("#include")) != std::string::npos) + { + size_t start = source.find('"', foundPos); + if (start == std::string::npos || start == source.size()-1) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + size_t end = source.find('"', start+1); + if (end == std::string::npos) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + std::string includeFilename = source.substr(start+1, end-(start+1)); + boost::filesystem::path includePath = shaderPath / includeFilename; + boost::filesystem::ifstream includeFstream; + includeFstream.open(includePath); + if (includeFstream.fail()) + { + std::cerr << "Failed to open " << includePath.string() << std::endl; + return false; + } + std::stringstream buffer; + buffer << includeFstream.rdbuf(); + source.replace(foundPos, (end-foundPos+1), buffer.str()); + + if (includedFiles.insert(includePath).second == false) + { + std::cerr << "Detected cyclic #includes" << std::endl; + return false; + } + } + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + if (endPos == std::string::npos) + { + std::cerr << "Unexpected EOF" << std::endl; + return false; + } + std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); + ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); + if (defineFound == defines.end()) + { + std::cerr << "Undefined " << define << std::endl; + return false; + } + else + { + source.replace(foundPos, endPos-foundPos, defineFound->second); + } + } + return true; + } + osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) { OpenThreads::ScopedLock lock(mMutex); @@ -38,36 +105,20 @@ namespace Shader std::stringstream buffer; buffer << stream.rdbuf(); - templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, buffer.str())).first; + // parse includes + std::string source = buffer.str(); + if (!parseIncludes(boost::filesystem::path(mPath), source)) + return NULL; + + templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; } ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - - const char escapeCharacter = '@'; - size_t foundPos = 0; - while ((foundPos = shaderSource.find(escapeCharacter)) != std::string::npos) - { - size_t endPos = shaderSource.find_first_of(" \n\r()[].;", foundPos); - if (endPos == std::string::npos) - { - std::cerr << "Unexpected EOF" << std::endl; - return NULL; - } - std::string define = shaderSource.substr(foundPos+1, endPos - (foundPos+1)); - DefineMap::const_iterator defineFound = defines.find(define); - if (defineFound == defines.end()) - { - std::cerr << "Undefined " << define << " in shader " << shaderTemplate << std::endl; - return NULL; - } - else - { - shaderSource.replace(foundPos, endPos-foundPos, defineFound->second); - } - } + if (!parseDefines(shaderSource, defines)) + return NULL; osg::ref_ptr shader (new osg::Shader(shaderType)); shader->setShaderSource(shaderSource); From 1223bca3d4405ef2a1e2e5512322c11ceb680a46 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 17 Feb 2016 23:29:26 +0100 Subject: [PATCH 32/99] Move doLighting to separate file --- files/shaders/CMakeLists.txt | 1 + files/shaders/lighting.glsl | 37 +++++++++++++++++++++++++++++ files/shaders/objects_vertex.glsl | 39 +------------------------------ 3 files changed, 39 insertions(+), 38 deletions(-) create mode 100644 files/shaders/lighting.glsl diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index edb5b51f2..a7b631139 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -8,6 +8,7 @@ set(SHADER_FILES water_nm.png objects_vertex.glsl objects_fragment.glsl + lighting.glsl ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl new file mode 100644 index 000000000..98490b8b8 --- /dev/null +++ b/files/shaders/lighting.glsl @@ -0,0 +1,37 @@ +#define MAX_LIGHTS 8 + +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +{ + vec3 lightDir; + float d; + +#if @colorMode == 2 + vec4 diffuse = vertexColor; + vec3 ambient = vertexColor.xyz; +#else + vec4 diffuse = gl_FrontMaterial.diffuse; + vec3 ambient = gl_FrontMaterial.ambient.xyz; +#endif + vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + + for (int i=0; i Date: Wed, 17 Feb 2016 23:39:06 +0100 Subject: [PATCH 33/99] Add per-pixel lighting code --- files/shaders/lighting.glsl | 4 ++++ files/shaders/objects_fragment.glsl | 15 +++++++++++++++ files/shaders/objects_vertex.glsl | 18 ++++++++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index 98490b8b8..f856bc6bd 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -33,5 +33,9 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) lightResult.xyz += gl_FrontMaterial.emission.xyz; #endif + // TODO: make clamp configurable + // the following produces fixed-function compatible lighting, w/o clamp arguably looks better + //lightResult = clamp(lightResult, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 1.0, 1.0)); + return lightResult; } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 016cae13f..f3a9aa766 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -22,7 +22,17 @@ varying vec2 emissiveMapUV; varying float depth; +#define PER_PIXEL_LIGHTING 0 + +#if !PER_PIXEL_LIGHTING varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColour; +#endif + +#include "lighting.glsl" void main() { @@ -40,7 +50,12 @@ void main() gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; #endif + +#if !PER_PIXEL_LIGHTING gl_FragData[0] *= lighting; +#else + gl_FragData[0] *= doLighting(passViewPos, passViewNormal, passColour); +#endif #if @emissiveMap gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index c322f6853..7a6f0ca21 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -18,7 +18,15 @@ varying vec2 emissiveMapUV; varying float depth; +#define PER_PIXEL_LIGHTING 0 + +#if !PER_PIXEL_LIGHTING varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColour; +#endif #include "lighting.glsl" @@ -47,9 +55,11 @@ void main(void) emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; #endif +#if !PER_PIXEL_LIGHTING lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); - - // TODO: make clamp configurable - // the following produces fixed-function compatible lighting, w/o clamp arguably looks better - //lighting = clamp(lighting, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 1.0, 1.0)); +#else + passViewPos = viewPos.xyz; + passViewNormal = viewNormal; + passColour = gl_Color; +#endif } From e5a37a70232f43cd6bd3b76afcb26d0053fe56b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 00:00:12 +0100 Subject: [PATCH 34/99] Add normal map code --- files/shaders/objects_fragment.glsl | 20 ++++++++++++++++++-- files/shaders/objects_vertex.glsl | 12 +++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index f3a9aa766..6346aa499 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -20,9 +20,15 @@ uniform sampler2D emissiveMap; varying vec2 emissiveMapUV; #endif +#if @normalMap +uniform sampler2D normalMap; +varying vec2 normalMapUV; +varying vec3 viewTangent; +#endif + varying float depth; -#define PER_PIXEL_LIGHTING 0 +#define PER_PIXEL_LIGHTING @normalMap #if !PER_PIXEL_LIGHTING varying vec4 lighting; @@ -50,11 +56,21 @@ void main() gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; #endif +#if @normalMap + vec3 viewNormal = passViewNormal; + vec3 normalTex = texture2D(normalMap, normalMapUV).xyz; + + vec3 viewBinormal = cross(viewTangent, viewNormal); + mat3 tbn = mat3(viewTangent, viewBinormal, viewNormal); + + viewNormal = normalize(tbn * (normalTex * 2.0 - 1.0)); +#endif + #if !PER_PIXEL_LIGHTING gl_FragData[0] *= lighting; #else - gl_FragData[0] *= doLighting(passViewPos, passViewNormal, passColour); + gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColour); #endif #if @emissiveMap diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 7a6f0ca21..08fdb4ef8 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -16,9 +16,14 @@ varying vec2 detailMapUV; varying vec2 emissiveMapUV; #endif +#if @normalMap +varying vec2 normalMapUV; +varying vec3 viewTangent; +#endif + varying float depth; -#define PER_PIXEL_LIGHTING 0 +#define PER_PIXEL_LIGHTING @normalMap #if !PER_PIXEL_LIGHTING varying vec4 lighting; @@ -55,6 +60,11 @@ void main(void) emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy; #endif +#if @normalMap + normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; + viewTangent = normalize(gl_NormalMatrix * gl_MultiTexCoord7.xyz); +#endif + #if !PER_PIXEL_LIGHTING lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); #else From 3969675afa9b793e1d25fdddf312973fcc50fe28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 16:42:17 +0100 Subject: [PATCH 35/99] Remove unused settings --- apps/openmw/mwgui/settingswindow.cpp | 33 ---------------- apps/openmw/mwgui/settingswindow.hpp | 6 --- files/mygui/openmw_settings_window.layout | 14 +------ files/settings-default.cfg | 48 ----------------------- 4 files changed, 1 insertion(+), 100 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b410204e9..14408d11a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -179,9 +179,6 @@ namespace MWGui getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyBox, "AnisotropyBox"); - getWidget(mShadersButton, "ShadersButton"); - getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); - getWidget(mShadowsTextureSize, "ShadowsTextureSize"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); @@ -211,8 +208,6 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); - mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); - mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -253,13 +248,6 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - - if (!Settings::Manager::getBool("shaders", "Objects")) - { - mShadowsEnabledButton->setEnabled(false); - } - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -339,12 +327,6 @@ namespace MWGui apply(); } - void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) - { - Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); - apply(); - } - void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -361,21 +343,6 @@ namespace MWGui newState = true; } - if (_sender == mShadersButton) - { - if (newState == false) - { - // shadows not supported - mShadowsEnabledButton->setEnabled(false); - mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); - Settings::Manager::setBool("enabled", "Shadows", false); - } - else - { - mShadowsEnabledButton->setEnabled(true); - } - } - if (_sender == mFullscreenButton) { // check if this resolution is supported in fullscreen diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 421465309..8d9394fad 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -32,13 +32,9 @@ namespace MWGui MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::Widget* mAnisotropyBox; - MyGUI::Button* mShadersButton; MyGUI::ComboBox* mWaterTextureSize; - MyGUI::Button* mShadowsEnabledButton; - MyGUI::ComboBox* mShadowsTextureSize; - // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; @@ -58,8 +54,6 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index 5d6d817d6..fb8a358fe 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -257,18 +257,6 @@ - - - - - - - - - - - - @@ -411,7 +399,6 @@ @@ -489,6 +476,7 @@ + --> diff --git a/files/settings-default.cfg b/files/settings-default.cfg index a5e750d4d..f6c5d29fc 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -277,54 +277,6 @@ refraction = false # Draw NPCs and creatures on water reflections. reflect actors = false -[Objects] - -# Enable shaders for objects other than water. Unused. -shaders = true - -[Terrain] - -# Use shaders for terrain? Unused. -shader = true - -# Distant land is rendered? Unused. -distant land = false - -[Shadows] - -# Enable shadows. Other shadow settings disabled if false. Unused. -enabled = false - -# Size of the shadow textures in pixels. Unused. (e.g. 256 to 2048). -texture size = 1024 - -# Actors cast shadows. Unused. -actor shadows = true - -# Static objects cast shadows. Unused. -statics shadows = true - -# Terrain cast shadows. Unused. -terrain shadows = true - -# Miscellaneous objects cast shadows. Unused. -misc shadows = true - -# Debugging of shadows. Unused. -debug = false - -# Fraction of distance after which shadow starts to fade out. Unused. -fade start = 0.8 - -# Split shadow maps, allowing for a larger shadow distance. Unused. -split = false - -# Distance for shadows if not split. Smaller is poorer. Unused. -shadow distance = 1300 - -# Distance for shadows if split. Unused. -split shadow distance = 14000 - [Windows] # Location and sizes of windows as a fraction of the OpenMW window or From a73512afb73ec7b4a29ebcc07cd05f3db80cef02 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 17:08:18 +0100 Subject: [PATCH 36/99] Add shader settings to settings.cfg --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ components/resource/scenemanager.cpp | 21 +++++++++++++++ components/resource/scenemanager.hpp | 13 +++++++++ components/shader/shadervisitor.cpp | 32 ++++++++++++++++++++--- components/shader/shadervisitor.hpp | 17 ++++++++++++ files/settings-default.cfg | 21 +++++++++++++++ files/shaders/lighting.glsl | 9 ++++--- files/shaders/objects_fragment.glsl | 7 +++-- files/shaders/objects_vertex.glsl | 2 +- 9 files changed, 115 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6b5cf460c..dfc1743f0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -169,6 +169,9 @@ namespace MWRender { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); + resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f4846a8a0..be0cd7dd7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -209,6 +209,9 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) , mShaderManager(new Shader::ShaderManager) + , mForceShaders(false) + , mClampLighting(false) + , mForcePerPixelLighting(false) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -220,6 +223,21 @@ namespace Resource { } + void SceneManager::setForceShaders(bool force) + { + mForceShaders = force; + } + + void SceneManager::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + void SceneManager::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -339,6 +357,9 @@ namespace Resource loaded->accept(setFilterSettingsControllerVisitor); Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); loaded->accept(shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index d86c746d7..2ef9f614b 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -40,6 +40,15 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + /// @see ShaderVisitor::setForceShaders + void setForceShaders(bool force); + + /// @see ShaderVisitor::setClampLighting + void setClampLighting(bool clamp); + + /// @see ShaderVisitor::setForcePerPixelLighting + void setForcePerPixelLighting(bool force); + void setShaderPath(const std::string& path); /// Get a read-only copy of this scene "template" @@ -106,6 +115,10 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); std::auto_ptr mShaderManager; + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + osg::ref_ptr mInstanceCache; OpenThreads::Mutex mSharedStateMutex; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a077cb09e..67362e0c6 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -16,7 +16,8 @@ namespace Shader { ShaderVisitor::ShaderRequirements::ShaderRequirements() - : mColorMaterial(false) + : mHasNormalMap(false) + , mColorMaterial(false) , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) , mTexStageRequiringTangents(-1) { @@ -24,6 +25,9 @@ namespace Shader ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mForceShaders(false) + , mClampLighting(false) + , mForcePerPixelLighting(false) , mShaderManager(shaderManager) , mDefaultVsTemplate(defaultVsTemplate) , mDefaultFsTemplate(defaultFsTemplate) @@ -31,6 +35,21 @@ namespace Shader mRequirements.push_back(ShaderRequirements()); } + void ShaderVisitor::setForceShaders(bool force) + { + mForceShaders = force; + } + + void ShaderVisitor::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + void ShaderVisitor::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + void ShaderVisitor::apply(osg::Node& node) { if (node.getStateSet()) @@ -61,6 +80,7 @@ namespace Shader if (texture->getName() == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mHasNormalMap = true; // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On stateset->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); } @@ -126,6 +146,9 @@ namespace Shader } } + defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = mClampLighting ? "1" : "0"; + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -161,7 +184,8 @@ namespace Shader } // TODO: find a better place for the stateset - createProgram(reqs, geometry.getOrCreateStateSet()); + if (reqs.mHasNormalMap || mForceShaders) + createProgram(reqs, geometry.getOrCreateStateSet()); } if (needPop) @@ -181,8 +205,10 @@ namespace Shader if (!mRequirements.empty()) { + const ShaderRequirements& reqs = mRequirements.back(); // TODO: find a better place for the stateset - createProgram(mRequirements.back(), drawable.getOrCreateStateSet()); + if (reqs.mHasNormalMap || mForceShaders) + createProgram(reqs, drawable.getOrCreateStateSet()); } if (needPop) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 507f1417e..fb29d503f 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -14,6 +14,17 @@ namespace Shader public: ShaderVisitor(ShaderManager& shaderManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + /// By default, only bump mapped objects will have a shader added to them. + /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. + void setForceShaders(bool force); + + /// Set whether lighting is clamped for visual compatibility with the fixed function pipeline. + void setClampLighting(bool clamp); + + /// By default, only bump mapped objects use per-pixel lighting. + /// Setting force = true will cause all shaders to use per-pixel lighting, regardless of having a bump map. + void setForcePerPixelLighting(bool force); + virtual void apply(osg::Node& node); virtual void apply(osg::Drawable& drawable); @@ -25,6 +36,10 @@ namespace Shader void popRequirements(); private: + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + ShaderManager& mShaderManager; struct ShaderRequirements @@ -34,6 +49,8 @@ namespace Shader // std::map mTextures; + bool mHasNormalMap; + bool mColorMaterial; // osg::Material::ColorMode int mVertexColorMode; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f6c5d29fc..e450f6518 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -157,6 +157,27 @@ texture min filter = linear # Texture mipmap type. (none, nearest, or linear). texture mipmap = nearest +[Shaders] + +# Force rendering with shaders. By default, only bump-mapped objects will use shaders. +# Enabling this option may cause slightly different visuals if the "clamp lighting" option +# is set to false. Otherwise, there should not be a visual difference. +force shaders = false + +# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. +# Has no effect if the 'force shaders' option is false. +# Enabling per-pixel lighting can result in visual differences to the original MW engine. It is not +# recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind +# rely on vertex lighting to look as intended. +force per pixel lighting = false + +# Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). +# Only affects objects that render with shaders (see 'force shaders' option). +# Setting this option to 'true' results in fixed-function compatible lighting, but the lighting +# may appear 'dull' and there might be color shifts. +# Setting this option to 'false' results in more realistic lighting. +clamp lighting = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index f856bc6bd..d3c51d3cd 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -33,9 +33,10 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) lightResult.xyz += gl_FrontMaterial.emission.xyz; #endif - // TODO: make clamp configurable - // the following produces fixed-function compatible lighting, w/o clamp arguably looks better - //lightResult = clamp(lightResult, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 1.0, 1.0)); - +#if @clamp + lightResult = clamp(lightResult, vec4(0.0, 0.0, 0.0, 0.0), vec4(1.0, 1.0, 1.0, 1.0)); +#else + lightResult = max(lightResult, 0.0); +#endif return lightResult; } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6346aa499..37c715e1a 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -28,7 +28,7 @@ varying vec3 viewTangent; varying float depth; -#define PER_PIXEL_LIGHTING @normalMap +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING varying vec4 lighting; @@ -56,8 +56,11 @@ void main() gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; #endif -#if @normalMap +#if PER_PIXEL_LIGHTING vec3 viewNormal = passViewNormal; +#endif + +#if @normalMap vec3 normalTex = texture2D(normalMap, normalMapUV).xyz; vec3 viewBinormal = cross(viewTangent, viewNormal); diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index 08fdb4ef8..940a2306e 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -23,7 +23,7 @@ varying vec3 viewTangent; varying float depth; -#define PER_PIXEL_LIGHTING @normalMap +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) #if !PER_PIXEL_LIGHTING varying vec4 lighting; From 12326073859acdbecd3681038c9b16e4be0340dd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 19:41:06 +0100 Subject: [PATCH 37/99] Remove pointless widget names in settings layout file --- apps/openmw/mwgui/settingswindow.cpp | 1 - apps/openmw/mwgui/settingswindow.hpp | 1 - files/mygui/openmw_settings_window.layout | 38 +++++++++++------------ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 14408d11a..a0833194b 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -175,7 +175,6 @@ namespace MWGui getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); - getWidget(mVSyncButton, "VSyncButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyBox, "AnisotropyBox"); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 8d9394fad..5b12cc557 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -28,7 +28,6 @@ namespace MWGui // graphics MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; - MyGUI::Button* mVSyncButton; MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::Widget* mAnisotropyBox; diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index fb8a358fe..8ff850cae 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -10,7 +10,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -84,7 +84,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -119,7 +119,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -152,7 +152,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -189,7 +189,7 @@ - + @@ -201,7 +201,7 @@ - + @@ -265,7 +265,7 @@ - + @@ -320,7 +320,7 @@ - + @@ -332,7 +332,7 @@ - + @@ -354,7 +354,7 @@ - + @@ -365,7 +365,7 @@ - + From 268594dcf145b17c98c4def32d78c334392d593b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 22:48:53 +0100 Subject: [PATCH 38/99] Recreate shader in NpcAnimation::setAlpha --- apps/openmw/mwrender/animation.cpp | 6 ++- apps/openmw/mwrender/util.cpp | 1 + components/resource/scenemanager.cpp | 10 +++++ components/resource/scenemanager.hpp | 3 ++ components/shader/shadervisitor.cpp | 67 ++++++++++++++++++++++------ components/shader/shadervisitor.hpp | 11 ++++- 6 files changed, 80 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 65ac7d877..3d030febf 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1246,13 +1246,15 @@ namespace MWRender material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - // FIXME: regenerate shader (mVertexColorMode) - mObjectRoot->setStateSet(stateset); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { mObjectRoot->setStateSet(NULL); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } setRenderBin(); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 0e1eaf0f4..876ec285f 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -18,6 +18,7 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); osg::ref_ptr stateset; if (node->getStateSet()) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index be0cd7dd7..172a6511a 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -228,6 +228,16 @@ namespace Resource mForceShaders = force; } + void SceneManager::recreateShaders(osg::ref_ptr node) + { + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAllowedToModifyStateSets(false); + node->accept(shaderVisitor); + } + void SceneManager::setClampLighting(bool clamp) { mClampLighting = clamp; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 2ef9f614b..db3de3f48 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -40,6 +40,9 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. + void recreateShaders(osg::ref_ptr node); + /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 67362e0c6..fcb9b8057 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -19,6 +19,7 @@ namespace Shader : mHasNormalMap(false) , mColorMaterial(false) , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mMaterialOverridden(false) , mTexStageRequiringTangents(-1) { } @@ -28,6 +29,7 @@ namespace Shader , mForceShaders(false) , mClampLighting(false) , mForcePerPixelLighting(false) + , mAllowedToModifyStateSets(true) , mShaderManager(shaderManager) , mDefaultVsTemplate(defaultVsTemplate) , mDefaultFsTemplate(defaultFsTemplate) @@ -55,7 +57,7 @@ namespace Shader if (node.getStateSet()) { pushRequirements(); - applyStateSet(node.getStateSet()); + applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); } @@ -63,8 +65,21 @@ namespace Shader traverse(node); } - void ShaderVisitor::applyStateSet(osg::StateSet* stateset) + osg::StateSet* getWritableStateSet(osg::Node& node) { + if (!node.getStateSet()) + return node.getOrCreateStateSet(); + + osg::ref_ptr newStateSet = osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + return newStateSet.get(); + } + + void ShaderVisitor::applyStateSet(osg::ref_ptr stateset, osg::Node& node) + { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); for(unsigned int unit=0;unitsetTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); } } else @@ -90,7 +107,12 @@ namespace Shader } } // remove state that has no effect when rendering with shaders - stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); + if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV)) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); + } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); @@ -98,9 +120,15 @@ namespace Shader { if (it->first.first == osg::StateAttribute::MATERIAL) { - const osg::Material* mat = static_cast(it->second.first.get()); - mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); - mRequirements.back().mVertexColorMode = mat->getColorMode(); + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; + + const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); + mRequirements.back().mVertexColorMode = mat->getColorMode(); + } } } } @@ -115,8 +143,14 @@ namespace Shader mRequirements.pop_back(); } - void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::StateSet *stateset) + void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getOrCreateStateSet(); + else + writableStateSet = getWritableStateSet(node); + ShaderManager::DefineMap defineMap; const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap" }; for (unsigned int i=0; isetAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { - stateset->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); } } } @@ -169,7 +203,7 @@ namespace Shader if (geometry.getStateSet()) { pushRequirements(); - applyStateSet(geometry.getStateSet()); + applyStateSet(geometry.getStateSet(), geometry); } if (!mRequirements.empty()) @@ -185,7 +219,7 @@ namespace Shader // TODO: find a better place for the stateset if (reqs.mHasNormalMap || mForceShaders) - createProgram(reqs, geometry.getOrCreateStateSet()); + createProgram(reqs, geometry); } if (needPop) @@ -200,7 +234,7 @@ namespace Shader if (drawable.getStateSet()) { pushRequirements(); - applyStateSet(drawable.getStateSet()); + applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) @@ -208,11 +242,16 @@ namespace Shader const ShaderRequirements& reqs = mRequirements.back(); // TODO: find a better place for the stateset if (reqs.mHasNormalMap || mForceShaders) - createProgram(reqs, drawable.getOrCreateStateSet()); + createProgram(reqs, drawable); } if (needPop) popRequirements(); } + void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) + { + mAllowedToModifyStateSets = allowed; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index fb29d503f..c301db47d 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -25,12 +25,17 @@ namespace Shader /// Setting force = true will cause all shaders to use per-pixel lighting, regardless of having a bump map. void setForcePerPixelLighting(bool force); + /// Set if we are allowed to modify StateSets encountered in the graph (default true). + /// @par If set to false, then instead of modifying, the StateSet will be cloned and this new StateSet will be assigned to the node. + /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. + void setAllowedToModifyStateSets(bool allowed); + virtual void apply(osg::Node& node); virtual void apply(osg::Drawable& drawable); virtual void apply(osg::Geometry& geometry); - void applyStateSet(osg::StateSet* stateset); + void applyStateSet(osg::ref_ptr stateset, osg::Node& node); void pushRequirements(); void popRequirements(); @@ -39,6 +44,7 @@ namespace Shader bool mForceShaders; bool mClampLighting; bool mForcePerPixelLighting; + bool mAllowedToModifyStateSets; ShaderManager& mShaderManager; @@ -54,6 +60,7 @@ namespace Shader bool mColorMaterial; // osg::Material::ColorMode int mVertexColorMode; + bool mMaterialOverridden; // -1 == no tangents required int mTexStageRequiringTangents; @@ -63,7 +70,7 @@ namespace Shader std::string mDefaultVsTemplate; std::string mDefaultFsTemplate; - void createProgram(const ShaderRequirements& reqs, osg::StateSet* stateset); + void createProgram(const ShaderRequirements& reqs, osg::Node& node); }; } From 5e12a1b4ef904c35c976526542ee306c0dc5787e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 18 Feb 2016 23:05:44 +0100 Subject: [PATCH 39/99] Add enchanted item glow to the shader --- apps/openmw/mwrender/animation.cpp | 20 +++++++++++++++----- components/shader/shadervisitor.cpp | 2 +- files/shaders/objects_fragment.glsl | 14 ++++++++++++-- files/shaders/objects_vertex.glsl | 15 +++++++++++++-- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3d030febf..ec0dab082 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -52,8 +52,8 @@ namespace class GlowUpdater : public SceneUtil::StateSetUpdater { public: - GlowUpdater(osg::Vec4f color, const std::vector >& textures) - : mTexUnit(1) // FIXME: might not always be 1 + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures) + : mTexUnit(texUnit) , mColor(color) , mTextures(textures) { @@ -1055,17 +1055,27 @@ namespace MWRender osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } - osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); + int texUnit = 1; // FIXME: might not always be 1 + osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); node->addUpdateCallback(glowupdater); - // TODO: regenerate shader - // allowedToModifyStatesets = false + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = NULL; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + + mResourceSystem->getSceneManager()->recreateShaders(node); } // TODO: Should not be here diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index fcb9b8057..331beab34 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -152,7 +152,7 @@ namespace Shader writableStateSet = getWritableStateSet(node); ShaderManager::DefineMap defineMap; - const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap" }; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap" }; for (unsigned int i=0; i Date: Thu, 18 Feb 2016 23:10:58 +0100 Subject: [PATCH 40/99] Use the lowest unused texture unit for the enchanted item glow texture --- apps/openmw/mwrender/animation.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ec0dab082..0418e34fd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1041,6 +1041,25 @@ namespace MWRender return mObjectRoot.get(); } + class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor + { + public: + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLowestUnusedTexUnit(0) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + int mLowestUnusedTexUnit; + }; + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) { std::vector > textures; @@ -1062,7 +1081,9 @@ namespace MWRender textures.push_back(tex); } - int texUnit = 1; // FIXME: might not always be 1 + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; + node->accept(findLowestUnusedTexUnitVisitor); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); node->addUpdateCallback(glowupdater); From 6000e48bba87eb22e8f4b5b29340e7f74c07d6fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 01:30:15 +0100 Subject: [PATCH 41/99] Add terrain shaders and normal map support Textures with _n filename suffix are automatically recognized as terrain normal maps. --- components/resource/scenemanager.cpp | 20 +++++ components/resource/scenemanager.hpp | 5 ++ components/terrain/material.cpp | 106 +++++++++++++++++++++++++-- components/terrain/material.hpp | 34 ++++++++- components/terrain/terraingrid.cpp | 31 +++++++- files/shaders/CMakeLists.txt | 2 + files/shaders/terrain_fragment.glsl | 64 ++++++++++++++++ files/shaders/terrain_vertex.glsl | 36 +++++++++ 8 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 files/shaders/terrain_fragment.glsl create mode 100644 files/shaders/terrain_vertex.glsl diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 172a6511a..57187c734 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -228,6 +228,11 @@ namespace Resource mForceShaders = force; } + bool SceneManager::getForceShaders() const + { + return mForceShaders; + } + void SceneManager::recreateShaders(osg::ref_ptr node) { Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); @@ -243,16 +248,31 @@ namespace Resource mClampLighting = clamp; } + bool SceneManager::getClampLighting() const + { + return mClampLighting; + } + void SceneManager::setForcePerPixelLighting(bool force) { mForcePerPixelLighting = force; } + bool SceneManager::getForcePerPixelLighting() const + { + return mForcePerPixelLighting; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types } + Shader::ShaderManager &SceneManager::getShaderManager() + { + return *mShaderManager.get(); + } + void SceneManager::setShaderPath(const std::string &path) { mShaderManager->setShaderPath(path); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index db3de3f48..8f2853789 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -40,17 +40,22 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + Shader::ShaderManager& getShaderManager(); + /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. void recreateShaders(osg::ref_ptr node); /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); + bool getForceShaders() const; /// @see ShaderVisitor::setClampLighting void setClampLighting(bool clamp); + bool getClampLighting() const; /// @see ShaderVisitor::setForcePerPixelLighting void setForcePerPixelLighting(bool force); + bool getForcePerPixelLighting() const; void setShaderPath(const std::string& path); diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 59d06f254..fe32fe543 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,21 +1,26 @@ #include "material.hpp" +#include +#include + #include #include #include #include #include -#include + +#include + namespace Terrain { - FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, + FixedFunctionTechnique::FixedFunctionTechnique(const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; - for (std::vector >::const_iterator it = layers.begin(); it != layers.end(); ++it) + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { osg::ref_ptr stateset (new osg::StateSet); @@ -53,7 +58,7 @@ namespace Terrain } // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = *it; + osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(texunit, tex.get()); osg::ref_ptr texMat (new osg::TexMat); @@ -66,9 +71,85 @@ namespace Terrain } } - Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) + { + bool firstLayer = true; + int i=0; + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) + { + osg::ref_ptr stateset (new osg::StateSet); + + if (!firstLayer) + { + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::EQUAL); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + int texunit = 0; + + stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + + osg::ref_ptr texMat (new osg::TexMat); + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); + stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + + if(!firstLayer) + { + ++texunit; + osg::ref_ptr blendmap = blendmaps.at(i++); + + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + + // This is to map corner vertices directly to the center of a blendmap texel. + osg::Matrixf texMat; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); + texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + + stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat)); + stateset->addUniform(new osg::Uniform("blendMap", texunit)); + } + + if (it->mNormalMap) + { + ++texunit; + stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); + stateset->addUniform(new osg::Uniform("normalMap", texunit)); + } + + Shader::ShaderManager::DefineMap defineMap; + defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = clampLighting ? "1" : "0"; + defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; + defineMap["blendMap"] = !firstLayer ? "1" : "0"; + defineMap["colorMode"] = "2"; + + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); + if (!vertexShader || !fragmentShader) + throw std::runtime_error("Unable to create shader"); + + stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); + + firstLayer = false; + + addPass(stateset); + } + } + + Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager& shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) - : mLayers(layers) + : mShaderManager(shaderManager) + , mUseShaders(useShaders) + , mForcePerPixelLighting(forcePerPixelLighting) + , mClampLighting(clampLighting) + , mLayers(layers) , mBlendmaps(blendmaps) , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) @@ -82,7 +163,18 @@ namespace Terrain bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + try + { + if (mUseShaders) + addTechnique(new ShaderTechnique(mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + else + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } + catch (std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index dd00e41ed..65349a5e7 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,14 +11,36 @@ namespace osg class Texture2D; } +namespace Shader +{ + class ShaderManager; +} + namespace Terrain { + struct TextureLayer + { + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mNormalMap; // optional + }; + class FixedFunctionTechnique : public osgFX::Technique { public: FixedFunctionTechnique( - const std::vector >& layers, + const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); + + protected: + virtual void define_passes() {} + }; + + class ShaderTechnique : public osgFX::Technique + { + public: + ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: @@ -28,8 +50,8 @@ namespace Terrain class Effect : public osgFX::Effect { public: - Effect( - const std::vector >& layers, + Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager& shaderManager, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -48,7 +70,11 @@ namespace Terrain } private: - std::vector > mLayers; + Shader::ShaderManager& mShaderManager; + bool mUseShaders; + bool mForcePerPixelLighting; + bool mClampLighting; + std::vector mLayers; std::vector > mBlendmaps; int mBlendmapScale; float mLayerTileSize; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index fefb0b8bc..ddbb2fe23 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -148,11 +148,15 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu osg::ref_ptr textureCompileDummy (new osg::Node); unsigned int dummyTextureCounter = 0; - std::vector > layerTextures; + bool useShaders = mResourceSystem->getSceneManager()->getForceShaders(); + if (!mResourceSystem->getSceneManager()->getClampLighting()) + useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps + std::vector layers; { OpenThreads::ScopedLock lock(mTextureCacheMutex); for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { + TextureLayer textureLayer; osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; if (!texture) { @@ -162,8 +166,26 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu mResourceSystem->getSceneManager()->applyFilterSettings(texture); mTextureCache[it->mDiffuseMap] = texture; } - layerTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); + textureLayer.mDiffuseMap = texture; + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + + if (!it->mNormalMap.empty()) + { + texture = mTextureCache[it->mNormalMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mNormalMap] = texture; + } + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + textureLayer.mNormalMap = texture; + useShaders = true; + } + + layers.push_back(textureLayer); } } @@ -185,7 +207,8 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + osg::ref_ptr effect (new Terrain::Effect(useShaders, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), + mResourceSystem->getSceneManager()->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index a7b631139..31db4a3eb 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -8,6 +8,8 @@ set(SHADER_FILES water_nm.png objects_vertex.glsl objects_fragment.glsl + terrain_vertex.glsl + terrain_fragment.glsl lighting.glsl ) diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl new file mode 100644 index 000000000..3bfd16b0e --- /dev/null +++ b/files/shaders/terrain_fragment.glsl @@ -0,0 +1,64 @@ +#version 120 + +varying vec2 uv; + +uniform sampler2D diffuseMap; + +#if @normalMap +uniform sampler2D normalMap; +#endif + +#if @blendMap +uniform sampler2D blendMap; +#endif + +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColor; +#endif + +#include "lighting.glsl" + +void main() +{ + vec2 diffuseMapUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; + + gl_FragData[0] = vec4(texture2D(diffuseMap, diffuseMapUV).xyz, 1.0); + +#if @blendMap + vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; + gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; +#endif + +#if PER_PIXEL_LIGHTING + vec3 viewNormal = passViewNormal; +#endif + +#if @normalMap + vec3 normalTex = texture2D(normalMap, diffuseMapUV).xyz; + + vec3 viewTangent = (gl_ModelViewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz; + vec3 viewBinormal = normalize(cross(viewTangent, viewNormal)); + viewTangent = normalize(cross(viewNormal, viewBinormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + mat3 tbn = mat3(viewTangent, viewBinormal, viewNormal); + + viewNormal = normalize(tbn * (normalTex * 2.0 - 1.0)); +#endif + + +#if !PER_PIXEL_LIGHTING + gl_FragData[0] *= lighting; +#else + gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor); +#endif + + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); +} diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl new file mode 100644 index 000000000..43cb5d31b --- /dev/null +++ b/files/shaders/terrain_vertex.glsl @@ -0,0 +1,36 @@ +#version 120 + +varying vec2 uv; +varying float depth; + +#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +varying vec4 lighting; +#else +varying vec3 passViewPos; +varying vec3 passViewNormal; +varying vec4 passColor; +#endif + +#include "lighting.glsl" + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + depth = gl_Position.z; + + vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); + gl_ClipVertex = viewPos; + vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + +#if !PER_PIXEL_LIGHTING + lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); +#else + passViewPos = viewPos.xyz; + passViewNormal = viewNormal; + passColor = gl_Color; +#endif + + uv = gl_MultiTexCoord0.xy; +} From ca0e1fe0e00232d2c94ef02fe978b3468dcecc8b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 01:45:28 +0100 Subject: [PATCH 42/99] Set the osg::Material on the terrain root node --- components/terrain/material.cpp | 4 ---- components/terrain/terraingrid.cpp | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fe32fe543..c1aef1f9d 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -154,10 +154,6 @@ namespace Terrain , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) { - osg::ref_ptr material (new osg::Material); - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); - selectTechnique(0); } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index ddbb2fe23..cbafe1677 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -56,6 +57,9 @@ TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceS , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) , mUnrefQueue(unrefQueue) { + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); } TerrainGrid::~TerrainGrid() From e2dc46c92e90da4ee1e96dadd52f821bd75bc8e4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 01:48:07 +0100 Subject: [PATCH 43/99] Update settings --- files/settings-default.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e450f6518..1c87c5dd5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -172,11 +172,11 @@ force shaders = false force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -# Only affects objects that render with shaders (see 'force shaders' option). +# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. # Setting this option to 'true' results in fixed-function compatible lighting, but the lighting # may appear 'dull' and there might be color shifts. # Setting this option to 'false' results in more realistic lighting. -clamp lighting = false +clamp lighting = true [Input] From 9d392487a9726637b68bd82673440ea8e19e28bc Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Fri, 19 Feb 2016 02:59:19 -0500 Subject: [PATCH 44/99] Fix names to be unique and condition values to be set to 0 --- apps/opencs/model/world/columns.cpp | 4 ++-- apps/opencs/model/world/nestedcoladapterimp.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index aa453ced8..32aa7edca 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -274,9 +274,9 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Name" }, + { ColumnId_InfoCondVar, "Variable/Object" }, { ColumnId_InfoCondComp, "Relation" }, - { ColumnId_InfoCondValue, "Value" }, + { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, { ColumnId_NpcAttributes, "NPC Attributes" }, diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index a8f1c229a..82df7bd11 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -674,7 +674,8 @@ namespace CSMWorld { case ConstInfoSelectWrapper::Comparison_Numeric: { - if (value.toInt(&conversionResult) && conversionResult) + // QVariant seems to have issues converting 0 + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.getVariant().setInteger(value.toInt()); @@ -689,7 +690,7 @@ namespace CSMWorld case ConstInfoSelectWrapper::Comparison_Boolean: case ConstInfoSelectWrapper::Comparison_Integer: { - if (value.toInt(&conversionResult) && conversionResult) + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { infoSelectWrapper.getVariant().setType(ESM::VT_Int); infoSelectWrapper.getVariant().setInteger(value.toInt()); From 5315866f611015b5a11f91820442cd053acd4ad4 Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Fri, 19 Feb 2016 03:03:10 -0500 Subject: [PATCH 45/99] Corrections to condition ranges and several other minor fixes --- apps/opencs/model/world/infoselectwrapper.cpp | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index 5271430f5..496f8e17f 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -46,7 +46,7 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = "PC Sneak", "PC Acrobatics", "PC Light Armor", - "PC Shorth Blade", + "PC Short Blade", "PC Marksman", "PC Merchantile", "PC Speechcraft", @@ -74,8 +74,8 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = "PC Corpus", "Weather", "PC Vampire", - "PC Level", - "PC Attacked", + "Level", + "Attacked", "Talked to PC", "PC Health", "Creature Target", @@ -239,33 +239,27 @@ std::string CSMWorld::ConstInfoSelectWrapper::toString() const { std::ostringstream stream; stream << convertToString(mFunctionName) << " "; - + if (mHasVariable) stream << mVariableName << " "; - + stream << convertToString(mRelationType) << " "; - + switch (mConstSelect.mValue.getType()) { - case ESM::VT_Short: - case ESM::VT_Long: case ESM::VT_Int: stream << mConstSelect.mValue.getInteger(); break; - + case ESM::VT_Float: stream << mConstSelect.mValue.getFloat(); break; - - case ESM::VT_String: - stream << mConstSelect.mValue.getString(); - break; - + default: stream << "(Invalid value type)"; break; } - + return stream.str(); } @@ -377,6 +371,7 @@ void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() case Function_NotClass: case Function_NotRace: case Function_NotCell: + case Function_NotLocal: case Function_PcExpelled: case Function_PcCommonDisease: case Function_PcBlightDisease: @@ -459,7 +454,7 @@ void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() // Numeric case Function_Global: case Function_Local: - case Function_NotLocal: + case Function_Health_Percent: case Function_PcHealthPercent: case Function_PcMagicka: @@ -559,14 +554,13 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const 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_NotLocal: case Function_PcExpelled: case Function_PcCommonDisease: case Function_PcBlightDisease: @@ -581,7 +575,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const case Function_TalkedToPc: case Function_ShouldAttack: case Function_Werewolf: - return std::pair(0,1); + return std::pair(0, 1); // Integer case Function_RankLow: @@ -633,27 +627,29 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const case Function_PcPersonality: case Function_PcLuck: case Function_Level: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + case Function_Fight: case Function_Hello: case Function_Alarm: case Function_Flee: - case Function_PcWerewolfKills: - return std::pair(0, IntMax); + return std::pair(0, 100); case Function_Weather: return std::pair(0, 9); case Function_FriendHit: - return std::pair(0,4); + return std::pair(0, 4); case Function_RankRequirement: return std::pair(0, 3); case Function_CreatureTarget: - return std::pair(0,2); + return std::pair(0, 2); case Function_PcGender: - return std::pair(0,1); + return std::pair(0, 1); case Function_FactionRankDifference: return std::pair(-9, 9); @@ -661,17 +657,16 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const // 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); + return std::pair(0, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); default: throw std::runtime_error("InfoSelectWrapper: function does not exist"); @@ -691,14 +686,14 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() c 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); + return std::pair(0, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); default: throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); @@ -839,7 +834,7 @@ void CSMWorld::InfoSelectWrapper::update() // Write Function bool writeIndex = false; - int functionIndex = static_cast(mFunctionName); + size_t functionIndex = static_cast(mFunctionName); switch (mFunctionName) { From 4e5462bc1977ebab9d07baf37c5b11d531686cce Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 14:23:55 +0100 Subject: [PATCH 46/99] Don't attempt to create a collision shape for an empty TriShape --- components/nifbullet/bulletnifloader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 19afe49d6..9ac544c42 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -266,6 +266,11 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (!shape->skin.empty()) isAnimated = false; + if (shape->data.empty()) + return; + if (shape->data->triangles->empty()) + return; + if (isAnimated) { if (!mCompoundShape) From 7071d286e81b05e0c7492d0adc964dd7247477eb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 14:59:47 +0100 Subject: [PATCH 47/99] Fix inverted check for 16-bit indices --- components/terrain/buffercache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 2a72ea59b..a72dac026 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -219,7 +219,7 @@ namespace Terrain osg::ref_ptr buffer; - if (verts*verts > (0xffffu)) + if (verts*verts <= (0xffffu)) buffer = createIndexBuffer(flags, verts); else buffer = createIndexBuffer(flags, verts); From 6a0ac824bd9c73089c7a56b5b63aa628fb30f90f Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 19 Feb 2016 15:00:50 +0100 Subject: [PATCH 48/99] Fix terrain error in OpenCS --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/terrain/buffercache.hpp | 1 + components/terrain/material.cpp | 6 +++--- components/terrain/material.hpp | 4 ++-- components/terrain/terraingrid.cpp | 7 ++++--- components/terrain/terraingrid.hpp | 15 +++++++++++++-- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dfc1743f0..3ad349ba7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -193,7 +193,7 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, mUnrefQueue.get())); + new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index d1a57f811..172b9d672 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index c1aef1f9d..54634d5d4 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -143,7 +143,7 @@ namespace Terrain } } - Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager& shaderManager, const std::vector &layers, const std::vector > &blendmaps, + Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) : mShaderManager(shaderManager) , mUseShaders(useShaders) @@ -161,8 +161,8 @@ namespace Terrain { try { - if (mUseShaders) - addTechnique(new ShaderTechnique(mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + if (mUseShaders && mShaderManager) + addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); else addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 65349a5e7..e353e5b3a 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -50,7 +50,7 @@ namespace Terrain class Effect : public osgFX::Effect { public: - Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager& shaderManager, + Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); @@ -70,7 +70,7 @@ namespace Terrain } private: - Shader::ShaderManager& mShaderManager; + Shader::ShaderManager* mShaderManager; bool mUseShaders; bool mForcePerPixelLighting; bool mClampLighting; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index cbafe1677..e008d5e52 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -51,11 +51,12 @@ namespace namespace Terrain { -TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) +TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) , mUnrefQueue(unrefQueue) + , mShaderManager(shaderManager) { osg::ref_ptr material (new osg::Material); material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); @@ -211,8 +212,8 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; - osg::ref_ptr effect (new Terrain::Effect(useShaders, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), - mResourceSystem->getSceneManager()->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale)); + osg::ref_ptr effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), + mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index d2276a0af..defcce8b3 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -4,13 +4,22 @@ #include #include "world.hpp" -#include "material.hpp" namespace SceneUtil { class UnrefQueue; } +namespace Shader +{ + class ShaderManager; +} + +namespace osg +{ + class Texture2D; +} + namespace Terrain { @@ -18,7 +27,7 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue = NULL); + TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager = NULL, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); /// Load a terrain cell and store it in cache for later use. @@ -59,6 +68,8 @@ namespace Terrain BufferCache mCache; osg::ref_ptr mUnrefQueue; + + Shader::ShaderManager* mShaderManager; }; } From 20942e66588ecefed85a78d9e9f69b15b23e24ba Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Fri, 19 Feb 2016 14:10:47 -0500 Subject: [PATCH 49/99] Fixes to testing if condition is always or never true --- apps/opencs/model/world/infoselectwrapper.cpp | 31 ++++++++++++------- apps/opencs/model/world/infoselectwrapper.hpp | 5 ++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index 496f8e17f..d036b1965 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -706,6 +706,13 @@ bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair return (value >= range.first && value <= range.second); } +template +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, + std::pair testRange) const +{ + return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); +} + template bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const { @@ -727,21 +734,22 @@ 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); + return false; case Relation_NotEqual: // If value is not within range, it will always be true return !rangeContains(conditionRange.first, validRange); + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If the valid range is completely within the condition range, it will always be true + return rangeFullyContains(conditionRange, validRange); + default: throw std::logic_error("InfoCondition: operator can not be used to compare"); } @@ -756,6 +764,11 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair con switch (mRelationType) { case Relation_Equal: + return !rangeContains(conditionRange.first, validRange); + + case Relation_NotEqual: + return false; + case Relation_Greater: case Relation_GreaterOrEqual: case Relation_Less: @@ -763,10 +776,6 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair con // 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 rangesMatch(conditionRange, validRange); - default: throw std::logic_error("InfoCondition: operator can not be used to compare"); } diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index 1aa86aeca..ce26a46dc 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -164,7 +164,7 @@ namespace CSMWorld bool variantTypeIsValid() const; const ESM::Variant& getVariant() const; - + std::string toString() const; protected: @@ -188,6 +188,9 @@ namespace CSMWorld template bool rangesOverlap(std::pair range1, std::pair range2) const; + template + bool rangeFullyContains(std::pair containing, std::pair test) const; + template bool rangesMatch(std::pair range1, std::pair range2) const; From 5cf2441b1073875df26d292db1cad1a0a26c461b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 17:57:19 +0100 Subject: [PATCH 50/99] ShaderVisitor: support automatic recognition of normal maps based on file pattern Introduce new settings 'auto use object normal maps', 'auto use terrain normal maps', 'normal map pattern' --- apps/openmw/mwrender/renderingmanager.cpp | 5 +- apps/openmw/mwrender/terrainstorage.cpp | 16 +--- apps/openmw/mwrender/terrainstorage.hpp | 4 +- components/esmterrain/storage.cpp | 27 ++++-- components/esmterrain/storage.hpp | 5 +- components/resource/imagemanager.cpp | 1 + components/resource/scenemanager.cpp | 17 +++- components/resource/scenemanager.hpp | 8 ++ components/shader/shadervisitor.cpp | 104 +++++++++++++++++----- components/shader/shadervisitor.hpp | 19 +++- files/settings-default.cfg | 12 +++ 11 files changed, 164 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3ad349ba7..ef1b469e3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -172,6 +172,8 @@ namespace MWRender resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); + resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); @@ -193,7 +195,8 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); + new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain normal maps", "Shaders")), + Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 02947b5dd..8d4bf2ea2 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,21 +9,9 @@ namespace MWRender { - TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload) - : ESMTerrain::Storage(vfs) + TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, bool autoUseNormalMaps) + : ESMTerrain::Storage(vfs, normalMapPattern, autoUseNormalMaps) { - if (preload) - { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - MWWorld::Store::iterator it = esmStore.get().begin(); - for (; it != esmStore.get().end(); ++it) - { - const ESM::Land* land = &*it; - land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); - } - } } void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index a12ffd540..7bd4b4fd5 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -14,9 +14,7 @@ namespace MWRender virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: - ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this - /// should be set to "true" in order to avoid race conditions. - TerrainStorage(const VFS::Manager* vfs, bool preload); + TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index cead73070..990102070 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -18,8 +18,10 @@ namespace ESMTerrain const float defaultHeight = -2048; - Storage::Storage(const VFS::Manager *vfs) + Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps) : mVFS(vfs) + , mNormalMapPattern(normalMapPattern) + , mAutoUseNormalMaps(autoUseNormalMaps) { } @@ -511,6 +513,8 @@ namespace ESMTerrain info.mParallax = false; info.mSpecular = false; info.mDiffuseMap = texture; + + /* std::string texture_ = texture; boost::replace_last(texture_, ".", "_nh."); @@ -519,21 +523,26 @@ namespace ESMTerrain info.mNormalMap = texture_; info.mParallax = true; } - else + */ + if (mAutoUseNormalMaps) { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); + std::string texture_ = texture; + boost::replace_last(texture_, ".", mNormalMapPattern + "."); if (mVFS->exists(texture_)) info.mNormalMap = texture_; } - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (mVFS->exists(texture_)) + /* { - info.mDiffuseMap = texture_; - info.mSpecular = true; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_diffusespec."); + if (mVFS->exists(texture_)) + { + info.mDiffuseMap = texture_; + info.mSpecular = true; + } } + */ mLayerInfoMap[texture] = info; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 9ca39e6f3..0c8095a11 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -27,7 +27,7 @@ namespace ESMTerrain virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: - Storage(const VFS::Manager* vfs); + Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false); /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for /// any of the data types specified via \a flags. Will also return a 0-pointer if there @@ -109,6 +109,9 @@ namespace ESMTerrain std::map mLayerInfoMap; OpenThreads::Mutex mLayerInfoMutex; + std::string mNormalMapPattern; + bool mAutoUseNormalMaps; + Terrain::LayerInfo getLayerInfo(const std::string& texture); }; diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 870fa370b..ea3adc704 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -121,6 +121,7 @@ namespace Resource } osg::Image* image = result.getImage(); + image->setFileName(normalized); if (!checkSupported(image, filename)) { mCache->addEntryToObjectCache(normalized, mWarningImage); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 57187c734..00030edc5 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -212,6 +212,7 @@ namespace Resource , mForceShaders(false) , mClampLighting(false) , mForcePerPixelLighting(false) + , mAutoUseNormalMaps(false) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -235,7 +236,7 @@ namespace Resource void SceneManager::recreateShaders(osg::ref_ptr node) { - Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); shaderVisitor.setForceShaders(mForceShaders); shaderVisitor.setClampLighting(mClampLighting); shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); @@ -263,6 +264,16 @@ namespace Resource return mForcePerPixelLighting; } + void SceneManager::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void SceneManager::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -386,10 +397,12 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); - Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); shaderVisitor.setForceShaders(mForceShaders); shaderVisitor.setClampLighting(mClampLighting); shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps); + shaderVisitor.setNormalMapPattern(mNormalMapPattern); loaded->accept(shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 8f2853789..d78466260 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -57,6 +57,12 @@ namespace Resource void setForcePerPixelLighting(bool force); bool getForcePerPixelLighting() const; + /// @see ShaderVisitor::setAutoUseNormalMaps + void setAutoUseNormalMaps(bool use); + + /// @see ShaderVisitor::setNormalMapPattern + void setNormalMapPattern(const std::string& pattern); + void setShaderPath(const std::string& path); /// Get a read-only copy of this scene "template" @@ -126,6 +132,8 @@ namespace Resource bool mForceShaders; bool mClampLighting; bool mForcePerPixelLighting; + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; osg::ref_ptr mInstanceCache; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 331beab34..a69a479e3 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -5,10 +5,15 @@ #include #include #include +#include #include #include +#include + +#include +#include #include "shadermanager.hpp" @@ -24,13 +29,20 @@ namespace Shader { } - ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + ShaderVisitor::ShaderRequirements::~ShaderRequirements() + { + + } + + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mClampLighting(false) , mForcePerPixelLighting(false) , mAllowedToModifyStateSets(true) + , mAutoUseNormalMaps(false) , mShaderManager(shaderManager) + , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) , mDefaultFsTemplate(defaultFsTemplate) { @@ -81,37 +93,69 @@ namespace Shader if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); - for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); - if (attr) + const osg::Texture* diffuseMap = NULL; + const osg::Texture* normalMap = NULL; + for(unsigned int unit=0;unitasTexture(); - if (texture) + const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (attr) { - if (!texture->getName().empty()) + const osg::Texture* texture = attr->asTexture(); + if (texture) { - mRequirements.back().mTextures[unit] = texture->getName(); - if (texture->getName() == "normalMap") + if (!texture->getName().empty()) { - mRequirements.back().mTexStageRequiringTangents = unit; - mRequirements.back().mHasNormalMap = true; - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On - writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = texture->getName(); + if (texture->getName() == "normalMap") + { + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mHasNormalMap = true; + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On + writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + normalMap = texture; + } + if (texture->getName() == "diffuseMap") + diffuseMap = texture; } + else + std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; } - else - std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + } + // remove state that has no effect when rendering with shaders + if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV)) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); } } - // remove state that has no effect when rendering with shaders - if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV)) + + if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL) { - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); + std::string normalMap = diffuseMap->getImage(0)->getFileName(); + boost::replace_last(normalMap, ".", mNormalMapPattern + "."); + if (mImageManager.getVFS()->exists(normalMap)) + { + osg::ref_ptr normalMapTex (new osg::Texture2D(mImageManager.getImage(normalMap))); + normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + normalMapTex->setName("normalMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "normalMap"; + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mHasNormalMap = true; + } } } @@ -209,8 +253,12 @@ namespace Shader if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); - if (reqs.mTexStageRequiringTangents != -1) + if (reqs.mTexStageRequiringTangents != -1 && mAllowedToModifyStateSets) { + if (geometry.getTexCoordArray(reqs.mTexStageRequiringTangents) == NULL) // assign tex coord array for normal map if necessary + // the normal-map may be assigned on-the-fly in applyStateSet() when mAutoUseNormalMaps is true + geometry.setTexCoordArray(reqs.mTexStageRequiringTangents, geometry.getTexCoordArray(0)); + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); generator->generate(&geometry, reqs.mTexStageRequiringTangents); @@ -254,4 +302,14 @@ namespace Shader mAllowedToModifyStateSets = allowed; } + void ShaderVisitor::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void ShaderVisitor::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index c301db47d..645a92969 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -3,6 +3,11 @@ #include +namespace Resource +{ + class ImageManager; +} + namespace Shader { @@ -12,7 +17,7 @@ namespace Shader class ShaderVisitor : public osg::NodeVisitor { public: - ShaderVisitor(ShaderManager& shaderManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. @@ -30,6 +35,11 @@ namespace Shader /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. void setAllowedToModifyStateSets(bool allowed); + /// Automatically use normal maps if a file with suitable name exists (see normal map pattern). + void setAutoUseNormalMaps(bool use); + + void setNormalMapPattern(const std::string& pattern); + virtual void apply(osg::Node& node); virtual void apply(osg::Drawable& drawable); @@ -46,15 +56,22 @@ namespace Shader bool mForcePerPixelLighting; bool mAllowedToModifyStateSets; + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; + ShaderManager& mShaderManager; + Resource::ImageManager& mImageManager; struct ShaderRequirements { ShaderRequirements(); + ~ShaderRequirements(); // std::map mTextures; + osg::ref_ptr mDiffuseMap; + bool mHasNormalMap; bool mColorMaterial; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1c87c5dd5..7403375c0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -178,6 +178,18 @@ force per pixel lighting = false # Setting this option to 'false' results in more realistic lighting. clamp lighting = true +# If this option is enabled, normal maps are automatically recognized and used if they are named appropriately +# (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (NIF file). +# Affects objects. +auto use object normal maps = false + +# See 'auto use object normal maps'. Affects terrain. +auto use terrain normal maps = false + +# The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') +normal map pattern = _n + [Input] # Capture control of the cursor prevent movement outside the window. From f01e8a69505418d8ba7ae05e312246517548361f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 19:02:11 +0100 Subject: [PATCH 51/99] Add specular mapping for objects --- apps/openmw/mwrender/renderingmanager.cpp | 6 ++- components/resource/scenemanager.cpp | 12 +++++ components/resource/scenemanager.hpp | 6 +++ components/shader/shadervisitor.cpp | 64 +++++++++++++++++++---- components/shader/shadervisitor.hpp | 9 +++- files/settings-default.cfg | 6 +++ files/shaders/lighting.glsl | 11 ++++ files/shaders/objects_fragment.glsl | 22 ++++++-- files/shaders/objects_vertex.glsl | 16 ++++-- 9 files changed, 132 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ef1b469e3..dde283b68 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -174,6 +174,8 @@ namespace MWRender resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); + resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); @@ -340,7 +342,9 @@ namespace MWRender { setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); - mSunLight->setDiffuse(SceneUtil::colourFromRGB(cell->mAmbi.mSunlight)); + osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); + mSunLight->setDiffuse(diffuse); + mSunLight->setSpecular(diffuse); mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 00030edc5..d0d10a3cd 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -274,6 +274,16 @@ namespace Resource mNormalMapPattern = pattern; } + void SceneManager::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void SceneManager::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -403,6 +413,8 @@ namespace Resource shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor.setNormalMapPattern(mNormalMapPattern); + shaderVisitor.setAutoUseSpecularMaps(mAutoUseSpecularMaps); + shaderVisitor.setSpecularMapPattern(mSpecularMapPattern); loaded->accept(shaderVisitor); // share state diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index d78466260..00eb68e76 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -63,6 +63,10 @@ namespace Resource /// @see ShaderVisitor::setNormalMapPattern void setNormalMapPattern(const std::string& pattern); + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + void setShaderPath(const std::string& path); /// Get a read-only copy of this scene "template" @@ -134,6 +138,8 @@ namespace Resource bool mForcePerPixelLighting; bool mAutoUseNormalMaps; std::string mNormalMapPattern; + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; osg::ref_ptr mInstanceCache; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a69a479e3..279954eb0 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -21,7 +21,7 @@ namespace Shader { ShaderVisitor::ShaderRequirements::ShaderRequirements() - : mHasNormalMap(false) + : mShaderRequired(false) , mColorMaterial(false) , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) , mMaterialOverridden(false) @@ -41,6 +41,7 @@ namespace Shader , mForcePerPixelLighting(false) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultVsTemplate(defaultVsTemplate) @@ -97,6 +98,7 @@ namespace Shader { const osg::Texture* diffuseMap = NULL; const osg::Texture* normalMap = NULL; + const osg::Texture* specularMap = NULL; for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); @@ -111,7 +113,7 @@ namespace Shader if (texture->getName() == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; - mRequirements.back().mHasNormalMap = true; + mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On @@ -120,6 +122,8 @@ namespace Shader } if (texture->getName() == "diffuseMap") diffuseMap = texture; + if (texture->getName() == "specularMap") + specularMap = texture; } else std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; @@ -154,7 +158,30 @@ namespace Shader writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "normalMap"; mRequirements.back().mTexStageRequiringTangents = unit; - mRequirements.back().mHasNormalMap = true; + mRequirements.back().mShaderRequired = true; + } + } + if (mAutoUseSpecularMaps && diffuseMap != NULL && specularMap == NULL) + { + std::string specularMap = diffuseMap->getImage(0)->getFileName(); + boost::replace_last(specularMap, ".", mSpecularMapPattern + "."); + if (mImageManager.getVFS()->exists(specularMap)) + { + std::cout << "using specmap " << specularMap << std::endl; + osg::ref_ptr specularMapTex (new osg::Texture2D(mImageManager.getImage(specularMap))); + specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + specularMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + specularMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + specularMapTex->setName("specularMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, specularMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "specularMap"; + mRequirements.back().mShaderRequired = true; } } } @@ -196,7 +223,7 @@ namespace Shader writableStateSet = getWritableStateSet(node); ShaderManager::DefineMap defineMap; - const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap" }; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap" }; for (unsigned int i=0; i::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) + { + if (geometry.getTexCoordArray(it->first) == NULL) + geometry.setTexCoordArray(it->first, geometry.getTexCoordArray(0)); + } + } + if (reqs.mTexStageRequiringTangents != -1 && mAllowedToModifyStateSets) { - if (geometry.getTexCoordArray(reqs.mTexStageRequiringTangents) == NULL) // assign tex coord array for normal map if necessary - // the normal-map may be assigned on-the-fly in applyStateSet() when mAutoUseNormalMaps is true - geometry.setTexCoordArray(reqs.mTexStageRequiringTangents, geometry.getTexCoordArray(0)); - osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); generator->generate(&geometry, reqs.mTexStageRequiringTangents); @@ -266,7 +300,7 @@ namespace Shader } // TODO: find a better place for the stateset - if (reqs.mHasNormalMap || mForceShaders) + if (reqs.mShaderRequired || mForceShaders) createProgram(reqs, geometry); } @@ -289,7 +323,7 @@ namespace Shader { const ShaderRequirements& reqs = mRequirements.back(); // TODO: find a better place for the stateset - if (reqs.mHasNormalMap || mForceShaders) + if (reqs.mShaderRequired || mForceShaders) createProgram(reqs, drawable); } @@ -312,4 +346,14 @@ namespace Shader mNormalMapPattern = pattern; } + void ShaderVisitor::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void ShaderVisitor::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 645a92969..2f957dfbf 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,10 @@ namespace Shader void setNormalMapPattern(const std::string& pattern); + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + virtual void apply(osg::Node& node); virtual void apply(osg::Drawable& drawable); @@ -59,6 +63,9 @@ namespace Shader bool mAutoUseNormalMaps; std::string mNormalMapPattern; + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; @@ -72,7 +79,7 @@ namespace Shader osg::ref_ptr mDiffuseMap; - bool mHasNormalMap; + bool mShaderRequired; bool mColorMaterial; // osg::Material::ColorMode diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7403375c0..98c33e834 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -184,12 +184,18 @@ clamp lighting = true # Affects objects. auto use object normal maps = false +auto use object specular maps = false + # See 'auto use object normal maps'. Affects terrain. auto use terrain normal maps = false +auto use terrain specular maps = false + # The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') normal map pattern = _n +specular map pattern = _spec + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index d3c51d3cd..d706563ba 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -40,3 +40,14 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) #endif return lightResult; } + + +vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, vec3 matSpec) +{ + vec3 lightDir = normalize(gl_LightSource[0].position.xyz); + float NdotL = max(dot(viewNormal, lightDir), 0.0); + if (NdotL < 0) + return vec3(0,0,0); + vec3 halfVec = normalize(lightDir - viewPos); + return pow(max(dot(viewNormal, halfVec), 0.0), 128) * gl_LightSource[0].specular.xyz * matSpec; +} diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 6366da82b..f572c24bb 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -32,6 +32,11 @@ varying vec2 envMapUV; uniform vec4 envMapColor; #endif +#if @specularMap +uniform sampler2D specularMap; +varying vec2 specularMapUV; +#endif + varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -39,10 +44,10 @@ varying float depth; #if !PER_PIXEL_LIGHTING varying vec4 lighting; #else -varying vec3 passViewPos; -varying vec3 passViewNormal; varying vec4 passColor; #endif +varying vec3 passViewPos; +varying vec3 passViewNormal; #include "lighting.glsl" @@ -62,9 +67,7 @@ void main() gl_FragData[0].xyz *= texture2D(darkMap, darkMapUV).xyz; #endif -#if PER_PIXEL_LIGHTING vec3 viewNormal = passViewNormal; -#endif #if @normalMap vec3 normalTex = texture2D(normalMap, normalMapUV).xyz; @@ -90,6 +93,17 @@ void main() gl_FragData[0].xyz += texture2D(envMap, envMapUV).xyz * envMapColor.xyz; #endif +#if @specularMap + vec4 specTex = texture2D(specularMap, specularMapUV); + float shininess = specTex.a * 255; + vec3 matSpec = specTex.xyz; +#else + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = gl_FrontMaterial.specular.xyz; +#endif + + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec); + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); } diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index a2c0e7d21..b1004aad3 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -25,6 +25,10 @@ varying vec3 viewTangent; varying vec2 envMapUV; #endif +#if @specularMap +varying vec2 specularMapUV; +#endif + varying float depth; #define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) @@ -32,10 +36,10 @@ varying float depth; #if !PER_PIXEL_LIGHTING varying vec4 lighting; #else -varying vec3 passViewPos; -varying vec3 passViewNormal; varying vec4 passColor; #endif +varying vec3 passViewPos; +varying vec3 passViewNormal; #include "lighting.glsl" @@ -76,11 +80,15 @@ void main(void) viewTangent = normalize(gl_NormalMatrix * gl_MultiTexCoord7.xyz); #endif +#if @specularMap + specularMapUV = (gl_TextureMatrix[@specularMapUV] * gl_MultiTexCoord@specularMapUV).xy; +#endif + #if !PER_PIXEL_LIGHTING lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); #else - passViewPos = viewPos.xyz; - passViewNormal = viewNormal; passColor = gl_Color; #endif + passViewPos = viewPos.xyz; + passViewNormal = viewNormal; } From 171e2936413e5b461537bfb6c9e28b0b807e3fed Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 19:26:59 +0100 Subject: [PATCH 52/99] ShaderManager: insert #line directives when including files --- components/shader/shadermanager.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 286642e57..ce77f46dc 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include +#include #include @@ -21,8 +23,11 @@ namespace Shader bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) { + boost::replace_all(source, "\r\n", "\n"); + std::set includedFiles; size_t foundPos = 0; + int fileNumber = 1; while ((foundPos = source.find("#include")) != std::string::npos) { size_t start = source.find('"', foundPos); @@ -46,9 +51,19 @@ namespace Shader std::cerr << "Failed to open " << includePath.string() << std::endl; return false; } + std::stringstream buffer; buffer << includeFstream.rdbuf(); - source.replace(foundPos, (end-foundPos+1), buffer.str()); + + // insert #line directives so we get correct line numbers in compiler errors + int includedFileNumber = fileNumber++; + + int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + + std::stringstream toInsert; + toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + + source.replace(foundPos, (end-foundPos+1), toInsert.str()); if (includedFiles.insert(includePath).second == false) { From 0db7163363e634ba06806198ab3e4f1c5f467255 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 19:54:47 +0100 Subject: [PATCH 53/99] Add specular mapping for terrain --- apps/openmw/mwrender/renderingmanager.cpp | 5 +++-- apps/openmw/mwrender/terrainstorage.cpp | 4 ++-- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/esmterrain/storage.cpp | 13 +++++++------ components/esmterrain/storage.hpp | 5 ++++- components/terrain/defs.hpp | 4 +++- components/terrain/material.cpp | 1 + components/terrain/material.hpp | 1 + components/terrain/terraingrid.cpp | 5 ++++- files/settings-default.cfg | 4 ++++ files/shaders/terrain_fragment.glsl | 19 ++++++++++++++----- files/shaders/terrain_vertex.glsl | 8 ++++---- 12 files changed, 48 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dde283b68..57f784b2c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -197,8 +197,9 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain normal maps", "Shaders")), - Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); + new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), + Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")), + Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8d4bf2ea2..cc1c3a0bc 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,8 +9,8 @@ namespace MWRender { - TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, bool autoUseNormalMaps) - : ESMTerrain::Storage(vfs, normalMapPattern, autoUseNormalMaps) + TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) + : ESMTerrain::Storage(vfs, normalMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) { } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 7bd4b4fd5..759c7dfcf 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -14,7 +14,7 @@ namespace MWRender virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: - TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false); + TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 990102070..68b32cf2a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -18,10 +18,12 @@ namespace ESMTerrain const float defaultHeight = -2048; - Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps) + Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : mVFS(vfs) , mNormalMapPattern(normalMapPattern) , mAutoUseNormalMaps(autoUseNormalMaps) + , mSpecularMapPattern(specularMapPattern) + , mAutoUseSpecularMaps(autoUseSpecularMaps) { } @@ -510,7 +512,7 @@ namespace ESMTerrain return found->second; Terrain::LayerInfo info; - info.mParallax = false; + //info.mParallax = false; info.mSpecular = false; info.mDiffuseMap = texture; @@ -532,17 +534,16 @@ namespace ESMTerrain info.mNormalMap = texture_; } - /* + if (mAutoUseSpecularMaps) { std::string texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); + boost::replace_last(texture_, ".", mSpecularMapPattern + "."); if (mVFS->exists(texture_)) { info.mDiffuseMap = texture_; info.mSpecular = true; } } - */ mLayerInfoMap[texture] = info; @@ -553,7 +554,7 @@ namespace ESMTerrain { Terrain::LayerInfo info; info.mDiffuseMap = "textures\\_land_default.dds"; - info.mParallax = false; + //info.mParallax = false; info.mSpecular = false; return info; } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 0c8095a11..7b8b844ff 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -27,7 +27,7 @@ namespace ESMTerrain virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: - Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false); + Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for /// any of the data types specified via \a flags. Will also return a 0-pointer if there @@ -112,6 +112,9 @@ namespace ESMTerrain std::string mNormalMapPattern; bool mAutoUseNormalMaps; + std::string mSpecularMapPattern; + bool mAutoUseSpecularMaps; + Terrain::LayerInfo getLayerInfo(const std::string& texture); }; diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 234e05a98..10095bec0 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -18,8 +18,10 @@ namespace Terrain { std::string mDiffuseMap; std::string mNormalMap; - bool mParallax; // Height info in normal map alpha channel? + //bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? + + bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 54634d5d4..b9405ef86 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -129,6 +129,7 @@ namespace Terrain defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = !firstLayer ? "1" : "0"; defineMap["colorMode"] = "2"; + defineMap["specularMap"] = it->mSpecular ? "1" : "0"; osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index e353e5b3a..61d5724d7 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -23,6 +23,7 @@ namespace Terrain { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional + bool mSpecular; }; class FixedFunctionTechnique : public osgFX::Technique diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index e008d5e52..31cac2e9b 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -162,6 +162,7 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { TextureLayer textureLayer; + textureLayer.mSpecular = it->mSpecular; osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; if (!texture) { @@ -187,9 +188,11 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu } textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); textureLayer.mNormalMap = texture; - useShaders = true; } + if (it->requiresShaders()) + useShaders = true; + layers.push_back(textureLayer); } } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 98c33e834..1fd13ecd9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -189,6 +189,8 @@ auto use object specular maps = false # See 'auto use object normal maps'. Affects terrain. auto use terrain normal maps = false +# If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture +# must contain the layer color in the RGB channel (as usual), and a specular multiplier in the alpha channel. auto use terrain specular maps = false # The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') @@ -196,6 +198,8 @@ normal map pattern = _n specular map pattern = _spec +terrain specular map pattern = _diffusespec + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index 3bfd16b0e..db43cbd3e 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -19,10 +19,10 @@ varying float depth; #if !PER_PIXEL_LIGHTING varying vec4 lighting; #else -varying vec3 passViewPos; -varying vec3 passViewNormal; varying vec4 passColor; #endif +varying vec3 passViewPos; +varying vec3 passViewNormal; #include "lighting.glsl" @@ -30,16 +30,15 @@ void main() { vec2 diffuseMapUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; - gl_FragData[0] = vec4(texture2D(diffuseMap, diffuseMapUV).xyz, 1.0); + vec4 diffuseTex = texture2D(diffuseMap, diffuseMapUV); + gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); #if @blendMap vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; #endif -#if PER_PIXEL_LIGHTING vec3 viewNormal = passViewNormal; -#endif #if @normalMap vec3 normalTex = texture2D(normalMap, diffuseMapUV).xyz; @@ -59,6 +58,16 @@ void main() gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor); #endif +#if @specularMap + float shininess = 128; // TODO: make configurable + vec3 matSpec = vec3(diffuseTex.a, diffuseTex.a, diffuseTex.a); +#else + float shininess = gl_FrontMaterial.shininess; + vec3 matSpec = gl_FrontMaterial.specular.xyz; +#endif + + gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos), shininess, matSpec); + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); } diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 43cb5d31b..46071d7f0 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -8,10 +8,10 @@ varying float depth; #if !PER_PIXEL_LIGHTING varying vec4 lighting; #else -varying vec3 passViewPos; -varying vec3 passViewNormal; varying vec4 passColor; #endif +varying vec3 passViewPos; +varying vec3 passViewNormal; #include "lighting.glsl" @@ -27,10 +27,10 @@ void main(void) #if !PER_PIXEL_LIGHTING lighting = doLighting(viewPos.xyz, viewNormal, gl_Color); #else - passViewPos = viewPos.xyz; - passViewNormal = viewNormal; passColor = gl_Color; #endif + passViewNormal = viewNormal; + passViewPos = viewPos.xyz; uv = gl_MultiTexCoord0.xy; } From 8f81df2bd3d97564f3521af5ec68ce4f4fa45b22 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 20:34:56 +0100 Subject: [PATCH 54/99] Cleanup --- apps/openmw/mwrender/water.cpp | 1 - components/nifosg/particle.hpp | 1 - components/shader/shadervisitor.cpp | 1 - components/terrain/terraingrid.cpp | 1 - 4 files changed, 4 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 8e9a7630c..a410527de 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index a1ed3f3d0..1e1714369 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -7,7 +7,6 @@ #include #include -#include #include #include diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 279954eb0..0940c4c9e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -167,7 +167,6 @@ namespace Shader boost::replace_last(specularMap, ".", mSpecularMapPattern + "."); if (mImageManager.getVFS()->exists(specularMap)) { - std::cout << "using specmap " << specularMap << std::endl; osg::ref_ptr specularMapTex (new osg::Texture2D(mImageManager.getImage(specularMap))); specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 31cac2e9b..abfac4a71 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,7 +2,6 @@ #include -#include #include #include From 4ca6e91292b4cdfda6df4bb66c0b9469a419c910 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 20:36:29 +0100 Subject: [PATCH 55/99] Fix in-code default settings so that we don't attempt to use shaders in OpenCS --- components/resource/scenemanager.cpp | 2 +- components/shader/shadervisitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index d0d10a3cd..503b4ace8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -210,7 +210,7 @@ namespace Resource : ResourceManager(vfs) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) - , mClampLighting(false) + , mClampLighting(true) , mForcePerPixelLighting(false) , mAutoUseNormalMaps(false) , mInstanceCache(new MultiObjectCache) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 0940c4c9e..bcce4a09c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -37,7 +37,7 @@ namespace Shader ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) - , mClampLighting(false) + , mClampLighting(true) , mForcePerPixelLighting(false) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) From cdefee3e648826ba191210d6bb407d95df7e5080 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 20:38:37 +0100 Subject: [PATCH 56/99] Add call to MultiObjectCache::releaseGLObjects --- components/resource/scenemanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 503b4ace8..0a0933474 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -480,6 +480,7 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { mCache->releaseGLObjects(state); + mInstanceCache->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) From e7682e04ae8a0553ecb75e696973fbbd093882b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 22:09:41 +0100 Subject: [PATCH 57/99] Cleanup --- components/shader/shadervisitor.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 2f957dfbf..682a9c60f 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -77,8 +77,6 @@ namespace Shader // std::map mTextures; - osg::ref_ptr mDiffuseMap; - bool mShaderRequired; bool mColorMaterial; From e25e0a0600b95c1f2c1d7a228fffc5b226465b10 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 20 Feb 2016 23:43:05 +0100 Subject: [PATCH 58/99] ShaderVisitor: assume the first texture unit is the diffuseMap --- components/shader/shadervisitor.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index bcce4a09c..46d4edf44 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -88,6 +88,15 @@ namespace Shader return newStateSet.get(); } + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap" }; + bool isTextureNameRecognized(const std::string& name) + { + for (unsigned int i=0; i stateset, osg::Node& node) { osg::StateSet* writableStateSet = NULL; @@ -107,10 +116,14 @@ namespace Shader const osg::Texture* texture = attr->asTexture(); if (texture) { - if (!texture->getName().empty()) + std::string texName = texture->getName(); + if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) + texName = "diffuseMap"; + + if (!texName.empty()) { - mRequirements.back().mTextures[unit] = texture->getName(); - if (texture->getName() == "normalMap") + mRequirements.back().mTextures[unit] = texName; + if (texName == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mShaderRequired = true; @@ -120,9 +133,9 @@ namespace Shader writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); normalMap = texture; } - if (texture->getName() == "diffuseMap") + if (texName == "diffuseMap") diffuseMap = texture; - if (texture->getName() == "specularMap") + if (texName == "specularMap") specularMap = texture; } else @@ -222,7 +235,6 @@ namespace Shader writableStateSet = getWritableStateSet(node); ShaderManager::DefineMap defineMap; - const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap" }; for (unsigned int i=0; i Date: Sun, 21 Feb 2016 00:48:18 +0100 Subject: [PATCH 59/99] Update settings documentation --- files/settings-default.cfg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1fd13ecd9..c886cee70 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -180,10 +180,14 @@ clamp lighting = true # If this option is enabled, normal maps are automatically recognized and used if they are named appropriately # (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). -# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (NIF file). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.nif or .osg file). # Affects objects. auto use object normal maps = false +# If this option is enabled, specular maps are automatically recognized and used if they are named appropriately +# (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.osg file, not supported in .nif files). +# Affects objects. auto use object specular maps = false # See 'auto use object normal maps'. Affects terrain. @@ -196,8 +200,10 @@ auto use terrain specular maps = false # The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') normal map pattern = _n +# The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') specular map pattern = _spec +# The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') terrain specular map pattern = _diffusespec [Input] From 8e826eefe69c739561da57d433826bc597ec498b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 21 Feb 2016 01:26:45 +0100 Subject: [PATCH 60/99] Add missing initialization --- components/resource/scenemanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 0a0933474..9965e7767 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -213,6 +213,7 @@ namespace Resource , mClampLighting(true) , mForcePerPixelLighting(false) , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) From 900b522d13ec43bbc4844dc435445f3823c9dd31 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 21 Feb 2016 01:28:42 +0100 Subject: [PATCH 61/99] Rename viewPos to viewDirection --- files/shaders/lighting.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl index d706563ba..08b369b5b 100644 --- a/files/shaders/lighting.glsl +++ b/files/shaders/lighting.glsl @@ -42,12 +42,12 @@ vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) } -vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, vec3 matSpec) +vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) { vec3 lightDir = normalize(gl_LightSource[0].position.xyz); float NdotL = max(dot(viewNormal, lightDir), 0.0); if (NdotL < 0) return vec3(0,0,0); - vec3 halfVec = normalize(lightDir - viewPos); + vec3 halfVec = normalize(lightDir - viewDirection); return pow(max(dot(viewNormal, halfVec), 0.0), 128) * gl_LightSource[0].specular.xyz * matSpec; } From cee608e31bec969272b5e7aa0049280e2f12b0db Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 21 Feb 2016 01:41:50 +0100 Subject: [PATCH 62/99] Make it work with appveyor's outdated OSG build --- components/resource/objectcache.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index 5fec6730d..7caf5366c 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -129,9 +129,12 @@ void ObjectCache::accept(osg::NodeVisitor &nv) ++itr) { osg::Object* object = itr->second.first.get(); - osg::Node* node = object->asNode(); - if (node) - node->accept(nv); + if (object) + { + osg::Node* node = dynamic_cast(object); + if (node) + node->accept(nv); + } } } From c5d3e6c9935f2d0f9ec863475f316e8ea00f096d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 14:31:02 +0100 Subject: [PATCH 63/99] Disable preloading of levelled lists --- apps/openmw/mwclass/creaturelevlist.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index e2f29ea72..0223fb634 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -56,8 +56,9 @@ namespace MWClass void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { + // disable for now, too many false positives + /* const MWWorld::LiveCellRef *ref = ptr.get(); - for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -68,6 +69,7 @@ namespace MWClass MWWorld::ManualRef ref(store, it->mId); ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); } + */ } void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const From 1667c807be651dbeb1be3055b4a5143f5e2db2c5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 15:21:06 +0100 Subject: [PATCH 64/99] Warn about particle emitters that have multiple parent node paths --- components/nifosg/particle.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 500339722..205b0fd54 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -267,6 +267,8 @@ void Emitter::emitParticles(double dt) const osg::Matrix psToWorld = worldMats[0]; worldToPs = osg::Matrix::inverse(psToWorld); } + if (worldMats.size() > 1) + std::cerr << "Found multiple parent node paths in particle emitter, this is not correctly supported " << std::endl; const osg::Matrix& ltw = getLocalToWorldMatrix(); osg::Matrix emitterToPs = ltw * worldToPs; From a6621626aa934207b4475ff4066387b42fbdef7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 15:22:37 +0100 Subject: [PATCH 65/99] Don't use multiple parent node paths in CSVRender::Object --- apps/opencs/view/render/object.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 33939625d..c220321d5 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -160,7 +160,6 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, mBaseNode->addCullCallback(new SceneUtil::LightListCallback); mOutline = new osgFX::Scribe; - mOutline->addChild(mBaseNode); mBaseNode->setUserData(new ObjectTag(this)); @@ -194,10 +193,14 @@ void CSVRender::Object::setSelected(bool selected) { mSelected = selected; + mOutline->removeChild(mBaseNode); mParentNode->removeChild(mOutline); mParentNode->removeChild(mBaseNode); if (selected) + { + mOutline->addChild(mBaseNode); mParentNode->addChild(mOutline); + } else mParentNode->addChild(mBaseNode); } From 055d35a2b09f5bcc6082b306fc5254779ae491fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 18:10:25 +0100 Subject: [PATCH 66/99] Revert "Warn about particle emitters that have multiple parent node paths" Not working correctly because osg won't ignore camera nodes. This reverts commit 1667c807be651dbeb1be3055b4a5143f5e2db2c5. --- components/nifosg/particle.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 205b0fd54..500339722 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -267,8 +267,6 @@ void Emitter::emitParticles(double dt) const osg::Matrix psToWorld = worldMats[0]; worldToPs = osg::Matrix::inverse(psToWorld); } - if (worldMats.size() > 1) - std::cerr << "Found multiple parent node paths in particle emitter, this is not correctly supported " << std::endl; const osg::Matrix& ltw = getLocalToWorldMatrix(); osg::Matrix emitterToPs = ltw * worldToPs; From 8bd16e4d5ab92035974b629349c6d8f3f5bbe977 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 18:58:19 +0100 Subject: [PATCH 67/99] Don't compute the world matrix multiple times --- apps/openmw/mwmechanics/character.cpp | 12 ++++++------ apps/openmw/mwrender/camera.cpp | 6 +++--- apps/openmw/mwrender/characterpreview.cpp | 6 +++--- apps/openmw/mwrender/rotatecontroller.cpp | 6 +++--- apps/openmw/mwrender/weaponanimation.cpp | 12 ++++++------ apps/openmw/mwworld/worldimp.cpp | 6 +++--- components/nifosg/particle.cpp | 6 +++--- components/resource/scenemanager.cpp | 6 +++--- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 67707d028..2f024838d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2140,10 +2140,10 @@ void CharacterController::updateHeadTracking(float duration) if (!mHeadTrackTarget.isEmpty()) { - osg::MatrixList mats = head->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf mat = mats[0]; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3f headPos = mat.getTrans(); osg::Vec3f direction; @@ -2154,9 +2154,9 @@ void CharacterController::updateHeadTracking(float duration) node = anim->getNode("Bip01 Head"); if (node != NULL) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - direction = mats[0].getTrans() - headPos; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (nodepaths.size()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; } else // no head node to look at, fall back to look at center of collision box diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 88934414f..5a3f2bea3 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -88,10 +88,10 @@ namespace MWRender const osg::Node* trackNode = mTrackingNode; if (!trackNode) return osg::Vec3d(); - osg::MatrixList mats = trackNode->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = trackNode->getParentalNodePaths(); + if (nodepaths.empty()) return osg::Vec3d(); - const osg::Matrix& worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3d position = worldMat.getTrans(); if (!isFirstPerson()) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b9f20f4ea..201124447 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -365,10 +365,10 @@ namespace MWRender traverse(node, nv); // Now update camera utilizing the updated head position - osg::MatrixList mats = mNodeToFollow->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); + if (!nodepaths.size()) return; - osg::Matrix worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 11f5b943d..534cc7490 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -44,11 +44,11 @@ void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) osg::Quat RotateController::getWorldOrientation(osg::Node *node) { // this could be optimized later, we just need the world orientation, not the full matrix - osg::MatrixList worldMats = node->getWorldMatrices(mRelativeTo); + osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); osg::Quat worldOrient; - if (!worldMats.empty()) + if (!nodepaths.empty()) { - osg::Matrixf worldMat = worldMats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldOrient = worldMat.getRotate(); } return worldOrient; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 8fd294ccd..2627d3fc6 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -114,10 +114,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) osg::Node* weaponNode = getWeaponNode(); if (!weaponNode) return; - osg::MatrixList mats = weaponNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = weaponNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); @@ -140,10 +140,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; osg::ref_ptr ammoNode = mAmmunition->getNode(); - osg::MatrixList mats = ammoNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 98517f543..f3d80ee67 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1051,9 +1051,9 @@ namespace MWWorld if(!node) node = anim->getNode("Bip01 Head"); if(node) { - osg::MatrixList mats = node->getWorldMatrices(); - if(!mats.empty()) - return mats[0]; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if(!nodepaths.empty()) + return osg::computeLocalToWorld(nodepaths[0]); } } return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 500339722..be2e708fb 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -261,10 +261,10 @@ void Emitter::emitParticles(double dt) osg::Matrix worldToPs; // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes - osg::MatrixList worldMats = getParticleSystem()->getWorldMatrices(); - if (!worldMats.empty()) + osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths(); + if (partsysNodePaths.size()) { - const osg::Matrix psToWorld = worldMats[0]; + osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]); worldToPs = osg::Matrix::inverse(psToWorld); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9965e7767..113406ef6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -69,10 +69,10 @@ namespace void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf worldMat = mats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node for (int i=0; inumParticles(); ++i) { From 90a99991d1eaca47b192e01fc4eb5cef7ae249f1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 19:06:12 +0100 Subject: [PATCH 68/99] Use empty() instead of !size() --- apps/openmw/mwdialogue/keywordsearch.hpp | 2 +- apps/openmw/mwgui/console.cpp | 2 +- apps/openmw/mwgui/containeritemmodel.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/water.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- components/esm/loadtes3.cpp | 4 ++-- components/nifosg/nifloader.cpp | 8 ++++---- components/nifosg/particle.cpp | 2 +- components/sceneutil/lightmanager.cpp | 2 +- components/sceneutil/workqueue.cpp | 2 +- components/shader/shadervisitor.cpp | 2 +- 16 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3532dc22b..f296f223f 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -158,7 +158,7 @@ public: } // resolve overlapping keywords - while (matches.size()) + while (!matches.empty()) { int longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 1777d86ad..aeff7dc39 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -240,7 +240,7 @@ namespace MWGui mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab - if (oldCaption == newCaption && matches.size()) + if (oldCaption == newCaption && !matches.empty()) { int i = 0; printOK(""); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index b2befc3ba..1e4749695 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -37,7 +37,7 @@ ContainerItemModel::ContainerItemModel(const std::vector& itemSour : mItemSources(itemSources) , mWorldItems(worldItems) { - assert (mItemSources.size()); + assert (!mItemSources.empty()); } ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8231e572e..27c7f6a93 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -255,7 +255,7 @@ namespace MWMechanics // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) { - if (mAllowedNodes.size()) + if (!mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2f024838d..0035ebb1a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2155,7 +2155,7 @@ void CharacterController::updateHeadTracking(float duration) if (node != NULL) { osg::NodePathList nodepaths = node->getParentalNodePaths(); - if (nodepaths.size()) + if (!nodepaths.empty()) direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; } else diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 201124447..c858b57b7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -366,7 +366,7 @@ namespace MWRender // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); - if (!nodepaths.size()) + if (nodepaths.empty()) return; osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 9e8a545f8..b3b6cc3b0 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -402,7 +402,7 @@ namespace MWRender || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); - if (!map.mImageData.size()) + if (map.mImageData.empty()) return; Files::IMemStream istream(&map.mImageData[0], map.mImageData.size()); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index be477735f..8340ab78a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -632,7 +632,7 @@ void LocalMap::MapSegment::initFogOfWar() void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) { const std::vector& data = esm.mImageData; - if (!data.size()) + if (data.empty()) { initFogOfWar(); return; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a410527de..0a379e50a 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -566,7 +566,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) textures.push_back(tex); } - if (!textures.size()) + if (textures.empty()) return; float fps = mFallback->getFallbackFloat("Water_SurfaceFPS"); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index e8ceaa40f..ae8601594 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -366,7 +366,7 @@ namespace MWSound else filelist = mMusicFiles[mCurrentPlaylist]; - if(!filelist.size()) + if(filelist.empty()) return; int i = Misc::Rng::rollDice(filelist.size()); diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index df35a2579..60d411fc6 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -53,14 +53,14 @@ void ESM::Header::load (ESMReader &esm) { esm.getSubHeader(); mSCRD.resize(esm.getSubSize()); - if (mSCRD.size()) + if (!mSCRD.empty()) esm.getExact(&mSCRD[0], mSCRD.size()); } if (esm.isNextSub("SCRS")) { esm.getSubHeader(); mSCRS.resize(esm.getSubSize()); - if (mSCRS.size()) + if (!mSCRS.empty()) esm.getExact(&mSCRS[0], mSCRS.size()); } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0b49419ff..975d6d6e0 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -962,7 +962,7 @@ namespace NifOsg if (uvSet >= (int)data->uvlist.size()) { std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; - if (data->uvlist.size()) + if (!data->uvlist.empty()) geometry->setTexCoordArray(textureStage, data->uvlist[0]); continue; } @@ -1040,7 +1040,7 @@ namespace NifOsg triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); const std::vector& morphs = morpher->data.getPtr()->mMorphs; - if (!morphs.size()) + if (morphs.empty()) return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) @@ -1228,7 +1228,7 @@ namespace NifOsg return NULL; } - if (!pixelData->mipmaps.size()) + if (pixelData->mipmaps.empty()) return NULL; unsigned char* data = new unsigned char[pixelData->data.size()]; @@ -1274,7 +1274,7 @@ namespace NifOsg void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { - if (boundTextures.size()) + if (!boundTextures.empty()) { // overriding a parent NiTexturingProperty, so remove what was previously bound for (unsigned int i=0; igetParentalNodePaths(); - if (partsysNodePaths.size()) + if (!partsysNodePaths.empty()) { osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]); worldToPs = osg::Matrix::inverse(psToWorld); diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 3950d0aea..da29647c2 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -351,7 +351,7 @@ namespace SceneUtil mLightList.push_back(&l); } } - if (mLightList.size()) + if (!mLightList.empty()) { unsigned int maxLights = static_cast (8 - mLightManager->getStartLight()); diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index bb1d1f1f7..bbd9f4840 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -87,7 +87,7 @@ osg::ref_ptr WorkQueue::removeWorkItem() { mCondition.wait(&mMutex); } - if (mQueue.size()) + if (!mQueue.empty()) { osg::ref_ptr item = mQueue.front(); mQueue.pop(); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 46d4edf44..c88200759 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -103,7 +103,7 @@ namespace Shader if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); - if (texAttributes.size()) + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = NULL; const osg::Texture* normalMap = NULL; From 5cdee454ef0d2ab6f39778a94b8f9f5121430f4b Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 19:13:56 +0100 Subject: [PATCH 69/99] Fix degree/radians mixup (Fixes #3213) --- apps/openmw/mwscript/transformationextensions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 64c126de1..95e2deee9 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -318,8 +318,8 @@ namespace MWScript ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. @@ -374,14 +374,14 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } }; From d05603c7fe0bf59b976b463cdca5fae05d17dc8c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 22 Feb 2016 19:37:19 +0100 Subject: [PATCH 70/99] Directly apply On Target 'When Strikes' enchantments instead of launching a projectile (Fixes #3212) --- apps/openmw/mwclass/creature.cpp | 13 +------- apps/openmw/mwclass/npc.cpp | 13 +------- apps/openmw/mwmechanics/combat.cpp | 40 ++++++++++++------------ apps/openmw/mwmechanics/combat.hpp | 2 ++ apps/openmw/mwmechanics/spellcasting.cpp | 25 +++++++++------ apps/openmw/mwmechanics/spellcasting.hpp | 3 +- 6 files changed, 41 insertions(+), 55 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 0f021b5a2..4a7af1099 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -306,18 +306,7 @@ namespace MWClass } // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); } else if (isBipedal(ptr)) { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 474985f7b..4c825f63f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -637,18 +637,7 @@ namespace MWClass damage *= store.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); MWMechanics::applyElementalShields(ptr, victim); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index a01dc7079..8d9d4cb3a 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -29,29 +29,29 @@ float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg: return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); } -bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) -{ - std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(attacker, victim); - cast.mHitPosition = hitPosition; - cast.cast(object); - return true; - } - } - return false; -} - } namespace MWMechanics { + bool applyOnStrikeEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) + { + std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(attacker, victim); + cast.mHitPosition = hitPosition; + cast.cast(object, false); + return true; + } + } + return false; + } + bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) @@ -215,9 +215,9 @@ namespace MWMechanics damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon - bool appliedEnchantment = applyEnchantment(attacker, victim, weapon, hitPosition); + bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) - appliedEnchantment = applyEnchantment(attacker, victim, projectile, hitPosition); + appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index ca78d7956..7d0b3b78f 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -6,6 +6,8 @@ namespace MWMechanics { +bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition); + /// @return can we block the attack? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 736d2a616..8882d14a8 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -687,7 +687,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item) + bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -754,15 +754,20 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - std::string projectileModel; - std::string sound; - float speed = 0; - getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); - if (!projectileModel.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName, - // Not needed, enchantments can only be cast by actors - osg::Vec3f(1,0,0)); + if (launchProjectile) + { + std::string projectileModel; + std::string sound; + float speed = 0; + getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); + if (!projectileModel.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, + false, enchantment->mEffects, mCaster, mSourceName, + // Not needed, enchantments can only be cast by actors + osg::Vec3f(1,0,0)); + } + else if (!mTarget.isEmpty()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5b48bd4a8..5bc618058 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -81,7 +81,8 @@ namespace MWMechanics bool cast (const ESM::Spell* spell); /// @note mCaster must be an actor - bool cast (const MWWorld::Ptr& item); + /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. + bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); From f4ed3894966d06657bb9cc66547a22193aca8faf Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 22 Feb 2016 15:48:25 -0500 Subject: [PATCH 71/99] InfoCondition autocompletion --- apps/opencs/model/world/data.cpp | 2 +- .../model/world/idcompletionmanager.cpp | 6 ++- .../view/world/idcompletiondelegate.cpp | 51 +++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6eccb7483..b54df596c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -271,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 20cd8652c..7f3221342 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -60,6 +60,10 @@ std::vector CSMWorld::IdCompletionManager::getDis { types.push_back(current->first); } + + // Hack for Display_InfoCondVar + types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); + return types; } @@ -104,7 +108,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - + mCompleters[current->first] = completer; } } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828..7f0f4ae46 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -1,6 +1,7 @@ #include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" @@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, return NULL; } + // The completer for InfoCondVar needs to return a completer based on the first column + if (display == CSMWorld::ColumnBase::Display_InfoCondVar) + { + QModelIndex sibling = index.sibling(index.row(), 0); + int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); + + switch (conditionFunction) + { + case CSMWorld::ConstInfoSelectWrapper::Function_Global: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Item: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Dead: + case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Local: + case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + { + return new CSVWidget::DropLineEdit(display, parent); + } + default: return 0; // The rest of them can't be edited anyway + } + } + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); From 1ae402476dc6583c40f1ef8e618bd819579b9d4e Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 22 Feb 2016 17:01:15 -0500 Subject: [PATCH 72/99] Journal verifier --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/tools/journalcheck.cpp | 79 ++++++++++++++++++++++++ apps/opencs/model/tools/journalcheck.hpp | 35 +++++++++++ apps/opencs/model/tools/tools.cpp | 3 + 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/tools/journalcheck.cpp create mode 100644 apps/opencs/model/tools/journalcheck.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index a657bade2..cbf89a7e5 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -42,7 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck topicinfocheck + mergestages gmstcheck topicinfocheck journalcheck ) opencs_hdrs_noqt (model/tools diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp new file mode 100644 index 000000000..bdd14ddf0 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -0,0 +1,79 @@ +#include "journalcheck.hpp" + +#include +#include + +CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, + const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals), mJournalInfos(journalInfos) +{} + +int CSMTools::JournalCheckStage::setup() +{ + return mJournals.getSize(); +} + +void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); + + if (journalRecord.isDeleted()) + return; + + const ESM::Dialogue &journal = journalRecord.get(); + int statusNamedCount = 0; + int totalInfoCount = 0; + std::set questIndices; + + CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); + + for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + { + const CSMWorld::Record infoRecord = (*it); + + if (infoRecord.isDeleted()) + continue; + + const CSMWorld::Info& journalInfo = infoRecord.get(); + + totalInfoCount += 1; + + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + } + + std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + + // Duplicate index + if (result.second == false) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + std::ostringstream stream; + stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + } + + if (totalInfoCount == 0) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + } + else if (statusNamedCount > 1) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + } +} diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp new file mode 100644 index 000000000..c9f619698 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_JOURNALCHECK_H +#define CSM_TOOLS_JOURNALCHECK_H + +#include + +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that journal infos are good + class JournalCheckStage : public CSMDoc::Stage + { + public: + + JournalCheckStage(const CSMWorld::IdCollection& journals, + const CSMWorld::InfoCollection& journalInfos); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mJournals; + const CSMWorld::InfoCollection& mJournalInfos; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index b6a04a236..f538a716e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -31,6 +31,7 @@ #include "mergeoperation.hpp" #include "gmstcheck.hpp" #include "topicinfocheck.hpp" +#include "journalcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -128,6 +129,8 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables().getDataSet(), mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifier.setOperation (mVerifierOperation); } From 062410bd8c83fe4b2cd927c2c6d6f5031a175c8e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 23 Feb 2016 10:56:18 +0100 Subject: [PATCH 73/99] Don't incorrectly remove TexEnv state --- components/shader/shadervisitor.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index c88200759..b06ceafde 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -142,13 +142,6 @@ namespace Shader std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; } } - // remove state that has no effect when rendering with shaders - if (stateset->getTextureAttribute(unit, osg::StateAttribute::TEXENV)) - { - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - writableStateSet->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); - } } if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL) From 5f4ace1bc21a8b55fbff9334ada1f175084022f6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 24 Feb 2016 21:06:41 +0100 Subject: [PATCH 74/99] Implement support for sphere map NiTextureEffects (Fixes #1827) --- components/nif/effect.cpp | 22 ++++--- components/nif/effect.hpp | 20 +++++++ components/nifosg/nifloader.cpp | 100 +++++++++++++++++++++++++++----- 3 files changed, 120 insertions(+), 22 deletions(-) diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 79cd10431..7947e301d 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -19,14 +19,20 @@ void NiTextureEffect::read(NIFStream *nif) { NiDynamicEffect::read(nif); - /* - 3 x Vector4 = [1,0,0,0] - int = 2 - int = 0 or 3 - int = 2 - int = 2 - */ - nif->skip(16*4); + // Model Projection Matrix + nif->skip(3 * 3 * sizeof(float)); + + // Model Projection Transform + nif->skip(3 * sizeof(float)); + + // Texture Filtering + nif->skip(4); + + clamp = nif->getUInt(); + + textureType = (TextureType)nif->getUInt(); + + coordGenType = (CoordGenType)nif->getUInt(); texture.read(nif); diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 02647e444..015809a68 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -70,6 +70,26 @@ struct NiSpotLight : public NiPointLight struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; + unsigned int clamp; + + enum TextureType + { + Projected_Light = 0, + Projected_Shadow = 1, + Environment_Map = 2, + Fog_Map = 3 + }; + TextureType textureType; + + enum CoordGenType + { + World_Parallel = 0, + World_Perspective, + Sphere_Map, + Specular_Cube_Map, + Diffuse_Cube_Map + }; + CoordGenType coordGenType; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 975d6d6e0..53a0cdc19 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // resource #include @@ -36,6 +37,7 @@ #include #include +#include #include #include #include @@ -439,6 +441,81 @@ namespace NifOsg return lod; } + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + { + if (!st) + return NULL; + + osg::ref_ptr image; + if (!st->external && !st->data.empty()) + { + image = handleInternalTexture(st->data.getPtr()); + } + else + { + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + image = imageManager->getImage(filename); + } + return image; + } + + void handleEffect(const Nif::Node* nifNode, osg::Node* node, Resource::ImageManager* imageManager) + { + if (nifNode->recType != Nif::RC_NiTextureEffect) + { + std::cerr << "Unhandled effect " << nifNode->recName << " in " << mFilename << std::endl; + return; + } + + const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); + if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) + { + std::cerr << "Unhandled NiTextureEffect type " << textureEffect->textureType << std::endl; + return; + } + + osg::ref_ptr texGen (new osg::TexGen); + switch (textureEffect->coordGenType) + { + case Nif::NiTextureEffect::World_Parallel: + texGen->setMode(osg::TexGen::OBJECT_LINEAR); + break; + case Nif::NiTextureEffect::World_Perspective: + texGen->setMode(osg::TexGen::EYE_LINEAR); + break; + case Nif::NiTextureEffect::Sphere_Map: + texGen->setMode(osg::TexGen::SPHERE_MAP); + break; + default: + std::cerr << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << std::endl; + return; + } + + osg::ref_ptr texture2d (new osg::Texture2D(handleSourceTexture(textureEffect->texture.getPtr(), imageManager))); + texture2d->setName("envMap"); + unsigned int clamp = static_cast(textureEffect->clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + int texUnit = 3; // FIXME + + osg::StateSet* stateset = node->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + } + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { @@ -582,13 +659,18 @@ namespace NifOsg const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { + const Nif::NodeList &effects = ninode->effects; + for (size_t i = 0; i < effects.length(); ++i) + { + if (!effects[i].empty()) + handleEffect(effects[i].getPtr(), node, imageManager); + } + const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - { handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); - } } } @@ -707,8 +789,7 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); - osg::ref_ptr texture (new osg::Texture2D(imageManager->getImage(filename))); + osg::ref_ptr texture (new osg::Texture2D(handleSourceTexture(st.getPtr(), imageManager))); texture->setWrap(osg::Texture::WRAP_S, wrapS); texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); @@ -1318,17 +1399,8 @@ namespace NifOsg std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; continue; } - osg::ref_ptr image; const Nif::NiSourceTexture *st = tex.texture.getPtr(); - if (!st->external && !st->data.empty()) - { - image = handleInternalTexture(st->data.getPtr()); - } - else - { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); - image = imageManager->getImage(filename); - } + osg::ref_ptr image = handleSourceTexture(st, imageManager); unsigned int clamp = static_cast(tex.clamp); int wrapT = (clamp) & 0x1; From 5e105da3ba538594ff8d4827a3f892587fe6ee74 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 24 Feb 2016 21:28:55 +0100 Subject: [PATCH 75/99] Generate texture coordinates per-pixel when normal map + environment map are used --- files/shaders/objects_fragment.glsl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index f572c24bb..a648f8484 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -89,10 +89,22 @@ void main() gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; #endif + #if @envMap + +#if @normalMap + // if using normal map + env map, take advantage of per-pixel normals for texCoordGen + vec3 viewVec = normalize(passViewPos.xyz); + vec3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + vec2 texCoordGen = vec2(r.x/m + 0.5, r.y/m + 0.5); + gl_FragData[0].xyz += texture2D(envMap, texCoordGen).xyz * envMapColor.xyz; +#else gl_FragData[0].xyz += texture2D(envMap, envMapUV).xyz * envMapColor.xyz; #endif +#endif + #if @specularMap vec4 specTex = texture2D(specularMap, specularMapUV); float shininess = specTex.a * 255; From 52f4c07648eb393759ee75a9e1f76cd797e221be Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 25 Feb 2016 20:35:58 +0100 Subject: [PATCH 76/99] Enable -Wundef --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 719df3e34..d0370c48d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,7 +377,7 @@ endif() # CXX Compiler settings if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) execute_process(COMMAND ${CMAKE_C_COMPILER} --version OUTPUT_VARIABLE CLANG_VERSION) From a7b78b37f555f2707857ade92640ec03613d06ed Mon Sep 17 00:00:00 2001 From: sandstranger Date: Thu, 25 Feb 2016 22:39:18 +0300 Subject: [PATCH 77/99] disable opengles for Android by default --- CMakeLists.txt | 8 +++----- components/sdlutil/sdlgraphicswindow.cpp | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 719df3e34..67a225153 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,10 +190,6 @@ if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() -if (ANDROID) - set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) -endif (ANDROID) - option(OPENGL_ES "enable opengl es support" FALSE ) if (OPENGL_ES) @@ -566,7 +562,9 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) -add_subdirectory (extern/osgQt) +if (USE_QT) + add_subdirectory (extern/osgQt) +endif() # Components add_subdirectory (components) diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 66d53b096..c2cf2536e 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -91,7 +91,7 @@ void GraphicsWindowSDL2::init() SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); -#ifdef OPENGL_ES +#if defined(OPENGL_ES) || defined(ANDROID) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); From cdccf03228065e2351d20291dae45ab1a2bf9a11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 25 Feb 2016 21:32:42 +0100 Subject: [PATCH 78/99] Fix mismatched delete --- components/nifosg/nifloader.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 53a0cdc19..ab6bfcff3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1312,8 +1312,6 @@ namespace NifOsg if (pixelData->mipmaps.empty()) return NULL; - unsigned char* data = new unsigned char[pixelData->data.size()]; - memcpy(data, &pixelData->data[0], pixelData->data.size()); unsigned int width = 0; unsigned int height = 0; @@ -1326,7 +1324,6 @@ namespace NifOsg if (mipSize + mip.dataOffset > pixelData->data.size()) { std::cerr << "Internal texture's mipmap data out of bounds" << std::endl; - delete data; return NULL; } @@ -1342,10 +1339,12 @@ namespace NifOsg if (width <= 0 || height <= 0) { std::cerr << "Width and height must be non zero " << std::endl; - delete data; return NULL; } + unsigned char* data = new unsigned char[pixelData->data.size()]; + memcpy(data, &pixelData->data[0], pixelData->data.size()); + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); image->setMipmapLevels(mipmapVector); image->flipVertical(); From f99cd15f00cbae69b91151d1fc5ae4fedb4abf22 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 26 Feb 2016 12:59:35 +0100 Subject: [PATCH 79/99] Vanilla-compatible activate / onActivate (Fixes #1629) See https://forum.openmw.org/viewtopic.php?f=6&t=3074&p=34618#p34635 --- apps/openmw/engine.cpp | 2 - apps/openmw/mwscript/interpretercontext.cpp | 30 +---------- apps/openmw/mwscript/interpretercontext.hpp | 13 ----- apps/openmw/mwscript/miscextensions.cpp | 5 +- apps/openmw/mwworld/localscripts.cpp | 12 +---- apps/openmw/mwworld/localscripts.hpp | 5 -- apps/openmw/mwworld/refdata.cpp | 56 +++++++++++++++++++-- apps/openmw/mwworld/refdata.hpp | 8 +++ apps/openmw/mwworld/worldimp.cpp | 15 ++---- components/esm/objectstate.cpp | 7 +++ components/esm/objectstate.hpp | 1 + 11 files changed, 78 insertions(+), 76 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5fd328022..62457cae6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -76,8 +76,6 @@ void OMW::Engine::executeLocalScripts() &script.second.getRefData().getLocals(), script.second); mEnvironment.getScriptManager()->run (script.first, interpreterContext); } - - localScripts.setIgnore (MWWorld::Ptr()); } void OMW::Engine::frame(float frametime) diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 7a6afe2e0..79f856398 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -138,8 +138,7 @@ namespace MWScript InterpreterContext::InterpreterContext ( MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) - : mLocals (locals), mReference (reference), - mActivationHandled (false), mTargetId (targetId) + : mLocals (locals), mReference (reference), mTargetId (targetId) { // If we run on a reference (local script, dialogue script or console with object // selected), store the ID of that reference store it so it can be inherited by @@ -477,37 +476,10 @@ namespace MWScript return static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); } - bool InterpreterContext::hasBeenActivated (const MWWorld::Ptr& ptr) - { - if (!mActivated.isEmpty() && mActivated==ptr) - { - mActivationHandled = true; - return true; - } - - return false; - } - - bool InterpreterContext::hasActivationBeenHandled() const - { - return mActivationHandled; - } - - void InterpreterContext::activate (const MWWorld::Ptr& ptr) - { - mActivated = ptr; - mActivationHandled = false; - } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { boost::shared_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); - if (mActivated == ptr) - { - mActivationHandled = true; - mActivated = MWWorld::Ptr(); - } } float InterpreterContext::getSecondsPassed() const diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 3c43444cc..fdd5aa55f 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -27,9 +27,6 @@ namespace MWScript Locals *mLocals; mutable MWWorld::Ptr mReference; - MWWorld::Ptr mActivated; - bool mActivationHandled; - std::string mTargetId; /// If \a id is empty, a reference the script is run from is returned or in case @@ -131,16 +128,6 @@ namespace MWScript virtual float getDistance (const std::string& name, const std::string& id = "") const; ///< @note if \a id is empty, assumes an implicit reference - bool hasBeenActivated (const MWWorld::Ptr& ptr); - ///< \attention Calling this function for the right reference will mark the action as - /// been handled. - - bool hasActivationBeenHandled() const; - - void activate (const MWWorld::Ptr& ptr); - ///< Store reference acted upon. The actual execution of the action does not - /// take place here. - void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 51f0c6c55..9e415faaa 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -142,7 +142,7 @@ namespace MWScript MWWorld::Ptr ptr = context.getReference(); - runtime.push (context.hasBeenActivated (ptr)); + runtime.push (ptr.getRefData().onActivate()); } }; @@ -158,7 +158,8 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr, MWMechanics::getPlayer()); + if (ptr.getRefData().activateByScript()) + context.executeActivation(ptr, MWMechanics::getPlayer()); } }; diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index f0174ac52..5f6d10b31 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -66,11 +66,6 @@ MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (s mIter = mScripts.end(); } -void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) -{ - mIgnore = ptr; -} - void MWWorld::LocalScripts::startIteration() { mIter = mScripts.begin(); @@ -81,11 +76,8 @@ bool MWWorld::LocalScripts::getNext(std::pair& script) while (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; - if (mIgnore.isEmpty() || iter->second!=mIgnore) - { - script = *iter; - return true; - } + script = *iter; + return true; } return false; } diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 6c2118ea8..c698daf04 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -17,17 +17,12 @@ namespace MWWorld { std::list > mScripts; std::list >::iterator mIter; - MWWorld::ConstPtr mIgnore; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); - void setIgnore (const ConstPtr& ptr); - ///< Mark a single reference for ignoring during iteration over local scripts (will revoke - /// previous ignores). - void startIteration(); ///< Set the iterator to the begin of the script list. diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 1da6b53fa..f85abcc32 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -8,8 +8,18 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +namespace +{ +enum RefDataFlags +{ + Flag_SuppressActivate = 1, // If set, activation will be suppressed and redirected to the OnActivate flag, which can then be handled by a script. + Flag_OnActivate = 2 +}; +} + namespace MWWorld { + void RefData::copy (const RefData& refData) { mBaseNode = refData.mBaseNode; @@ -19,6 +29,7 @@ namespace MWWorld mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; + mFlags = refData.mFlags; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -32,7 +43,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { @@ -45,7 +56,7 @@ namespace MWWorld : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), - mChanged(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } @@ -55,8 +66,12 @@ namespace MWWorld mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), - mChanged(true) // Loading from a savegame -> assume changed + mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { + // "Note that the ActivationFlag_UseEnabled is saved to the reference, + // which will result in permanently suppressed activation if the reference script is removed. + // This occurred when removing the animated containers mod, and the fix in MCP is to reset UseEnabled to true on loading a game." + mFlags &= (~Flag_SuppressActivate); } RefData::RefData (const RefData& refData) @@ -80,6 +95,7 @@ namespace MWWorld objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; + objectState.mFlags = mFlags; } RefData& RefData::operator= (const RefData& refData) @@ -219,4 +235,38 @@ namespace MWWorld { return mChanged; } + + bool RefData::activate() + { + if (!(mFlags & Flag_SuppressActivate)) + return true; + else + { + mFlags |= Flag_OnActivate; + return false; + } + } + + bool RefData::onActivate() + { + mFlags |= Flag_SuppressActivate; + + if (mFlags & Flag_OnActivate) + { + mFlags &= (~Flag_OnActivate); + return true; + } + return false; + } + + bool RefData::activateByScript() + { + if (mFlags & Flag_SuppressActivate) + { + mFlags &= (~Flag_SuppressActivate); + return true; + } + else + return false; + } } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index d87ffdb70..9e662e430 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -50,6 +50,8 @@ namespace MWWorld bool mChanged; + unsigned int mFlags; + public: RefData(); @@ -122,6 +124,12 @@ namespace MWWorld const CustomData *getCustomData() const; + bool activate(); + + bool onActivate(); + + bool activateByScript(); + bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f3d80ee67..ea2069bd2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3148,25 +3148,16 @@ namespace MWWorld void World::activate(const Ptr &object, const Ptr &actor) { - MWScript::InterpreterContext interpreterContext (&object.getRefData().getLocals(), object); - interpreterContext.activate (object); - - std::string script = object.getClass().getScript (object); - breakInvisibility(actor); if (mScriptsEnabled) { - if (!script.empty()) + if (object.getRefData().activate()) { - getLocalScripts().setIgnore (object); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + boost::shared_ptr action = (object.getClass().activate(object, actor)); + action->execute (actor); } - if (!interpreterContext.hasActivationBeenHandled()) - interpreterContext.executeActivation(object, actor); } - else - interpreterContext.executeActivation(object, actor); } struct ResetActorsVisitor diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index d736bab66..b80c72ffe 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -27,6 +27,9 @@ void ESM::ObjectState::load (ESMReader &esm) if (esm.isNextSub("LROT")) esm.skipHSub(); // local rotation, no longer used + mFlags = 0; + esm.getHNOT (mFlags, "FLAG"); + // obsolete int unused; esm.getHNOT(unused, "LTIM"); @@ -55,6 +58,9 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const if (!inInventory) esm.writeHNT ("POS_", mPosition, 24); + if (mFlags != 0) + esm.writeHNT ("FLAG", mFlags); + if (!mHasCustomState) esm.writeHNT ("HCUS", false); } @@ -70,6 +76,7 @@ void ESM::ObjectState::blank() mPosition.pos[i] = 0; mPosition.rot[i] = 0; } + mFlags = 0; mHasCustomState = true; } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 215cb74ba..5b78074af 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -24,6 +24,7 @@ namespace ESM unsigned char mEnabled; int mCount; ESM::Position mPosition; + unsigned int mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; From a97eae864d1cfaae4df1e4c5f3975bb5bfed3ef9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 26 Feb 2016 13:15:41 +0100 Subject: [PATCH 80/99] Fix the ObstacleCheck time step (Fixes #3211) --- apps/openmw/mwmechanics/obstacle.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 5815d8cbe..11bbd1e31 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -120,14 +120,10 @@ namespace MWMechanics const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); - // actors can move at most 60 fps (the physics framerate). - // the max() can be removed if we implement physics interpolation. - float movementDuration = std::max(1/60.f, duration); - if(mDistSameSpot == -1) mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); - float distSameSpot = mDistSameSpot * movementDuration; + float distSameSpot = mDistSameSpot * duration; bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; From d60786b5da5e0f8c8d66405466543cee0c83f03c Mon Sep 17 00:00:00 2001 From: Rob Cutmore Date: Fri, 26 Feb 2016 07:41:17 -0500 Subject: [PATCH 81/99] Do not show cell markers for interior cells --- apps/opencs/view/render/cell.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index e7b135891..641aeb088 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../../model/world/idtable.hpp" @@ -308,12 +309,19 @@ void CSVRender::Cell::setCellArrows (int mask) void CSVRender::Cell::setCellMarker() { bool cellExists = false; + bool isInteriorCell = false; + int cellIndex = mData.getCells().searchId(mId); if (cellIndex > -1) { - cellExists = !mData.getCells().getRecord(cellIndex).isDeleted(); + const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); + cellExists = !cellRecord.isDeleted(); + isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; + } + + if (!isInteriorCell) { + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } - mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const From c3ef387208614527937f5b54d50ec864c59dba39 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 27 Feb 2016 12:53:07 +0100 Subject: [PATCH 82/99] Vanilla-compatible creature/NPC respawning (Fixes #2369, Fixes #2467) --- apps/openmw/mwbase/world.hpp | 11 ++++---- apps/openmw/mwclass/creature.cpp | 13 +++++++++- apps/openmw/mwclass/creaturelevlist.cpp | 23 ++++++++++++++++- apps/openmw/mwclass/npc.cpp | 13 +++++++++- apps/openmw/mwmechanics/creaturestats.cpp | 10 ++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 4 +++ apps/openmw/mwstate/statemanagerimp.cpp | 3 ++- apps/openmw/mwworld/cellstore.cpp | 31 ++++++++++++----------- apps/openmw/mwworld/scene.cpp | 27 ++++++++++++-------- apps/openmw/mwworld/scene.hpp | 10 +++++--- apps/openmw/mwworld/worldimp.cpp | 20 +++++++-------- apps/openmw/mwworld/worldimp.hpp | 11 ++++---- components/esm/creaturestats.cpp | 5 ++++ components/esm/creaturestats.hpp | 1 + 14 files changed, 128 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 52697d670..99cbe9654 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -231,15 +231,16 @@ namespace MWBase virtual float getTimeScaleFactor() const = 0; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position) = 0; + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position) = 0; + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true) = 0; - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true) = 0; + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4a7af1099..182177d52 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -759,7 +759,18 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (isFlagBitSet(ptr, ESM::Creature::Respawn)) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (isFlagBitSet(ptr, ESM::Creature::Respawn) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 0223fb634..cd55d31a1 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -44,7 +44,28 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - customData.mSpawn = true; + if (customData.mSpawn) + return; + + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + { + const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); + if (creature.getRefData().getCount() == 0) + customData.mSpawn = true; + else if (creatureStats.isDead()) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); + if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + customData.mSpawn = true; + } + } + else + customData.mSpawn = true; } void CreatureLevList::registerSelf() diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4c825f63f..ee0112ac9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1292,7 +1292,18 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 6933c40a3..50b1a89b3 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -187,6 +187,9 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { + if (!mDead) + mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); + mDead = true; mDynamic[index].setModifier(0); @@ -503,6 +506,7 @@ namespace MWMechanics state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; + state.mTimeOfDeath = mTimeOfDeath.toEsm(); mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); @@ -549,6 +553,7 @@ namespace MWMechanics mLevel = state.mLevel; mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; + mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); @@ -622,6 +627,11 @@ namespace MWMechanics mDeathAnimation = index; } + MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const + { + return mTimeOfDeath; + } + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 46c5bab31..734e87319 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -65,6 +65,8 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + MWWorld::TimeStamp mTimeOfDeath; + public: typedef std::pair SummonKey; // private: @@ -259,6 +261,8 @@ namespace MWMechanics unsigned char getDeathAnimation() const; void setDeathAnimation(unsigned char index); + MWWorld::TimeStamp getTimeOfDeath() const; + int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89d31c485..1bf97cb7e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -493,7 +493,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - // Do not trigger erroneous cellChanged events + // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. + // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } catch (const std::exception& e) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dcaa73e93..1572ae1b0 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -902,21 +902,22 @@ namespace MWWorld Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } + } + + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); } } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index cd0954260..63bb4758a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -240,7 +240,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) { std::pair result = mActiveCells.insert(cell); @@ -270,12 +270,13 @@ namespace MWWorld } } - cell->respawn(); - // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + if (respawn) + cell->respawn(); + // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); @@ -329,7 +330,7 @@ namespace MWWorld } } - void Scene::changeCellGrid (int X, int Y) + void Scene::changeCellGrid (int X, int Y, bool changeEvent) { Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -403,7 +404,7 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); } } } @@ -411,7 +412,8 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y); MWBase::Environment::get().getWindowManager()->changeCell(current); - mCellChanged = true; + if (changeEvent) + mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); } @@ -484,7 +486,7 @@ namespace MWWorld return mActiveCells; } - void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); @@ -530,7 +532,7 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cell. - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, true); @@ -540,21 +542,24 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mCellChanged = true; MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + if (changeEvent) + mCellChanged = true; + + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); } - void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) + void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { int x = 0; int y = 0; MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - changeCellGrid(x, y); + changeCellGrid(x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 5c429f7c7..6cba9c3be 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -71,7 +71,7 @@ namespace MWWorld void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int X, int Y); + void changeCellGrid (int X, int Y, bool changeEvent = true); void getGridCenter(int& cellX, int& cellY); @@ -90,7 +90,7 @@ namespace MWWorld void unloadCell (CellStoreCollection::iterator iter); - void loadCell (CellStore *cell, Loading::Listener* loadingListener); + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn); void playerMoved (const osg::Vec3f& pos); @@ -103,11 +103,13 @@ namespace MWWorld bool hasCellChanged() const; ///< Has the set of active cells changed, since the last frame? - void changeToInteriorCell (const std::string& cellName, const ESM::Position& position); + void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true); ///< Move to interior cell. + /// @param changeEvent Set cellChanged flag? - void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos); + void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to exterior cell. + /// @param changeEvent Set cellChanged flag? void changeToVoid(); ///< Change into a void diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ea2069bd2..e6b8f25a2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -962,11 +962,11 @@ namespace MWWorld return mTimeScale->getFloat(); } - void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != cellName) + if (changeEvent && mCurrentWorldSpace != cellName) { // changed worldspace mProjectileManager->clear(); @@ -976,34 +976,34 @@ namespace MWWorld } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToInteriorCell(cellName, position); + mWorldScene->changeToInteriorCell(cellName, position, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToExteriorCell (const ESM::Position& position) + void World::changeToExteriorCell (const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != "sys::default") // FIXME + if (changeEvent && mCurrentWorldSpace != "sys::default") // FIXME { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToExteriorCell(position, true); + mWorldScene->changeToExteriorCell(position, true, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange) + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent) { - if (!detectWorldSpaceChange) + if (!changeEvent) mCurrentWorldSpace = cellId.mWorldspace; if (cellId.mPaged) - changeToExteriorCell (position); + changeToExteriorCell (position, changeEvent); else - changeToInteriorCell (cellId.mWorldspace, position); + changeToInteriorCell (cellId.mWorldspace, position, changeEvent); } void World::markCellAsUnchanged() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6cc5cdc11..ce5aa417e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -324,15 +324,16 @@ namespace MWWorld virtual float getTimeScaleFactor() const; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position); + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent = true); ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position); + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent = true); ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true); - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true); + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 9bdbf9668..2200297e2 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -87,6 +87,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mDeathAnimation = 0; esm.getHNOT (mDeathAnimation, "DANM"); + esm.getHNOT (mTimeOfDeath, "DTIM"); + mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); @@ -193,6 +195,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDeathAnimation) esm.writeHNT ("DANM", mDeathAnimation); + if (mTimeOfDeath.mHour != 0 && mTimeOfDeath.mDay != 0) + esm.writeHNT ("DTIM", mTimeOfDeath); + mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 426e89055..66b708e27 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -57,6 +57,7 @@ namespace ESM bool mRecalcDynamicStats; int mDrawState; unsigned char mDeathAnimation; + ESM::TimeStamp mTimeOfDeath; int mLevel; From f2f601b9588da34ca5e1bd4f6d130dc081755c1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 27 Feb 2016 13:13:24 +0100 Subject: [PATCH 83/99] Implement corpse clearing (Fixes #2363) --- apps/openmw/mwworld/cellstore.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 1572ae1b0..e4bfde04a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -889,11 +889,19 @@ namespace MWWorld return mFogState.get(); } + void clearCorpse(const MWWorld::Ptr& ptr) + { + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); + if (!ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + void CellStore::respawn() { if (mState == State_Loaded) { - static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); @@ -907,16 +915,19 @@ namespace MWWorld for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) { Ptr ptr (&*it, this); + clearCorpse(ptr); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) { Ptr ptr (&*it, this); + clearCorpse(ptr); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) { Ptr ptr (&*it, this); + // no need to clearCorpse, handled as part of mCreatures ptr.getClass().respawn(ptr); } } From 3a2dccad4b046f850481155682133c4b4f29bf83 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 27 Feb 2016 13:40:53 +0100 Subject: [PATCH 84/99] Implement 'Show' script instruction --- apps/openmw/mwscript/docs/vmformat.txt | 2 + apps/openmw/mwscript/locals.cpp | 26 ++++++++++ apps/openmw/mwscript/locals.hpp | 6 +++ apps/openmw/mwscript/miscextensions.cpp | 66 +++++++++++++++++++++++++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + 6 files changed, 103 insertions(+) diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 93219c649..d3070c79b 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -449,5 +449,7 @@ op 0x2000300: EnableLevelupMenu op 0x2000301: ToggleScripts op 0x2000302: Fixme op 0x2000303: Fixme, explicit +op 0x2000304: Show +op 0x2000305: Show, explicit opcodes 0x2000304-0x3ffffff unused diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index ee93ea2e7..5c9ffa07a 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -97,6 +97,32 @@ namespace MWScript return 0; } + float Locals::getFloatVar(const std::string &script, const std::string &var) + { + ensure (script); + + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + char type = locals.getType(var); + if(index != -1) + { + switch(type) + { + case 's': + return mShorts.at (index); + + case 'l': + return mLongs.at (index); + + case 'f': + return mFloats.at(index); + default: + return 0; + } + } + return 0; + } + bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { ensure (script); diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index bb4df42bf..d63411a94 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -51,6 +51,12 @@ namespace MWScript /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, const std::string& var); + /// if var does not exist, returns 0 + /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary + float getFloatVar (const std::string& script, const std::string& var); + /// \note If locals have not been configured yet, no data is written. /// /// \return Locals written? diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9e415faaa..1830be693 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -849,6 +849,70 @@ namespace MWScript } }; + template + class OpShow : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime, false); + std::string var = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + std::stringstream output; + + if (!ptr.isEmpty()) + { + const std::string& script = ptr.getClass().getScript(ptr); + if (script.empty()) + { + output << ptr.getCellRef().getRefId() << " has no script " << std::endl; + } + else + { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals(script); + char type = locals.getType(var); + switch (type) + { + case 'l': + case 's': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getIntVar(script, var); + break; + case 'f': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getFloatVar(script, var); + break; + default: + output << "unknown local '" << var << "' for '" << ptr.getCellRef().getRefId() << "'"; + break; + } + } + } + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + char type = world->getGlobalVariableType (var); + + switch (type) + { + case 's': + output << runtime.getContext().getGlobalShort (var); + break; + case 'l': + output << runtime.getContext().getGlobalLong (var); + break; + case 'f': + output << runtime.getContext().getGlobalFloat (var); + break; + default: + output << "unknown global variable"; + } + } + runtime.getContext().report(output.str()); + } + }; + template class OpShowVars : public Interpreter::Opcode0 { @@ -1266,6 +1330,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); + interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); + interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 6916945f9..161d8adb4 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -300,6 +300,7 @@ namespace Compiler extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); + extensions.registerInstruction ("show", "c", opcodeShow, opcodeShowExplicit); extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction("tgm", "", opcodeToggleGodMode); extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index feed5513e..430b88484 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -277,6 +277,8 @@ namespace Compiler const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; + const int opcodeShow = 0x2000304; + const int opcodeShowExplicit = 0x2000305; const int opcodeToggleGodMode = 0x200021f; const int opcodeToggleScripts = 0x2000301; const int opcodeDisableLevitation = 0x2000220; From 84e9d346efdd2fb3b4c2ca55af01381361f95992 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 28 Feb 2016 16:47:33 +0100 Subject: [PATCH 85/99] Remove duplicate include --- apps/openmw/mwrender/water.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 0a379e50a..340c07785 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include From fc3de3302e7b99d9a1bb695441f3f35b56845bd0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 28 Feb 2016 16:47:41 +0100 Subject: [PATCH 86/99] Create a collision shape for the default terrain --- apps/openmw/mwworld/scene.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 63bb4758a..65348763a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -268,6 +268,13 @@ namespace MWWorld mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts); } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, -2048.f); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), + worldsize / (verts-1), verts); + } } // register local scripts From 27577ce765d18e82098847901e3155524a5794e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 28 Feb 2016 16:49:18 +0100 Subject: [PATCH 87/99] Add ESM::Land::DEFAULT_HEIGHT --- apps/openmw/mwworld/scene.cpp | 2 +- components/esm/loadland.hpp | 3 +++ components/esmterrain/storage.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 65348763a..ba2829469 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -271,7 +271,7 @@ namespace MWWorld else { static std::vector defaultHeight; - defaultHeight.resize(verts*verts, -2048.f); + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts); } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 8a8d6fdd2..a2bf1573e 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -46,6 +46,9 @@ struct Land DATA_VTEX = 16 }; + // default height to use in case there is no Land record + static const int DEFAULT_HEIGHT = -2048; + // number of vertices per side static const int LAND_SIZE = 65; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 68b32cf2a..86d1e08e6 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,7 +16,7 @@ namespace ESMTerrain { - const float defaultHeight = -2048; + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : mVFS(vfs) From 04d51d58713aa8e3294d3f3c023a524592c112ff Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 13:25:09 +0100 Subject: [PATCH 88/99] Always update the ingredient icons (Fixes #3220) --- apps/openmw/mwgui/alchemywindow.cpp | 43 +++++++++-------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 3299846b7..61a0efc46 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -67,46 +67,29 @@ namespace MWGui { MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); - if (result == MWMechanics::Alchemy::Result_NoName) + switch (result) { + case MWMechanics::Alchemy::Result_NoName: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage37}"); - return; - } - - // check if mortar & pestle is available (always needed) - if (result == MWMechanics::Alchemy::Result_NoMortarAndPestle) - { + break; + case MWMechanics::Alchemy::Result_NoMortarAndPestle: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage45}"); - return; - } - - // make sure 2 or more ingredients were selected - if (result == MWMechanics::Alchemy::Result_LessThanTwoIngredients) - { + break; + case MWMechanics::Alchemy::Result_LessThanTwoIngredients: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage6a}"); - return; - } - - if (result == MWMechanics::Alchemy::Result_NoEffects) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); - MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); - return; - } - - if (result == MWMechanics::Alchemy::Result_Success) - { + break; + case MWMechanics::Alchemy::Result_Success: MWBase::Environment::get().getWindowManager()->messageBox("#{sPotionSuccess}"); MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f); - } - else if (result == MWMechanics::Alchemy::Result_RandomFailure) - { - // potion failed + break; + case MWMechanics::Alchemy::Result_NoEffects: + case MWMechanics::Alchemy::Result_RandomFailure: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); + break; } - // reduce count of the ingredients + // remove ingredient slots that have been fully used up for (int i=0; i<4; ++i) if (mIngredients[i]->isUserString("ToolTipType")) { From be2f20f564e1c27537dc693464b377887e71b997 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 16:54:00 +0100 Subject: [PATCH 89/99] PlaceAt: attempt to select a safe spawn location (Fixes #2515, Fixes #1384, Fixes #2925) --- .../mwscript/transformationextensions.cpp | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 95e2deee9..9c950c8ff 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -492,6 +492,15 @@ namespace MWScript { public: + osg::Vec3f getSpawnPosition(const osg::Vec3f& origin, const osg::Quat& orientation, int direction, float distance) + { + if(direction == 0) return origin + (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 1) return origin - (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 2) return origin - (orientation * osg::Vec3f(1,0,0)) * distance; + else if(direction == 3) return origin + (orientation * osg::Vec3f(1,0,0)) * distance; + else return origin; + } + virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr actor = pc @@ -514,40 +523,52 @@ namespace MWScript if (!actor.isInCell()) throw std::runtime_error ("actor is not in a cell"); + ESM::Position ipos = actor.getRefData().getPosition(); + osg::Vec3f pos(ipos.asVec3()); + osg::Quat rot(ipos.rot[2], osg::Vec3f(0,0,-1)); + + for (int i=0; i<4; ++i) + { + // check if spawn point is safe, fall back to another direction if not + osg::Vec3f spawnPoint = getSpawnPosition(ipos.asVec3(), rot, direction, distance); + spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later + + if (!MWBase::Environment::get().getWorld()->castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), + pos.x(), pos.y(), pos.z() + 20)) + { + // safe + ipos.pos[0] = spawnPoint.x(); + ipos.pos[1] = spawnPoint.y(); + ipos.pos[2] = spawnPoint.z(); + break; + } + direction = (direction+1) % 4; + } + + if (actor.getClass().isActor()) + { + // TODO: should this depend on the 'direction' parameter? + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + } + else + { + ipos.rot[0] = actor.getRefData().getPosition().rot[0]; + ipos.rot[1] = actor.getRefData().getPosition().rot[1]; + ipos.rot[2] = actor.getRefData().getPosition().rot[2]; + } + + for (int i=0; igetStore(), itemID, 1); ref.getPtr().getCellRef().setPosition(ipos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + placed.getClass().adjustPosition(placed, true); // snap to ground } } }; From 11f00e3aa961fb9f9554df406e1840f0fe87b05c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:05:18 +0100 Subject: [PATCH 90/99] Rename safePlaceObject to placeObject --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- apps/openmw/mwmechanics/summoning.cpp | 2 +- apps/openmw/mwscript/transformationextensions.cpp | 6 +++--- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 99cbe9654..566853fc7 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -278,7 +278,7 @@ namespace MWBase virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index cd55d31a1..e0e890b60 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -120,7 +120,7 @@ namespace MWClass const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 636e19907..7c4d077e6 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -147,7 +147,7 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,ipos); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); if (anim) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 9c950c8ff..7d9877168 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -434,7 +434,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } } @@ -482,7 +482,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } }; @@ -567,7 +567,7 @@ namespace MWScript MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, 1); ref.getPtr().getCellRef().setPosition(ipos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,ipos); placed.getClass().adjustPosition(placed, true); // snap to ground } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e6b8f25a2..3a4b0109c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1325,7 +1325,7 @@ namespace MWWorld rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } @@ -3041,7 +3041,7 @@ namespace MWWorld MWWorld::ManualRef ref(getStore(), selectedCreature, 1); ref.getPtr().getCellRef().setPosition(ipos); - safePlaceObject(ref.getPtr(), cell, ipos); + placeObject(ref.getPtr(), cell, ipos); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index ce5aa417e..9f21135e5 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -367,8 +367,8 @@ namespace MWWorld /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); - ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + ///< place an object. Makes a copy of the Ptr. virtual float getMaxActivationDistance(); From 6df71f6250f7a06c4dfc93d18d91025ce27d3526 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:16:33 +0100 Subject: [PATCH 91/99] Factor out safePlaceObject function --- apps/openmw/mwbase/world.hpp | 6 ++- .../mwscript/transformationextensions.cpp | 51 +------------------ apps/openmw/mwworld/worldimp.cpp | 37 ++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 6 ++- 4 files changed, 48 insertions(+), 52 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 566853fc7..de5f15d64 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -279,7 +279,11 @@ namespace MWBase virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; - ///< place an object in a "safe" location (ie not in the void, etc). + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const = 0; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 7d9877168..e9e13e74f 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -492,15 +492,6 @@ namespace MWScript { public: - osg::Vec3f getSpawnPosition(const osg::Vec3f& origin, const osg::Quat& orientation, int direction, float distance) - { - if(direction == 0) return origin + (orientation * osg::Vec3f(0,1,0)) * distance; - else if(direction == 1) return origin - (orientation * osg::Vec3f(0,1,0)) * distance; - else if(direction == 2) return origin - (orientation * osg::Vec3f(1,0,0)) * distance; - else if(direction == 3) return origin + (orientation * osg::Vec3f(1,0,0)) * distance; - else return origin; - } - virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr actor = pc @@ -523,52 +514,12 @@ namespace MWScript if (!actor.isInCell()) throw std::runtime_error ("actor is not in a cell"); - ESM::Position ipos = actor.getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - osg::Quat rot(ipos.rot[2], osg::Vec3f(0,0,-1)); - - for (int i=0; i<4; ++i) - { - // check if spawn point is safe, fall back to another direction if not - osg::Vec3f spawnPoint = getSpawnPosition(ipos.asVec3(), rot, direction, distance); - spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later - - if (!MWBase::Environment::get().getWorld()->castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), - pos.x(), pos.y(), pos.z() + 20)) - { - // safe - ipos.pos[0] = spawnPoint.x(); - ipos.pos[1] = spawnPoint.y(); - ipos.pos[2] = spawnPoint.z(); - break; - } - direction = (direction+1) % 4; - } - - if (actor.getClass().isActor()) - { - // TODO: should this depend on the 'direction' parameter? - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - } - else - { - ipos.rot[0] = actor.getRefData().getPosition().rot[0]; - ipos.rot[1] = actor.getRefData().getPosition().rot[1]; - ipos.rot[2] = actor.getRefData().getPosition().rot[2]; - } - - for (int i=0; igetStore(), itemID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,ipos); - placed.getClass().adjustPosition(placed, true); // snap to ground + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); } } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3a4b0109c..be2baf737 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1330,6 +1330,43 @@ namespace MWWorld return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } + MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) + { + ESM::Position ipos = referenceObject.getRefData().getPosition(); + osg::Vec3f pos(ipos.asVec3()); + osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1)); + + for (int i=0; i<4; ++i) + { + // check if spawn point is safe, fall back to another direction if not + osg::Vec3f spawnPoint = pos; + if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance; + else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance; + spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later + + if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), + pos.x(), pos.y(), pos.z() + 20)) + { + // safe + ipos.pos[0] = spawnPoint.x(); + ipos.pos[1] = spawnPoint.y(); + ipos.pos[2] = spawnPoint.z(); + break; + } + direction = (direction+1) % 4; + } + + ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; + ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1]; + ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; + + MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); + placed.getClass().adjustPosition(placed, true); // snap to ground + return placed; + } + void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = 8192; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9f21135e5..d1298a39b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -368,7 +368,11 @@ namespace MWWorld virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); - ///< place an object. Makes a copy of the Ptr. + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance); + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual float getMaxActivationDistance(); From be62ae758a706bcbabf847424686bf1c7894987d Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:26:52 +0100 Subject: [PATCH 92/99] Fall back to sides first --- apps/openmw/mwworld/worldimp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be2baf737..6d7ceee43 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1336,8 +1336,11 @@ namespace MWWorld osg::Vec3f pos(ipos.asVec3()); osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1)); + int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; + for (int i=0; i<4; ++i) { + direction = fallbackDirections[i]; // check if spawn point is safe, fall back to another direction if not osg::Vec3f spawnPoint = pos; if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance; @@ -1355,7 +1358,6 @@ namespace MWWorld ipos.pos[2] = spawnPoint.z(); break; } - direction = (direction+1) % 4; } ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; From 7485abe5c311a7e66f05c2382debdb888a57c8fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:27:07 +0100 Subject: [PATCH 93/99] Use safePlaceObject for summoned creature spawning --- apps/openmw/mwmechanics/summoning.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 7c4d077e6..cf7771485 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -114,31 +114,16 @@ namespace MWMechanics bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end(); if (!found) { - ESM::Position ipos = mActor.getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - - osg::Quat rot (-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - const std::string& creatureGmst = summonMap[it->first]; std::string creatureID = MWBase::Environment::get().getWorld()->getStore().get().find(creatureGmst)->getString(); if (!creatureID.empty()) { - MWWorld::CellStore* store = mActor.getCell(); int creatureActorId = -1; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); @@ -147,7 +132,7 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); if (anim) From 36bb255bc0da04bdac805b61a2126330d1a25cdd Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:30:38 +0100 Subject: [PATCH 94/99] Use safePlaceObject in spawnRandomCreature --- apps/openmw/mwworld/worldimp.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6d7ceee43..2ab027cd6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3064,23 +3064,9 @@ namespace MWWorld if (selectedCreature.empty()) return; - ESM::Position ipos = mPlayer->getPlayer().getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - osg::Quat rot(-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); MWWorld::ManualRef ref(getStore(), selectedCreature, 1); - ref.getPtr().getCellRef().setPosition(ipos); - placeObject(ref.getPtr(), cell, ipos); + safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f); } } From 1e5b4bea0ac53cb544ec3a351c4a0f48ed3c5511 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 17:50:18 +0100 Subject: [PATCH 95/99] Don't create an unnecessary osg::Group when copying over a rig --- components/sceneutil/attach.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index d8cfa428a..38330901d 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -56,9 +56,18 @@ namespace SceneUtil CopyRigVisitor copyVisitor(handle, filter); toAttach->accept(copyVisitor); - master->asGroup()->addChild(handle); - - return handle; + if (handle->getNumChildren() == 1) + { + osg::ref_ptr newHandle = handle->getChild(0); + handle->removeChild(newHandle); + master->asGroup()->addChild(newHandle); + return newHandle; + } + else + { + master->asGroup()->addChild(handle); + return handle; + } } else { From 8791063110e140f1eadd06a1c2ea191944882804 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 18:20:56 +0100 Subject: [PATCH 96/99] Fix "RigGeometry rendering with no skeleton" warnings in rare cases --- components/sceneutil/skeleton.cpp | 11 ++++++++++- components/sceneutil/skeleton.hpp | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index d1299c058..f311de246 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -38,6 +38,8 @@ Skeleton::Skeleton() , mNeedToUpdateBoneMatrices(true) , mActive(true) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -48,6 +50,8 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -111,6 +115,11 @@ void Skeleton::updateBoneMatrices(osg::NodeVisitor* nv) mLastFrameNumber = nv->getTraversalNumber(); + if (mLastFrameNumber % 2 == 0) + mTraversedEvenFrame = true; + else + mTraversedOddFrame = true; + if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) @@ -140,7 +149,7 @@ void Skeleton::traverse(osg::NodeVisitor& nv) if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node - && mLastFrameNumber != 0 && mLastFrameNumber+2 <= nv.getTraversalNumber()) + && mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame) return; osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index d98d36751..9ca1dd49d 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -69,6 +69,8 @@ namespace SceneUtil bool mActive; unsigned int mLastFrameNumber; + bool mTraversedEvenFrame; + bool mTraversedOddFrame; }; } From 87871d7d54260b4df53a0f3fc99c46cdcda5f041 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 29 Feb 2016 20:23:19 +0100 Subject: [PATCH 97/99] Don't add RotateController to an uncontrolled node --- apps/openmw/mwrender/animation.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0418e34fd..7f7183b9e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1353,9 +1353,24 @@ namespace MWRender if (found != getNodeMap().end()) { osg::MatrixTransform* node = found->second; - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.insert(std::make_pair(node, mHeadController)); + + bool foundKeyframeCtrl = false; + osg::Callback* cb = node->getUpdateCallback(); + while (cb) + { + if (dynamic_cast(cb)) + { + foundKeyframeCtrl = true; + break; + } + } + + if (foundKeyframeCtrl) + { + mHeadController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mHeadController); + mActiveControllers.insert(std::make_pair(node, mHeadController)); + } } } } From 7d8bd56e11e3eaee1cc6a7685ed61931ed4ea8bb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 1 Mar 2016 11:21:06 +0100 Subject: [PATCH 98/99] create new records with basic data in the same step without updates inbetween (Fixes #3207) --- apps/opencs/model/world/commands.cpp | 5 +---- apps/opencs/model/world/idtable.cpp | 15 +++++++++++++++ apps/opencs/model/world/idtable.hpp | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 097e83f7c..f8c72a390 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -68,9 +68,6 @@ void CSMWorld::ModifyCommand::undo() void CSMWorld::CreateCommand::applyModifications() { - for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); - if (!mNestedValues.empty()) { CSMWorld::IdTree *tree = dynamic_cast(&mModel); @@ -114,7 +111,7 @@ void CSMWorld::CreateCommand::setType (UniversalId::Type type) void CSMWorld::CreateCommand::redo() { - mModel.addRecord (mId, mType); + mModel.addRecordWithData (mId, mValues, mType); applyModifications(); } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bd1179cea..4d8a0842f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -149,6 +149,21 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::addRecordWithData (const std::string& id, + const std::map& data, UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (id, type); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id, type); + + for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) + mIdCollection->setData (index, iter->first, iter->second); + + endInsertRows(); +} + void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9ecba0214..8c2f8a46d 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -53,6 +53,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void addRecordWithData (const std::string& id, const std::map& data, + UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, const std::string& destination, UniversalId::Type type = UniversalId::Type_None); From 2821f46a181371d5fac71d235c60f6008fbfba24 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 1 Mar 2016 11:31:08 +0100 Subject: [PATCH 99/99] Corpse clearing fix --- apps/openmw/mwmechanics/creaturestats.cpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 2 +- components/esm/creaturestats.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 50b1a89b3..13396e3fa 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -22,7 +22,7 @@ namespace MWMechanics mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), - mDeathAnimation(0), mLevel (0) + mDeathAnimation(0), mTimeOfDeath(), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index e4bfde04a..13f74e22b 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -893,7 +893,7 @@ namespace MWWorld { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); - if (!ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) MWBase::Environment::get().getWorld()->deleteObject(ptr); } diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 2200297e2..9b60382e9 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -87,6 +87,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mDeathAnimation = 0; esm.getHNOT (mDeathAnimation, "DANM"); + mTimeOfDeath.mDay = 0; + mTimeOfDeath.mHour = 0; esm.getHNOT (mTimeOfDeath, "DTIM"); mSpells.load(esm);