Merge remote-tracking branch 'scrawl/master'

deque
Marc Zinnschlag 11 years ago
commit 93ed019ca3

@ -23,7 +23,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 terraingrid
terrainstorage renderconst effectmanager weaponanimation
)
add_openmw_dir (mwinput

@ -181,7 +181,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
, mActivationDistanceOverride(-1)
, mGrab(true)
, mScriptBlacklistUse (true)
, mExportFonts(false)
{
std::srand ( std::time(NULL) );
MWClass::registerClasses();
@ -372,7 +372,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
MWGui::WindowManager* window = new MWGui::WindowManager(
mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"),
mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding);
mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts);
mEnvironment.setWindowManager (window);
// Create sound system
@ -576,4 +576,9 @@ void OMW::Engine::setScriptBlacklist (const std::vector<std::string>& list)
void OMW::Engine::setScriptBlacklistUse (bool use)
{
mScriptBlacklistUse = use;
}
}
void OMW::Engine::enableFontExport(bool exportFonts)
{
mExportFonts = exportFonts;
}

@ -83,6 +83,8 @@ namespace OMW
// Grab mouse?
bool mGrab;
bool mExportFonts;
Compiler::Extensions mExtensions;
Compiler::Context *mScriptContext;
@ -187,6 +189,8 @@ namespace OMW
void setScriptBlacklistUse (bool use);
void enableFontExport(bool exportFonts);
private:
Files::ConfigurationManager& mCfgMgr;
};

@ -168,6 +168,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
("no-grab", "Don't grab mouse cursor")
("export-fonts", bpo::value<bool>()->implicit_value(true)
->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory")
("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override");
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
@ -268,6 +271,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
engine.setSoundUsage(!variables["no-sound"].as<bool>());
engine.setFallbackValues(variables["fallback"].as<FallbackMap>().mMap);
engine.setActivationDistanceOverride (variables["activate-dist"].as<int>());
engine.enableFontExport(variables["export-fonts"].as<bool>());
return true;
}

@ -368,18 +368,21 @@ namespace MWClass
if (damage > 0.f)
{
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
* getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
if (!attacker.isEmpty())
{
getCreatureStats(ptr).setKnockedDown(true);
// Check for knockdown
float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat();
float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified()
* getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
if (ishealth && agilityTerm <= damage && knockdownTerm <= roll)
{
getCreatureStats(ptr).setKnockedDown(true);
}
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
}
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
damage = std::max(1.f, damage);

@ -56,8 +56,11 @@ namespace MWClass
{
const std::string model = getModel(ptr);
MWWorld::LiveCellRef<ESM::Light> *ref =
ptr.get<ESM::Light>();
// Insert even if model is empty, so that the light is added
renderingInterface.getObjects().insertModel(ptr, model);
renderingInterface.getObjects().insertModel(ptr, model, false, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault));
}
void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const

@ -672,7 +672,7 @@ namespace MWClass
if (damage < 0.001f)
damage = 0;
if(damage > 0.0f)
if(damage > 0.0f && !attacker.isEmpty())
{
// 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying
// something, alert the character controller, scripts, etc.
@ -700,7 +700,7 @@ namespace MWClass
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
if(damage > 0 && ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage
if(damage > 0 && ishealth)
{
// Hit percentages:
// cuirass = 30%
@ -918,8 +918,6 @@ namespace MWClass
float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() *
gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat());
if(npcdata->mNpcStats.isWerewolf())
runSpeed *= gmst.fWereWolfRunMult->getFloat();
float moveSpeed;
if(normalizedEncumbrance >= 1.0f)
@ -951,6 +949,9 @@ namespace MWClass
if(getMovementSettings(ptr).mPosition[0] != 0 && getMovementSettings(ptr).mPosition[1] == 0)
moveSpeed *= 0.75f;
if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing)
moveSpeed *= gmst.fWereWolfRunMult->getFloat();
return moveSpeed;
}

@ -145,11 +145,8 @@ namespace MWClass
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->getFloat();
for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it)
{
it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue)
|| (i == 1 && alchemySkill >= fWortChanceValue*2)
|| (i == 2 && alchemySkill >= fWortChanceValue*3)
|| (i == 3 && alchemySkill >= fWortChanceValue*4));
it->mKnown = (i <= 1 && alchemySkill >= fWortChanceValue)
|| (i <= 3 && alchemySkill >= fWortChanceValue*2);
++i;
}

@ -338,7 +338,7 @@ namespace MWGui
MWBase::Environment::get().getMechanicsManager()->reportCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft,
item.getClass().getValue(item));
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
return;
}
}

@ -134,7 +134,7 @@ namespace MWGui
mEncoding = encoding;
}
void FontLoader::loadAllFonts()
void FontLoader::loadAllFonts(bool exportToFile)
{
Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups ();
for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it)
@ -142,7 +142,7 @@ namespace MWGui
Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "*.fnt");
for (Ogre::StringVector::iterator resource = resourcesInThisGroup->begin(); resource != resourcesInThisGroup->end(); ++resource)
{
loadFont(*resource);
loadFont(*resource, exportToFile);
}
}
}
@ -168,7 +168,7 @@ namespace MWGui
float ascent;
} GlyphInfo;
void FontLoader::loadFont(const std::string &fileName)
void FontLoader::loadFont(const std::string &fileName, bool exportToFile)
{
Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName);
@ -221,6 +221,9 @@ namespace MWGui
width, height, 0, Ogre::PF_BYTE_RGBA);
texture->loadImage(image);
if (exportToFile)
image.save(resourceName + ".png");
// Register the font with MyGUI
MyGUI::ResourceManualFont* font = static_cast<MyGUI::ResourceManualFont*>(
MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont"));
@ -240,10 +243,10 @@ namespace MWGui
for(int i = 0; i < 256; i++)
{
int x1 = data[i].top_left.x*width;
int y1 = data[i].top_left.y*height;
int w = data[i].top_right.x*width - x1;
int h = data[i].bottom_left.y*height - y1;
float x1 = data[i].top_left.x*width;
float y1 = data[i].top_left.y*height;
float w = data[i].top_right.x*width - x1;
float h = data[i].bottom_left.y*height - y1;
ToUTF8::Utf8Encoder encoder(mEncoding);
unsigned long unicodeVal = utf8ToUnicode(getUtf8(i, encoder, mEncoding));
@ -257,6 +260,7 @@ namespace MWGui
code->addAttribute("advance", data[i].width);
code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ MyGUI::utility::toString((fontSize-data[i].ascent)));
code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height));
// More hacks! The french game uses several win1252 characters that are not included
// in the cp437 encoding of the font. Fall back to similar available characters.
@ -302,6 +306,7 @@ namespace MWGui
code->addAttribute("advance", data[i].width);
code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ MyGUI::utility::toString((fontSize-data[i].ascent)));
code->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height));
}
}
@ -317,6 +322,7 @@ namespace MWGui
cursorCode->addAttribute("advance", data[i].width);
cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ MyGUI::utility::toString((fontSize-data[i].ascent)));
cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height));
}
// Question mark, use for NotDefined marker (used for glyphs not existing in the font)
@ -331,6 +337,7 @@ namespace MWGui
cursorCode->addAttribute("advance", data[i].width);
cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " "
+ MyGUI::utility::toString((fontSize-data[i].ascent)));
cursorCode->addAttribute("size", MyGUI::IntSize(data[i].width, data[i].height));
}
}
@ -348,7 +355,13 @@ namespace MWGui
cursorCode->addAttribute("coord", "0 0 0 0");
cursorCode->addAttribute("advance", "0");
cursorCode->addAttribute("bearing", "0 0");
cursorCode->addAttribute("size", "0 0");
}
if (exportToFile)
{
xmlDocument.createDeclaration();
xmlDocument.save(resourceName + ".xml");
}
font->deserialization(root, MyGUI::Version(3,2,0));

@ -12,12 +12,15 @@ namespace MWGui
{
public:
FontLoader (ToUTF8::FromType encoding);
void loadAllFonts ();
/// @param exportToFile export the converted fonts (Images and XML with glyph metrics) to files?
void loadAllFonts (bool exportToFile);
private:
ToUTF8::FromType mEncoding;
void loadFont (const std::string& fileName);
/// @param exportToFile export the converted font (Image and XML with glyph metrics) to files?
void loadFont (const std::string& fileName, bool exportToFile);
};
}

