Merge remote-tracking branch 'scrawl/master'

This commit is contained in:
Marc Zinnschlag 2014-06-29 10:34:48 +02:00
commit 604509ac7e
54 changed files with 1174 additions and 620 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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()

View file

@ -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)

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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;

View file

@ -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;

View file

@ -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()

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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++)

View file

@ -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() {}

View file

@ -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;
}

View file

@ -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());

View file

@ -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;

View file

@ -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;

View 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()
{
}
}

View 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

View file

@ -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) -

View file

@ -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());
}
};

View file

@ -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);
}
};

View file

@ -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())

View file

@ -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,

View file

@ -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,

View file

@ -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);

View file

@ -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;
}
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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();

View file

View 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);

View file

@ -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();

View 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;
}
}

View 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

View file

@ -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)

View file

@ -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; }

View file

@ -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 &center, QuadTreeNode* parent)
QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, 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 &center)
@ -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);

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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; }
};
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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>