forked from mirror/openmw-tes3mp
Merge remote-tracking branch 'scrawl/master'
This commit is contained in:
commit
93ed019ca3
82 changed files with 1299 additions and 840 deletions
|
@ -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;
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
|
||||
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 ¢er, 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,22 +403,21 @@ namespace MWWorld
|
|||
|
||||
void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
|
||||
{
|
||||
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);
|
||||
|
||||
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();
|
||||
std::string loadingInteriorText = "#{sLoadingMessage2}";
|
||||
loadingListener->setLabel(loadingInteriorText);
|
||||
Loading::ScopedLoad load(loadingListener);
|
||||
|
||||
mRendering.enableTerrain(false);
|
||||
|
||||
if(!loadcell)
|
||||
{
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
|
|
|
@ -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);
|
||||
|
|
530
components/esmterrain/storage.cpp
Normal file
530
components/esmterrain/storage.cpp
Normal file
|
@ -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 ¢er, 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;
|
||||
}
|
||||
|
||||
}
|
117
components/esmterrain/storage.hpp
Normal file
117
components/esmterrain/storage.hpp
Normal file
|
@ -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
|
||||
{
|
1
extern/sdl4ogre/CMakeLists.txt
vendored
1
extern/sdl4ogre/CMakeLists.txt
vendored
|
@ -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})
|
||||
|
|
8
extern/shiny/Extra/core.h
vendored
8
extern/shiny/Extra/core.h
vendored
|
@ -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)
|
||||
|
|
40
extern/shiny/Main/Factory.cpp
vendored
40
extern/shiny/Main/Factory.cpp
vendored
|
@ -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))
|
||||
{
|
||||
--i;
|
||||
if (m->createForConfiguration (configuration, i))
|
||||
if (mListener)
|
||||
mListener->materialCreated (m, configuration, 0);
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
|
||||
for (LodConfigurationMap::iterator it = mLodConfigurations.begin(); it != mLodConfigurations.end(); ++it)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
2
extern/shiny/Main/Factory.hpp
vendored
2
extern/shiny/Main/Factory.hpp
vendored
|
@ -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);
|
||||
|
||||
|
|
3
extern/shiny/Main/MaterialInstance.cpp
vendored
3
extern/shiny/Main/MaterialInstance.cpp
vendored
|
@ -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);
|
||||
|
||||
|
|
3
extern/shiny/Main/Preprocessor.cpp
vendored
3
extern/shiny/Main/Preprocessor.cpp
vendored
|
@ -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.
|
||||
|
|
2
extern/shiny/Main/Preprocessor.hpp
vendored
2
extern/shiny/Main/Preprocessor.hpp
vendored
|
@ -1,8 +1,6 @@
|
|||
#ifndef SH_PREPROCESSOR_H
|
||||
#define SH_PREPROCESSOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
2
extern/shiny/Platforms/Ogre/OgrePlatform.cpp
vendored
2
extern/shiny/Platforms/Ogre/OgrePlatform.cpp
vendored
|
@ -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…
Reference in a new issue