@ -37,6 +37,7 @@ namespace MWGui
, mLastYSize(0)
, mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr()))
, mPreviewDirty(true)
, mPreviewResize(true)
, mDragAndDrop(dragAndDrop)
, mSelectedItem(-1)
, mGuiMode(GM_Inventory)
@ -91,8 +92,18 @@ namespace MWGui
mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr());
mSortModel = new SortFilterItemModel(mTradeModel);
mItemView->setModel(mSortModel);
mPreview.reset(NULL);
mAvatarImage->setImageTexture("");
MyGUI::ITexture* tex = MyGUI::RenderManager::getInstance().getTexture("CharacterPreview");
if (tex)
MyGUI::RenderManager::getInstance().destroyTexture(tex);
mPreview.reset(new MWRender::InventoryPreview(mPtr));
mPreview->setup();
mPreviewDirty = true;
mPreviewResize = true;
}
void InventoryWindow::setGuiMode(GuiMode mode)
@ -125,7 +136,7 @@ namespace MWGui
Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height);
if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight())
mPreviewDirty = true;
mPreviewResize = true;
mMainWidget->setPosition(pos);
mMainWidget->setSize(size);
@ -333,7 +344,7 @@ namespace MWGui
{
mLastXSize = mMainWidget->getSize().width;
mLastYSize = mMainWidget->getSize().height;
mPreviewDirty = true;
mPreviewResize = true;
}
}
@ -366,6 +377,12 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setWeaponVisibility(!mPinned);
}
void InventoryWindow::onTitleDoubleClicked()
{
if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory);
}
void InventoryWindow::useItem(const MWWorld::Ptr &ptr)
{
const std::string& script = ptr.getClass().getScript(ptr);
@ -403,9 +420,13 @@ namespace MWGui
else
mSkippedToEquip = ptr;
mItemView->update();
if (isVisible())
{
mItemView->update();
notifyContentChanged();
notifyContentChanged();
}
// else: will be updated in open()
}
void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender)
@ -491,16 +512,23 @@ namespace MWGui
void InventoryWindow::doRenderUpdate ()
{
if (mPreviewDirty)
mPreview->onFrame();
if (mPreviewResize)
{
mPreviewDirty = false;
mPreviewResize = false;
MyGUI::IntSize size = mAvatarImage->getSize();
mPreview->update (size.width, size.height);
mPreview->resize(size.width, size.height);
mAvatarImage->setImageTexture("CharacterPreview");
mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height)));
mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height)));
}
if (mPreviewDirty)
{
mPreviewDirty = false;
mPreview->update ();
mAvatarImage->setImageTexture("CharacterPreview");
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
+ boost::lexical_cast<std::string>(static_cast<int>(mPtr.getClass().getArmorRating(mPtr))));

@ -53,6 +53,7 @@ namespace MWGui
DragAndDrop* mDragAndDrop;
bool mPreviewDirty;
bool mPreviewResize;
int mSelectedItem;
MWWorld::Ptr mPtr;
@ -98,6 +99,7 @@ namespace MWGui
void onFilterChanged(MyGUI::Widget* _sender);
void onAvatarClicked(MyGUI::Widget* _sender);
void onPinToggled();
void onTitleDoubleClicked();
void updateEncumbranceBar();
void notifyContentChanged();

@ -123,6 +123,9 @@ namespace
getPage (LeftBookPage)->adviseLinkClicked (callback);
getPage (RightBookPage)->adviseLinkClicked (callback);
getPage (LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel);
getPage (RightBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel);
}
{
@ -488,6 +491,14 @@ namespace
MWBase::Environment::get().getWindowManager ()->popGuiMode ();
}
void notifyMouseWheel(MyGUI::Widget* sender, int rel)
{
if (rel < 0)
notifyNextPage(sender);
else
notifyPrevPage(sender);
}
void notifyNextPage(MyGUI::Widget* _sender)
{
if (!mStates.empty ())
@ -509,7 +520,7 @@ namespace
{
unsigned int & page = mStates.top ().mPage;
if(page > 0)
if(page >= 2)
{
page -= 2;
updateShowingPages ();

@ -536,6 +536,12 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setMinimapVisibility(!mPinned);
}
void MapWindow::onTitleDoubleClicked()
{
if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map);
}
void MapWindow::open()
{
// force markers to foreground

@ -150,6 +150,7 @@ namespace MWGui
protected:
virtual void onPinToggled();
virtual void onTitleDoubleClicked();
virtual void notifyPlayerUpdate();
virtual void notifyMapChanged();

@ -1,3 +1,6 @@
#ifndef OPENMW_MWGUI_SCREENFADER_H
#define OPENMW_MWGUI_SCREENFADER_H
#include "windowbase.hpp"
namespace MWGui
@ -37,3 +40,5 @@ namespace MWGui
};
}
#endif

@ -45,6 +45,7 @@ namespace MWGui
, NoDrop(drag, mMainWidget)
, mHeight(0)
, mWidth(0)
, mWindowSize(mMainWidget->getSize())
{
mSpellIcons = new SpellIcons();
@ -66,6 +67,12 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->setSpellVisibility(!mPinned);
}
void SpellWindow::onTitleDoubleClicked()
{
if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic);
}
void SpellWindow::open()
{
updateSpells();
@ -302,7 +309,11 @@ namespace MWGui
void SpellWindow::onWindowResize(MyGUI::Window* _sender)
{
updateSpells();
if (mMainWidget->getSize() != mWindowSize)
{
mWindowSize = mMainWidget->getSize();
updateSpells();
}
}
void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender)

@ -29,6 +29,8 @@ namespace MWGui
int mHeight;
int mWidth;
MyGUI::IntSize mWindowSize;
std::string mSpellToDelete;
void addGroup(const std::string& label, const std::string& label2);
@ -42,6 +44,7 @@ namespace MWGui
void onDeleteSpellAccept();
virtual void onPinToggled();
virtual void onTitleDoubleClicked();
virtual void open();
SpellIcons* mSpellIcons;

@ -591,4 +591,10 @@ namespace MWGui
{
MWBase::Environment::get().getWindowManager()->setHMSVisibility(!mPinned);
}
void StatsWindow::onTitleDoubleClicked()
{
if (!mPinned)
MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats);
}
}

@ -74,6 +74,7 @@ namespace MWGui
protected:
virtual void onPinToggled();
virtual void onTitleDoubleClicked();
};
}
#endif

@ -308,7 +308,7 @@ namespace MWGui
it->mBase.getClass().getValue(it->mBase)
* it->mCount);
onCancelButtonClicked(mCancelButton);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
return;
}
}

@ -6,6 +6,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
@ -162,7 +163,7 @@ namespace MWGui
// go back to game mode
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue);
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
// advance time
MWBase::Environment::get().getWorld ()->advanceTime (2);

@ -9,6 +9,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
@ -171,12 +172,13 @@ namespace MWGui
MWBase::Environment::get().getWorld()->advanceTime(hours);
}
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel);
MWBase::Environment::get().getDialogueManager()->goodbyeSelected();
// Teleports any followers, too.
MWWorld::ActionTeleport action(interior ? cellname : "", pos);
action.execute(player);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue);
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0);
MWBase::Environment::get().getWindowManager()->fadeScreenIn(1);
}

@ -177,8 +177,7 @@ namespace MWGui
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified())
&& (stats.getHealth().getCurrent() >= stats.getHealth().getModified())
bool full = (stats.getHealth().getCurrent() >= stats.getHealth().getModified())
&& (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified());
MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player);
bool werewolf = npcstats.isWerewolf();

@ -73,7 +73,7 @@ namespace MWGui
WindowManager::WindowManager(
const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre,
const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts,
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding)
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts)
: mConsoleOnlyScripts(consoleOnlyScripts)
, mGuiManager(NULL)
, mRendering(ogre)
@ -148,7 +148,7 @@ namespace MWGui
// Load fonts
FontLoader fontLoader (encoding);
fontLoader.loadAllFonts();
fontLoader.loadAllFonts(exportFonts);
//Register own widgets with MyGUI
MyGUI::FactoryManager::getInstance().registerFactory<MWGui::Widgets::MWSkill>("Widget");

@ -97,7 +97,7 @@ namespace MWGui
WindowManager(const Compiler::Extensions& extensions, int fpsLevel,
OEngine::Render::OgreRenderer *mOgre, const std::string& logpath,
const std::string& cacheDir, bool consoleOnlyScripts,
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding);
Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts);
virtual ~WindowManager();
void initUI();

