forked from mirror/openmw-tes3mp
Merge remote-tracking branch 'scrawl/master'
This commit is contained in:
commit
604509ac7e
54 changed files with 1174 additions and 620 deletions
|
@ -15,7 +15,7 @@ add_openmw_dir (mwrender
|
|||
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
|
||||
actors objects renderinginterface localmap occlusionquery water shadows
|
||||
characterpreview globalmap videoplayer ripplesimulation refraction
|
||||
terrainstorage renderconst effectmanager weaponanimation
|
||||
terrainstorage renderconst effectmanager weaponanimation terraingrid
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
|
|
@ -381,10 +381,7 @@ static void crash_handler(const char *logfile)
|
|||
|
||||
if(logfile)
|
||||
{
|
||||
char cwd[MAXPATHLEN];
|
||||
getcwd(cwd, MAXPATHLEN);
|
||||
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
|
||||
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL);
|
||||
}
|
||||
exit(0);
|
||||
|
|
|
@ -274,6 +274,9 @@ namespace MWBase
|
|||
virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0;
|
||||
///< Adjust position after load to be on ground. Must be called after model load.
|
||||
|
||||
virtual void fixPosition (const MWWorld::Ptr& actor) = 0;
|
||||
///< Attempt to fix position so that the Ptr is no longer inside collision geometry.
|
||||
|
||||
virtual void deleteObject (const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
|
||||
|
|
|
@ -168,7 +168,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Armor> *ref =
|
||||
ptr.get<ESM::Armor>();
|
||||
|
||||
return ref->mBase->mData.mValue * (static_cast<float>(getItemHealth(ptr)) / getItemMaxHealth(ptr));
|
||||
return ref->mBase->mData.mValue;
|
||||
}
|
||||
|
||||
void Armor::registerSelf()
|
||||
|
|
|
@ -127,7 +127,7 @@ namespace MWClass
|
|||
MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}");
|
||||
unlock(ptr); //Call the function here. because that makes sense.
|
||||
// using a key disarms the trap
|
||||
ptr.getCellRef().getTrap() = "";
|
||||
ptr.getCellRef().setTrap("");
|
||||
}
|
||||
|
||||
if (!needKey || hasKey)
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Lockpick> *ref =
|
||||
ptr.get<ESM::Lockpick>();
|
||||
|
||||
return ref->mBase->mData.mValue * (static_cast<float>(getItemHealth(ptr)) / getItemMaxHealth(ptr));
|
||||
return ref->mBase->mData.mValue;
|
||||
}
|
||||
|
||||
void Lockpick::registerSelf()
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Probe> *ref =
|
||||
ptr.get<ESM::Probe>();
|
||||
|
||||
return ref->mBase->mData.mValue * (static_cast<float>(getItemHealth(ptr)) / getItemMaxHealth(ptr));
|
||||
return ref->mBase->mData.mValue;
|
||||
}
|
||||
|
||||
void Probe::registerSelf()
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Repair> *ref =
|
||||
ptr.get<ESM::Repair>();
|
||||
|
||||
return ref->mBase->mData.mValue * (static_cast<float>(getItemHealth(ptr)) / getItemMaxHealth(ptr));
|
||||
return ref->mBase->mData.mValue;
|
||||
}
|
||||
|
||||
void Repair::registerSelf()
|
||||
|
|
|
@ -154,7 +154,7 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Weapon> *ref =
|
||||
ptr.get<ESM::Weapon>();
|
||||
|
||||
return ref->mBase->mData.mValue * (static_cast<float>(getItemHealth(ptr)) / getItemMaxHealth(ptr));
|
||||
return ref->mBase->mData.mValue;
|
||||
}
|
||||
|
||||
void Weapon::registerSelf()
|
||||
|
|
|
@ -586,13 +586,32 @@ namespace MWGui
|
|||
//Clear the list of topics
|
||||
mTopicsList->clear();
|
||||
|
||||
if (mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||
bool dispositionVisible = false;
|
||||
if (mPtr.getClass().isNpc())
|
||||
{
|
||||
dispositionVisible = true;
|
||||
mDispositionBar->setProgressRange(100);
|
||||
mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr));
|
||||
mDispositionText->eraseText(0, mDispositionText->getTextLength());
|
||||
mDispositionText->addText("#B29154"+boost::lexical_cast<std::string>(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154");
|
||||
}
|
||||
|
||||
bool dispositionWasVisible = mDispositionBar->getVisible();
|
||||
|
||||
if (dispositionVisible && !dispositionWasVisible)
|
||||
{
|
||||
mDispositionBar->setVisible(true);
|
||||
float offset = mDispositionBar->getHeight()+5;
|
||||
mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset));
|
||||
mTopicsList->adjustSize();
|
||||
}
|
||||
else if (!dispositionVisible && dispositionWasVisible)
|
||||
{
|
||||
mDispositionBar->setVisible(false);
|
||||
float offset = mDispositionBar->getHeight()+5;
|
||||
mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset));
|
||||
mTopicsList->adjustSize();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogueWindow::goodbye()
|
||||
|
|
|
@ -170,7 +170,7 @@ namespace MWGui
|
|||
BookPage* mHistory;
|
||||
Widgets::MWList* mTopicsList;
|
||||
MyGUI::ScrollBar* mScrollBar;
|
||||
MyGUI::ProgressPtr mDispositionBar;
|
||||
MyGUI::Progress* mDispositionBar;
|
||||
MyGUI::EditBox* mDispositionText;
|
||||
|
||||
PersuasionDialog mPersuasionDialog;
|
||||
|
|
|
@ -23,6 +23,19 @@
|
|||
#include "countdialog.hpp"
|
||||
#include "dialogue.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int getEffectiveValue (MWWorld::Ptr item, int count)
|
||||
{
|
||||
int price = item.getClass().getValue(item) * count;
|
||||
if (item.getClass().hasItemHealth(item))
|
||||
price *= (static_cast<float>(item.getClass().getItemHealth(item)) / item.getClass().getItemMaxHealth(item));
|
||||
return price;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
const float TradeWindow::sBalanceChangeInitialPause = 0.5;
|
||||
|
@ -465,7 +478,7 @@ namespace MWGui
|
|||
|
||||
void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem)
|
||||
{
|
||||
int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, boughtItem);
|
||||
int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), boughtItem);
|
||||
|
||||
mCurrentBalance += diff;
|
||||
mCurrentMerchantOffer += diff;
|
||||
|
@ -475,7 +488,7 @@ namespace MWGui
|
|||
|
||||
void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem)
|
||||
{
|
||||
int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, !soldItem);
|
||||
int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), !soldItem);
|
||||
|
||||
mCurrentBalance -= diff;
|
||||
mCurrentMerchantOffer -= diff;
|
||||
|
|
|
@ -21,6 +21,17 @@ void WindowBase::setVisible(bool visible)
|
|||
open();
|
||||
else if (wasVisible && !visible)
|
||||
close();
|
||||
|
||||
// This is needed as invisible widgets can retain key focus.
|
||||
if (!visible)
|
||||
{
|
||||
MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget();
|
||||
while (keyFocus != mMainWidget && keyFocus != NULL)
|
||||
keyFocus = keyFocus->getParent();
|
||||
|
||||
if (keyFocus == mMainWidget)
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowBase::isVisible()
|
||||
|
|
|
@ -118,6 +118,7 @@ namespace MWMechanics
|
|||
mStrength = 0;
|
||||
mCell = NULL;
|
||||
mLastTargetPos = Ogre::Vector3(0,0,0);
|
||||
mMinMaxAttackDurationInitialised = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -219,8 +220,6 @@ namespace MWMechanics
|
|||
if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0;
|
||||
}
|
||||
|
||||
mTimerAttack -= duration;
|
||||
|
||||
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
|
||||
float attacksPeriod = 1.0f;
|
||||
|
||||
|
@ -228,12 +227,16 @@ namespace MWMechanics
|
|||
|
||||
if(mReadyToAttack)
|
||||
{
|
||||
if (mMinMaxAttackDuration[0][0] == 0)
|
||||
if (!mMinMaxAttackDurationInitialised)
|
||||
{
|
||||
// TODO: this must be updated when a different weapon is equipped
|
||||
getMinMaxAttackDuration(actor, mMinMaxAttackDuration);
|
||||
mMinMaxAttackDurationInitialised = true;
|
||||
}
|
||||
|
||||
if (mTimerAttack <= 0) mAttack = false;
|
||||
if (mTimerAttack < 0) mAttack = false;
|
||||
|
||||
mTimerAttack -= duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -326,6 +329,8 @@ namespace MWMechanics
|
|||
else attackType = ESM::Weapon::AT_Chop; // cause it's =0
|
||||
|
||||
mStrength = static_cast<float>(rand()) / RAND_MAX;
|
||||
|
||||
// Note: may be 0 for some animations
|
||||
mTimerAttack = mMinMaxAttackDuration[attackType][0] +
|
||||
(mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength;
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ namespace MWMechanics
|
|||
|
||||
float mStrength; // this is actually make sense only in ranged combat
|
||||
float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations
|
||||
bool mMinMaxAttackDurationInitialised;
|
||||
|
||||
bool mForceNoShortcut;
|
||||
ESM::Position mShortcutFailPos;
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace MWMechanics
|
|||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
||||
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
|
||||
{
|
||||
mIdle.resize(8, 0);
|
||||
init();
|
||||
}
|
||||
|
||||
|
@ -652,7 +653,7 @@ namespace MWMechanics
|
|||
wander->mData.mDuration = mDuration;
|
||||
wander->mData.mTimeOfDay = mTimeOfDay;
|
||||
wander->mStartTime = mStartTime.toEsm();
|
||||
assert (mIdle.size() >= 8);
|
||||
assert (mIdle.size() == 8);
|
||||
for (int i=0; i<8; ++i)
|
||||
wander->mData.mIdle[i] = mIdle[i];
|
||||
wander->mData.mShouldRepeat = mRepeat;
|
||||
|
|
|
@ -336,7 +336,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
{
|
||||
std::string::size_type swimpos = movement.find("swim");
|
||||
if(swimpos == std::string::npos)
|
||||
movement.clear();
|
||||
{
|
||||
std::string::size_type runpos = movement.find("run");
|
||||
if (runpos != std::string::npos)
|
||||
{
|
||||
movement.replace(runpos, runpos+3, "walk");
|
||||
if (!mAnimation->hasAnimation(movement))
|
||||
movement.clear();
|
||||
}
|
||||
else
|
||||
movement.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
movegroup = MWRender::Animation::Group_LowerBody;
|
||||
|
@ -608,7 +618,7 @@ void CharacterController::updateIdleStormState()
|
|||
{
|
||||
Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
||||
Ogre::Vector3 characterDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis();
|
||||
inStormDirection = stormDirection.angleBetween(characterDirection) < Ogre::Degree(40);
|
||||
inStormDirection = stormDirection.angleBetween(characterDirection) > Ogre::Degree(120);
|
||||
}
|
||||
if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm"))
|
||||
{
|
||||
|
@ -1395,6 +1405,9 @@ void CharacterController::update(float duration)
|
|||
if (mMovementAnimVelocity == 0)
|
||||
world->queueMovement(mPtr, vec);
|
||||
}
|
||||
else
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
world->queueMovement(mPtr, Ogre::Vector3(0.0f));
|
||||
|
||||
movement = vec;
|
||||
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0;
|
||||
|
@ -1436,6 +1449,8 @@ void CharacterController::update(float duration)
|
|||
if(mMovementAnimVelocity > 0)
|
||||
world->queueMovement(mPtr, moved);
|
||||
}
|
||||
else if (mAnimation)
|
||||
mAnimation->updateEffects(duration);
|
||||
mSkipAnim = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,12 +144,6 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly)
|
|||
}
|
||||
else
|
||||
mAttachedObjects.clear();
|
||||
|
||||
for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
|
||||
{
|
||||
if(mObjectRoot->mControllers[i].getSource().isNull())
|
||||
mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
struct AddGlow
|
||||
|
@ -309,6 +303,12 @@ void Animation::addAnimSource(const std::string &model)
|
|||
ctrls[i].setSource(mAnimationTimePtr[grp]);
|
||||
grpctrls[grp].push_back(ctrls[i]);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < mObjectRoot->mControllers.size(); ++i)
|
||||
{
|
||||
if (mObjectRoot->mControllers[i].getSource().isNull())
|
||||
mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::clearAnimSources()
|
||||
|
@ -467,16 +467,17 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node
|
|||
const std::string stop = groupname+": stop";
|
||||
float starttime = std::numeric_limits<float>::max();
|
||||
float stoptime = 0.0f;
|
||||
NifOgre::TextKeyMap::const_iterator keyiter(keys.begin());
|
||||
while(keyiter != keys.end())
|
||||
// Have to find keys in reverse (see reset method)
|
||||
NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin());
|
||||
while(keyiter != keys.rend())
|
||||
{
|
||||
if(keyiter->second == start || keyiter->second == loopstart)
|
||||
starttime = keyiter->first;
|
||||
else if(keyiter->second == loopstop || keyiter->second == stop)
|
||||
{
|
||||
stoptime = keyiter->first;
|
||||
starttime = keyiter->first;
|
||||
break;
|
||||
}
|
||||
else if(keyiter->second == loopstop || keyiter->second == stop)
|
||||
stoptime = keyiter->first;
|
||||
++keyiter;
|
||||
}
|
||||
|
||||
|
@ -592,31 +593,39 @@ void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &posi
|
|||
|
||||
bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint)
|
||||
{
|
||||
const NifOgre::TextKeyMap::const_iterator groupstart = findGroupStart(keys, groupname);
|
||||
// Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two
|
||||
// separate walkforward keys, and the last one is supposed to be used.
|
||||
NifOgre::TextKeyMap::const_reverse_iterator groupend(keys.rbegin());
|
||||
for(;groupend != keys.rend();++groupend)
|
||||
{
|
||||
if(groupend->second.compare(0, groupname.size(), groupname) == 0 &&
|
||||
groupend->second.compare(groupname.size(), 2, ": ") == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
std::string starttag = groupname+": "+start;
|
||||
NifOgre::TextKeyMap::const_iterator startkey(groupstart);
|
||||
while(startkey != keys.end() && startkey->second != starttag)
|
||||
NifOgre::TextKeyMap::const_reverse_iterator startkey(groupend);
|
||||
while(startkey != keys.rend() && startkey->second != starttag)
|
||||
++startkey;
|
||||
if(startkey == keys.end() && start == "loop start")
|
||||
if(startkey == keys.rend() && start == "loop start")
|
||||
{
|
||||
starttag = groupname+": start";
|
||||
startkey = groupstart;
|
||||
while(startkey != keys.end() && startkey->second != starttag)
|
||||
startkey = groupend;
|
||||
while(startkey != keys.rend() && startkey->second != starttag)
|
||||
++startkey;
|
||||
}
|
||||
if(startkey == keys.end())
|
||||
if(startkey == keys.rend())
|
||||
return false;
|
||||
|
||||
const std::string stoptag = groupname+": "+stop;
|
||||
NifOgre::TextKeyMap::const_iterator stopkey(groupstart);
|
||||
while(stopkey != keys.end()
|
||||
NifOgre::TextKeyMap::const_reverse_iterator stopkey(groupend);
|
||||
while(stopkey != keys.rend()
|
||||
// We have to ignore extra garbage at the end.
|
||||
// The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
|
||||
// Why, just why? :(
|
||||
&& (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag))
|
||||
++stopkey;
|
||||
if(stopkey == keys.end())
|
||||
if(stopkey == keys.rend())
|
||||
return false;
|
||||
|
||||
if(startkey->first > stopkey->first)
|
||||
|
@ -628,18 +637,24 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s
|
|||
state.mStopTime = stopkey->first;
|
||||
|
||||
state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint);
|
||||
|
||||
// mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation
|
||||
// (see handleTextKey). But if startpoint is already past these keys, we need to assign them now.
|
||||
if(state.mTime > state.mStartTime)
|
||||
{
|
||||
const std::string loopstarttag = groupname+": loop start";
|
||||
const std::string loopstoptag = groupname+": loop stop";
|
||||
NifOgre::TextKeyMap::const_iterator key(groupstart);
|
||||
while(key->first <= state.mTime && key != stopkey)
|
||||
|
||||
NifOgre::TextKeyMap::const_reverse_iterator key(groupend);
|
||||
for (; key != startkey && key != keys.rend(); ++key)
|
||||
{
|
||||
if(key->second == loopstarttag)
|
||||
if (key->first > state.mTime)
|
||||
continue;
|
||||
|
||||
if (key->second == loopstarttag)
|
||||
state.mLoopStartTime = key->first;
|
||||
else if(key->second == loopstoptag)
|
||||
else if (key->second == loopstoptag)
|
||||
state.mLoopStopTime = key->first;
|
||||
++key;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,7 +669,8 @@ void split(const std::string &s, char delim, std::vector<std::string> &elems) {
|
|||
}
|
||||
}
|
||||
|
||||
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key)
|
||||
void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key,
|
||||
const NifOgre::TextKeyMap& textkeys)
|
||||
{
|
||||
//float time = key->first;
|
||||
const std::string &evt = key->second;
|
||||
|
@ -728,6 +744,34 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
|
|||
else
|
||||
mPtr.getClass().hit(mPtr);
|
||||
}
|
||||
else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0
|
||||
&& evt.compare(off, len, "start") == 0)
|
||||
{
|
||||
NifOgre::TextKeyMap::const_iterator hitKey = key;
|
||||
|
||||
// Not all animations have a hit key defined. If there is none, the hit happens with the start key.
|
||||
bool hasHitKey = false;
|
||||
while (hitKey != textkeys.end())
|
||||
{
|
||||
if (hitKey->second == groupname + ": hit")
|
||||
{
|
||||
hasHitKey = true;
|
||||
break;
|
||||
}
|
||||
if (hitKey->second == groupname + ": stop")
|
||||
break;
|
||||
++hitKey;
|
||||
}
|
||||
if (!hasHitKey)
|
||||
{
|
||||
if (groupname == "attack1")
|
||||
mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop);
|
||||
else if (groupname == "attack2")
|
||||
mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash);
|
||||
else if (groupname == "attack3")
|
||||
mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust);
|
||||
}
|
||||
}
|
||||
else if (evt.compare(off, len, "shoot attach") == 0)
|
||||
attachArrow();
|
||||
else if (evt.compare(off, len, "shoot release") == 0)
|
||||
|
@ -806,7 +850,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo
|
|||
NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime));
|
||||
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
||||
{
|
||||
handleTextKey(state, groupname, textkey);
|
||||
handleTextKey(state, groupname, textkey, textkeys);
|
||||
++textkey;
|
||||
}
|
||||
|
||||
|
@ -821,7 +865,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo
|
|||
textkey = textkeys.lower_bound(state.mTime);
|
||||
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
||||
{
|
||||
handleTextKey(state, groupname, textkey);
|
||||
handleTextKey(state, groupname, textkey, textkeys);
|
||||
++textkey;
|
||||
}
|
||||
}
|
||||
|
@ -999,7 +1043,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
|
|||
|
||||
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
||||
{
|
||||
handleTextKey(state, stateiter->first, textkey);
|
||||
handleTextKey(state, stateiter->first, textkey, textkeys);
|
||||
++textkey;
|
||||
}
|
||||
|
||||
|
@ -1013,7 +1057,7 @@ Ogre::Vector3 Animation::runAnimation(float duration)
|
|||
textkey = textkeys.lower_bound(state.mTime);
|
||||
while(textkey != textkeys.end() && textkey->first <= state.mTime)
|
||||
{
|
||||
handleTextKey(state, stateiter->first, textkey);
|
||||
handleTextKey(state, stateiter->first, textkey, textkeys);
|
||||
++textkey;
|
||||
}
|
||||
|
||||
|
@ -1039,7 +1083,10 @@ Ogre::Vector3 Animation::runAnimation(float duration)
|
|||
}
|
||||
|
||||
for(size_t i = 0;i < mObjectRoot->mControllers.size();i++)
|
||||
mObjectRoot->mControllers[i].update();
|
||||
{
|
||||
if(!mObjectRoot->mControllers[i].getSource().isNull())
|
||||
mObjectRoot->mControllers[i].update();
|
||||
}
|
||||
|
||||
// Apply group controllers
|
||||
for(size_t grp = 0;grp < sNumGroups;grp++)
|
||||
|
|
|
@ -173,7 +173,8 @@ protected:
|
|||
const std::string &groupname, const std::string &start, const std::string &stop,
|
||||
float startpoint);
|
||||
|
||||
void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key);
|
||||
void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key,
|
||||
const NifOgre::TextKeyMap& map);
|
||||
|
||||
/* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle
|
||||
* systems in the model are ignored (useful for NPCs, where only the skeleton is needed for
|
||||
|
@ -225,9 +226,6 @@ public:
|
|||
virtual void preRender (Ogre::Camera* camera);
|
||||
|
||||
virtual void setAlpha(float alpha) {}
|
||||
private:
|
||||
void updateEffects(float duration);
|
||||
|
||||
|
||||
public:
|
||||
void updatePtr(const MWWorld::Ptr &ptr);
|
||||
|
@ -300,6 +298,9 @@ public:
|
|||
|
||||
virtual Ogre::Vector3 runAnimation(float duration);
|
||||
|
||||
/// This is typically called as part of runAnimation, but may be called manually if needed.
|
||||
void updateEffects(float duration);
|
||||
|
||||
virtual void showWeapons(bool showWeapon);
|
||||
virtual void showCarriedLeft(bool show) {}
|
||||
virtual void attachArrow() {}
|
||||
|
|
|
@ -184,6 +184,18 @@ Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration)
|
|||
{
|
||||
Ogre::Vector3 ret = Animation::runAnimation(duration);
|
||||
pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton());
|
||||
|
||||
if (!mWeapon.isNull())
|
||||
{
|
||||
for (unsigned int i=0; i<mWeapon->mControllers.size(); ++i)
|
||||
mWeapon->mControllers[i].update();
|
||||
}
|
||||
if (!mShield.isNull())
|
||||
{
|
||||
for (unsigned int i=0; i<mShield->mControllers.size(); ++i)
|
||||
mShield->mControllers[i].update();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <openengine/bullet/physic.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/terrain/world.hpp>
|
||||
#include <components/terrain/defaultworld.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
@ -45,6 +45,7 @@
|
|||
#include "globalmap.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "effectmanager.hpp"
|
||||
#include "terraingrid.hpp"
|
||||
|
||||
using namespace MWRender;
|
||||
using namespace Ogre;
|
||||
|
@ -223,6 +224,9 @@ MWRender::Camera* RenderingManager::getCamera() const
|
|||
|
||||
void RenderingManager::removeCell (MWWorld::CellStore *store)
|
||||
{
|
||||
if (store->isExterior())
|
||||
mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
|
||||
mLocalMap->saveFogOfWar(store);
|
||||
mObjects->removeCell(store);
|
||||
mActors->removeCell(store);
|
||||
|
@ -241,6 +245,9 @@ bool RenderingManager::toggleWater()
|
|||
|
||||
void RenderingManager::cellAdded (MWWorld::CellStore *store)
|
||||
{
|
||||
if (store->isExterior())
|
||||
mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
|
||||
|
||||
mObjects->buildStaticGeometry (*store);
|
||||
sh::Factory::getInstance().unloadUnreferencedMaterials();
|
||||
mDebugging->cellAdded(store);
|
||||
|
@ -1039,9 +1046,12 @@ void RenderingManager::enableTerrain(bool enable)
|
|||
{
|
||||
if (!mTerrain)
|
||||
{
|
||||
mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
|
||||
Settings::Manager::getBool("distant land", "Terrain"),
|
||||
Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64);
|
||||
if (Settings::Manager::getBool("distant land", "Terrain"))
|
||||
mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
|
||||
Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64);
|
||||
else
|
||||
mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain,
|
||||
Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY);
|
||||
mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"),
|
||||
Settings::Manager::getBool("split", "Shadows"));
|
||||
mTerrain->update(mRendering.getCamera()->getRealPosition());
|
||||
|
|
|
@ -219,6 +219,7 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera)
|
|||
, mSceneMgr(NULL)
|
||||
, mAtmosphereDay(NULL)
|
||||
, mAtmosphereNight(NULL)
|
||||
, mCloudNode(NULL)
|
||||
, mClouds()
|
||||
, mNextClouds()
|
||||
, mCloudBlendFactor(0.0f)
|
||||
|
@ -240,10 +241,11 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera)
|
|||
, mRainTimer(0)
|
||||
, mRainSpeed(0)
|
||||
, mRainFrequency(1)
|
||||
, mStormDirection(0,-1,0)
|
||||
, mIsStorm(false)
|
||||
{
|
||||
mSceneMgr = root->getCreator();
|
||||
mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
mRootNode->setInheritOrientation(false);
|
||||
}
|
||||
|
||||
void SkyManager::create()
|
||||
|
@ -335,8 +337,8 @@ void SkyManager::create()
|
|||
mObjects.push_back(objects);
|
||||
|
||||
// Clouds
|
||||
SceneNode* clouds_node = mRootNode->createChildSceneNode();
|
||||
objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif");
|
||||
mCloudNode = mRootNode->createChildSceneNode();
|
||||
objects = NifOgre::Loader::createObjects(mCloudNode, "meshes\\sky_clouds_01.nif");
|
||||
for(size_t i = 0;i < objects->mEntities.size();i++)
|
||||
{
|
||||
Entity* clouds_ent = objects->mEntities[i];
|
||||
|
@ -429,6 +431,16 @@ void SkyManager::updateRain(float dt)
|
|||
Ogre::SceneNode* offsetNode = sceneNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight));
|
||||
|
||||
NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect);
|
||||
for (unsigned int i=0; i<objects->mEntities.size(); ++i)
|
||||
{
|
||||
objects->mEntities[i]->setRenderQueueGroup(RQG_Alpha);
|
||||
objects->mEntities[i]->setVisibilityFlags(RV_Sky);
|
||||
}
|
||||
for (unsigned int i=0; i<objects->mParticles.size(); ++i)
|
||||
{
|
||||
objects->mParticles[i]->setRenderQueueGroup(RQG_Alpha);
|
||||
objects->mParticles[i]->setVisibilityFlags(RV_Sky);
|
||||
}
|
||||
mRainModels[offsetNode] = objects;
|
||||
}
|
||||
}
|
||||
|
@ -443,8 +455,16 @@ void SkyManager::update(float duration)
|
|||
{
|
||||
for (unsigned int i=0; i<mParticle->mControllers.size(); ++i)
|
||||
mParticle->mControllers[i].update();
|
||||
|
||||
if (mIsStorm)
|
||||
mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection));
|
||||
}
|
||||
|
||||
if (mIsStorm)
|
||||
mCloudNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection));
|
||||
else
|
||||
mCloudNode->setOrientation(Ogre::Quaternion::IDENTITY);
|
||||
|
||||
updateRain(duration);
|
||||
|
||||
// UV Scroll the clouds
|
||||
|
@ -529,6 +549,7 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather)
|
|||
mRainEnabled = !mRainEffect.empty();
|
||||
mRainFrequency = weather.mRainFrequency;
|
||||
mRainSpeed = weather.mRainSpeed;
|
||||
mIsStorm = weather.mIsStorm;
|
||||
|
||||
if (mCurrentParticleEffect != weather.mParticleEffect)
|
||||
{
|
||||
|
@ -666,6 +687,11 @@ void SkyManager::sunDisable()
|
|||
mSunEnabled = false;
|
||||
}
|
||||
|
||||
void SkyManager::setStormDirection(const Vector3 &direction)
|
||||
{
|
||||
mStormDirection = direction;
|
||||
}
|
||||
|
||||
void SkyManager::setSunDirection(const Vector3& direction, bool is_moon)
|
||||
{
|
||||
if (!mCreated) return;
|
||||
|
|
|
@ -150,6 +150,8 @@ namespace MWRender
|
|||
|
||||
void setRainSpeed(float speed);
|
||||
|
||||
void setStormDirection(const Ogre::Vector3& direction);
|
||||
|
||||
void setSunDirection(const Ogre::Vector3& direction, bool is_moon);
|
||||
|
||||
void setMasserDirection(const Ogre::Vector3& direction);
|
||||
|
@ -185,6 +187,8 @@ namespace MWRender
|
|||
|
||||
bool mMoonRed;
|
||||
|
||||
bool mIsStorm;
|
||||
|
||||
float mHour;
|
||||
int mDay;
|
||||
int mMonth;
|
||||
|
@ -203,6 +207,8 @@ namespace MWRender
|
|||
Ogre::SceneNode* mAtmosphereDay;
|
||||
Ogre::SceneNode* mAtmosphereNight;
|
||||
|
||||
Ogre::SceneNode* mCloudNode;
|
||||
|
||||
std::vector<NifOgre::ObjectScenePtr> mObjects;
|
||||
|
||||
Ogre::SceneNode* mParticleNode;
|
||||
|
@ -211,6 +217,8 @@ namespace MWRender
|
|||
std::map<Ogre::SceneNode*, NifOgre::ObjectScenePtr> mRainModels;
|
||||
float mRainTimer;
|
||||
|
||||
Ogre::Vector3 mStormDirection;
|
||||
|
||||
// remember some settings so we don't have to apply them again if they didnt change
|
||||
Ogre::String mClouds;
|
||||
Ogre::String mNextClouds;
|
||||
|
|
165
apps/openmw/mwrender/terraingrid.cpp
Normal file
165
apps/openmw/mwrender/terraingrid.cpp
Normal file
|
@ -0,0 +1,165 @@
|
|||
#include "terraingrid.hpp"
|
||||
|
||||
#include <OgreSceneManager.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include <components/terrain/chunk.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align)
|
||||
: Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align)
|
||||
, mVisible(true)
|
||||
{
|
||||
mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
}
|
||||
|
||||
TerrainGrid::~TerrainGrid()
|
||||
{
|
||||
while (!mGrid.empty())
|
||||
{
|
||||
unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second);
|
||||
}
|
||||
|
||||
mSceneMgr->destroySceneNode(mRootNode);
|
||||
}
|
||||
|
||||
void TerrainGrid::update(const Ogre::Vector3 &cameraPos)
|
||||
{
|
||||
}
|
||||
|
||||
void TerrainGrid::loadCell(int x, int y)
|
||||
{
|
||||
if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
|
||||
return; // already loaded
|
||||
|
||||
Ogre::Vector2 center(x+0.5, y+0.5);
|
||||
float minH, maxH;
|
||||
if (!mStorage->getMinMaxHeights(1, center, minH, maxH))
|
||||
return; // no terrain defined
|
||||
|
||||
Ogre::Vector3 min (-0.5*mStorage->getCellWorldSize(),
|
||||
-0.5*mStorage->getCellWorldSize(),
|
||||
minH);
|
||||
Ogre::Vector3 max (0.5*mStorage->getCellWorldSize(),
|
||||
0.5*mStorage->getCellWorldSize(),
|
||||
maxH);
|
||||
|
||||
Ogre::AxisAlignedBox bounds(min, max);
|
||||
|
||||
GridElement element;
|
||||
|
||||
Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize();
|
||||
element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0));
|
||||
|
||||
std::vector<float> positions;
|
||||
std::vector<float> normals;
|
||||
std::vector<Ogre::uint8> colours;
|
||||
mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours);
|
||||
|
||||
element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours);
|
||||
element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0));
|
||||
|
||||
std::vector<Ogre::PixelBox> blendmaps;
|
||||
std::vector<Terrain::LayerInfo> layerList;
|
||||
mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList);
|
||||
|
||||
element.mMaterialGenerator.setLayerList(layerList);
|
||||
|
||||
// upload blendmaps to GPU
|
||||
std::vector<Ogre::TexturePtr> blendTextures;
|
||||
for (std::vector<Ogre::PixelBox>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
||||
{
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format);
|
||||
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true));
|
||||
map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format);
|
||||
blendTextures.push_back(map);
|
||||
}
|
||||
|
||||
element.mMaterialGenerator.setBlendmapList(blendTextures);
|
||||
|
||||
element.mSceneNode->attachObject(element.mChunk);
|
||||
updateMaterial(element);
|
||||
|
||||
mGrid[std::make_pair(x,y)] = element;
|
||||
}
|
||||
|
||||
void TerrainGrid::updateMaterial(GridElement &element)
|
||||
{
|
||||
element.mMaterialGenerator.enableShadows(getShadowsEnabled());
|
||||
element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled());
|
||||
element.mChunk->setMaterial(element.mMaterialGenerator.generate());
|
||||
}
|
||||
|
||||
void TerrainGrid::unloadCell(int x, int y)
|
||||
{
|
||||
Grid::iterator it = mGrid.find(std::make_pair(x,y));
|
||||
if (it == mGrid.end())
|
||||
return;
|
||||
|
||||
GridElement& element = it->second;
|
||||
delete element.mChunk;
|
||||
element.mChunk = NULL;
|
||||
|
||||
const std::vector<Ogre::TexturePtr>& blendmaps = element.mMaterialGenerator.getBlendmapList();
|
||||
for (std::vector<Ogre::TexturePtr>::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it)
|
||||
Ogre::TextureManager::getSingleton().remove((*it)->getName());
|
||||
|
||||
mSceneMgr->destroySceneNode(element.mSceneNode);
|
||||
element.mSceneNode = NULL;
|
||||
|
||||
mGrid.erase(it);
|
||||
}
|
||||
|
||||
void TerrainGrid::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it)
|
||||
{
|
||||
updateMaterial(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
bool TerrainGrid::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
void TerrainGrid::setVisible(bool visible)
|
||||
{
|
||||
mVisible = visible;
|
||||
mRootNode->setVisible(visible);
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
int cellX, cellY;
|
||||
MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY);
|
||||
|
||||
Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY));
|
||||
if (it == mGrid.end())
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
|
||||
Terrain::Chunk* chunk = it->second.mChunk;
|
||||
Ogre::SceneNode* node = it->second.mSceneNode;
|
||||
Ogre::AxisAlignedBox box = chunk->getBoundingBox();
|
||||
box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition());
|
||||
return box;
|
||||
}
|
||||
|
||||
void TerrainGrid::syncLoad()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
75
apps/openmw/mwrender/terraingrid.hpp
Normal file
75
apps/openmw/mwrender/terraingrid.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#ifndef OPENMW_MWRENDER_TERRAINGRID_H
|
||||
#define OPENMW_MWRENDER_TERRAINGRID_H
|
||||
|
||||
#include <components/terrain/world.hpp>
|
||||
#include <components/terrain/material.hpp>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
class Chunk;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
struct GridElement
|
||||
{
|
||||
Ogre::SceneNode* mSceneNode;
|
||||
|
||||
Terrain::MaterialGenerator mMaterialGenerator;
|
||||
|
||||
Terrain::Chunk* mChunk;
|
||||
};
|
||||
|
||||
/// @brief Simple terrain implementation that loads cells in a grid, with no LOD
|
||||
class TerrainGrid : public Terrain::World
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
TerrainGrid(Ogre::SceneManager* sceneMgr,
|
||||
Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align);
|
||||
~TerrainGrid();
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
virtual void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
virtual void loadCell(int x, int y);
|
||||
virtual void unloadCell(int x, int y);
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible);
|
||||
virtual bool getVisible();
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows);
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
virtual void syncLoad();
|
||||
|
||||
private:
|
||||
void updateMaterial (GridElement& element);
|
||||
|
||||
typedef std::map<std::pair<int, int>, GridElement> Grid;
|
||||
Grid mGrid;
|
||||
|
||||
Ogre::SceneNode* mRootNode;
|
||||
bool mVisible;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -46,7 +46,7 @@ namespace MWRender
|
|||
/// Create textures holding layer blend values for a terrain chunk.
|
||||
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
/// @note May be called from *one* background thread.
|
||||
/// @note May be called from background threads.
|
||||
/// @param chunkSize size of the terrain chunk in cell units
|
||||
/// @param chunkCenter center of the chunk in cell units
|
||||
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
|
||||
|
@ -62,7 +62,7 @@ namespace MWRender
|
|||
/// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once.
|
||||
/// @note The terrain chunks shouldn't be larger than one cell since otherwise we might
|
||||
/// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used.
|
||||
/// @note May be called from *one* background thread.
|
||||
/// @note May be called from background threads.
|
||||
/// @param nodes A collection of nodes for which to retrieve the aforementioned data
|
||||
/// @param out Output vector
|
||||
/// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) -
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace MWScript
|
|||
if (world->findExteriorPosition(cell, pos))
|
||||
{
|
||||
world->changeToExteriorCell(pos);
|
||||
world->fixPosition(world->getPlayerPtr());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -79,6 +80,7 @@ namespace MWScript
|
|||
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
|
||||
|
||||
world->changeToExteriorCell (pos);
|
||||
world->fixPosition(world->getPlayerPtr());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -623,7 +623,8 @@ namespace MWScript
|
|||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end())
|
||||
{
|
||||
player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] -1;
|
||||
player.getClass().getNpcStats(player).getFactionRanks()[factionID] =
|
||||
std::max(0, player.getClass().getNpcStats(player).getFactionRanks()[factionID]-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1031,7 +1032,7 @@ namespace MWScript
|
|||
return;
|
||||
|
||||
std::map<std::string, int>& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks ();
|
||||
ranks[factionID] = ranks[factionID]+1;
|
||||
ranks[factionID] = std::min(9, ranks[factionID]+1);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1058,7 +1059,7 @@ namespace MWScript
|
|||
return;
|
||||
|
||||
std::map<std::string, int>& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks ();
|
||||
ranks[factionID] = ranks[factionID]-1;
|
||||
ranks[factionID] = std::max(0, ranks[factionID]-1);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -193,8 +193,10 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name)
|
|||
}
|
||||
|
||||
// Then check cells that are already listed
|
||||
for (std::map<std::pair<int, int>, CellStore>::iterator iter = mExteriors.begin();
|
||||
iter!=mExteriors.end(); ++iter)
|
||||
// Search in reverse, this is a workaround for an ambiguous chargen_plank reference in the vanilla game.
|
||||
// there is one at -22,16 and one at -2,-9, the latter should be used.
|
||||
for (std::map<std::pair<int, int>, CellStore>::reverse_iterator iter = mExteriors.rbegin();
|
||||
iter!=mExteriors.rend(); ++iter)
|
||||
{
|
||||
Ptr ptr = getPtrAndCache (name, iter->second);
|
||||
if (!ptr.isEmpty())
|
||||
|
|
|
@ -58,13 +58,16 @@ void animateCollisionShapes (std::map<OEngine::Physic::RigidBody*, OEngine::Phys
|
|||
for (std::map<std::string, int>::iterator shapeIt = shapes.begin();
|
||||
shapeIt != shapes.end(); ++shapeIt)
|
||||
{
|
||||
Ogre::Node* bone = animation->getNode(shapeIt->first);
|
||||
|
||||
// FIXME: this will happen for nodes with empty names. Ogre's SkeletonInstance::cloneBoneAndChildren
|
||||
// will assign an auto-generated name if the bone name was empty. We could use the bone handle instead of
|
||||
// the bone name, but that is a bit tricky to retrieve.
|
||||
Ogre::Node* bone;
|
||||
if (shapeIt->first.empty())
|
||||
// HACK: see NifSkeletonLoader::buildBones
|
||||
bone = animation->getNode(" ");
|
||||
else
|
||||
bone = animation->getNode(shapeIt->first);
|
||||
|
||||
if (bone == NULL)
|
||||
continue;
|
||||
throw std::runtime_error("can't find bone");
|
||||
|
||||
btCompoundShape* compound = dynamic_cast<btCompoundShape*>(instance.mCompound);
|
||||
|
||||
|
@ -215,7 +218,7 @@ namespace MWWorld
|
|||
|
||||
|
||||
public:
|
||||
static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
|
||||
static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine, float maxHeight)
|
||||
{
|
||||
const ESM::Position &refpos = ptr.getRefData().getPosition();
|
||||
Ogre::Vector3 position(refpos.pos);
|
||||
|
@ -224,7 +227,6 @@ namespace MWWorld
|
|||
if (!physicActor)
|
||||
return position;
|
||||
|
||||
const int maxHeight = 200.f;
|
||||
OEngine::Physic::ActorTracer tracer;
|
||||
tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine);
|
||||
if(tracer.mFraction >= 1.0f)
|
||||
|
@ -246,7 +248,7 @@ namespace MWWorld
|
|||
|
||||
// Early-out for totally static creatures
|
||||
// (Not sure if gravity should still apply?)
|
||||
if (!ptr.getClass().canWalk(ptr) && !isFlying && !ptr.getClass().canSwim(ptr))
|
||||
if (!ptr.getClass().canWalk(ptr) && !ptr.getClass().canFly(ptr) && !ptr.getClass().canSwim(ptr))
|
||||
return position;
|
||||
|
||||
/* Anything to collide with? */
|
||||
|
@ -315,15 +317,22 @@ namespace MWWorld
|
|||
wasOnGround = physicActor->getOnGround(); // store current state
|
||||
tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible
|
||||
if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope)
|
||||
{
|
||||
isOnGround = true;
|
||||
// if we're on the ground, don't try to fall any more
|
||||
velocity.z = std::max(0.0f, velocity.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: isOnGround was initialised false, so should stay false if falling or sliding horizontally
|
||||
if(isOnGround)
|
||||
// Now that we have the effective movement vector, apply wind forces to it
|
||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||
{
|
||||
// if we're on the ground, don't try to fall any more
|
||||
velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above
|
||||
Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
||||
Ogre::Degree angle = stormDirection.angleBetween(velocity);
|
||||
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
||||
.find("fStromWalkMult")->getFloat();
|
||||
velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f));
|
||||
}
|
||||
|
||||
Ogre::Vector3 newPosition = position;
|
||||
|
@ -593,9 +602,9 @@ namespace MWWorld
|
|||
return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName());
|
||||
}
|
||||
|
||||
Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr)
|
||||
Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight)
|
||||
{
|
||||
return MovementSolver::traceDown(ptr, mEngine);
|
||||
return MovementSolver::traceDown(ptr, mEngine, maxHeight);
|
||||
}
|
||||
|
||||
void PhysicsSystem::addHeightField (float* heights,
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace MWWorld
|
|||
void stepSimulation(float dt);
|
||||
|
||||
std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with
|
||||
Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr);
|
||||
Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight);
|
||||
|
||||
std::pair<float, std::string> getFacedHandle(float queryDistance);
|
||||
std::pair<std::string,Ogre::Vector3> getHitContact(const std::string &name,
|
||||
|
|
|
@ -67,6 +67,9 @@ namespace MWWorld
|
|||
Ogre::Vector3 pos(caster.getRefData().getPosition().pos);
|
||||
pos.z += height;
|
||||
|
||||
if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible
|
||||
return;
|
||||
|
||||
Ogre::Quaternion orient;
|
||||
if (caster.getClass().isActor())
|
||||
orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
|
||||
|
@ -188,6 +191,11 @@ namespace MWWorld
|
|||
|
||||
hit = true;
|
||||
}
|
||||
|
||||
// Explodes when hitting water
|
||||
if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos))
|
||||
hit = true;
|
||||
|
||||
if (hit)
|
||||
{
|
||||
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
|
||||
|
|
|
@ -552,23 +552,16 @@ namespace MWWorld
|
|||
template <>
|
||||
class Store<ESM::Cell> : public StoreBase
|
||||
{
|
||||
struct ExtCmp
|
||||
{
|
||||
bool operator()(const ESM::Cell &x, const ESM::Cell &y) {
|
||||
if (x.mData.mX == y.mData.mX) {
|
||||
return x.mData.mY < y.mData.mY;
|
||||
}
|
||||
return x.mData.mX < y.mData.mX;
|
||||
}
|
||||
};
|
||||
|
||||
struct DynamicExtCmp
|
||||
{
|
||||
bool operator()(const std::pair<int, int> &left, const std::pair<int, int> &right) const {
|
||||
if (left.first == right.first) {
|
||||
return left.second < right.second;
|
||||
}
|
||||
return left.first < right.first;
|
||||
if (left.first == right.first && left.second == right.second)
|
||||
return false;
|
||||
|
||||
if (left.first == right.first)
|
||||
return left.second > right.second;
|
||||
|
||||
return left.first > right.first;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -115,7 +115,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa
|
|||
mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true),
|
||||
mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0),
|
||||
mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0),
|
||||
mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false)
|
||||
mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false),
|
||||
mStormDirection(0,1,0)
|
||||
{
|
||||
//Globals
|
||||
mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0");
|
||||
|
@ -389,6 +390,17 @@ void WeatherManager::update(float duration)
|
|||
mWindSpeed = mResult.mWindSpeed;
|
||||
mIsStorm = mResult.mIsStorm;
|
||||
|
||||
if (mIsStorm)
|
||||
{
|
||||
MWWorld::Ptr player = world->getPlayerPtr();
|
||||
Ogre::Vector3 playerPos (player.getRefData().getPosition().pos);
|
||||
Ogre::Vector3 redMountainPos (19950, 72032, 27831);
|
||||
|
||||
mStormDirection = (playerPos - redMountainPos);
|
||||
mStormDirection.z = 0;
|
||||
mRendering->getSkyManager()->setStormDirection(mStormDirection);
|
||||
}
|
||||
|
||||
mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor);
|
||||
|
||||
// disable sun during night
|
||||
|
@ -812,5 +824,5 @@ bool WeatherManager::isInStorm() const
|
|||
|
||||
Ogre::Vector3 WeatherManager::getStormDirection() const
|
||||
{
|
||||
return Ogre::Vector3(0,-1,0);
|
||||
return mStormDirection;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <OgreColourValue.h>
|
||||
#include <OgreVector3.h>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -127,10 +128,10 @@ namespace MWWorld
|
|||
// Rain sound effect
|
||||
std::string mRainLoopSoundID;
|
||||
|
||||
// Is this an ash storm / blight storm? This controls two things:
|
||||
// - The particle node will be oriented so that the particles appear to come from the Red Mountain. (not implemented yet)
|
||||
// Is this an ash storm / blight storm? If so, the following will happen:
|
||||
// - The particles and clouds will be oriented so they appear to come from the Red Mountain.
|
||||
// - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation)
|
||||
// Possible effect on movement speed?
|
||||
// - Slower movement when walking against the storm (fStromWalkMult)
|
||||
bool mIsStorm;
|
||||
|
||||
// How fast does rain travel down?
|
||||
|
@ -202,6 +203,8 @@ namespace MWWorld
|
|||
float mHour;
|
||||
float mWindSpeed;
|
||||
bool mIsStorm;
|
||||
Ogre::Vector3 mStormDirection;
|
||||
|
||||
MWWorld::Fallback* mFallback;
|
||||
void setFallbackWeather(Weather& weather,const std::string& name);
|
||||
MWRender::RenderingManager* mRendering;
|
||||
|
|
|
@ -1166,7 +1166,7 @@ namespace MWWorld
|
|||
|
||||
if (!isFlying(ptr))
|
||||
{
|
||||
Ogre::Vector3 traced = mPhysics->traceDown(ptr);
|
||||
Ogre::Vector3 traced = mPhysics->traceDown(ptr, 200);
|
||||
if (traced.z < pos.pos[2])
|
||||
pos.pos[2] = traced.z;
|
||||
}
|
||||
|
@ -1174,6 +1174,17 @@ namespace MWWorld
|
|||
moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]);
|
||||
}
|
||||
|
||||
void World::fixPosition(const Ptr &actor)
|
||||
{
|
||||
const float dist = 8000;
|
||||
ESM::Position pos (actor.getRefData().getPosition());
|
||||
pos.pos[2] += dist;
|
||||
actor.getRefData().setPosition(pos);
|
||||
|
||||
Ogre::Vector3 traced = mPhysics->traceDown(actor, dist*1.1);
|
||||
moveObject(actor, actor.getCell(), traced.x, traced.y, traced.z);
|
||||
}
|
||||
|
||||
void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust)
|
||||
{
|
||||
rotateObjectImp(ptr, Ogre::Vector3(Ogre::Degree(x).valueRadians(),
|
||||
|
@ -1764,6 +1775,9 @@ namespace MWWorld
|
|||
bool
|
||||
World::isFlying(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
|
||||
bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0);
|
||||
|
||||
if(!ptr.getClass().isActor())
|
||||
return false;
|
||||
|
||||
|
@ -1771,9 +1785,8 @@ namespace MWWorld
|
|||
return false;
|
||||
|
||||
if (ptr.getClass().canFly(ptr))
|
||||
return true;
|
||||
return !isParalyzed;
|
||||
|
||||
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
|
||||
if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0
|
||||
&& isLevitationEnabled())
|
||||
return true;
|
||||
|
|
|
@ -263,6 +263,9 @@ namespace MWWorld
|
|||
virtual void adjustPosition (const Ptr& ptr);
|
||||
///< Adjust position after load to be on ground. Must be called after model load.
|
||||
|
||||
virtual void fixPosition (const Ptr& actor);
|
||||
///< Attempt to fix position so that the Ptr is no longer inside collision geometry.
|
||||
|
||||
virtual void enable (const Ptr& ptr);
|
||||
|
||||
virtual void disable (const Ptr& ptr);
|
||||
|
|
|
@ -72,7 +72,7 @@ add_component_dir (translation
|
|||
|
||||
add_definitions(-DTERRAIN_USE_SHADER=1)
|
||||
add_component_dir (terrain
|
||||
quadtreenode chunk world storage material buffercache defs
|
||||
quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader
|
||||
)
|
||||
|
||||
add_component_dir (loadinglistener
|
||||
|
|
|
@ -108,6 +108,7 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
|
|||
for(int i=0; i <n;i++)
|
||||
delete (mCompoundShape->getChildShape(i));
|
||||
delete mCompoundShape;
|
||||
mShape->mAnimatedShapes.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -14,10 +14,24 @@ namespace NifOgre
|
|||
void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent)
|
||||
{
|
||||
Ogre::Bone *bone;
|
||||
if(!skel->hasBone(node->name))
|
||||
bone = skel->createBone(node->name);
|
||||
if (node->name.empty())
|
||||
{
|
||||
// HACK: use " " instead of empty name, otherwise Ogre will replace it with an auto-generated
|
||||
// name in SkeletonInstance::cloneBoneAndChildren.
|
||||
static const char* emptyname = " ";
|
||||
if (!skel->hasBone(emptyname))
|
||||
bone = skel->createBone(emptyname);
|
||||
else
|
||||
bone = skel->createBone();
|
||||
}
|
||||
else
|
||||
bone = skel->createBone();
|
||||
{
|
||||
if(!skel->hasBone(node->name))
|
||||
bone = skel->createBone(node->name);
|
||||
else
|
||||
bone = skel->createBone();
|
||||
}
|
||||
|
||||
if(parent) parent->addChild(bone);
|
||||
mNifToOgreHandleMap[node->recIndex] = bone->getHandle();
|
||||
|
||||
|
|
0
components/terrain/backgroundloader.cpp
Normal file
0
components/terrain/backgroundloader.cpp
Normal file
|
@ -8,19 +8,17 @@
|
|||
|
||||
#include <extern/shiny/Main/Factory.hpp>
|
||||
|
||||
|
||||
#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data)
|
||||
Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
||||
const std::vector<float>& positions, const std::vector<float>& normals, const std::vector<Ogre::uint8>& colours)
|
||||
: mBounds(bounds)
|
||||
, mOwnMaterial(false)
|
||||
{
|
||||
mVertexData = OGRE_NEW Ogre::VertexData;
|
||||
mVertexData->vertexStart = 0;
|
||||
mVertexData->vertexCount = data.mPositions.size()/3;
|
||||
mVertexData->vertexCount = positions.size()/3;
|
||||
|
||||
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
|
||||
Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration;
|
||||
|
@ -48,9 +46,9 @@ namespace Terrain
|
|||
Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
|
||||
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
|
||||
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true);
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true);
|
||||
|
||||
mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
|
||||
mVertexData->vertexBufferBinding->setBinding(1, normalBuffer);
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
namespace Terrain
|
||||
{
|
||||
|
||||
class BufferCache;
|
||||
struct LoadResponseData;
|
||||
|
||||
/**
|
||||
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
|
||||
* @brief A movable object representing a chunk of terrain.
|
||||
*/
|
||||
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
|
||||
{
|
||||
public:
|
||||
Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data);
|
||||
Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds,
|
||||
const std::vector<float>& positions,
|
||||
const std::vector<float>& normals,
|
||||
const std::vector<Ogre::uint8>& colours);
|
||||
|
||||
virtual ~Chunk();
|
||||
|
||||
|
|
315
components/terrain/defaultworld.cpp
Normal file
315
components/terrain/defaultworld.cpp
Normal file
|
@ -0,0 +1,315 @@
|
|||
#include "defaultworld.hpp"
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreRenderTexture.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "quadtreenode.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isPowerOfTwo(int x)
|
||||
{
|
||||
return ( (x > 0) && ((x & (x - 1)) == 0) );
|
||||
}
|
||||
|
||||
int nextPowerOfTwo (int v)
|
||||
{
|
||||
if (isPowerOfTwo(v)) return v;
|
||||
int depth=0;
|
||||
while(v)
|
||||
{
|
||||
v >>= 1;
|
||||
depth++;
|
||||
}
|
||||
return 1 << depth;
|
||||
}
|
||||
|
||||
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
|
||||
{
|
||||
if (center == node->getCenter())
|
||||
return node;
|
||||
|
||||
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NE));
|
||||
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SE));
|
||||
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NW));
|
||||
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SW));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
const Ogre::uint REQ_ID_CHUNK = 1;
|
||||
const Ogre::uint REQ_ID_LAYERS = 2;
|
||||
|
||||
DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
|
||||
: World(sceneMgr, storage, visibilityFlags, shaders, align)
|
||||
, mMinBatchSize(minBatchSize)
|
||||
, mMaxBatchSize(maxBatchSize)
|
||||
, mVisible(true)
|
||||
, mMaxX(0)
|
||||
, mMinX(0)
|
||||
, mMaxY(0)
|
||||
, mMinY(0)
|
||||
, mChunksLoading(0)
|
||||
, mWorkQueueChannel(0)
|
||||
, mLayerLoadPending(true)
|
||||
{
|
||||
#if TERRAIN_USE_SHADER == 0
|
||||
if (mShaders)
|
||||
std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
|
||||
mShaders = false;
|
||||
#endif
|
||||
|
||||
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
||||
|
||||
/// \todo make composite map size configurable
|
||||
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
|
||||
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
|
||||
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
|
||||
mCompositeMapRenderTarget->setAutoUpdated(false);
|
||||
mCompositeMapRenderTarget->addViewport(compositeMapCam);
|
||||
|
||||
storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
|
||||
|
||||
int origSizeX = mMaxX-mMinX;
|
||||
int origSizeY = mMaxY-mMinY;
|
||||
|
||||
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
|
||||
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
|
||||
|
||||
// Adjust the center according to the new size
|
||||
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
|
||||
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
|
||||
|
||||
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
|
||||
// While building the quadtree, remember leaf nodes since we need to load their layers
|
||||
LayersRequestData data;
|
||||
data.mPack = getShadersEnabled();
|
||||
|
||||
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL);
|
||||
buildQuadTree(mRootNode, data.mNodes);
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initAabb();
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initNeighbours();
|
||||
//loadingListener->indicateProgress();
|
||||
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
mWorkQueueChannel = wq->getChannel("LargeTerrain");
|
||||
wq->addRequestHandler(mWorkQueueChannel, this);
|
||||
wq->addResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
// Start loading layers in the background (for leaf nodes)
|
||||
wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
|
||||
}
|
||||
|
||||
DefaultWorld::~DefaultWorld()
|
||||
{
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
wq->removeRequestHandler(mWorkQueueChannel, this);
|
||||
wq->removeResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
delete mRootNode;
|
||||
}
|
||||
|
||||
void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
|
||||
{
|
||||
float halfSize = node->getSize()/2.f;
|
||||
|
||||
if (node->getSize() <= mMinBatchSize)
|
||||
{
|
||||
// We arrived at a leaf
|
||||
float minZ,maxZ;
|
||||
Ogre::Vector2 center = node->getCenter();
|
||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
||||
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
|
||||
{
|
||||
Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
|
||||
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
|
||||
convertBounds(bounds);
|
||||
node->setBoundingBox(bounds);
|
||||
leafs.push_back(node);
|
||||
}
|
||||
else
|
||||
node->markAsDummy(); // no data available for this node, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getCenter().x - halfSize > mMaxX
|
||||
|| node->getCenter().x + halfSize < mMinX
|
||||
|| node->getCenter().y - halfSize > mMaxY
|
||||
|| node->getCenter().y + halfSize < mMinY )
|
||||
// Out of bounds of the actual terrain - this will happen because
|
||||
// we rounded the size up to the next power of two
|
||||
{
|
||||
node->markAsDummy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a leaf, create its children
|
||||
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
|
||||
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
|
||||
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
|
||||
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
|
||||
buildQuadTree(node->getChild(SW), leafs);
|
||||
buildQuadTree(node->getChild(SE), leafs);
|
||||
buildQuadTree(node->getChild(NW), leafs);
|
||||
buildQuadTree(node->getChild(NE), leafs);
|
||||
|
||||
// if all children are dummy, we are also dummy
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
if (!node->getChild((ChildDirection)i)->isDummy())
|
||||
return;
|
||||
}
|
||||
node->markAsDummy();
|
||||
}
|
||||
|
||||
void DefaultWorld::update(const Ogre::Vector3& cameraPos)
|
||||
{
|
||||
if (!mVisible)
|
||||
return;
|
||||
mRootNode->update(cameraPos);
|
||||
mRootNode->updateIndexBuffers();
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
if (center.x > mMaxX
|
||||
|| center.x < mMinX
|
||||
|| center.y > mMaxY
|
||||
|| center.y < mMinY)
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
QuadTreeNode* node = findNode(center, mRootNode);
|
||||
return node->getWorldBoundingBox();
|
||||
}
|
||||
|
||||
void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target)
|
||||
{
|
||||
mCompositeMapRenderTarget->update();
|
||||
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
||||
}
|
||||
|
||||
void DefaultWorld::clearCompositeMapSceneManager()
|
||||
{
|
||||
mCompositeMapSceneMgr->destroyAllManualObjects();
|
||||
mCompositeMapSceneMgr->clearScene();
|
||||
}
|
||||
|
||||
void DefaultWorld::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
mRootNode->applyMaterials();
|
||||
}
|
||||
|
||||
void DefaultWorld::setVisible(bool visible)
|
||||
{
|
||||
if (visible && !mVisible)
|
||||
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
|
||||
else if (!visible && mVisible)
|
||||
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
|
||||
|
||||
mVisible = visible;
|
||||
}
|
||||
|
||||
bool DefaultWorld::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
void DefaultWorld::syncLoad()
|
||||
{
|
||||
while (mChunksLoading || mLayerLoadPending)
|
||||
{
|
||||
OGRE_THREAD_SLEEP(0);
|
||||
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
if (req->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
|
||||
|
||||
QuadTreeNode* node = data.mNode;
|
||||
|
||||
LoadResponseData* responseData = new LoadResponseData();
|
||||
|
||||
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
|
||||
responseData->mPositions, responseData->mNormals, responseData->mColours);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
|
||||
|
||||
LayersResponseData* responseData = new LayersResponseData();
|
||||
|
||||
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
assert(res->succeeded() && "Response failure not handled");
|
||||
|
||||
if (res->getRequest()->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
|
||||
|
||||
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
|
||||
|
||||
requestData.mNode->load(*data);
|
||||
|
||||
delete data;
|
||||
|
||||
--mChunksLoading;
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
|
||||
|
||||
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
|
||||
{
|
||||
it->mTarget->loadLayers(*it);
|
||||
}
|
||||
|
||||
delete data;
|
||||
|
||||
mRootNode->loadMaterials();
|
||||
|
||||
mLayerLoadPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DefaultWorld::queueLoad(QuadTreeNode *node)
|
||||
{
|
||||
LoadRequestData data;
|
||||
data.mNode = node;
|
||||
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
|
||||
++mChunksLoading;
|
||||
}
|
||||
}
|
156
components/terrain/defaultworld.hpp
Normal file
156
components/terrain/defaultworld.hpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
#ifndef COMPONENTS_TERRAIN_H
|
||||
#define COMPONENTS_TERRAIN_H
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreTexture.h>
|
||||
#include <OgreWorkQueue.h>
|
||||
|
||||
#include "world.hpp"
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class QuadTreeNode;
|
||||
class Storage;
|
||||
|
||||
/**
|
||||
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
|
||||
* Near cells are rendered with alpha splatting, distant cells are merged
|
||||
* together in batches and have their layers pre-rendered onto a composite map. \n
|
||||
* Cracks at LOD transitions are avoided using stitching.
|
||||
* @note Multiple cameras are not supported yet
|
||||
*/
|
||||
class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
/// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
|
||||
/// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
|
||||
DefaultWorld(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
|
||||
~DefaultWorld();
|
||||
|
||||
/// Update chunk LODs according to this camera position
|
||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
||||
virtual void update (const Ogre::Vector3& cameraPos);
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
|
||||
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible);
|
||||
virtual bool getVisible();
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows);
|
||||
|
||||
int getMaxBatchSize() { return mMaxBatchSize; }
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
void syncLoad();
|
||||
|
||||
private:
|
||||
// Called from a background worker thread
|
||||
Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
|
||||
// Called from the main thread
|
||||
void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
|
||||
Ogre::uint16 mWorkQueueChannel;
|
||||
|
||||
bool mVisible;
|
||||
|
||||
QuadTreeNode* mRootNode;
|
||||
Ogre::SceneNode* mRootSceneNode;
|
||||
|
||||
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
|
||||
int mChunksLoading;
|
||||
|
||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
||||
|
||||
/// Bounds in cell units
|
||||
float mMinX, mMaxX, mMinY, mMaxY;
|
||||
|
||||
/// Minimum size of a terrain batch along one side (in cell units)
|
||||
float mMinBatchSize;
|
||||
/// Maximum size of a terrain batch along one side (in cell units)
|
||||
float mMaxBatchSize;
|
||||
|
||||
void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
|
||||
|
||||
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
|
||||
bool mLayerLoadPending;
|
||||
|
||||
public:
|
||||
// ----INTERNAL----
|
||||
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
|
||||
|
||||
bool areLayersLoaded() { return !mLayerLoadPending; }
|
||||
|
||||
// Delete all quads
|
||||
void clearCompositeMapSceneManager();
|
||||
void renderCompositeMap (Ogre::TexturePtr target);
|
||||
|
||||
// Adds a WorkQueue request to load a chunk for this node in the background.
|
||||
void queueLoad (QuadTreeNode* node);
|
||||
|
||||
private:
|
||||
Ogre::RenderTarget* mCompositeMapRenderTarget;
|
||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
||||
};
|
||||
|
||||
struct LoadRequestData
|
||||
{
|
||||
QuadTreeNode* mNode;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LoadResponseData
|
||||
{
|
||||
std::vector<float> mPositions;
|
||||
std::vector<float> mNormals;
|
||||
std::vector<Ogre::uint8> mColours;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersRequestData
|
||||
{
|
||||
std::vector<QuadTreeNode*> mNodes;
|
||||
bool mPack;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersResponseData
|
||||
{
|
||||
std::vector<LayerCollection> mLayerCollections;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -36,8 +36,8 @@ std::string getBlendmapComponentForLayer (int layerIndex)
|
|||
namespace Terrain
|
||||
{
|
||||
|
||||
MaterialGenerator::MaterialGenerator(bool shaders)
|
||||
: mShaders(shaders)
|
||||
MaterialGenerator::MaterialGenerator()
|
||||
: mShaders(true)
|
||||
, mShadows(false)
|
||||
, mSplitShadows(false)
|
||||
, mNormalMapping(true)
|
||||
|
|
|
@ -11,11 +11,7 @@ namespace Terrain
|
|||
class MaterialGenerator
|
||||
{
|
||||
public:
|
||||
/// @param layerList layer textures
|
||||
/// @param blendmapList blend textures
|
||||
/// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one),
|
||||
/// so if this parameter is true, then the supplied blend maps are expected to be packed.
|
||||
MaterialGenerator (bool shaders);
|
||||
MaterialGenerator ();
|
||||
|
||||
void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; }
|
||||
bool hasLayers() { return mLayerList.size(); }
|
||||
|
@ -23,6 +19,7 @@ namespace Terrain
|
|||
const std::vector<Ogre::TexturePtr>& getBlendmapList() { return mBlendmapList; }
|
||||
void setCompositeMap (const std::string& name) { mCompositeMap = name; }
|
||||
|
||||
void enableShaders(bool shaders) { mShaders = shaders; }
|
||||
void enableShadows(bool shadows) { mShadows = shadows; }
|
||||
void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; }
|
||||
void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <OgreMaterialManager.h>
|
||||
#include <OgreTextureManager.h>
|
||||
|
||||
#include "world.hpp"
|
||||
#include "defaultworld.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "buffercache.hpp"
|
||||
|
@ -142,7 +142,7 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent)
|
||||
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent)
|
||||
: mMaterialGenerator(NULL)
|
||||
, mIsDummy(false)
|
||||
, mSize(size)
|
||||
|
@ -178,7 +178,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
|
|||
|
||||
mSceneNode->setPosition(sceneNodePos);
|
||||
|
||||
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
|
||||
mMaterialGenerator = new MaterialGenerator();
|
||||
mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled());
|
||||
}
|
||||
|
||||
void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er)
|
||||
|
@ -386,11 +387,9 @@ void QuadTreeNode::load(const LoadResponseData &data)
|
|||
{
|
||||
assert (!mChunk);
|
||||
|
||||
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data);
|
||||
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
|
||||
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours);
|
||||
mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags());
|
||||
mChunk->setCastShadows(true);
|
||||
if (!mTerrain->getDistantLandEnabled())
|
||||
mChunk->setRenderingDistance(8192);
|
||||
mSceneNode->attachObject(mChunk);
|
||||
|
||||
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
|
||||
|
@ -550,7 +549,8 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
|||
if (mIsDummy)
|
||||
{
|
||||
// TODO - store this default material somewhere instead of creating one for each empty cell
|
||||
MaterialGenerator matGen(mTerrain->getShadersEnabled());
|
||||
MaterialGenerator matGen;
|
||||
matGen.enableShaders(mTerrain->getShadersEnabled());
|
||||
std::vector<LayerInfo> layer;
|
||||
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
|
||||
matGen.setLayerList(layer);
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Ogre
|
|||
|
||||
namespace Terrain
|
||||
{
|
||||
class World;
|
||||
class DefaultWorld;
|
||||
class Chunk;
|
||||
class MaterialGenerator;
|
||||
struct LoadResponseData;
|
||||
|
@ -48,7 +48,7 @@ namespace Terrain
|
|||
/// @param size size (in *cell* units!)
|
||||
/// @param center center (in *cell* units!)
|
||||
/// @param parent parent node
|
||||
QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
|
||||
QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
|
||||
~QuadTreeNode();
|
||||
|
||||
/// Rebuild all materials
|
||||
|
@ -95,7 +95,7 @@ namespace Terrain
|
|||
|
||||
const Ogre::AxisAlignedBox& getWorldBoundingBox();
|
||||
|
||||
World* getTerrain() { return mTerrain; }
|
||||
DefaultWorld* getTerrain() { return mTerrain; }
|
||||
|
||||
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them.
|
||||
/// @param force Always choose to render this node, even if not the perfect LOD.
|
||||
|
@ -158,7 +158,7 @@ namespace Terrain
|
|||
|
||||
Chunk* mChunk;
|
||||
|
||||
World* mTerrain;
|
||||
DefaultWorld* mTerrain;
|
||||
|
||||
Ogre::TexturePtr mCompositeMap;
|
||||
|
||||
|
|
|
@ -1,356 +1,60 @@
|
|||
#include "world.hpp"
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreRenderTexture.h>
|
||||
#include <OgreSceneNode.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include "storage.hpp"
|
||||
#include "quadtreenode.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isPowerOfTwo(int x)
|
||||
{
|
||||
return ( (x > 0) && ((x & (x - 1)) == 0) );
|
||||
}
|
||||
|
||||
int nextPowerOfTwo (int v)
|
||||
{
|
||||
if (isPowerOfTwo(v)) return v;
|
||||
int depth=0;
|
||||
while(v)
|
||||
{
|
||||
v >>= 1;
|
||||
depth++;
|
||||
}
|
||||
return 1 << depth;
|
||||
}
|
||||
|
||||
Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node)
|
||||
{
|
||||
if (center == node->getCenter())
|
||||
return node;
|
||||
|
||||
if (center.x > node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NE));
|
||||
else if (center.x > node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SE));
|
||||
else if (center.x < node->getCenter().x && center.y > node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::NW));
|
||||
else //if (center.x < node->getCenter().x && center.y < node->getCenter().y)
|
||||
return findNode(center, node->getChild(Terrain::SW));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
const Ogre::uint REQ_ID_CHUNK = 1;
|
||||
const Ogre::uint REQ_ID_LAYERS = 2;
|
||||
World::World(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool shaders, Alignment align)
|
||||
: mStorage(storage)
|
||||
, mSceneMgr(sceneMgr)
|
||||
, mVisibilityFlags(visibilityFlags)
|
||||
, mShaders(shaders)
|
||||
, mAlign(align)
|
||||
, mCache(storage->getCellVertices())
|
||||
{
|
||||
}
|
||||
|
||||
World::World(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
|
||||
: mStorage(storage)
|
||||
, mMinBatchSize(minBatchSize)
|
||||
, mMaxBatchSize(maxBatchSize)
|
||||
, mSceneMgr(sceneMgr)
|
||||
, mVisibilityFlags(visibilityFlags)
|
||||
, mDistantLand(distantLand)
|
||||
, mShaders(shaders)
|
||||
, mVisible(true)
|
||||
, mAlign(align)
|
||||
, mMaxX(0)
|
||||
, mMinX(0)
|
||||
, mMaxY(0)
|
||||
, mMinY(0)
|
||||
, mChunksLoading(0)
|
||||
, mWorkQueueChannel(0)
|
||||
, mCache(storage->getCellVertices())
|
||||
, mLayerLoadPending(true)
|
||||
World::~World()
|
||||
{
|
||||
delete mStorage;
|
||||
}
|
||||
|
||||
float World::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
{
|
||||
return mStorage->getHeightAt(worldPos);
|
||||
}
|
||||
|
||||
void World::convertPosition(float &x, float &y, float &z)
|
||||
{
|
||||
Terrain::convertPosition(mAlign, x, y, z);
|
||||
}
|
||||
|
||||
void World::convertPosition(Ogre::Vector3 &pos)
|
||||
{
|
||||
convertPosition(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void World::convertBounds(Ogre::AxisAlignedBox& bounds)
|
||||
{
|
||||
switch (mAlign)
|
||||
{
|
||||
#if TERRAIN_USE_SHADER == 0
|
||||
if (mShaders)
|
||||
std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl;
|
||||
mShaders = false;
|
||||
#endif
|
||||
|
||||
mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
|
||||
|
||||
/// \todo make composite map size configurable
|
||||
Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a");
|
||||
mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual(
|
||||
"terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET);
|
||||
mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget();
|
||||
mCompositeMapRenderTarget->setAutoUpdated(false);
|
||||
mCompositeMapRenderTarget->addViewport(compositeMapCam);
|
||||
|
||||
storage->getBounds(mMinX, mMaxX, mMinY, mMaxY);
|
||||
|
||||
int origSizeX = mMaxX-mMinX;
|
||||
int origSizeY = mMaxY-mMinY;
|
||||
|
||||
// Dividing a quad tree only works well for powers of two, so round up to the nearest one
|
||||
int size = nextPowerOfTwo(std::max(origSizeX, origSizeY));
|
||||
|
||||
// Adjust the center according to the new size
|
||||
float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f;
|
||||
float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f;
|
||||
|
||||
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
||||
|
||||
// While building the quadtree, remember leaf nodes since we need to load their layers
|
||||
LayersRequestData data;
|
||||
data.mPack = getShadersEnabled();
|
||||
|
||||
mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL);
|
||||
buildQuadTree(mRootNode, data.mNodes);
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initAabb();
|
||||
//loadingListener->indicateProgress();
|
||||
mRootNode->initNeighbours();
|
||||
//loadingListener->indicateProgress();
|
||||
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
mWorkQueueChannel = wq->getChannel("LargeTerrain");
|
||||
wq->addRequestHandler(mWorkQueueChannel, this);
|
||||
wq->addResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
// Start loading layers in the background (for leaf nodes)
|
||||
wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data));
|
||||
}
|
||||
|
||||
World::~World()
|
||||
{
|
||||
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
|
||||
wq->removeRequestHandler(mWorkQueueChannel, this);
|
||||
wq->removeResponseHandler(mWorkQueueChannel, this);
|
||||
|
||||
delete mRootNode;
|
||||
delete mStorage;
|
||||
}
|
||||
|
||||
void World::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
|
||||
{
|
||||
float halfSize = node->getSize()/2.f;
|
||||
|
||||
if (node->getSize() <= mMinBatchSize)
|
||||
{
|
||||
// We arrived at a leaf
|
||||
float minZ,maxZ;
|
||||
Ogre::Vector2 center = node->getCenter();
|
||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
||||
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
|
||||
{
|
||||
Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
|
||||
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
|
||||
convertBounds(bounds);
|
||||
node->setBoundingBox(bounds);
|
||||
leafs.push_back(node);
|
||||
}
|
||||
else
|
||||
node->markAsDummy(); // no data available for this node, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->getCenter().x - halfSize > mMaxX
|
||||
|| node->getCenter().x + halfSize < mMinX
|
||||
|| node->getCenter().y - halfSize > mMaxY
|
||||
|| node->getCenter().y + halfSize < mMinY )
|
||||
// Out of bounds of the actual terrain - this will happen because
|
||||
// we rounded the size up to the next power of two
|
||||
{
|
||||
node->markAsDummy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a leaf, create its children
|
||||
node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f);
|
||||
node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f));
|
||||
node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
|
||||
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
|
||||
buildQuadTree(node->getChild(SW), leafs);
|
||||
buildQuadTree(node->getChild(SE), leafs);
|
||||
buildQuadTree(node->getChild(NW), leafs);
|
||||
buildQuadTree(node->getChild(NE), leafs);
|
||||
|
||||
// if all children are dummy, we are also dummy
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
if (!node->getChild((ChildDirection)i)->isDummy())
|
||||
return;
|
||||
}
|
||||
node->markAsDummy();
|
||||
}
|
||||
|
||||
void World::update(const Ogre::Vector3& cameraPos)
|
||||
{
|
||||
if (!mVisible)
|
||||
return;
|
||||
mRootNode->update(cameraPos);
|
||||
mRootNode->updateIndexBuffers();
|
||||
}
|
||||
|
||||
Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center)
|
||||
{
|
||||
if (center.x > mMaxX
|
||||
|| center.x < mMinX
|
||||
|| center.y > mMaxY
|
||||
|| center.y < mMinY)
|
||||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
QuadTreeNode* node = findNode(center, mRootNode);
|
||||
return node->getWorldBoundingBox();
|
||||
}
|
||||
|
||||
void World::renderCompositeMap(Ogre::TexturePtr target)
|
||||
{
|
||||
mCompositeMapRenderTarget->update();
|
||||
target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer());
|
||||
}
|
||||
|
||||
void World::clearCompositeMapSceneManager()
|
||||
{
|
||||
mCompositeMapSceneMgr->destroyAllManualObjects();
|
||||
mCompositeMapSceneMgr->clearScene();
|
||||
}
|
||||
|
||||
float World::getHeightAt(const Ogre::Vector3 &worldPos)
|
||||
{
|
||||
return mStorage->getHeightAt(worldPos);
|
||||
}
|
||||
|
||||
void World::applyMaterials(bool shadows, bool splitShadows)
|
||||
{
|
||||
mShadows = shadows;
|
||||
mSplitShadows = splitShadows;
|
||||
mRootNode->applyMaterials();
|
||||
}
|
||||
|
||||
void World::setVisible(bool visible)
|
||||
{
|
||||
if (visible && !mVisible)
|
||||
mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode);
|
||||
else if (!visible && mVisible)
|
||||
mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode);
|
||||
|
||||
mVisible = visible;
|
||||
}
|
||||
|
||||
bool World::getVisible()
|
||||
{
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
void World::convertPosition(float &x, float &y, float &z)
|
||||
{
|
||||
Terrain::convertPosition(mAlign, x, y, z);
|
||||
}
|
||||
|
||||
void World::convertPosition(Ogre::Vector3 &pos)
|
||||
{
|
||||
convertPosition(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void World::convertBounds(Ogre::AxisAlignedBox& bounds)
|
||||
{
|
||||
switch (mAlign)
|
||||
{
|
||||
case Align_XY:
|
||||
return;
|
||||
case Align_XZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
// Because we changed sign of Z
|
||||
std::swap(bounds.getMinimum().z, bounds.getMaximum().z);
|
||||
return;
|
||||
case Align_YZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void World::syncLoad()
|
||||
{
|
||||
while (mChunksLoading || mLayerLoadPending)
|
||||
{
|
||||
OGRE_THREAD_SLEEP(0);
|
||||
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
if (req->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
|
||||
|
||||
QuadTreeNode* node = data.mNode;
|
||||
|
||||
LoadResponseData* responseData = new LoadResponseData();
|
||||
|
||||
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
|
||||
responseData->mPositions, responseData->mNormals, responseData->mColours);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
const LayersRequestData data = Ogre::any_cast<LayersRequestData>(req->getData());
|
||||
|
||||
LayersResponseData* responseData = new LayersResponseData();
|
||||
|
||||
getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack);
|
||||
|
||||
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
|
||||
}
|
||||
}
|
||||
|
||||
void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
|
||||
{
|
||||
assert(res->succeeded() && "Response failure not handled");
|
||||
|
||||
if (res->getRequest()->getType() == REQ_ID_CHUNK)
|
||||
{
|
||||
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
|
||||
|
||||
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
|
||||
|
||||
requestData.mNode->load(*data);
|
||||
|
||||
delete data;
|
||||
|
||||
--mChunksLoading;
|
||||
}
|
||||
else // REQ_ID_LAYERS
|
||||
{
|
||||
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
|
||||
|
||||
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
|
||||
{
|
||||
it->mTarget->loadLayers(*it);
|
||||
}
|
||||
|
||||
delete data;
|
||||
|
||||
mRootNode->loadMaterials();
|
||||
|
||||
mLayerLoadPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
void World::queueLoad(QuadTreeNode *node)
|
||||
{
|
||||
LoadRequestData data;
|
||||
data.mNode = node;
|
||||
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
|
||||
++mChunksLoading;
|
||||
case Align_XY:
|
||||
return;
|
||||
case Align_XZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
// Because we changed sign of Z
|
||||
std::swap(bounds.getMinimum().z, bounds.getMaximum().z);
|
||||
return;
|
||||
case Align_YZ:
|
||||
convertPosition(bounds.getMinimum());
|
||||
convertPosition(bounds.getMaximum());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,50 +1,38 @@
|
|||
#ifndef COMPONENTS_TERRAIN_H
|
||||
#define COMPONENTS_TERRAIN_H
|
||||
#ifndef COMPONENTS_TERRAIN_WORLD_H
|
||||
#define COMPONENTS_TERRAIN_WORLD_H
|
||||
|
||||
#include <OgreAxisAlignedBox.h>
|
||||
#include <OgreTexture.h>
|
||||
#include <OgreWorkQueue.h>
|
||||
#include <OgreVector3.h>
|
||||
|
||||
#include "defs.hpp"
|
||||
#include "buffercache.hpp"
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class Camera;
|
||||
class SceneManager;
|
||||
}
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
class QuadTreeNode;
|
||||
class Storage;
|
||||
|
||||
/**
|
||||
* @brief A quadtree-based terrain implementation suitable for large data sets. \n
|
||||
* Near cells are rendered with alpha splatting, distant cells are merged
|
||||
* together in batches and have their layers pre-rendered onto a composite map. \n
|
||||
* Cracks at LOD transitions are avoided using stitching.
|
||||
* @note Multiple cameras are not supported yet
|
||||
* @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed
|
||||
* is up to the implementation.
|
||||
*/
|
||||
class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
|
||||
class World
|
||||
{
|
||||
public:
|
||||
/// @note takes ownership of \a storage
|
||||
/// @param sceneMgr scene manager to use
|
||||
/// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..)
|
||||
/// @param visbilityFlags visibility flags for the created meshes
|
||||
/// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera.
|
||||
/// This is a temporary option until it can be streamlined.
|
||||
/// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually
|
||||
/// faster so this is just here for compatibility.
|
||||
/// @param align The align of the terrain, see Alignment enum
|
||||
/// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree.
|
||||
/// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree.
|
||||
World(Ogre::SceneManager* sceneMgr,
|
||||
Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize);
|
||||
~World();
|
||||
Storage* storage, int visiblityFlags, bool shaders, Alignment align);
|
||||
virtual ~World();
|
||||
|
||||
bool getDistantLandEnabled() { return mDistantLand; }
|
||||
bool getShadersEnabled() { return mShaders; }
|
||||
bool getShadowsEnabled() { return mShadows; }
|
||||
bool getSplitShadowsEnabled() { return mSplitShadows; }
|
||||
|
@ -54,138 +42,60 @@ namespace Terrain
|
|||
/// Update chunk LODs according to this camera position
|
||||
/// @note Calling this method might lead to composite textures being rendered, so it is best
|
||||
/// not to call it when render commands are still queued, since that would cause a flush.
|
||||
void update (const Ogre::Vector3& cameraPos);
|
||||
virtual void update (const Ogre::Vector3& cameraPos) = 0;
|
||||
|
||||
// This is only a hint and may be ignored by the implementation.
|
||||
virtual void loadCell(int x, int y) {}
|
||||
virtual void unloadCell(int x, int y) {}
|
||||
|
||||
/// Get the world bounding box of a chunk of terrain centered at \a center
|
||||
Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center);
|
||||
virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0;
|
||||
|
||||
Ogre::SceneManager* getSceneManager() { return mSceneMgr; }
|
||||
|
||||
Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; }
|
||||
|
||||
Storage* getStorage() { return mStorage; }
|
||||
|
||||
/// Show or hide the whole terrain
|
||||
/// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
void setVisible(bool visible);
|
||||
bool getVisible();
|
||||
/// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden
|
||||
virtual void setVisible(bool visible) = 0;
|
||||
virtual bool getVisible() = 0;
|
||||
|
||||
/// Recreate materials used by terrain chunks. This should be called whenever settings of
|
||||
/// the material factory are changed. (Relying on the factory to update those materials is not
|
||||
/// enough, since turning a feature on/off can change the number of texture units available for layer/blend
|
||||
/// textures, and to properly respond to this we may need to change the structure of the material, such as
|
||||
/// adding or removing passes. This can only be achieved by a full rebuild.)
|
||||
void applyMaterials(bool shadows, bool splitShadows);
|
||||
virtual void applyMaterials(bool shadows, bool splitShadows) = 0;
|
||||
|
||||
int getVisiblityFlags() { return mVisibilityFlags; }
|
||||
|
||||
int getMaxBatchSize() { return mMaxBatchSize; }
|
||||
|
||||
void enableSplattingShader(bool enabled);
|
||||
int getVisibilityFlags() { return mVisibilityFlags; }
|
||||
|
||||
Alignment getAlign() { return mAlign; }
|
||||
|
||||
/// Wait until all background loading is complete.
|
||||
void syncLoad();
|
||||
virtual void syncLoad() {}
|
||||
|
||||
private:
|
||||
// Called from a background worker thread
|
||||
Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
|
||||
// Called from the main thread
|
||||
void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
|
||||
Ogre::uint16 mWorkQueueChannel;
|
||||
|
||||
bool mDistantLand;
|
||||
protected:
|
||||
bool mShaders;
|
||||
bool mShadows;
|
||||
bool mSplitShadows;
|
||||
bool mVisible;
|
||||
Alignment mAlign;
|
||||
|
||||
QuadTreeNode* mRootNode;
|
||||
Ogre::SceneNode* mRootSceneNode;
|
||||
Storage* mStorage;
|
||||
|
||||
int mVisibilityFlags;
|
||||
|
||||
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
|
||||
int mChunksLoading;
|
||||
|
||||
Ogre::SceneManager* mSceneMgr;
|
||||
Ogre::SceneManager* mCompositeMapSceneMgr;
|
||||
|
||||
/// Bounds in cell units
|
||||
float mMinX, mMaxX, mMinY, mMaxY;
|
||||
|
||||
/// Minimum size of a terrain batch along one side (in cell units)
|
||||
float mMinBatchSize;
|
||||
/// Maximum size of a terrain batch along one side (in cell units)
|
||||
float mMaxBatchSize;
|
||||
|
||||
void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
|
||||
|
||||
BufferCache mCache;
|
||||
|
||||
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
|
||||
bool mLayerLoadPending;
|
||||
|
||||
public:
|
||||
// ----INTERNAL----
|
||||
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
|
||||
BufferCache& getBufferCache() { return mCache; }
|
||||
|
||||
bool areLayersLoaded() { return !mLayerLoadPending; }
|
||||
|
||||
// Delete all quads
|
||||
void clearCompositeMapSceneManager();
|
||||
void renderCompositeMap (Ogre::TexturePtr target);
|
||||
|
||||
// Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign
|
||||
void convertPosition (float& x, float& y, float& z);
|
||||
void convertPosition (Ogre::Vector3& pos);
|
||||
void convertBounds (Ogre::AxisAlignedBox& bounds);
|
||||
|
||||
// Adds a WorkQueue request to load a chunk for this node in the background.
|
||||
void queueLoad (QuadTreeNode* node);
|
||||
|
||||
private:
|
||||
Ogre::RenderTarget* mCompositeMapRenderTarget;
|
||||
Ogre::TexturePtr mCompositeMapRenderTexture;
|
||||
};
|
||||
|
||||
struct LoadRequestData
|
||||
{
|
||||
QuadTreeNode* mNode;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LoadResponseData
|
||||
{
|
||||
std::vector<float> mPositions;
|
||||
std::vector<float> mNormals;
|
||||
std::vector<Ogre::uint8> mColours;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersRequestData
|
||||
{
|
||||
std::vector<QuadTreeNode*> mNodes;
|
||||
bool mPack;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
struct LayersResponseData
|
||||
{
|
||||
std::vector<LayerCollection> mLayerCollections;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
|
||||
{ return o; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -3,25 +3,27 @@
|
|||
#ifdef SH_VERTEX_SHADER
|
||||
|
||||
SH_BEGIN_PROGRAM
|
||||
shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
|
||||
shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
|
||||
shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix)
|
||||
shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix)
|
||||
shVertexInput(float2, uv0)
|
||||
shOutput(float2, UV)
|
||||
shOutput(float, alphaFade)
|
||||
|
||||
SH_START_PROGRAM
|
||||
{
|
||||
float4x4 viewFixed = view;
|
||||
float4x4 worldviewFixed = worldview;
|
||||
|
||||
#if !SH_GLSL
|
||||
viewFixed[0][3] = 0;
|
||||
viewFixed[1][3] = 0;
|
||||
viewFixed[2][3] = 0;
|
||||
worldviewFixed[0][3] = 0;
|
||||
worldviewFixed[1][3] = 0;
|
||||
worldviewFixed[2][3] = 0;
|
||||
#else
|
||||
viewFixed[3][0] = 0;
|
||||
viewFixed[3][1] = 0;
|
||||
viewFixed[3][2] = 0;
|
||||
worldviewFixed[3][0] = 0;
|
||||
worldviewFixed[3][1] = 0;
|
||||
worldviewFixed[3][2] = 0;
|
||||
#endif
|
||||
shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition));
|
||||
|
||||
shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition));
|
||||
UV = uv0;
|
||||
alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#ifdef SH_VERTEX_SHADER
|
||||
|
||||
SH_BEGIN_PROGRAM
|
||||
shUniform(float4x4, view) @shAutoConstant(view, view_matrix)
|
||||
shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
|
||||
shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix)
|
||||
shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix)
|
||||
|
||||
shVertexInput(float2, uv0)
|
||||
shOutput(float2, UV)
|
||||
|
@ -12,17 +12,18 @@ shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix)
|
|||
|
||||
SH_START_PROGRAM
|
||||
{
|
||||
float4x4 viewFixed = view;
|
||||
float4x4 worldviewFixed = worldview;
|
||||
#if !SH_GLSL
|
||||
viewFixed[0][3] = 0;
|
||||
viewFixed[1][3] = 0;
|
||||
viewFixed[2][3] = 0;
|
||||
worldviewFixed[0][3] = 0;
|
||||
worldviewFixed[1][3] = 0;
|
||||
worldviewFixed[2][3] = 0;
|
||||
#else
|
||||
viewFixed[3][0] = 0;
|
||||
viewFixed[3][1] = 0;
|
||||
viewFixed[3][2] = 0;
|
||||
worldviewFixed[3][0] = 0;
|
||||
worldviewFixed[3][1] = 0;
|
||||
worldviewFixed[3][2] = 0;
|
||||
#endif
|
||||
shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition));
|
||||
|
||||
shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition));
|
||||
UV = uv0;
|
||||
|
||||
fade = (shInputPosition.z > 50) ? 1 : 0;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<Property key="Colour" value="0000FF"/>
|
||||
<Property key="Static" value="1"/>
|
||||
<Property key="WordWrap" value="true"/>
|
||||
<Property key="TextShadow" value="true"/>
|
||||
<Child type="TextBox" skin="MW_DispEdit" offset="0 0 0 -4" align="Stretch" name="Client"/>
|
||||
</Skin>
|
||||
|
||||
|
|
Loading…
Reference in a new issue