@ -11,6 +11,17 @@ namespace MWGui
mPinButton = window->getSkinWidget ("Button");
mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked);
MyGUI::Button* button = NULL;
MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action");
for (MyGUI::VectorWidgetPtr::iterator it = widgets.begin(); it != widgets.end(); ++it)
{
if ((*it)->isUserString("HideWindowOnDoubleClick"))
button = (*it)->castType<MyGUI::Button>();
}
if (button)
button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick);
}
void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender)
@ -25,6 +36,11 @@ namespace MWGui
onPinToggled();
}
void WindowPinnableBase::onDoubleClick(MyGUI::Widget *_sender)
{
onTitleDoubleClicked();
}
void WindowPinnableBase::setPinned(bool pinned)
{
if (pinned != mPinned)

@ -17,9 +17,11 @@ namespace MWGui
private:
void onPinButtonClicked(MyGUI::Widget* _sender);
void onDoubleClick(MyGUI::Widget* _sender);
protected:
virtual void onPinToggled() = 0;
virtual void onTitleDoubleClicked() = 0;
MyGUI::Widget* mPinButton;
bool mPinned;

@ -89,6 +89,58 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
return false;
}
class CheckActorCommanded : public MWMechanics::EffectSourceVisitor
{
MWWorld::Ptr mActor;
public:
bool mCommanded;
CheckActorCommanded(MWWorld::Ptr actor)
: mActor(actor)
, mCommanded(false){}
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, int casterActorId,
float magnitude, float remainingTime = -1)
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc())
|| (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name()))
&& casterActorId == player.getClass().getCreatureStats(player).getActorId()
&& magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel())
mCommanded = true;
}
};
void adjustCommandedActor (const MWWorld::Ptr& actor)
{
CheckActorCommanded check(actor);
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
stats.getActiveSpells().visitEffectSources(check);
bool hasCommandPackage = false;
std::list<MWMechanics::AiPackage*>::const_iterator it;
for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow &&
dynamic_cast<MWMechanics::AiFollow*>(*it)->isCommanded())
{
hasCommandPackage = true;
break;
}
}
if (check.mCommanded && !hasCommandPackage)
{
MWMechanics::AiFollow package("player", true);
stats.getAiSequence().stack(package, actor);
}
else if (!check.mCommanded && hasCommandPackage)
{
stats.getAiSequence().erase(it);
}
}
void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
{
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
@ -107,6 +159,30 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
}
}
void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId)
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId);
if (!ptr.isEmpty())
{
// TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
// plays though, which is a rather lame exploit in vanilla.
MWBase::Environment::get().getWorld()->deleteObject(ptr);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
}
else
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std::vector<int>& graveyard = casterStats.getSummonedCreatureGraveyard();
graveyard.push_back(creatureActorId);
}
}
}
namespace MWMechanics
@ -268,7 +344,8 @@ namespace MWMechanics
void Actors::adjustMagicEffects (const MWWorld::Ptr& creature)
{
CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature);
if (creatureStats.isDead())
return;
MagicEffects now = creatureStats.getSpells().getMagicEffects();
if (creature.getTypeName()==typeid (ESM::NPC).name())
@ -290,17 +367,17 @@ namespace MWMechanics
{
CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr);
int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase();
int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase();
int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getBase();
int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getBase();
int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getBase();
int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getModified();
int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified();
int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified();
int agility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified();
int endurance = creatureStats.getAttribute(ESM::Attribute::Endurance).getModified();
double magickaFactor =
creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 0.5;
creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).mMagnitude * 0.1 + 1;
DynamicStat<float> magicka = creatureStats.getMagicka();
float diff = (static_cast<int>(intelligence + magickaFactor*intelligence)) - magicka.getBase();
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
magicka.modify(diff);
creatureStats.setMagicka(magicka);
@ -612,9 +689,9 @@ namespace MWMechanics
summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID";
}
std::map<int, int>& creatureMap = creatureStats.getSummonedCreatureMap();
for (std::map<int, std::string>::iterator it = summonMap.begin(); it != summonMap.end(); ++it)
{
std::map<int, int>& creatureMap = creatureStats.getSummonedCreatureMap();
bool found = creatureMap.find(it->first) != creatureMap.end();
int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude;
if (found != (magnitude > 0))
@ -665,34 +742,31 @@ namespace MWMechanics
}
else
{
// Summon lifetime has expired. Try to delete the creature.
int actorId = creatureMap[it->first];
creatureMap.erase(it->first);
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId);
if (!ptr.isEmpty())
{
// TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
// plays though, which is a rather lame exploit in vanilla.
MWBase::Environment::get().getWorld()->deleteObject(ptr);
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_End");
if (fx)
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel,
"", Ogre::Vector3(ptr.getRefData().getPosition().pos));
}
else
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
graveyard.push_back(actorId);
}
// Effect has ended
std::map<int, int>::iterator foundCreature = creatureMap.find(it->first);
cleanupSummonedCreature(creatureStats, foundCreature->second);
creatureMap.erase(foundCreature);
}
}
}
for (std::map<int, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead())
{
// Purge the magic effect so a new creature can be summoned if desired
creatureStats.getActiveSpells().purgeEffect(it->first);
if (ptr.getClass().hasInventoryStore(ptr))
ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first);
cleanupSummonedCreature(creatureStats, it->second);
creatureMap.erase(it++);
}
else
++it;
}
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
for (std::vector<int>::iterator it = graveyard.begin(); it != graveyard.end(); )
{
@ -1005,7 +1079,10 @@ namespace MWMechanics
if (MWBase::Environment::get().getMechanicsManager()->isAIActive())
{
if (timerUpdateAITargets == 0)
{
{
if (iter->first != player)
adjustCommandedActor(iter->first);
for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{
if (it->first == iter->first || iter->first == player) // player is not AI-controlled

@ -51,14 +51,17 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door
float x = pos.pos[0] - tPos.pos[0];
float y = pos.pos[1] - tPos.pos[1];
float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC
// For example, if the NPC is directly facing the door this will be pi/2
// Make actor move away from the door
actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday
actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
//Make all nearby actors also avoid the door
// Turn away from the door and move when turn completed
if (zTurn(actor, Ogre::Radian(std::atan2(x,y) + mAdjAngle), Ogre::Degree(5)))
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
else
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
actor.getClass().getMovementSettings(actor).mPosition[0] = 0;
// Make all nearby actors also avoid the door
std::vector<MWWorld::Ptr> actors;
MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors);
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); it++) {

@ -75,6 +75,7 @@ namespace MWMechanics
}
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false);
const float* const leaderPos = actor.getRefData().getPosition().pos;

@ -16,16 +16,16 @@
#include "steering.hpp"
MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("")
: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("")
{
}
MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId)
: mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId)
{
}
MWMechanics::AiFollow::AiFollow(const std::string &actorId)
: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("")
MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded)
: mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("")
{
}
@ -99,6 +99,11 @@ int MWMechanics::AiFollow::getTypeId() const
return TypeIdFollow;
}
bool MWMechanics::AiFollow::isCommanded() const
{
return mCommanded;
}
void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const
{
std::auto_ptr<ESM::AiSequence::AiFollow> follow(new ESM::AiSequence::AiFollow());
@ -109,6 +114,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co
follow->mRemainingDuration = mRemainingDuration;
follow->mCellId = mCellId;
follow->mAlwaysFollow = mAlwaysFollow;
follow->mCommanded = mCommanded;
ESM::AiSequence::AiPackageContainer package;
package.mType = ESM::AiSequence::Ai_Follow;
@ -120,6 +126,7 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
: mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration)
, mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ)
, mActorId(follow->mTargetId), mCellId(follow->mCellId)
, mCommanded(follow->mCommanded)
{
}

@ -27,7 +27,7 @@ namespace MWMechanics
/// Follow Actor for duration or until you arrive at a position in a cell
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
/// Follow Actor indefinitively
AiFollow(const std::string &ActorId);
AiFollow(const std::string &ActorId, bool commanded=false);
AiFollow(const ESM::AiSequence::AiFollow* follow);
@ -44,10 +44,13 @@ namespace MWMechanics
virtual void writeState (ESM::AiSequence::AiSequence& sequence) const;
bool isCommanded() const;
private:
/// This will make the actor always follow.
/** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/
bool mAlwaysFollow;
bool mCommanded;
float mRemainingDuration; // Seconds
float mX;
float mY;

@ -82,6 +82,20 @@ std::list<AiPackage*>::const_iterator AiSequence::end() const
return mPackages.end();
}
void AiSequence::erase(std::list<AiPackage*>::const_iterator package)
{
// Not sure if manually terminated packages should trigger mDone, probably not?
for(std::list<AiPackage*>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{
if (package == it)
{
mPackages.erase(it);
return;
}
}
throw std::runtime_error("can't find package to erase");
}
bool AiSequence::isInCombat() const
{
for(std::list<AiPackage*>::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it)
@ -245,6 +259,9 @@ void AiSequence::clear()
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor)
{
if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
throw std::runtime_error("Can't add AI packages to player");
if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue)
{
// Notify AiWander of our current position so we can return to it after combat finished

@ -54,6 +54,8 @@ namespace MWMechanics
std::list<AiPackage*>::const_iterator begin() const;
std::list<AiPackage*>::const_iterator end() const;
void erase (std::list<AiPackage*>::const_iterator package);
/// Returns currently executing AiPackage type
/** \see enum AiPackage::TypeId **/
int getTypeId() const;

@ -395,22 +395,35 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
CharacterState walkState = runStateToWalkState(mMovementState);
const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState));
anim = stateinfo->groupname;
}
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f)
{
speedmult = mMovementSpeed / vel;
if (mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f)
speedmult = mMovementSpeed / vel;
else
// Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward),
// then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist
// we will play without any scaling.
// Makes the speed attribute of most water creatures totally useless.
// And again, this can not be fixed without patching game data.
speedmult = 1.f;
}
else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed
else if (mMovementSpeed > 0.0f)
else
{
// The first person anims don't have any velocity to calculate a speed multiplier from.
// We use the third person velocities instead.
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
mMovementAnimationControlled = false;
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f)
{
speedmult = mMovementSpeed / vel;
}
else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed
else if (mMovementSpeed > 0.0f)
{
// The first person anims don't have any velocity to calculate a speed multiplier from.
// We use the third person velocities instead.
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
mMovementAnimationControlled = false;
}
}
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
}
@ -1511,6 +1524,8 @@ void CharacterController::update(float duration)
else if (mAnimation)
mAnimation->updateEffects(duration);
mSkipAnim = false;
mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead());
}

@ -34,7 +34,6 @@ MWMechanics::NpcStats::NpcStats()
, mProfit(0)
, mTimeToStartDrowning(20.0)
, mLastDrowningHit(0)
, mLevelHealthBonus(0)
{
mSkillIncreases.resize (ESM::Attribute::Length, 0);
}
@ -262,8 +261,7 @@ void MWMechanics::NpcStats::levelUp()
// "When you gain a level, in addition to increasing three primary attributes, your Health
// will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level,
// the Health increase is calculated from the increased Endurance"
mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat();
updateHealth();
setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->getFloat());
setLevel(getLevel()+1);
}
@ -273,7 +271,7 @@ void MWMechanics::NpcStats::updateHealth()
const int endurance = getAttribute(ESM::Attribute::Endurance).getBase();
const int strength = getAttribute(ESM::Attribute::Strength).getBase();
setHealth(static_cast<int> (0.5 * (strength + endurance)) + mLevelHealthBonus);
setHealth(static_cast<int> (0.5 * (strength + endurance)));
}
int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const
@ -497,7 +495,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
state.mTimeToStartDrowning = mTimeToStartDrowning;
state.mLastDrowningHit = mLastDrowningHit;
state.mLevelHealthBonus = mLevelHealthBonus;
}
void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
@ -549,5 +546,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
mTimeToStartDrowning = state.mTimeToStartDrowning;
mLastDrowningHit = state.mLastDrowningHit;
mLevelHealthBonus = state.mLevelHealthBonus;
}

@ -52,8 +52,6 @@ namespace MWMechanics
/// time since last hit from drowning
float mLastDrowningHit;
float mLevelHealthBonus;
public:
NpcStats();
@ -104,7 +102,7 @@ namespace MWMechanics
void updateHealth();
///< Calculate health based on endurance and strength.
/// Called at character creation and at level up.
/// Called at character creation.
void flagAsUsed (const std::string& id);

@ -252,12 +252,12 @@ namespace MWMechanics
}
break;
case ESM::MagicEffect::Soultrap:
if ((target.getClass().isActor() && target.getClass().isNpc())
|| (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
if (!target.getClass().isNpc() // no messagebox for NPCs
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
return false;
return true; // must still apply to get visual effect and have target regard it as attack
}
break;
case ESM::MagicEffect::AlmsiviIntervention:

@ -468,7 +468,13 @@ 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;
// Have to find keys in reverse (see reset method)
// Pick the last Loop Stop key and the last Loop Start key.
// This is required because of broken text keys in AshVampire.nif.
// It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback
// but the animation velocity calculation uses the second one.
// As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated,
// because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough.
NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin());
while(keyiter != keys.rend())
{
@ -477,8 +483,18 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node
starttime = keyiter->first;
break;
}
else if(keyiter->second == loopstop || keyiter->second == stop)
++keyiter;
}
keyiter = keys.rbegin();
while(keyiter != keys.rend())
{
if (keyiter->second == stop)
stoptime = keyiter->first;
else if (keyiter->second == loopstop)
{
stoptime = keyiter->first;
break;
}
++keyiter;
}
@ -1410,6 +1426,15 @@ void ObjectAnimation::addLight(const ESM::Light *light)
addExtraLight(mInsert->getCreator(), mObjectRoot, light);
}
void ObjectAnimation::removeParticles()
{
for (unsigned int i=0; i<mObjectRoot->mParticles.size(); ++i)
{
mObjectRoot->mSceneMgr->destroyParticleSystem(mObjectRoot->mParticles[i]);
}
mObjectRoot->mParticles.clear();
}
class FindEntityTransparency {
public:

@ -306,6 +306,7 @@ public:
virtual void attachArrow() {}
virtual void releaseArrow() {}
void enableLights(bool enable);
virtual void enableHeadAnimation(bool enable) {}
Ogre::AxisAlignedBox getWorldBounds();
@ -323,6 +324,7 @@ public:
ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model);
void addLight(const ESM::Light *light);
void removeParticles();
bool canBatch() const;
void fillBatch(Ogre::StaticGeometry *sg);

@ -37,6 +37,7 @@ namespace MWRender
, mViewport(NULL)
, mCamera(NULL)
, mNode(NULL)
, mRecover(false)
{
mCharacter.mCell = NULL;
}
@ -46,6 +47,16 @@ namespace MWRender
}
void CharacterPreview::onFrame()
{
if (mRecover)
{
setupRenderTarget();
mRenderTarget->update();
mRecover = false;
}
}
void CharacterPreview::setup ()
{
mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC);
@ -83,19 +94,10 @@ namespace MWRender
mCamera->setNearClipDistance (0.01);
mCamera->setFarClipDistance (1000);
mTexture = Ogre::TextureManager::getSingleton().getByName (mName);
if (mTexture.isNull ())
mTexture = Ogre::TextureManager::getSingleton().createManual(mName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET);
mTexture = Ogre::TextureManager::getSingleton().createManual(mName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mSizeX, mSizeY, 0, Ogre::PF_A8R8G8B8, Ogre::TU_RENDERTARGET, this);
mRenderTarget = mTexture->getBuffer()->getRenderTarget();
mRenderTarget->removeAllViewports ();
mViewport = mRenderTarget->addViewport(mCamera);
mViewport->setOverlaysEnabled(false);
mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0));
mViewport->setShadowsEnabled(false);
mRenderTarget->setActive(true);
mRenderTarget->setAutoUpdated (false);
setupRenderTarget();
onSetup ();
}
@ -107,6 +109,7 @@ namespace MWRender
mSceneMgr->destroyAllCameras();
delete mAnimation;
Ogre::Root::getSingleton().destroySceneManager(mSceneMgr);
Ogre::TextureManager::getSingleton().remove(mName);
}
}
@ -127,12 +130,39 @@ namespace MWRender
onSetup();
}
void CharacterPreview::loadResource(Ogre::Resource *resource)
{
Ogre::Texture* tex = dynamic_cast<Ogre::Texture*>(resource);
if (!tex)
return;
tex->createInternalResources();
mRenderTarget = NULL;
mViewport = NULL;
mRecover = true;
}
void CharacterPreview::setupRenderTarget()
{
mRenderTarget = mTexture->getBuffer()->getRenderTarget();
mRenderTarget->removeAllViewports ();
mViewport = mRenderTarget->addViewport(mCamera);
mViewport->setOverlaysEnabled(false);
mViewport->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0));
mViewport->setShadowsEnabled(false);
mRenderTarget->setActive(true);
mRenderTarget->setAutoUpdated (false);
}
// --------------------------------------------------------------------------------------------------
InventoryPreview::InventoryPreview(MWWorld::Ptr character)
: CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0))
, mSelectionBuffer(NULL)
, mSizeX(0)
, mSizeY(0)
{
}
@ -141,7 +171,21 @@ namespace MWRender
delete mSelectionBuffer;
}
void InventoryPreview::update(int sizeX, int sizeY)
void InventoryPreview::resize(int sizeX, int sizeY)
{
mSizeX = sizeX;
mSizeY = sizeY;
mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024)));
mTexture->load();
if (!mRenderTarget)
setupRenderTarget();
mRenderTarget->update();
}
void InventoryPreview::update()
{
mAnimation->updateParts();
@ -197,15 +241,25 @@ namespace MWRender
mAnimation->runAnimation(0.0f);
mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024)));
mNode->setOrientation (Ogre::Quaternion::IDENTITY);
mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024)));
mTexture->load();
if (!mRenderTarget)
setupRenderTarget();
mRenderTarget->update();
mSelectionBuffer->update();
}
void InventoryPreview::setupRenderTarget()
{
CharacterPreview::setupRenderTarget();
mViewport->setDimensions (0, 0, std::min(1.f, float(mSizeX) / float(512)), std::min(1.f, float(mSizeY) / float(1024)));
}
int InventoryPreview::getSlotSelected (int posX, int posY)
{
return mSelectionBuffer->getSelected (posX, posY);
@ -243,6 +297,7 @@ namespace MWRender
void RaceSelectionPreview::render()
{
mTexture->load();
mRenderTarget->update();
}

@ -22,7 +22,7 @@ namespace MWRender
class NpcAnimation;
class CharacterPreview
class CharacterPreview : public Ogre::ManualResourceLoader
{
public:
CharacterPreview(MWWorld::Ptr character, int sizeX, int sizeY, const std::string& name,
@ -34,6 +34,13 @@ namespace MWRender
virtual void rebuild();
void onFrame();
void loadResource(Ogre::Resource *resource);
private:
bool mRecover; // Texture content was lost and needs to be re-rendered
private:
CharacterPreview(const CharacterPreview&);
CharacterPreview& operator=(const CharacterPreview&);
@ -41,6 +48,8 @@ namespace MWRender
protected:
virtual bool renderHeadOnly() { return false; }
virtual void setupRenderTarget();
Ogre::TexturePtr mTexture;
Ogre::RenderTarget* mRenderTarget;
Ogre::Viewport* mViewport;
@ -72,11 +81,17 @@ namespace MWRender
virtual ~InventoryPreview();
virtual void onSetup();
void update(int sizeX, int sizeY);
void update(); // Render preview again, e.g. after changed equipment
void resize(int sizeX, int sizeY);
int getSlotSelected(int posX, int posY);
protected:
virtual void setupRenderTarget();
private:
int mSizeX;
int mSizeY;
OEngine::Render::SelectionBuffer* mSelectionBuffer;
};

@ -67,11 +67,16 @@ namespace MWRender
{
HeadAnimationTime::HeadAnimationTime(MWWorld::Ptr reference)
: mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0)
: mReference(reference), mTalkStart(0), mTalkStop(0), mBlinkStart(0), mBlinkStop(0), mValue(0), mEnabled(true)
{
resetBlinkTimer();
}
void HeadAnimationTime::setEnabled(bool enabled)
{
mEnabled = enabled;
}
void HeadAnimationTime::resetBlinkTimer()
{
mBlinkTimer = -(2 + (std::rand() / double(RAND_MAX*1.0)) * 6);
@ -79,6 +84,9 @@ void HeadAnimationTime::resetBlinkTimer()
void HeadAnimationTime::update(float dt)
{
if (!mEnabled)
return;
if (MWBase::Environment::get().getSoundManager()->sayDone(mReference))
{
mBlinkTimer += dt;
@ -864,6 +872,11 @@ void NpcAnimation::setAlpha(float alpha)
}
}
void NpcAnimation::enableHeadAnimation(bool enable)
{
mHeadAnimationTime->setEnabled(enable);
}
void NpcAnimation::preRender(Ogre::Camera *camera)
{
Animation::preRender(camera);

@ -26,6 +26,8 @@ private:
float mBlinkTimer;
bool mEnabled;
float mValue;
private:
void resetBlinkTimer();
@ -34,6 +36,8 @@ public:
void update(float dt);
void setEnabled(bool enabled);
void setTalkStart(float value);
void setTalkStop(float value);
void setBlinkStart(float value);
@ -125,6 +129,8 @@ public:
ViewMode viewMode=VM_Normal);
virtual ~NpcAnimation();
virtual void enableHeadAnimation(bool enable);
virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); }
virtual Ogre::Vector3 runAnimation(float timepassed);

@ -73,14 +73,19 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr)
ptr.getRefData().setBaseNode(insert);
}
void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch)
void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch, bool addLight)
{
insertBegin(ptr);
std::auto_ptr<ObjectAnimation> anim(new ObjectAnimation(ptr, mesh));
if(ptr.getTypeName() == typeid(ESM::Light).name())
anim->addLight(ptr.get<ESM::Light>()->mBase);
{
if (addLight)
anim->addLight(ptr.get<ESM::Light>()->mBase);
else
anim->removeParticles();
}
if (!mesh.empty())
{

@ -41,7 +41,7 @@ public:
, mRootNode(NULL)
{}
~Objects(){}
void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false);
void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false, bool addLight=false);
ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr);

@ -23,6 +23,7 @@
#include <components/settings/settings.hpp>
#include <components/terrain/defaultworld.hpp>
#include <components/terrain/terraingrid.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp"
@ -45,7 +46,6 @@
#include "globalmap.hpp"
#include "terrainstorage.hpp"
#include "effectmanager.hpp"
#include "terraingrid.hpp"
using namespace MWRender;
using namespace Ogre;
@ -1045,7 +1045,7 @@ void RenderingManager::enableTerrain(bool enable)
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,
mTerrain = new Terrain::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"));

@ -1,22 +1,11 @@
#include "terrainstorage.hpp"
#include <OgreVector2.h>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreResourceBackgroundQueue.h>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include <components/terrain/quadtreenode.hpp>
#include <components/misc/resourcehelpers.hpp>
namespace MWRender
{
@ -60,515 +49,4 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().find(index, plugin);
}
bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
{
assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int cellX = origin.x;
int cellY = origin.y;
const ESM::Land* land = getLand(cellX, cellY);
if (!land)
return false;
min = std::numeric_limits<float>().max();
max = -std::numeric_limits<float>().max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mHasData)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
}
void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
{
Ogre::Vector3 n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4);
normal.normalise();
}
void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mLandData->mUsingColours)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
}
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int startX = origin.x;
int startY = origin.y;
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
colours.resize(numVerts*numVerts*4);
positions.resize(numVerts*numVerts*3);
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
Ogre::ColourValue color;
float vertY;
float vertX;
float vertY_ = 0; // of current cell corner
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !land->mHasData)
land = NULL;
bool hasColors = land && land->mLandData->mUsingColours;
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
if (colStart == 0 && vertY_ != 0)
colStart += increment;
if (rowStart == 0 && vertX_ != 0)
rowStart += increment;
vertY = vertY_;
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
if (land)
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
else
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
if (land)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0);
normals[vertX*numVerts*3 + vertY*3] = normal.x;
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
if (hasColors)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
assert(vertX_ == numVerts); // Ensure we covered whole area
}
assert(vertY_ == numVerts); // Ensure we covered whole area
}
TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
if (x < 0)
{
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
ESM::Land* land = getLand(cellX, cellY);
if (land)
{
int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->mPlugin);
}
else
return std::make_pair(0,0);
}
std::string TerrainStorage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "textures\\_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
// NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
//TODO this is needed due to MWs messed up texture handling
std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture);
return texture;
}
void TerrainStorage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
{
for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
{
out.push_back(Terrain::LayerCollection());
out.back().mTarget = *it;
getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
}
}
void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
}
void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
// and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
int cellX = origin.x;
int cellY = origin.y;
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
// Due to the way the blending works, the base layer will always shine through in between
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
// To get a consistent look, we need to make sure to use the same base layer in all cells.
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
textureIndices.insert(id);
}
// Makes sure the indices are sorted, or rather,
// retrieved as sorted. This is important to keep the splatting order
// consistent across cells.
std::map<UniqueTextureId, int> textureIndicesMap;
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
Ogre::uchar* pData =
OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
memset(pData, 0, blendmapSize*blendmapSize*channels);
for (int y=0; y<blendmapSize; ++y)
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
pData[y*blendmapSize*channels + x*channels + channel] = 255;
else
pData[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
}
}
float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos)
{
int cellX = std::floor(worldPos.x / 8192.f);
int cellY = std::floor(worldPos.y / 8192.f);
ESM::Land* land = getLand(cellX, cellY);
if (!land)
return -2048;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f;
float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor);
int startY = static_cast<int>(nY * factor);
int endX = startX + 1;
int endY = startY + 1;
assert(endX < ESM::Land::LAND_SIZE);
assert(endY < ESM::Land::LAND_SIZE);
// now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor;
float startYTS = startY * invFactor;
float endXTS = endX * invFactor;
float endYTS = endY * invFactor;
// get parametric from start coord to next point
float xParam = (nX - startXTS) * factor;
float yParam = (nY - startYTS) * factor;
/* For even / odd tri strip rows, triangles are this shape:
even odd
3---2 3---2
| / | | \ |
0---1 0---1
*/
// Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space
Ogre::Plane plane;
// (At the moment, all rows have the same triangle alignment)
if (true)
{
// odd row
bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri)
plane.redefine(v0, v1, v3);
else
plane.redefine(v1, v2, v3);
}
else
{
// even row
bool secondTri = (yParam > xParam);
if (secondTri)
plane.redefine(v0, v2, v3);
else
plane.redefine(v0, v1, v2);
}
// Solve plane equation for z
return (-plane.normal.x * nX
-plane.normal.y * nY
- plane.d) / plane.normal.z * 8192;
}
float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture)
{
// Already have this cached?
if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
return mLayerInfoMap[texture];
Terrain::LayerInfo info;
info.mParallax = false;
info.mSpecular = false;
info.mDiffuseMap = texture;
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
{
info.mNormalMap = texture_;
info.mParallax = true;
}
else
{
texture_ = texture;
boost::replace_last(texture_, ".", "_n.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
info.mNormalMap = texture_;
}
texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
{
info.mDiffuseMap = texture_;
info.mSpecular = true;
}
// This wasn't cached, so the textures are probably not loaded either.
// Background load them so they are hopefully already loaded once we need them!
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
if (!info.mNormalMap.empty())
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
mLayerInfoMap[texture] = info;
return info;
}
Terrain::LayerInfo TerrainStorage::getDefaultLayer()
{
Terrain::LayerInfo info;
info.mDiffuseMap = "textures\\_land_default.dds";
info.mParallax = false;
info.mSpecular = false;
return info;
}
float TerrainStorage::getCellWorldSize()
{
return ESM::Land::REAL_SIZE;
}
int TerrainStorage::getCellVertices()
{
return ESM::Land::LAND_SIZE;
}
}

@ -1,15 +1,13 @@
#ifndef MWRENDER_TERRAINSTORAGE_H
#define MWRENDER_TERRAINSTORAGE_H
#include <components/esm/loadland.hpp>
#include <components/esm/loadltex.hpp>
#include <components/terrain/storage.hpp>
#include <components/esmterrain/storage.hpp>
namespace MWRender
{
class TerrainStorage : public Terrain::Storage
/// @brief Connects the ESM Store used in OpenMW with the ESMTerrain storage.
class TerrainStorage : public ESMTerrain::Storage
{
private:
virtual ESM::Land* getLand (int cellX, int cellY);
@ -18,92 +16,6 @@ namespace MWRender
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
/// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children.
/// @param size size of the chunk in cell units
/// @param center center of the chunk in cell units
/// @param min min height will be stored here
/// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
/// @param lodLevel LOD level, 0 = most detailed
/// @param size size of the terrain chunk in cell units
/// @param center center of the chunk in cell units
/// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours);
/// 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 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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
/// 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 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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer();
/// Get the transformation factor for mapping cell units to world units.
virtual float getCellWorldSize();
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices();
private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
// Non-virtual
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
};
}

@ -363,8 +363,19 @@ namespace MWScript
runtime.pop();
int cx,cy;
MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
MWBase::Environment::get().getWorld()->moveObject(ptr,
MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z);
// another morrowind oddity: player will be moved to the exterior cell at this location,
// non-player actors will move within the cell they are in.
if (ptr.getRefData().getHandle() == "player")
{
MWBase::Environment::get().getWorld()->moveObject(ptr,
MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z);
}
else
{
MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z);
}
float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity

@ -701,7 +701,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& state)
for (std::vector<std::pair<ESM::LightState, int> >::const_iterator iter (state.mLights.begin());
iter!=state.mLights.end(); ++iter)
{
getState (lights, iter->first);
int slot = iter->second;
setSlot (getState (lights, iter->first), slot);
}
mLevelledItemMap = state.mLevelledItemMap;

@ -612,9 +612,9 @@ namespace MWWorld
}
}
std::vector<std::string> PhysicsSystem::getCollisions(const Ptr &ptr)
std::vector<std::string> PhysicsSystem::getCollisions(const Ptr &ptr, int collisionGroup, int collisionMask)
{
return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName());
return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName(), collisionGroup, collisionMask);
}
Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight)

@ -55,7 +55,7 @@ namespace MWWorld
void stepSimulation(float dt);
std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with
std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr, int collisionGroup, int collisionMask); ///< get handles this object collides with
Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight);
std::pair<float, std::string> getFacedHandle(float queryDistance);

@ -244,6 +244,8 @@ namespace MWWorld
mPlayer.load (player.mObject);
getPlayer().getClass().getCreatureStats(getPlayer()).getAiSequence().clear();
MWBase::World& world = *MWBase::Environment::get().getWorld();
try

@ -403,21 +403,20 @@ namespace MWWorld
void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
{
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
bool loadcell = (mCurrentCell == NULL);
if(!loadcell)
loadcell = *mCurrentCell != *cell;
Nif::NIFFile::CacheLock lock;
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
mRendering.enableTerrain(false);
std::string loadingInteriorText = "#{sLoadingMessage2}";
loadingListener->setLabel(loadingInteriorText);
Loading::ScopedLoad load(loadingListener);
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
bool loadcell = (mCurrentCell == NULL);
if(!loadcell)
loadcell = *mCurrentCell != *cell;
mRendering.enableTerrain(false);
if(!loadcell)
{

@ -1284,7 +1284,8 @@ namespace MWWorld
bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f;
/// \todo should use convexSweepTest here
std::vector<std::string> collisions = mPhysics->getCollisions(it->first);
std::vector<std::string> collisions = mPhysics->getCollisions(it->first, OEngine::Physic::CollisionType_Actor
, OEngine::Physic::CollisionType_Actor);
for (std::vector<std::string>::iterator cit = collisions.begin(); cit != collisions.end(); ++cit)
{
MWWorld::Ptr ptr = getPtrViaHandle(*cit);
@ -1300,7 +1301,6 @@ namespace MWWorld
// we need to undo the rotation
localRotateObject(it->first, 0, 0, oldRot);
reached = false;
//break; //Removed in case multiple actors are touching
}
}

@ -45,6 +45,10 @@ add_component_dir (esm
aisequence
)
add_component_dir (esmterrain
storage
)
add_component_dir (misc
utf8stream stringops resourcehelpers
)
@ -72,7 +76,7 @@ add_component_dir (translation
add_definitions(-DTERRAIN_USE_SHADER=1)
add_component_dir (terrain
quadtreenode chunk world defaultworld storage material buffercache defs
quadtreenode chunk world defaultworld terraingrid storage material buffercache defs
)
add_component_dir (loadinglistener

@ -58,6 +58,8 @@ namespace AiSequence
esm.getHNT (mRemainingDuration, "DURA");
mCellId = esm.getHNOString ("CELL");
esm.getHNT (mAlwaysFollow, "ALWY");
mCommanded = false;
esm.getHNOT (mCommanded, "CMND");
}
void AiFollow::save(ESMWriter &esm) const
@ -68,6 +70,7 @@ namespace AiSequence
if (!mCellId.empty())
esm.writeHNString ("CELL", mCellId);
esm.writeHNT ("ALWY", mAlwaysFollow);
esm.writeHNT ("CMND", mCommanded);
}
void AiActivate::load(ESMReader &esm)

@ -96,6 +96,7 @@ namespace ESM
float mRemainingDuration;
bool mAlwaysFollow;
bool mCommanded;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;

@ -10,6 +10,9 @@ namespace ESM
void DialInfo::load(ESMReader &esm)
{
mQuestStatus = QS_None;
mFactionLess = false;
mPrev = esm.getHNString("PNAM");
mNext = esm.getHNString("NNAM");
@ -49,7 +52,6 @@ void DialInfo::load(ESMReader &esm)
return;
}
mFactionLess = false;
if (subName.val == REC_FNAM)
{
mFaction = esm.getHString();
@ -104,8 +106,6 @@ void DialInfo::load(ESMReader &esm)
return;
}
mQuestStatus = QS_None;
if (subName.val == REC_QSTN)
mQuestStatus = QS_Name;
else if (subName.val == REC_QSTF)

@ -25,7 +25,7 @@ struct Light
Negative = 0x004, // Negative light - i.e. darkness
Flicker = 0x008,
Fire = 0x010,
OffDefault = 0x020, // Off by default
OffDefault = 0x020, // Off by default - does not burn while placed in a cell, but can burn when equipped by an NPC
FlickerSlow = 0x040,
Pulse = 0x080,
PulseSlow = 0x100

@ -78,8 +78,9 @@ void ESM::NpcStats::load (ESMReader &esm)
mLastDrowningHit = 0;
esm.getHNOT (mLastDrowningHit, "DRLH");
mLevelHealthBonus = 0;
esm.getHNOT (mLevelHealthBonus, "LVLH");
// No longer used
float levelHealthBonus = 0;
esm.getHNOT (levelHealthBonus, "LVLH");
mCrimeId = -1;
esm.getHNOT (mCrimeId, "CRID");
@ -148,9 +149,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const
if (mLastDrowningHit)
esm.writeHNT ("DRLH", mLastDrowningHit);
if (mLevelHealthBonus)
esm.writeHNT ("LVLH", mLevelHealthBonus);
if (mCrimeId != -1)
esm.writeHNT ("CRID", mCrimeId);
}

@ -46,7 +46,6 @@ namespace ESM
std::vector<std::string> mUsedIds;
float mTimeToStartDrowning;
float mLastDrowningHit;
float mLevelHealthBonus;
int mCrimeId;
void load (ESMReader &esm);

@ -0,0 +1,530 @@
#include "storage.hpp"
#include <OgreVector2.h>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreResourceBackgroundQueue.h>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp>
#include <components/terrain/quadtreenode.hpp>
#include <components/misc/resourcehelpers.hpp>
namespace ESMTerrain
{
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
{
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int cellX = origin.x;
int cellY = origin.y;
const ESM::Land* land = getLand(cellX, cellY);
if (!land)
return false;
min = std::numeric_limits<float>().max();
max = -std::numeric_limits<float>().max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mHasData)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
}
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
{
Ogre::Vector3 n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4);
normal.normalise();
}
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mLandData->mUsingColours)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int startX = origin.x;
int startY = origin.y;
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
colours.resize(numVerts*numVerts*4);
positions.resize(numVerts*numVerts*3);
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
Ogre::ColourValue color;
float vertY;
float vertX;
float vertY_ = 0; // of current cell corner
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !land->mHasData)
land = NULL;
bool hasColors = land && land->mLandData->mUsingColours;
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
if (colStart == 0 && vertY_ != 0)
colStart += increment;
if (rowStart == 0 && vertX_ != 0)
rowStart += increment;
vertY = vertY_;
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
if (land)
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
else
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
if (land)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0);
normals[vertX*numVerts*3 + vertY*3] = normal.x;
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
if (hasColors)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
assert(vertX_ == numVerts); // Ensure we covered whole area
}
assert(vertY_ == numVerts); // Ensure we covered whole area
}
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
if (x < 0)
{
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
ESM::Land* land = getLand(cellX, cellY);
if (land)
{
int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->mPlugin);
}
else
return std::make_pair(0,0);
}
std::string Storage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded?
// NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
//TODO this is needed due to MWs messed up texture handling
std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture);
return texture;
}
void Storage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
{
for (std::vector<Terrain::QuadTreeNode*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it)
{
out.push_back(Terrain::LayerCollection());
out.back().mTarget = *it;
getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers);
}
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
}
void Storage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
// TODO - blending isn't completely right yet; the blending radius appears to be
// different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap
// and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
int cellX = origin.x;
int cellY = origin.y;
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
// Due to the way the blending works, the base layer will always shine through in between
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
// To get a consistent look, we need to make sure to use the same base layer in all cells.
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
textureIndices.insert(id);
}
// Makes sure the indices are sorted, or rather,
// retrieved as sorted. This is important to keep the splatting order
// consistent across cells.
std::map<UniqueTextureId, int> textureIndicesMap;
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
Ogre::uchar* pData =
OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
memset(pData, 0, blendmapSize*blendmapSize*channels);
for (int y=0; y<blendmapSize; ++y)
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
pData[y*blendmapSize*channels + x*channels + channel] = 255;
else
pData[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
}
}
float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
{
int cellX = std::floor(worldPos.x / 8192.f);
int cellY = std::floor(worldPos.y / 8192.f);
ESM::Land* land = getLand(cellX, cellY);
if (!land)
return -2048;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f;
float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor);
int startY = static_cast<int>(nY * factor);
int endX = startX + 1;
int endY = startY + 1;
assert(endX < ESM::Land::LAND_SIZE);
assert(endY < ESM::Land::LAND_SIZE);
// now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor;
float startYTS = startY * invFactor;
float endXTS = endX * invFactor;
float endYTS = endY * invFactor;
// get parametric from start coord to next point
float xParam = (nX - startXTS) * factor;
float yParam = (nY - startYTS) * factor;
/* For even / odd tri strip rows, triangles are this shape:
even odd
3---2 3---2
| / | | \ |
0---1 0---1
*/
// Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space
Ogre::Plane plane;
// (At the moment, all rows have the same triangle alignment)
if (true)
{
// odd row
bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri)
plane.redefine(v0, v1, v3);
else
plane.redefine(v1, v2, v3);
}
else
{
// even row
bool secondTri = (yParam > xParam);
if (secondTri)
plane.redefine(v0, v2, v3);
else
plane.redefine(v0, v1, v2);
}
// Solve plane equation for z
return (-plane.normal.x * nX
-plane.normal.y * nY
- plane.d) / plane.normal.z * 8192;
}
float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
{
// Already have this cached?
if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
return mLayerInfoMap[texture];
Terrain::LayerInfo info;
info.mParallax = false;
info.mSpecular = false;
info.mDiffuseMap = texture;
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
{
info.mNormalMap = texture_;
info.mParallax = true;
}
else
{
texture_ = texture;
boost::replace_last(texture_, ".", "_n.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
info.mNormalMap = texture_;
}
texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(texture_))
{
info.mDiffuseMap = texture_;
info.mSpecular = true;
}
// This wasn't cached, so the textures are probably not loaded either.
// Background load them so they are hopefully already loaded once we need them!
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General");
if (!info.mNormalMap.empty())
Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General");
mLayerInfoMap[texture] = info;
return info;
}
Terrain::LayerInfo Storage::getDefaultLayer()
{
Terrain::LayerInfo info;
info.mDiffuseMap = "textures\\_land_default.dds";
info.mParallax = false;
info.mSpecular = false;
return info;
}
float Storage::getCellWorldSize()
{
return ESM::Land::REAL_SIZE;
}
int Storage::getCellVertices()
{
return ESM::Land::LAND_SIZE;
}
}

@ -0,0 +1,117 @@
#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H
#define COMPONENTS_ESM_TERRAIN_STORAGE_H
#include <components/terrain/storage.hpp>
#include <components/esm/loadland.hpp>
#include <components/esm/loadltex.hpp>
namespace ESMTerrain
{
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage
{
private:
// Not implemented in this class, because we need different Store implementations for game and editor
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public:
// Not implemented in this class, because we need different Store implementations for game and editor
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
/// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children.
/// @param size size of the chunk in cell units
/// @param center center of the chunk in cell units
/// @param min min height will be stored here
/// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
/// @param lodLevel LOD level, 0 = most detailed
/// @param size size of the terrain chunk in cell units
/// @param center center of the chunk in cell units
/// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours);
/// 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 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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
/// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information.
/// 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 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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
virtual void getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer();
/// Get the transformation factor for mapping cell units to world units.
virtual float getCellWorldSize();
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices();
private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
// Non-virtual
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
};
}
#endif

@ -4,12 +4,9 @@
#include <OgreSceneNode.h>
#include <OgreAxisAlignedBox.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "chunk.hpp"
#include <components/terrain/chunk.hpp>
namespace MWRender
namespace Terrain
{
TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align)
@ -145,8 +142,10 @@ void TerrainGrid::setVisible(bool visible)
Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center)
{
int cellX, cellY;
MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY);
float cellSize = getStorage()->getCellWorldSize();
int cellX = std::floor(center.x/cellSize);
int cellY = std::floor(center.y/cellSize);
Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY));
if (it == mGrid.end())

@ -1,16 +1,12 @@
#ifndef OPENMW_MWRENDER_TERRAINGRID_H
#define OPENMW_MWRENDER_TERRAINGRID_H
#ifndef COMPONENTS_TERRAIN_TERRAINGRID_H
#define COMPONENTS_TERRAIN_TERRAINGRID_H
#include <components/terrain/world.hpp>
#include <components/terrain/material.hpp>
#include "world.hpp"
#include "material.hpp"
namespace Terrain
{
class Chunk;
}
namespace MWRender
{
struct GridElement
{

@ -15,6 +15,7 @@ endif ()
set(SDL4OGRE_HEADER_FILES
OISCompat.h
cursormanager.hpp
events.h
)
add_library(${SDL4OGRE_LIBRARY} STATIC ${SDL4OGRE_SOURCE_FILES} ${SDL4OGRE_HEADER_FILES})

@ -1,13 +1,16 @@
#if SH_HLSL == 1 || SH_CG == 1
#define shTexture2D sampler2D
#define shTexture3D sampler3D
#define shSample(tex, coord) tex2D(tex, coord)
#define shCubicSample(tex, coord) texCUBE(tex, coord)
#define shLerp(a, b, t) lerp(a, b, t)
#define shSaturate(a) saturate(a)
#define shSampler2D(name) , uniform sampler2D name : register(s@shCounter(0)) @shUseSampler(name)
#define shSampler3D(name) , uniform sampler3D name : register(s@shCounter(0)) @shUseSampler(name)
#define shSamplerCube(name) , uniform samplerCUBE name : register(s@shCounter(0)) @shUseSampler(name)
#define shMatrixMult(m, v) mul(m, v)
@ -67,6 +70,7 @@
#define int3 ivec3
#define int4 ivec4
#define shTexture2D sampler2D
#define shTexture3D sampler3D
#define shSample(tex, coord) texture2D(tex, coord)
#define shCubicSample(tex, coord) textureCube(tex, coord)
#define shLerp(a, b, t) mix(a, b, t)
@ -76,6 +80,8 @@
#define shSampler2D(name) uniform sampler2D name; @shUseSampler(name)
#define shSampler3D(name) uniform sampler3D name; @shUseSampler(name)
#define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name)
#define shMatrixMult(m, v) (m * v)

@ -278,14 +278,15 @@ namespace sh
MaterialMap::iterator it = mMaterials.find(name);
if (it != mMaterials.end())
return &(it->second);
else
else
return NULL;
}
MaterialInstance* Factory::findInstance (const std::string& name)
{
assert (mMaterials.find(name) != mMaterials.end());
return &mMaterials.find(name)->second;
MaterialInstance* m = searchInstance(name);
assert (m);
return m;
}
MaterialInstance* Factory::requestMaterial (const std::string& name, const std::string& configuration, unsigned short lodIndex)
@ -297,27 +298,24 @@ namespace sh
if (m)
{
// make sure all lod techniques below (higher lod) exist
int i = lodIndex;
while (i>0)
if (m->createForConfiguration (configuration, 0))
{
if (mListener)
mListener->materialCreated (m, configuration, 0);
}
else
return NULL;
for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it)
{
--i;
if (m->createForConfiguration (configuration, i))
if (m->createForConfiguration (configuration, it->first))
{
if (mListener)
mListener->materialCreated (m, configuration, i);
mListener->materialCreated (m, configuration, it->first);
}
else
return NULL;
}
if (m->createForConfiguration (configuration, lodIndex))
{
if (mListener)
mListener->materialCreated (m, configuration, lodIndex);
}
else
return NULL;
}
return m;
}
@ -625,8 +623,14 @@ namespace sh
{
MaterialInstance* m = searchInstance (name);
assert(m);
m->createForConfiguration (configuration, 0);
}
for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it)
{
m->createForConfiguration (configuration, it->first);
}
}
bool Factory::removeCache(const std::string& pattern)
{

@ -259,8 +259,8 @@ namespace sh
Platform* mPlatform;
MaterialInstance* findInstance (const std::string& name);
private:
MaterialInstance* searchInstance (const std::string& name);
/// @return was anything removed?
bool removeCache (const std::string& pattern);

@ -163,7 +163,8 @@ namespace sh
mTexUnits.push_back(texUnit);
// set texture unit indices (required by GLSL)
if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL || mFactory->getCurrentLanguage() == Language_GLSLES))
if (useShaders && ((hasVertex && foundVertex) || (hasFragment && foundFragment)) && (mFactory->getCurrentLanguage () == Language_GLSL
|| mFactory->getCurrentLanguage() == Language_GLSLES))
{
pass->setTextureUnitIndex (foundVertex ? GPT_Vertex : GPT_Fragment, texIt->getName(), i);

@ -9,8 +9,7 @@
/*
Almost exact copy of load_file_to_string policy found in
boost::wave headers with the only change that it uses
boost::filesystem facility to handle UTF-8 paths used
throughout OpenMW (bfs::fstream, bfs::path).
boost::filesystem facility to handle UTF-8 paths properly on windows.
Original namespace is used due to required bost::wave
internal symbols.

@ -1,8 +1,6 @@
#ifndef SH_PREPROCESSOR_H
#define SH_PREPROCESSOR_H
#include <stdint.h>
#include <string>
#include <vector>

@ -151,7 +151,7 @@ namespace sh
else if (typeid(*value) == typeid(IntValue))
type = Ogre::GCT_INT1;
else
throw std::runtime_error("unexpected type");
throw std::runtime_error("unexpected type");
params->addConstantDefinition(name, type);
mSharedParameters[name] = params;
}

@ -829,6 +829,7 @@
window by dragging the caption. -->
<Child type="Button" offset="4 4 248 20" align="HStretch Top" name="Action">
<Property key="Scale" value="1 1 0 0"/>
<Property key="HideWindowOnDoubleClick" value="1"/>
</Child>
<Child type="Button" skin="PinUp" offset="232 4 19 19" align="Right Top" name="Button"/>

@ -661,12 +661,14 @@ namespace Physic
};
std::vector<std::string> PhysicEngine::getCollisions(const std::string& name)
std::vector<std::string> PhysicEngine::getCollisions(const std::string& name, int collisionGroup, int collisionMask)
{
RigidBody* body = getRigidBody(name);
if (!body) // fall back to raycasting body if there is no collision body
body = getRigidBody(name, true);
ContactTestResultCallback callback;
callback.m_collisionFilterGroup = collisionGroup;
callback.m_collisionFilterMask = collisionMask;
mDynamicsWorld->contactTest(body, callback);
return callback.mResult;
}

@ -311,7 +311,7 @@ namespace Physic
std::pair<bool, float> sphereCast (float radius, btVector3& from, btVector3& to);
///< @return (hit, relative distance)
std::vector<std::string> getCollisions(const std::string& name);
std::vector<std::string> getCollisions(const std::string& name, int collisionGroup, int collisionMask);
// Get the nearest object that's inside the given object, filtering out objects of the
// provided name

@ -18,22 +18,42 @@ namespace Render
{
SelectionBuffer::SelectionBuffer(Ogre::Camera *camera, int sizeX, int sizeY, int visibilityFlags)
: mCamera(camera)
, mVisibilityFlags(visibilityFlags)
{
mTexture = Ogre::TextureManager::getSingleton().createManual("SelectionBuffer",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, sizeX, sizeY, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET, this);
setupRenderTarget();
mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3);
}
void SelectionBuffer::setupRenderTarget()
{
mRenderTarget = mTexture->getBuffer()->getRenderTarget();
Ogre::Viewport* vp = mRenderTarget->addViewport(camera);
Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera);
vp->setOverlaysEnabled(false);
vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 0));
vp->setShadowsEnabled(false);
vp->setMaterialScheme("selectionbuffer");
if (visibilityFlags != 0)
vp->setVisibilityMask (visibilityFlags);
if (mVisibilityFlags != 0)
vp->setVisibilityMask (mVisibilityFlags);
mRenderTarget->setActive(true);
mRenderTarget->setAutoUpdated (false);
}
mCurrentColour = Ogre::ColourValue(0.3, 0.3, 0.3);
void SelectionBuffer::loadResource(Ogre::Resource *resource)
{
Ogre::Texture* tex = dynamic_cast<Ogre::Texture*>(resource);
if (!tex)
return;
tex->createInternalResources();
mRenderTarget = NULL;
// Don't need to re-render texture, because we have a copy in system memory (mBuffer)
}
SelectionBuffer::~SelectionBuffer()
@ -45,6 +65,9 @@ namespace Render
{
Ogre::MaterialManager::getSingleton ().addListener (this);
if (mRenderTarget == NULL)
setupRenderTarget();
mRenderTarget->update();
Ogre::MaterialManager::getSingleton ().removeListener (this);

@ -20,7 +20,7 @@ namespace Render
}
};
class SelectionBuffer : public Ogre::MaterialManager::Listener
class SelectionBuffer : public Ogre::MaterialManager::Listener, public Ogre::ManualResourceLoader
{
public:
SelectionBuffer(Ogre::Camera* camera, int sizeX, int sizeY, int visibilityFlags);
@ -31,6 +31,8 @@ namespace Render
void update();
virtual void loadResource(Ogre::Resource* resource);
virtual Ogre::Technique* handleSchemeNotFound (
unsigned short schemeIndex, const Ogre::String &schemeName, Ogre::Material *originalMaterial,
unsigned short lodIndex, const Ogre::Renderable *rend);
@ -46,7 +48,12 @@ namespace Render
Ogre::ColourValue mCurrentColour;
Ogre::Camera* mCamera;
int mVisibilityFlags;
void getNextColour();
void setupRenderTarget();
};
}

Loading…
Cancel
Save