forked from teamnwah/openmw-tes3coop
Merge branch 'master' into script
This commit is contained in:
commit
d6e212a02b
56 changed files with 1439 additions and 778 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -40,6 +40,7 @@ resources
|
|||
|
||||
## generated objects
|
||||
apps/openmw/config.hpp
|
||||
components/version/version.hpp
|
||||
Docs/mainpage.hpp
|
||||
moc_*.cxx
|
||||
*.cxx_parameters
|
||||
|
|
18
.travis.yml
18
.travis.yml
|
@ -4,17 +4,17 @@ compiler:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- next
|
||||
- /openmw-.*$/
|
||||
before_install:
|
||||
- pwd
|
||||
- git submodule update --init --recursive
|
||||
- git fetch --tags
|
||||
- echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
|
||||
- echo "yes" | sudo apt-add-repository ppa:openmw/openmw
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev
|
||||
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
|
||||
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
|
||||
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
|
||||
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock
|
||||
- sudo apt-get install -qq libqt4-dev
|
||||
- sudo apt-get install -qq libopenal-dev
|
||||
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
|
||||
- sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
|
||||
- sudo mkdir /usr/src/gtest/build
|
||||
- cd /usr/src/gtest/build
|
||||
|
@ -37,3 +37,9 @@ notifications:
|
|||
email:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#openmw"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
|
|
|
@ -18,11 +18,26 @@ include (OpenMWMacros)
|
|||
|
||||
# Version
|
||||
|
||||
set (OPENMW_VERSION_MAJOR 0)
|
||||
set (OPENMW_VERSION_MINOR 27)
|
||||
set (OPENMW_VERSION_RELEASE 0)
|
||||
include(GetGitRevisionDescription)
|
||||
|
||||
get_git_tag_revision(TAGHASH --tags --max-count=1 "HEAD...")
|
||||
get_git_head_revision(REFSPEC COMMITHASH)
|
||||
git_describe(VERSION --tags ${TAGHASH})
|
||||
|
||||
string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}")
|
||||
if (MATCH)
|
||||
string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}")
|
||||
string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}")
|
||||
string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}")
|
||||
|
||||
set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}")
|
||||
set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}")
|
||||
set(OPENMW_VERSION_TAGHASH "${TAGHASH}")
|
||||
|
||||
message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...")
|
||||
else (MATCH)
|
||||
message(FATAL_ERROR "Failed to get valid version information from Git")
|
||||
endif (MATCH)
|
||||
|
||||
# doxygen main page
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#include "maindialog.hpp"
|
||||
|
||||
#include <components/version/version.hpp>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QDate>
|
||||
#include <QTime>
|
||||
#include <QPushButton>
|
||||
#include <QFontDatabase>
|
||||
#include <QInputDialog>
|
||||
|
@ -67,6 +72,22 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
|
|||
// Remove what's this? button
|
||||
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
// Add version information to bottom of the window
|
||||
QString revision(OPENMW_VERSION_COMMITHASH);
|
||||
QString tag(OPENMW_VERSION_TAGHASH);
|
||||
|
||||
if (revision == tag) {
|
||||
versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION));
|
||||
} else {
|
||||
versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10)));
|
||||
}
|
||||
|
||||
// Add the compile date and time
|
||||
versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
|
||||
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
|
||||
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
|
||||
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
|
||||
|
||||
createIcons();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI
|
|||
|
||||
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
|
||||
: QSortFilterProxyModel (parent)
|
||||
{}
|
||||
{
|
||||
setSortCaseSensitivity (Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
|
||||
{
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
|
||||
# config file
|
||||
configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp")
|
||||
|
||||
# local files
|
||||
set(GAME
|
||||
main.cpp
|
||||
|
@ -12,7 +8,6 @@ if(NOT WIN32)
|
|||
endif()
|
||||
set(GAME_HEADER
|
||||
engine.hpp
|
||||
config.hpp
|
||||
)
|
||||
source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
#include <components/version/version.hpp>
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
#include <SDL.h>
|
||||
|
@ -30,8 +31,6 @@ extern int is_debugger_attached(void);
|
|||
#include <OSX/macUtils.h>
|
||||
#endif
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#include <boost/version.hpp>
|
||||
/**
|
||||
* Workaround for problems with whitespaces in paths in older versions of Boost library
|
||||
|
|
|
@ -57,6 +57,12 @@ namespace MWGui
|
|||
class InventoryWindow;
|
||||
class ContainerWindow;
|
||||
class DialogueWindow;
|
||||
|
||||
enum ShowInDialogueMode {
|
||||
ShowInDialogueMode_IfPossible,
|
||||
ShowInDialogueMode_Only,
|
||||
ShowInDialogueMode_Never
|
||||
};
|
||||
}
|
||||
|
||||
namespace SFO
|
||||
|
@ -226,7 +232,7 @@ namespace MWBase
|
|||
virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0;
|
||||
///< Hides dialog and schedules dialog to be deleted.
|
||||
|
||||
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false) = 0;
|
||||
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;
|
||||
virtual void staticMessageBox(const std::string& message) = 0;
|
||||
virtual void removeStaticMessageBox() = 0;
|
||||
virtual int readPressedButton() = 0;
|
||||
|
|
|
@ -529,8 +529,8 @@ namespace MWClass
|
|||
float moveSpeed;
|
||||
if(normalizedEncumbrance >= 1.0f)
|
||||
moveSpeed = 0.0f;
|
||||
else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
|
||||
world->isLevitationEnabled())
|
||||
else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
|
||||
world->isLevitationEnabled()))
|
||||
{
|
||||
float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
|
||||
mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace MWGui
|
|||
getWidget(mRightPane, "RightPane");
|
||||
getWidget(mArmorRating, "ArmorRating");
|
||||
|
||||
mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
|
||||
mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
|
||||
|
||||
getWidget(mItemView, "ItemView");
|
||||
mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected);
|
||||
|
@ -76,11 +76,12 @@ namespace MWGui
|
|||
|
||||
void InventoryWindow::adjustPanes()
|
||||
{
|
||||
const float aspect = 0.5; // fixed aspect ratio for the left pane
|
||||
mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 );
|
||||
mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4,
|
||||
const float aspect = 0.5; // fixed aspect ratio for the avatar image
|
||||
float leftPaneWidth = (mMainWidget->getSize().height-44-mArmorRating->getHeight()) * aspect;
|
||||
mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 );
|
||||
mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4,
|
||||
mRightPane->getPosition().top,
|
||||
mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15,
|
||||
mMainWidget->getSize().width - 12 - leftPaneWidth - 15,
|
||||
mMainWidget->getSize().height-44 );
|
||||
}
|
||||
|
||||
|
@ -418,9 +419,9 @@ namespace MWGui
|
|||
else
|
||||
{
|
||||
MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left);
|
||||
MyGUI::IntPoint relPos = mousePos - mAvatar->getAbsolutePosition ();
|
||||
int realX = int(float(relPos.left) / float(mAvatar->getSize().width) * 512.f );
|
||||
int realY = int(float(relPos.top) / float(mAvatar->getSize().height) * 1024.f );
|
||||
MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition ();
|
||||
int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f );
|
||||
int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f );
|
||||
|
||||
MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY);
|
||||
if (itemSelected.isEmpty ())
|
||||
|
@ -487,11 +488,18 @@ namespace MWGui
|
|||
if (mPreviewDirty)
|
||||
{
|
||||
mPreviewDirty = false;
|
||||
MyGUI::IntSize size = mAvatar->getSize();
|
||||
MyGUI::IntSize size = mAvatarImage->getSize();
|
||||
|
||||
mPreview.update (size.width, size.height);
|
||||
mAvatarImage->setSize(MyGUI::IntSize(std::max(mAvatar->getSize().width, 512), std::max(mAvatar->getSize().height, 1024)));
|
||||
|
||||
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)));
|
||||
|
||||
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
|
||||
+ boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
|
||||
if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
|
||||
mArmorRating->setCaptionWithReplacing (boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,9 +510,6 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells();
|
||||
|
||||
mPreviewDirty = true;
|
||||
|
||||
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
|
||||
+ boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
|
||||
}
|
||||
|
||||
void InventoryWindow::pickUpObject (MWWorld::Ptr object)
|
||||
|
@ -551,9 +556,4 @@ namespace MWGui
|
|||
|
||||
MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count);
|
||||
}
|
||||
|
||||
MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord ()
|
||||
{
|
||||
return mAvatar->getAbsoluteCoord ();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,6 @@ namespace MWGui
|
|||
|
||||
void pickUpObject (MWWorld::Ptr object);
|
||||
|
||||
MyGUI::IntCoord getAvatarScreenCoord();
|
||||
|
||||
MWWorld::Ptr getAvatarSelectedItem(int x, int y);
|
||||
|
||||
void rebuildAvatar() {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef MWGUI_MAPWINDOW_H
|
||||
#define MWGUI_MAPWINDOW_H
|
||||
|
||||
#include <libs/platform/stdint.h>
|
||||
|
||||
#include "windowpinnablebase.hpp"
|
||||
|
||||
namespace MWRender
|
||||
|
|
|
@ -245,11 +245,11 @@ namespace MWGui
|
|||
}
|
||||
mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding;
|
||||
|
||||
MyGUI::IntCoord absCoord;
|
||||
absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2;
|
||||
absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2;
|
||||
MyGUI::IntPoint absPos;
|
||||
absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
|
||||
absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
|
||||
|
||||
mMainWidget->setCoord(absCoord);
|
||||
mMainWidget->setPosition(absPos);
|
||||
mMainWidget->setSize(mainWidgetSize);
|
||||
|
||||
MyGUI::IntCoord messageWidgetCoord;
|
||||
|
|
|
@ -182,7 +182,7 @@ namespace MWGui
|
|||
}
|
||||
else if (type == "AvatarItemSelection")
|
||||
{
|
||||
MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord ();
|
||||
MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord();
|
||||
MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top);
|
||||
int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f );
|
||||
int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f );
|
||||
|
|
|
@ -648,19 +648,14 @@ namespace MWGui
|
|||
mGarbageDialogs.push_back(dialog);
|
||||
}
|
||||
|
||||
void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, bool showInDialogueModeOnly)
|
||||
void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode)
|
||||
{
|
||||
if (buttons.empty()) {
|
||||
/* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */
|
||||
if (getMode() == GM_Dialogue) {
|
||||
if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) {
|
||||
mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message));
|
||||
} else {
|
||||
if (showInDialogueModeOnly) {
|
||||
if (getMode() == GM_Dialogue)
|
||||
} else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) {
|
||||
mMessageBoxManager->createMessageBox(message);
|
||||
} else {
|
||||
mMessageBoxManager->createMessageBox(message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mMessageBoxManager->createInteractiveMessageBox(message, buttons);
|
||||
|
@ -1035,6 +1030,11 @@ namespace MWGui
|
|||
{
|
||||
mSelectedSpell = "";
|
||||
mHud->unsetSelectedSpell();
|
||||
|
||||
MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer();
|
||||
if (player->getDrawState() == MWMechanics::DrawState_Spell)
|
||||
player->setDrawState(MWMechanics::DrawState_Nothing);
|
||||
|
||||
mSpellWindow->setTitle("#{sNone}");
|
||||
}
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ namespace MWGui
|
|||
|
||||
virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted.
|
||||
|
||||
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false);
|
||||
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible);
|
||||
virtual void staticMessageBox(const std::string& message);
|
||||
virtual void removeStaticMessageBox();
|
||||
virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)
|
||||
|
|
|
@ -646,6 +646,10 @@ namespace MWInput
|
|||
if (!mControlSwitch["playermagic"])
|
||||
return;
|
||||
|
||||
// Not allowed if no spell selected
|
||||
if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty())
|
||||
return;
|
||||
|
||||
MWMechanics::DrawState_ state = mPlayer->getDrawState();
|
||||
if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
|
||||
mPlayer->setDrawState(MWMechanics::DrawState_Spell);
|
||||
|
@ -748,7 +752,8 @@ namespace MWInput
|
|||
|
||||
void InputManager::showQuickKeysMenu()
|
||||
{
|
||||
if (!MWBase::Environment::get().getWindowManager()->isGuiMode ())
|
||||
if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()
|
||||
&& MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1)
|
||||
MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu);
|
||||
else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu)
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu);
|
||||
|
|
|
@ -776,24 +776,7 @@ namespace MWMechanics
|
|||
{
|
||||
if(!paused)
|
||||
{
|
||||
// Note: we need to do this before any of the animations are updated.
|
||||
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
||||
// so updating VFX immediately after that would just remove the particle effects instantly.
|
||||
// There needs to be a magic effect update in between.
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
iter->second->updateContinuousVfx();
|
||||
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
{
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get(
|
||||
ESM::MagicEffect::Paralyze).mMagnitude > 0)
|
||||
iter->second->skipAnim();
|
||||
iter->second->update(duration);
|
||||
}
|
||||
}
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
// Reset data from previous frame
|
||||
for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
// Reset last hit object, which is only valid for one frame
|
||||
|
@ -802,6 +785,35 @@ namespace MWMechanics
|
|||
iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
|
||||
}
|
||||
|
||||
// AI and magic effects update
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
{
|
||||
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||
{
|
||||
updateActor(iter->first, duration);
|
||||
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
|
||||
updateNpc(iter->first, duration, paused);
|
||||
}
|
||||
}
|
||||
|
||||
// Looping magic VFX update
|
||||
// Note: we need to do this before any of the animations are updated.
|
||||
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
|
||||
// so updating VFX immediately after that would just remove the particle effects instantly.
|
||||
// There needs to be a magic effect update in between.
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
iter->second->updateContinuousVfx();
|
||||
|
||||
// Animation/movement update
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
|
||||
{
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get(
|
||||
ESM::MagicEffect::Paralyze).mMagnitude > 0)
|
||||
iter->second->skipAnim();
|
||||
iter->second->update(duration);
|
||||
}
|
||||
|
||||
// Kill dead actors
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
|
||||
{
|
||||
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
|
||||
|
@ -812,10 +824,6 @@ namespace MWMechanics
|
|||
if(iter->second->isDead())
|
||||
iter->second->resurrect();
|
||||
|
||||
updateActor(iter->first, duration);
|
||||
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
|
||||
updateNpc(iter->first, duration, paused);
|
||||
|
||||
if(!stats.isDead())
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ namespace MWMechanics
|
|||
float rangeMelee;
|
||||
float rangeCloseUp;
|
||||
bool distantCombat = false;
|
||||
if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_ThowWeapon)
|
||||
if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown)
|
||||
{
|
||||
rangeMelee = 1000; // TODO: should depend on archer skill
|
||||
rangeCloseUp = 0; //doesn't needed when attacking from distance
|
||||
|
|
|
@ -119,7 +119,7 @@ static const struct WeaponInfo {
|
|||
{ WeapType_TwoWide, "2w", "weapontwowide" },
|
||||
{ WeapType_BowAndArrow, "1h", "bowandarrow" },
|
||||
{ WeapType_Crossbow, "crossbow", "crossbow" },
|
||||
{ WeapType_ThowWeapon, "1h", "throwweapon" },
|
||||
{ WeapType_Thrown, "1h", "throwweapon" },
|
||||
{ WeapType_PickProbe, "1h", "pickprobe" },
|
||||
{ WeapType_Spell, "spell", "spellcast" },
|
||||
};
|
||||
|
@ -157,7 +157,14 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
|
||||
if(mHitState == CharState_None)
|
||||
{
|
||||
if(knockdown)
|
||||
if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0)
|
||||
{
|
||||
mHitState = CharState_KnockOut;
|
||||
mCurrentHit = "knockout";
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul);
|
||||
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
|
||||
}
|
||||
else if(knockdown)
|
||||
{
|
||||
mHitState = CharState_KnockDown;
|
||||
mCurrentHit = "knockdown";
|
||||
|
@ -187,6 +194,12 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
|
||||
mHitState = CharState_None;
|
||||
}
|
||||
else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0)
|
||||
{
|
||||
mHitState = CharState_KnockDown;
|
||||
mAnimation->disable(mCurrentHit);
|
||||
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
||||
|
@ -302,11 +315,24 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
if(!mCurrentMovement.empty())
|
||||
{
|
||||
float vel, speedmult = 1.0f;
|
||||
|
||||
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
||||
|
||||
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 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);
|
||||
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
|
||||
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
|
||||
|
||||
mMovementAnimVelocity = vel;
|
||||
}
|
||||
else mMovementAnimVelocity = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,7 +393,7 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I
|
|||
*weaptype = WeapType_Crossbow;
|
||||
break;
|
||||
case ESM::Weapon::MarksmanThrown:
|
||||
*weaptype = WeapType_ThowWeapon;
|
||||
*weaptype = WeapType_Thrown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -403,6 +429,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
, mIdleState(CharState_None)
|
||||
, mMovementState(CharState_None)
|
||||
, mMovementSpeed(0.0f)
|
||||
, mMovementAnimVelocity(0.0f)
|
||||
, mDeathState(CharState_None)
|
||||
, mHitState(CharState_None)
|
||||
, mUpperBodyState(UpperCharState_Nothing)
|
||||
|
@ -534,6 +561,7 @@ bool CharacterController::updateWeaponState()
|
|||
{
|
||||
getWeaponGroup(weaptype, weapgroup);
|
||||
mAnimation->showWeapons(false);
|
||||
mAnimation->setWeaponGroup(weapgroup);
|
||||
|
||||
mAnimation->play(weapgroup, Priority_Weapon,
|
||||
MWRender::Animation::Group_UpperBody, true,
|
||||
|
@ -588,6 +616,19 @@ bool CharacterController::updateWeaponState()
|
|||
if(isWeapon)
|
||||
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
||||
|
||||
// Cancel attack if we no longer have ammunition
|
||||
bool ammunition = true;
|
||||
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt);
|
||||
else if (mWeaponType == WeapType_BowAndArrow)
|
||||
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow);
|
||||
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
{
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
}
|
||||
|
||||
float complete;
|
||||
bool animPlaying;
|
||||
if(stats.getAttackingOrSpell())
|
||||
|
@ -692,10 +733,10 @@ bool CharacterController::updateWeaponState()
|
|||
if(item.getRefData().getCount())
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
|
||||
}
|
||||
else
|
||||
else if (ammunition)
|
||||
{
|
||||
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow ||
|
||||
mWeaponType == WeapType_ThowWeapon)
|
||||
mWeaponType == WeapType_Thrown)
|
||||
mAttackType = "shoot";
|
||||
else
|
||||
{
|
||||
|
@ -759,12 +800,59 @@ bool CharacterController::updateWeaponState()
|
|||
}
|
||||
}
|
||||
|
||||
mAnimation->setPitchFactor(0.f);
|
||||
if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
|
||||
{
|
||||
switch (mUpperBodyState)
|
||||
{
|
||||
case UpperCharState_StartToMinAttack:
|
||||
mAnimation->setPitchFactor(complete);
|
||||
break;
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
case UpperCharState_MinHitToHit:
|
||||
mAnimation->setPitchFactor(1.f);
|
||||
break;
|
||||
case UpperCharState_FollowStartToFollowStop:
|
||||
if (animPlaying)
|
||||
mAnimation->setPitchFactor(1.f-complete);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (mWeaponType == WeapType_Crossbow)
|
||||
{
|
||||
switch (mUpperBodyState)
|
||||
{
|
||||
case UpperCharState_EquipingWeap:
|
||||
mAnimation->setPitchFactor(complete);
|
||||
break;
|
||||
case UpperCharState_UnEquipingWeap:
|
||||
mAnimation->setPitchFactor(1.f-complete);
|
||||
break;
|
||||
case UpperCharState_WeapEquiped:
|
||||
case UpperCharState_StartToMinAttack:
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
case UpperCharState_MinHitToHit:
|
||||
case UpperCharState_FollowStartToFollowStop:
|
||||
mAnimation->setPitchFactor(1.f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!animPlaying)
|
||||
{
|
||||
if(mUpperBodyState == UpperCharState_EquipingWeap ||
|
||||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
||||
mUpperBodyState == UpperCharState_CastingSpell)
|
||||
{
|
||||
if (ammunition && mWeaponType == WeapType_Crossbow)
|
||||
mAnimation->attachArrow();
|
||||
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it
|
||||
if(mHitState == CharState_Hit)
|
||||
|
@ -773,6 +861,7 @@ bool CharacterController::updateWeaponState()
|
|||
//commenting out following 2 lines will give a bit different combat dynamics(slower)
|
||||
mHitState = CharState_None;
|
||||
mCurrentHit.clear();
|
||||
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
|
||||
}
|
||||
}
|
||||
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
|
||||
|
@ -788,6 +877,13 @@ bool CharacterController::updateWeaponState()
|
|||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
|
||||
if(!mAnimation->isPlaying(mCurrentWeapon))
|
||||
mAnimation->play(mCurrentWeapon, Priority_Weapon,
|
||||
MWRender::Animation::Group_UpperBody, false,
|
||||
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
||||
break;
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
if(mAttackType == "shoot")
|
||||
{
|
||||
|
@ -837,6 +933,22 @@ bool CharacterController::updateWeaponState()
|
|||
}
|
||||
}
|
||||
|
||||
//if playing combat animation and lowerbody is not busy switch to whole body animation
|
||||
if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying)
|
||||
{
|
||||
if( mMovementState != CharState_None ||
|
||||
mJumpState != JumpState_None ||
|
||||
mHitState != CharState_None ||
|
||||
MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
|
||||
cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
|
||||
{
|
||||
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
|
||||
}
|
||||
}
|
||||
|
||||
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()
|
||||
|
@ -1121,15 +1233,20 @@ void CharacterController::update(float duration)
|
|||
if (!mSkipAnim)
|
||||
{
|
||||
rot *= Ogre::Math::RadiansToDegrees(1.0f);
|
||||
if(mHitState != CharState_KnockDown)
|
||||
if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut)
|
||||
{
|
||||
world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
|
||||
}
|
||||
else //avoid z-rotating for knockdown
|
||||
world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true);
|
||||
|
||||
// always control actual movement by animation unless this:
|
||||
// FIXME: actor falling/landing should be controlled by physics engine
|
||||
if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None))
|
||||
{
|
||||
world->queueMovement(mPtr, vec);
|
||||
}
|
||||
}
|
||||
|
||||
movement = vec;
|
||||
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0;
|
||||
|
|
|
@ -93,6 +93,7 @@ enum CharacterState {
|
|||
|
||||
CharState_Hit,
|
||||
CharState_KnockDown,
|
||||
CharState_KnockOut,
|
||||
CharState_Block
|
||||
};
|
||||
|
||||
|
@ -105,7 +106,7 @@ enum WeaponType {
|
|||
WeapType_TwoWide,
|
||||
WeapType_BowAndArrow,
|
||||
WeapType_Crossbow,
|
||||
WeapType_ThowWeapon,
|
||||
WeapType_Thrown,
|
||||
WeapType_PickProbe,
|
||||
|
||||
WeapType_Spell
|
||||
|
@ -144,6 +145,7 @@ class CharacterController
|
|||
CharacterState mMovementState;
|
||||
std::string mCurrentMovement;
|
||||
float mMovementSpeed;
|
||||
float mMovementAnimVelocity;
|
||||
|
||||
CharacterState mDeathState;
|
||||
std::string mCurrentDeath;
|
||||
|
|
|
@ -179,9 +179,6 @@ namespace MWMechanics
|
|||
|
||||
mDynamic[index] = value;
|
||||
|
||||
if (index == 2 && value.getCurrent() < 0)
|
||||
setKnockedDown(true);
|
||||
|
||||
if (index==0 && mDynamic[index].getCurrent()<1)
|
||||
{
|
||||
if (!mDead)
|
||||
|
|
|
@ -220,16 +220,18 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
|
|||
/// \todo check if character is the player, if levelling is ever implemented for NPCs
|
||||
MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1);
|
||||
|
||||
std::vector <std::string> noButtons;
|
||||
|
||||
std::stringstream message;
|
||||
message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""))
|
||||
% std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}")
|
||||
% static_cast<int> (base);
|
||||
MWBase::Environment::get().getWindowManager ()->messageBox(message.str());
|
||||
MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), noButtons, MWGui::ShowInDialogueMode_Never);
|
||||
|
||||
if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt())
|
||||
{
|
||||
// levelup is possible now
|
||||
MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}");
|
||||
MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", noButtons, MWGui::ShowInDialogueMode_Never);
|
||||
}
|
||||
|
||||
getSkill (skillIndex).setBase (base);
|
||||
|
|
|
@ -158,7 +158,9 @@ namespace
|
|||
namespace MWMechanics
|
||||
{
|
||||
PathFinder::PathFinder()
|
||||
:mIsPathConstructed(false),mIsGraphConstructed(false)
|
||||
: mIsPathConstructed(false),
|
||||
mIsGraphConstructed(false),
|
||||
mCell(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,24 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectIt->mEffectID);
|
||||
|
||||
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate)
|
||||
{
|
||||
if (caster.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() &&
|
||||
(effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention ||
|
||||
effectIt->mEffectID == ESM::MagicEffect::DivineIntervention ||
|
||||
effectIt->mEffectID == ESM::MagicEffect::Mark ||
|
||||
effectIt->mEffectID == ESM::MagicEffect::Recall))
|
||||
{
|
||||
if (caster.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// If player is healing someone, show the target's HP bar
|
||||
if (caster.getRefData().getHandle() == "player" && target != caster
|
||||
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
|
||||
|
|
|
@ -53,7 +53,6 @@ void Animation::EffectAnimationTime::setValue(Ogre::Real)
|
|||
|
||||
Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
|
||||
: mPtr(ptr)
|
||||
, mCamera(NULL)
|
||||
, mInsert(node)
|
||||
, mSkelBase(NULL)
|
||||
, mAccumRoot(NULL)
|
||||
|
@ -696,6 +695,12 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
|
|||
else
|
||||
mPtr.getClass().hit(mPtr);
|
||||
}
|
||||
else if (evt.compare(off, len, "shoot attach") == 0)
|
||||
attachArrow();
|
||||
else if (evt.compare(off, len, "shoot release") == 0)
|
||||
releaseArrow();
|
||||
else if (evt.compare(off, len, "shoot follow attach") == 0)
|
||||
attachArrow();
|
||||
|
||||
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
|
||||
MWBase::Environment::get().getWorld()->castSpell(mPtr);
|
||||
|
@ -870,6 +875,27 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp
|
|||
return true;
|
||||
}
|
||||
|
||||
float Animation::getStartTime(const std::string &groupname) const
|
||||
{
|
||||
AnimSourceList::const_iterator iter(mAnimSources.begin());
|
||||
for(;iter != mAnimSources.end();iter++)
|
||||
{
|
||||
const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
|
||||
NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
|
||||
if(found != keys.end())
|
||||
return found->first;
|
||||
}
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
float Animation::getCurrentTime(const std::string &groupname) const
|
||||
{
|
||||
AnimStateMap::const_iterator iter = mStates.find(groupname);
|
||||
if(iter == mStates.end())
|
||||
return -1.f;
|
||||
|
||||
return iter->second.mTime;
|
||||
}
|
||||
|
||||
void Animation::disable(const std::string &groupname)
|
||||
{
|
||||
|
@ -1044,9 +1070,8 @@ bool Animation::allowSwitchViewMode() const
|
|||
{
|
||||
for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
|
||||
{
|
||||
if(stateiter->second.mGroups == Group_UpperBody
|
||||
|| (stateiter->first.size()==4 && stateiter->first.find("hit") != std::string::npos)
|
||||
|| (stateiter->first.find("knock") != std::string::npos) )
|
||||
if(stateiter->second.mPriority > MWMechanics::Priority_Movement
|
||||
&& stateiter->second.mPriority < MWMechanics::Priority_Torch)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -121,7 +121,6 @@ protected:
|
|||
std::vector<EffectParams> mEffects;
|
||||
|
||||
MWWorld::Ptr mPtr;
|
||||
Camera *mCamera;
|
||||
|
||||
Ogre::SceneNode *mInsert;
|
||||
Ogre::Entity *mSkelBase;
|
||||
|
@ -271,27 +270,37 @@ public:
|
|||
*/
|
||||
bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
|
||||
|
||||
/// Get the absolute position in the animation track of the first text key with the given group.
|
||||
float getStartTime(const std::string &groupname) const;
|
||||
|
||||
/// Get the current absolute position in the animation track for the animation that is currently playing from the given group.
|
||||
float getCurrentTime(const std::string& groupname) const;
|
||||
|
||||
/** Disables the specified animation group;
|
||||
* \param groupname Animation group to disable.
|
||||
*/
|
||||
void disable(const std::string &groupname);
|
||||
void changeGroups(const std::string &groupname, int group);
|
||||
|
||||
virtual void setWeaponGroup(const std::string& group) {}
|
||||
|
||||
/** Retrieves the velocity (in units per second) that the animation will move. */
|
||||
float getVelocity(const std::string &groupname) const;
|
||||
|
||||
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
|
||||
/// to indicate the facing orientation of the character.
|
||||
virtual void setPitchFactor(float factor) {}
|
||||
|
||||
virtual Ogre::Vector3 runAnimation(float duration);
|
||||
|
||||
virtual void showWeapons(bool showWeapon);
|
||||
virtual void showCarriedLeft(bool show) {}
|
||||
|
||||
virtual void attachArrow() {}
|
||||
virtual void releaseArrow() {}
|
||||
void enableLights(bool enable);
|
||||
|
||||
Ogre::AxisAlignedBox getWorldBounds();
|
||||
|
||||
void setCamera(Camera *cam)
|
||||
{ mCamera = cam; }
|
||||
|
||||
Ogre::Node *getNode(const std::string &name);
|
||||
|
||||
// Attaches the given object to a bone on this object's base skeleton. If the bone doesn't
|
||||
|
|
|
@ -338,11 +338,9 @@ namespace MWRender
|
|||
if(mAnimation && mAnimation != anim)
|
||||
{
|
||||
mAnimation->setViewMode(NpcAnimation::VM_Normal);
|
||||
mAnimation->setCamera(NULL);
|
||||
mAnimation->detachObjectFromBone(mCamera);
|
||||
}
|
||||
mAnimation = anim;
|
||||
mAnimation->setCamera(this);
|
||||
|
||||
processViewChange();
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace MWRender
|
|||
|
||||
|
||||
InventoryPreview::InventoryPreview(MWWorld::Ptr character)
|
||||
: CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 62, -200), Ogre::Vector3(0, 62, 0))
|
||||
: CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0))
|
||||
, mSelectionBuffer(NULL)
|
||||
{
|
||||
}
|
||||
|
@ -161,13 +161,13 @@ namespace MWRender
|
|||
type == ESM::Weapon::LongBladeOneHand ||
|
||||
type == ESM::Weapon::BluntOneHand ||
|
||||
type == ESM::Weapon::AxeOneHand ||
|
||||
type == ESM::Weapon::MarksmanThrown)
|
||||
type == ESM::Weapon::MarksmanThrown ||
|
||||
type == ESM::Weapon::MarksmanCrossbow ||
|
||||
type == ESM::Weapon::MarksmanBow)
|
||||
groupname = "inventoryweapononehand";
|
||||
else if(type == ESM::Weapon::LongBladeTwoHand ||
|
||||
type == ESM::Weapon::BluntTwoClose ||
|
||||
type == ESM::Weapon::AxeTwoHand ||
|
||||
type == ESM::Weapon::MarksmanCrossbow ||
|
||||
type == ESM::Weapon::MarksmanBow)
|
||||
type == ESM::Weapon::AxeTwoHand)
|
||||
groupname = "inventoryweapontwohand";
|
||||
else if(type == ESM::Weapon::BluntTwoWide ||
|
||||
type == ESM::Weapon::SpearTwoWide)
|
||||
|
|
|
@ -291,7 +291,7 @@ namespace MWRender
|
|||
unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc;
|
||||
|
||||
assert(imageX < image.getWidth());
|
||||
assert(imageY < image.getWidth());
|
||||
assert(imageY < image.getHeight());
|
||||
|
||||
if (image.getColourAt(imageX, imageY, 0).a > 0)
|
||||
exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY));
|
||||
|
|
|
@ -71,6 +71,27 @@ float HeadAnimationTime::getValue() const
|
|||
return 1;
|
||||
}
|
||||
|
||||
float WeaponAnimationTime::getValue() const
|
||||
{
|
||||
if (mWeaponGroup.empty())
|
||||
return 0;
|
||||
float current = mAnimation->getCurrentTime(mWeaponGroup);
|
||||
if (current == -1)
|
||||
return 0;
|
||||
return current - mStartTime;
|
||||
}
|
||||
|
||||
void WeaponAnimationTime::setGroup(const std::string &group)
|
||||
{
|
||||
mWeaponGroup = group;
|
||||
mStartTime = mAnimation->getStartTime(mWeaponGroup);
|
||||
}
|
||||
|
||||
void WeaponAnimationTime::updateStartTime()
|
||||
{
|
||||
setGroup(mWeaponGroup);
|
||||
}
|
||||
|
||||
static NpcAnimation::PartBoneMap createPartListMap()
|
||||
{
|
||||
NpcAnimation::PartBoneMap result;
|
||||
|
@ -121,11 +142,13 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
|
|||
mShowCarriedLeft(true),
|
||||
mFirstPersonOffset(0.f, 0.f, 0.f),
|
||||
mAlpha(1.f),
|
||||
mNpcType(Type_Normal)
|
||||
mNpcType(Type_Normal),
|
||||
mPitchFactor(0)
|
||||
{
|
||||
mNpc = mPtr.get<ESM::NPC>()->mBase;
|
||||
|
||||
mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr));
|
||||
mWeaponAnimationTime = Ogre::SharedPtr<WeaponAnimationTime>(new WeaponAnimationTime(this));
|
||||
|
||||
for(size_t i = 0;i < ESM::PRT_Count;i++)
|
||||
{
|
||||
|
@ -223,6 +246,8 @@ void NpcAnimation::updateNpcBase()
|
|||
for(size_t i = 0;i < ESM::PRT_Count;i++)
|
||||
removeIndividualPart((ESM::PartReferenceType)i);
|
||||
updateParts();
|
||||
|
||||
mWeaponAnimationTime->updateStartTime();
|
||||
}
|
||||
|
||||
void NpcAnimation::updateParts()
|
||||
|
@ -498,16 +523,25 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
|
|||
Ogre::Vector3 ret = Animation::runAnimation(timepassed);
|
||||
|
||||
Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton();
|
||||
if(mViewMode == VM_FirstPerson && mCamera)
|
||||
if(mViewMode == VM_FirstPerson)
|
||||
{
|
||||
float pitch = mCamera->getPitch();
|
||||
float pitch = mPtr.getRefData().getPosition().rot[0];
|
||||
Ogre::Node *node = baseinst->getBone("Bip01 Neck");
|
||||
node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD);
|
||||
node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD);
|
||||
|
||||
// This has to be done before this function ends;
|
||||
// updateSkeletonInstance, below, touches the hands.
|
||||
node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD);
|
||||
}
|
||||
else if (mPitchFactor > 0)
|
||||
{
|
||||
// In third person mode we may still need pitch for ranged weapon targeting
|
||||
float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor;
|
||||
Ogre::Node *node = baseinst->getBone("Bip01 Spine2");
|
||||
node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
|
||||
node = baseinst->getBone("Bip01 Spine1");
|
||||
node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
|
||||
}
|
||||
mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame.
|
||||
|
||||
for(size_t i = 0;i < ESM::PRT_Count;i++)
|
||||
|
@ -588,9 +622,6 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
|||
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// type == ESM::PRT_Weapon should get an animation source based on the current offset
|
||||
// of the weapon attack animation (from its beginning, or start marker?)
|
||||
std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[type]->mControllers.begin());
|
||||
for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++)
|
||||
{
|
||||
|
@ -600,6 +631,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
|||
|
||||
if (type == ESM::PRT_Head)
|
||||
ctrl->setSource(mHeadAnimationTime);
|
||||
else if (type == ESM::PRT_Weapon)
|
||||
ctrl->setSource(mWeaponAnimationTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,6 +696,18 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon);
|
||||
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
|
||||
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
|
||||
|
||||
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
|
||||
attachArrow();
|
||||
else
|
||||
mAmmunition.setNull();
|
||||
}
|
||||
else
|
||||
mAmmunition.setNull();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -693,6 +738,52 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
removeIndividualPart(ESM::PRT_Shield);
|
||||
}
|
||||
|
||||
void NpcAnimation::attachArrow()
|
||||
{
|
||||
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (weaponSlot != inv.end() && weaponSlot->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
showWeapons(true);
|
||||
else
|
||||
{
|
||||
NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon];
|
||||
|
||||
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo == inv.end())
|
||||
return;
|
||||
std::string model = ammo->getClass().getModel(*ammo);
|
||||
|
||||
mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model);
|
||||
Ogre::Vector3 glowColor = getEnchantmentColor(*ammo);
|
||||
setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0,
|
||||
!ammo->getClass().getEnchantment(*ammo).empty(), &glowColor);
|
||||
|
||||
std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
|
||||
std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
|
||||
}
|
||||
}
|
||||
|
||||
void NpcAnimation::releaseArrow()
|
||||
{
|
||||
// Thrown weapons get detached now
|
||||
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (weapon != inv.end() && weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
{
|
||||
showWeapons(false);
|
||||
inv.remove(*weapon, 1, mPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// With bows and crossbows only the used arrow/bolt gets detached
|
||||
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo == inv.end())
|
||||
return;
|
||||
inv.remove(*ammo, 1, mPtr);
|
||||
mAmmunition.setNull();
|
||||
}
|
||||
}
|
||||
|
||||
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
|
||||
{
|
||||
// During first auto equip, we don't play any sounds.
|
||||
|
|
|
@ -25,6 +25,23 @@ public:
|
|||
{ }
|
||||
};
|
||||
|
||||
class WeaponAnimationTime : public Ogre::ControllerValue<Ogre::Real>
|
||||
{
|
||||
private:
|
||||
Animation* mAnimation;
|
||||
std::string mWeaponGroup;
|
||||
float mStartTime;
|
||||
public:
|
||||
WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {}
|
||||
void setGroup(const std::string& group);
|
||||
void updateStartTime();
|
||||
|
||||
virtual Ogre::Real getValue() const;
|
||||
virtual void setValue(Ogre::Real value)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener
|
||||
{
|
||||
public:
|
||||
|
@ -71,8 +88,10 @@ private:
|
|||
Ogre::Vector3 mFirstPersonOffset;
|
||||
|
||||
Ogre::SharedPtr<HeadAnimationTime> mHeadAnimationTime;
|
||||
Ogre::SharedPtr<WeaponAnimationTime> mWeaponAnimationTime;
|
||||
|
||||
float mAlpha;
|
||||
float mPitchFactor;
|
||||
|
||||
void updateNpcBase();
|
||||
|
||||
|
@ -105,11 +124,22 @@ public:
|
|||
ViewMode viewMode=VM_Normal);
|
||||
virtual ~NpcAnimation();
|
||||
|
||||
virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); }
|
||||
|
||||
virtual Ogre::Vector3 runAnimation(float timepassed);
|
||||
|
||||
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
|
||||
/// to indicate the facing orientation of the character.
|
||||
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
||||
|
||||
virtual void showWeapons(bool showWeapon);
|
||||
virtual void showCarriedLeft(bool showa);
|
||||
|
||||
virtual void attachArrow();
|
||||
virtual void releaseArrow();
|
||||
|
||||
NifOgre::ObjectScenePtr mAmmunition;
|
||||
|
||||
void setViewMode(ViewMode viewMode);
|
||||
|
||||
void updateParts();
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
#include "terrainstorage.hpp"
|
||||
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
#include <OgreRenderSystem.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -53,4 +62,509 @@ 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,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
|
||||
{
|
||||
// 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;
|
||||
|
||||
std::vector<uint8_t> colors;
|
||||
colors.resize(numVerts*numVerts*4);
|
||||
std::vector<float> positions;
|
||||
positions.resize(numVerts*numVerts*3);
|
||||
std::vector<float> normals;
|
||||
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(&colors[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
|
||||
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
|
||||
land->loadData(ESM::Land::DATA_VTEX);
|
||||
|
||||
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 "_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);
|
||||
|
||||
std::string texture = ltex->mTexture;
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
|
||||
bool pack, std::vector<Ogre::TexturePtr> &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;
|
||||
std::vector<Ogre::uchar> data;
|
||||
data.resize(blendmapSize * blendmapSize * channels, 0);
|
||||
|
||||
for (int i=0; i<numBlendmaps; ++i)
|
||||
{
|
||||
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format);
|
||||
|
||||
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)
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 255;
|
||||
else
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// All done, upload to GPU
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
|
||||
map->loadRawData(stream, blendmapSize, blendmapSize, format);
|
||||
blendmaps.push_back(map);
|
||||
}
|
||||
}
|
||||
|
||||
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 = "textures\\" + texture;
|
||||
std::string texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_nh.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
{
|
||||
info.mNormalMap = "textures\\" + texture_;
|
||||
info.mParallax = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_n.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
info.mNormalMap = "textures\\" + texture_;
|
||||
}
|
||||
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_diffusespec.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
{
|
||||
info.mDiffuseMap = "textures\\" + texture_;
|
||||
info.mSpecular = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,8 +12,75 @@ namespace MWRender
|
|||
virtual ESM::Land* getLand (int cellX, int cellY);
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
|
||||
public:
|
||||
|
||||
/// Get bounds of the whole terrain in cell units
|
||||
virtual Ogre::AxisAlignedBox getBounds();
|
||||
///< Get bounds in cell units
|
||||
|
||||
/// Get the minimum and maximum heights of a terrain chunk.
|
||||
/// @note Should only be called for chunks <= 1 cell, 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.
|
||||
/// @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 vertexBuffer buffer to write vertices
|
||||
/// @param normalBuffer buffer to write vertex normals
|
||||
/// @param colourBuffer buffer to write vertex colours
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
|
||||
|
||||
/// 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.
|
||||
/// @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::TexturePtr>& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace MWScript
|
|||
msgBox = boost::str(boost::format(msgBox) % count % itemName);
|
||||
}
|
||||
std::vector <std::string> noButtons;
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -142,7 +142,7 @@ namespace MWScript
|
|||
msgBox = boost::str (boost::format(msgBox) % itemName);
|
||||
}
|
||||
std::vector <std::string> noButtons;
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
@ -451,6 +452,14 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id);
|
||||
|
||||
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
|
||||
|
||||
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
|
||||
id == wm->getSelectedSpell())
|
||||
{
|
||||
wm->unsetSelectedSpell();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -461,6 +461,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem(
|
|||
}
|
||||
|
||||
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor)
|
||||
{
|
||||
int retCount = ContainerStore::remove(item, count, actor);
|
||||
|
||||
if (!item.getRefData().getCount())
|
||||
{
|
||||
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||
{
|
||||
|
@ -469,13 +473,11 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
|
|||
|
||||
if (*mSlots[slot] == item)
|
||||
{
|
||||
// restacking is disabled cause it may break removal
|
||||
unequipSlot(slot, actor, false);
|
||||
unequipSlot(slot, actor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int retCount = ContainerStore::remove(item, count, actor);
|
||||
}
|
||||
|
||||
// If an armor/clothing item is removed, try to find a replacement,
|
||||
// but not for the player nor werewolves.
|
||||
|
@ -500,9 +502,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
|
|||
return retCount;
|
||||
}
|
||||
|
||||
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack)
|
||||
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor)
|
||||
{
|
||||
ContainerStoreIterator it = getSlot(slot);
|
||||
ContainerStoreIterator it = mSlots[slot];
|
||||
|
||||
if (it != end())
|
||||
{
|
||||
|
@ -511,8 +513,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
|
|||
// empty this slot
|
||||
mSlots[slot] = end();
|
||||
|
||||
if (restack) {
|
||||
// restack item previously in this slot
|
||||
// restack the previously equipped item with other (non-equipped) items
|
||||
for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
|
||||
{
|
||||
if (stacks(*iter, *it))
|
||||
|
@ -523,7 +524,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actor.getRefData().getHandle() == "player")
|
||||
{
|
||||
|
|
|
@ -168,12 +168,10 @@ namespace MWWorld
|
|||
///
|
||||
/// @return the number of items actually removed
|
||||
|
||||
ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true);
|
||||
ContainerStoreIterator unequipSlot(int slot, const Ptr& actor);
|
||||
///< Unequip \a slot.
|
||||
///
|
||||
/// @return an iterator to the item that was previously in the slot
|
||||
/// (if \a restack is true, the item can be re-stacked so its count
|
||||
/// may differ from when it was equipped).
|
||||
|
||||
ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor);
|
||||
///< Unequip an item identified by its Ptr. An exception is thrown
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
namespace ESM
|
||||
{
|
||||
class ObjectState;
|
||||
struct ObjectState;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace ESM
|
|||
{
|
||||
class Script;
|
||||
class CellRef;
|
||||
class ObjectState;
|
||||
struct ObjectState;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
|
|
157
cmake/GetGitRevisionDescription.cmake
Normal file
157
cmake/GetGitRevisionDescription.cmake
Normal file
|
@ -0,0 +1,157 @@
|
|||
# - Returns a version string from Git
|
||||
#
|
||||
# These functions force a re-configure on each git commit so that you can
|
||||
# trust the values of the variables in your build system.
|
||||
#
|
||||
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the refspec and sha hash of the current head revision
|
||||
#
|
||||
# git_describe(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe on the source tree, and adjusting
|
||||
# the output so that it tests false if an error occurs.
|
||||
#
|
||||
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe --exact-match on the source tree,
|
||||
# and adjusting the output so that it tests false if there was no exact
|
||||
# matching tag.
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
if(__get_git_revision_description)
|
||||
return()
|
||||
endif()
|
||||
set(__get_git_revision_description YES)
|
||||
|
||||
# We must run the following at "include" time, not at function call time,
|
||||
# to find the path to this module rather than the path to a calling list file
|
||||
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
|
||||
|
||||
function(get_git_head_revision _refspecvar _hashvar)
|
||||
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
|
||||
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
|
||||
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
|
||||
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
endwhile()
|
||||
|
||||
# check if this is a submodule
|
||||
if(NOT IS_DIRECTORY ${GIT_DIR})
|
||||
file(READ ${GIT_DIR} submodule)
|
||||
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
|
||||
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
|
||||
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
|
||||
endif()
|
||||
|
||||
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
|
||||
|
||||
if(NOT EXISTS "${GIT_DATA}")
|
||||
file(MAKE_DIRECTORY "${GIT_DATA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${GIT_DIR}/HEAD")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(HEAD_FILE "${GIT_DATA}/HEAD")
|
||||
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
|
||||
|
||||
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
|
||||
"${GIT_DATA}/grabRef.cmake" @ONLY)
|
||||
include("${GIT_DATA}/grabRef.cmake")
|
||||
|
||||
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
|
||||
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_describe _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
|
||||
#get_git_head_revision(refspec hash)
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
#if(NOT hash)
|
||||
# set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
# return()
|
||||
#endif()
|
||||
|
||||
# TODO sanitize
|
||||
#if((${ARGN}" MATCHES "&&") OR
|
||||
# (ARGN MATCHES "||") OR
|
||||
# (ARGN MATCHES "\\;"))
|
||||
# message("Please report the following error to the project!")
|
||||
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
|
||||
#endif()
|
||||
|
||||
#message(STATUS "Arguments to execute_process: ${ARGN}")
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
describe
|
||||
#${hash}
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(get_git_tag_revision _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
rev-list
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
38
cmake/GetGitRevisionDescription.cmake.in
Normal file
38
cmake/GetGitRevisionDescription.cmake.in
Normal file
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
# Internal file for GetGitRevisionDescription.cmake
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
set(HEAD_HASH)
|
||||
|
||||
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
|
||||
|
||||
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
|
||||
if(HEAD_CONTENTS MATCHES "ref")
|
||||
# named branch
|
||||
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
|
||||
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
set(HEAD_HASH "${HEAD_REF}")
|
||||
endif()
|
||||
else()
|
||||
# detached HEAD
|
||||
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
endif()
|
||||
|
||||
if(NOT HEAD_HASH)
|
||||
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
|
||||
string(STRIP "${HEAD_HASH}" HEAD_HASH)
|
||||
endif()
|
|
@ -1,5 +1,9 @@
|
|||
project (Components)
|
||||
set (CMAKE_BUILD_TYPE DEBUG)
|
||||
|
||||
# Version file
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp")
|
||||
|
||||
# source files
|
||||
|
||||
add_component_dir (settings
|
||||
|
@ -79,6 +83,10 @@ add_component_dir (ogreinit
|
|||
ogreinit ogreplugin
|
||||
)
|
||||
|
||||
add_component_dir (version
|
||||
version
|
||||
)
|
||||
|
||||
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef COMPILER_EXTENSIONS_H_INCLUDED
|
||||
#define COMPILER_EXTENSINOS_H_INCLUDED
|
||||
#define COMPILER_EXTENSIONS_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
|
|
@ -48,9 +48,10 @@ namespace Interpreter
|
|||
}
|
||||
else if (c=='f' || c=='F' || c=='.')
|
||||
{
|
||||
while (c!='f' && i<message.size())
|
||||
while (c!='f' && i+1<message.size())
|
||||
{
|
||||
++i;
|
||||
c = message[i];
|
||||
}
|
||||
|
||||
float value = runtime[0].mFloat;
|
||||
|
|
|
@ -749,8 +749,8 @@ class NIFObjectLoader
|
|||
emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f,
|
||||
partctrl->velocity + partctrl->velocityRandom*0.5f);
|
||||
emitter->setEmissionRate(partctrl->emitRate);
|
||||
emitter->setTimeToLive(partctrl->lifetime - partctrl->lifetimeRandom*0.5f,
|
||||
partctrl->lifetime + partctrl->lifetimeRandom*0.5f);
|
||||
emitter->setTimeToLive(partctrl->lifetime,
|
||||
partctrl->lifetime + partctrl->lifetimeRandom);
|
||||
emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x));
|
||||
emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y));
|
||||
emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z));
|
||||
|
@ -882,6 +882,9 @@ class NIFObjectLoader
|
|||
Ogre::ControllerFunctionRealPtr func(function);
|
||||
|
||||
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
||||
|
||||
if (partflags&Nif::NiNode::ParticleFlag_AutoPlay)
|
||||
partsys->fastForward(1, 0.1);
|
||||
}
|
||||
ctrl = ctrl->next;
|
||||
}
|
||||
|
|
|
@ -18,11 +18,13 @@ namespace Terrain
|
|||
mVertexData = OGRE_NEW Ogre::VertexData;
|
||||
mVertexData->vertexStart = 0;
|
||||
|
||||
unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
|
||||
|
||||
// Set the total number of vertices
|
||||
size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1);
|
||||
size_t numVertsOneSide = mNode->getSize() * (verts-1);
|
||||
numVertsOneSide /= 1 << lodLevel;
|
||||
numVertsOneSide += 1;
|
||||
assert((int)numVertsOneSide == ESM::Land::LAND_SIZE);
|
||||
assert(numVertsOneSide == verts);
|
||||
mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
|
||||
|
||||
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)
|
||||
|
|
|
@ -168,7 +168,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
|
|||
if (mParent)
|
||||
pos = mParent->getCenter();
|
||||
pos = mCenter - pos;
|
||||
mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0));
|
||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
||||
mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0));
|
||||
|
||||
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
|
||||
}
|
||||
|
@ -203,6 +204,7 @@ void QuadTreeNode::initNeighbours()
|
|||
|
||||
void QuadTreeNode::initAabb()
|
||||
{
|
||||
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
|
||||
if (hasChildren())
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
|
@ -210,11 +212,11 @@ void QuadTreeNode::initAabb()
|
|||
mChildren[i]->initAabb();
|
||||
mBounds.merge(mChildren[i]->getBoundingBox());
|
||||
}
|
||||
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z),
|
||||
Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z));
|
||||
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z),
|
||||
Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, mBounds.getMaximum().z));
|
||||
}
|
||||
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0),
|
||||
mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0));
|
||||
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0),
|
||||
mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0));
|
||||
}
|
||||
|
||||
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
|
||||
|
@ -430,11 +432,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
|
|||
// TODO - store this default material somewhere instead of creating one for each empty cell
|
||||
MaterialGenerator matGen(mTerrain->getShadersEnabled());
|
||||
std::vector<LayerInfo> layer;
|
||||
LayerInfo info;
|
||||
info.mDiffuseMap = "textures\\_land_default.dds";
|
||||
info.mParallax = false;
|
||||
info.mSpecular = false;
|
||||
layer.push_back(info);
|
||||
layer.push_back(mTerrain->getStorage()->getDefaultLayer());
|
||||
matGen.setLayerList(layer);
|
||||
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
|
||||
return;
|
||||
|
|
|
@ -108,7 +108,7 @@ namespace Terrain
|
|||
void destroyChunks(bool children);
|
||||
|
||||
/// Get the effective LOD level if this node was rendered in one chunk
|
||||
/// with ESM::Land::LAND_SIZE^2 vertices
|
||||
/// with Storage::getCellVertices^2 vertices
|
||||
size_t getNativeLodLevel() { return mLodLevel; }
|
||||
|
||||
/// Get the effective current LOD level used by the chunk rendering this node
|
||||
|
|
|
@ -1,509 +0,0 @@
|
|||
#include "storage.hpp"
|
||||
|
||||
#include <OgreVector2.h>
|
||||
#include <OgreTextureManager.h>
|
||||
#include <OgreStringConverter.h>
|
||||
#include <OgreRenderSystem.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
||||
struct VertexElement
|
||||
{
|
||||
Ogre::Vector3 pos;
|
||||
Ogre::Vector3 normal;
|
||||
Ogre::ColourValue colour;
|
||||
};
|
||||
|
||||
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,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
|
||||
{
|
||||
// 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;
|
||||
|
||||
std::vector<uint8_t> colors;
|
||||
colors.resize(numVerts*numVerts*4);
|
||||
std::vector<float> positions;
|
||||
positions.resize(numVerts*numVerts*3);
|
||||
std::vector<float> normals;
|
||||
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(&colors[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
|
||||
|
||||
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
|
||||
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
|
||||
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!land->isDataLoaded(ESM::Land::DATA_VTEX))
|
||||
land->loadData(ESM::Land::DATA_VTEX);
|
||||
|
||||
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 "_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);
|
||||
|
||||
std::string texture = ltex->mTexture;
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
|
||||
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<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;
|
||||
std::vector<Ogre::uchar> data;
|
||||
data.resize(blendmapSize * blendmapSize * channels, 0);
|
||||
|
||||
for (int i=0; i<numBlendmaps; ++i)
|
||||
{
|
||||
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
|
||||
static int count=0;
|
||||
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/"
|
||||
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format);
|
||||
|
||||
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)
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 255;
|
||||
else
|
||||
data[y*blendmapSize*channels + x*channels + channel] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// All done, upload to GPU
|
||||
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
|
||||
map->loadRawData(stream, blendmapSize, blendmapSize, format);
|
||||
blendmaps.push_back(map);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
LayerInfo Storage::getLayerInfo(const std::string& texture)
|
||||
{
|
||||
// Already have this cached?
|
||||
if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
|
||||
return mLayerInfoMap[texture];
|
||||
|
||||
LayerInfo info;
|
||||
info.mParallax = false;
|
||||
info.mSpecular = false;
|
||||
info.mDiffuseMap = "textures\\" + texture;
|
||||
std::string texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_nh.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
{
|
||||
info.mNormalMap = "textures\\" + texture_;
|
||||
info.mParallax = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_n.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
info.mNormalMap = "textures\\" + texture_;
|
||||
}
|
||||
|
||||
texture_ = texture;
|
||||
boost::replace_last(texture_, ".", "_diffusespec.");
|
||||
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
|
||||
{
|
||||
info.mDiffuseMap = "textures\\" + texture_;
|
||||
info.mSpecular = true;
|
||||
}
|
||||
|
||||
mLayerInfoMap[texture] = info;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -24,9 +24,6 @@ namespace Terrain
|
|||
{
|
||||
public:
|
||||
virtual ~Storage() {}
|
||||
private:
|
||||
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
|
||||
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
|
||||
|
||||
public:
|
||||
/// Get bounds of the whole terrain in cell units
|
||||
|
@ -40,7 +37,7 @@ namespace Terrain
|
|||
/// @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
|
||||
bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
|
||||
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
|
||||
|
||||
/// Fill vertex buffers for a terrain chunk.
|
||||
/// @param lodLevel LOD level, 0 = most detailed
|
||||
|
@ -49,10 +46,10 @@ namespace Terrain
|
|||
/// @param vertexBuffer buffer to write vertices
|
||||
/// @param normalBuffer buffer to write vertex normals
|
||||
/// @param colourBuffer buffer to write vertex colours
|
||||
void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
|
||||
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
|
||||
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
|
||||
Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0;
|
||||
|
||||
/// 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
|
||||
|
@ -64,31 +61,19 @@ namespace Terrain
|
|||
/// 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
|
||||
void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
|
||||
std::vector<Ogre::TexturePtr>& blendmaps,
|
||||
std::vector<LayerInfo>& layerList);
|
||||
std::vector<LayerInfo>& layerList) = 0;
|
||||
|
||||
float getHeightAt (const Ogre::Vector3& worldPos);
|
||||
virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
|
||||
|
||||
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);
|
||||
virtual LayerInfo getDefaultLayer() = 0;
|
||||
|
||||
float getVertexHeight (const ESM::Land* land, int x, int y);
|
||||
/// Get the transformation factor for mapping cell units to world units.
|
||||
virtual float getCellWorldSize() = 0;
|
||||
|
||||
// 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, LayerInfo> mLayerInfoMap;
|
||||
|
||||
LayerInfo getLayerInfo(const std::string& texture);
|
||||
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
|
||||
virtual int getCellVertices() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include <OgreHardwarePixelBuffer.h>
|
||||
#include <OgreRoot.h>
|
||||
|
||||
#include <components/esm/loadland.hpp>
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
|
||||
#include "storage.hpp"
|
||||
|
@ -114,9 +113,10 @@ namespace Terrain
|
|||
// We arrived at a leaf
|
||||
float minZ,maxZ;
|
||||
Ogre::Vector2 center = node->getCenter();
|
||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
||||
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
|
||||
node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ),
|
||||
Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ)));
|
||||
node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
|
||||
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)));
|
||||
else
|
||||
node->markAsDummy(); // no data available for this node, skip it
|
||||
return;
|
||||
|
@ -169,8 +169,9 @@ namespace Terrain
|
|||
return Ogre::AxisAlignedBox::BOX_NULL;
|
||||
QuadTreeNode* node = findNode(center, mRootNode);
|
||||
Ogre::AxisAlignedBox box = node->getBoundingBox();
|
||||
box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192,
|
||||
box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192);
|
||||
float cellWorldSize = getStorage()->getCellWorldSize();
|
||||
box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize,
|
||||
box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize);
|
||||
return box;
|
||||
}
|
||||
|
||||
|
@ -208,6 +209,8 @@ namespace Terrain
|
|||
|
||||
Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
|
||||
{
|
||||
unsigned int verts = mStorage->getCellVertices();
|
||||
|
||||
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
|
||||
{
|
||||
numIndices = mIndexBufferMap[flags]->getNumIndexes();
|
||||
|
@ -224,11 +227,11 @@ namespace Terrain
|
|||
bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
|
||||
|
||||
size_t increment = 1 << lodLevel;
|
||||
assert((int)increment < ESM::Land::LAND_SIZE);
|
||||
assert(increment < verts);
|
||||
std::vector<short> indices;
|
||||
indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment);
|
||||
indices.reserve((verts-1)*(verts-1)*2*3 / increment);
|
||||
|
||||
size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1;
|
||||
size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1;
|
||||
// If any edge needs stitching we'll skip all edges at this point,
|
||||
// mainly because stitching one edge would have an effect on corners and on the adjacent edges
|
||||
if (anyDeltas)
|
||||
|
@ -242,13 +245,13 @@ namespace Terrain
|
|||
{
|
||||
for (size_t col = colStart; col < colEnd; col += increment)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+increment);
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+increment)+row+increment);
|
||||
indices.push_back(verts*col+row+increment);
|
||||
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment);
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+increment)+row);
|
||||
indices.push_back(verts*(col+increment)+row+increment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,96 +264,96 @@ namespace Terrain
|
|||
// South
|
||||
size_t row = 0;
|
||||
size_t outerStep = 1 << (lodDeltas[South] + lodLevel);
|
||||
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
||||
for (size_t col = 0; col < verts-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
// Make sure not to touch the right edge
|
||||
if (col+outerStep == ESM::Land::LAND_SIZE-1)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep);
|
||||
if (col+outerStep == verts-1)
|
||||
indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep);
|
||||
indices.push_back(verts*(col+outerStep)+row+innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
if (col+i == 0 || col+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep);
|
||||
indices.push_back(verts*(col)+row);
|
||||
indices.push_back(verts*(col+i+innerStep)+row+innerStep);
|
||||
indices.push_back(verts*(col+i)+row+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
// North
|
||||
row = ESM::Land::LAND_SIZE-1;
|
||||
row = verts-1;
|
||||
outerStep = 1 << (lodDeltas[North] + lodLevel);
|
||||
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep)
|
||||
for (size_t col = 0; col < verts-1; col += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
indices.push_back(verts*col+row);
|
||||
// Make sure not to touch the left edge
|
||||
if (col == 0)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep);
|
||||
indices.push_back(verts*(col+innerStep)+row-innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep);
|
||||
indices.push_back(verts*col+row-innerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the left or right edges
|
||||
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
if (col+i == 0 || col+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row);
|
||||
indices.push_back(verts*(col+i)+row-innerStep);
|
||||
indices.push_back(verts*(col+i+innerStep)+row-innerStep);
|
||||
indices.push_back(verts*(col+outerStep)+row);
|
||||
}
|
||||
}
|
||||
|
||||
// West
|
||||
size_t col = 0;
|
||||
outerStep = 1 << (lodDeltas[West] + lodLevel);
|
||||
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
||||
for (size_t row = 0; row < verts-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
indices.push_back(verts*col+row);
|
||||
// Make sure not to touch the top edge
|
||||
if (row+outerStep == ESM::Land::LAND_SIZE-1)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep);
|
||||
if (row+outerStep == verts-1)
|
||||
indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep);
|
||||
indices.push_back(verts*(col+innerStep)+row+outerStep);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
if (row+i == 0 || row+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep);
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*(col+innerStep)+row+i);
|
||||
indices.push_back(verts*(col+innerStep)+row+i+innerStep);
|
||||
}
|
||||
}
|
||||
|
||||
// East
|
||||
col = ESM::Land::LAND_SIZE-1;
|
||||
col = verts-1;
|
||||
outerStep = 1 << (lodDeltas[East] + lodLevel);
|
||||
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep)
|
||||
for (size_t row = 0; row < verts-1; row += outerStep)
|
||||
{
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
indices.push_back(verts*col+row);
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
// Make sure not to touch the bottom edge
|
||||
if (row == 0)
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep);
|
||||
indices.push_back(verts*(col-innerStep)+row+innerStep);
|
||||
else
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row);
|
||||
indices.push_back(verts*(col-innerStep)+row);
|
||||
|
||||
for (size_t i = 0; i < outerStep; i += innerStep)
|
||||
{
|
||||
// Make sure not to touch the top or bottom edges
|
||||
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep)
|
||||
if (row+i == 0 || row+i == verts-1-innerStep)
|
||||
continue;
|
||||
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep);
|
||||
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i);
|
||||
indices.push_back(verts*col+row+outerStep);
|
||||
indices.push_back(verts*(col-innerStep)+row+i+innerStep);
|
||||
indices.push_back(verts*(col-innerStep)+row+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
#ifndef VERSION_HPP
|
||||
#define VERSION_HPP
|
||||
|
||||
#define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@
|
||||
#define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@
|
||||
#define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@
|
||||
#define OPENMW_VERSION "@OPENMW_VERSION@"
|
||||
|
||||
#endif
|
||||
#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@"
|
||||
#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@"
|
||||
|
||||
#endif // VERSION_HPP
|
||||
|
|
@ -12,11 +12,10 @@
|
|||
|
||||
<!-- Avatar -->
|
||||
<Widget type="Widget" skin="MW_Box" position="8 38 212 185" name="Avatar" align="Left Top Stretch">
|
||||
<Widget type="ImageBox" skin="ImageBox" position="0 0 212 161" align="Stretch" name="AvatarImage">
|
||||
<UserString key="ToolTipType" value="AvatarItemSelection"/>
|
||||
<Widget type="ImageBox" skin="ImageBox" position="0 0 212 185" align="Stretch" name="AvatarImage">
|
||||
<Property key="NeedMouse" value="false"/>
|
||||
</Widget>
|
||||
<Widget type="TextBox" skin="ProgressText" position="0 150 212 24" align="HCenter Bottom" name="ArmorRating">
|
||||
<Widget type="TextBox" skin="ProgressText" position="0 161 212 24" align="HStretch Bottom" name="ArmorRating">
|
||||
<Property key="NeedMouse" value="false"/>
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
|
|
@ -100,7 +100,9 @@
|
|||
</Widget>
|
||||
|
||||
<Widget type="Widget" skin="MW_Box" position="8 148 212 152" align="Left Top Stretch">
|
||||
<Widget type="Button" skin="" position="4 4 204 18" name="Attrib1Box" align="Left Top HStretch">
|
||||
<!-- TODO: this should be a scroll view -->
|
||||
<Widget type="Widget" skin="" position="4 4 204 144" align="Left Top Stretch">
|
||||
<Widget type="Button" skin="" position="0 0 204 18" name="Attrib1Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
|
||||
|
@ -114,7 +116,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 22 204 18" name="Attrib2Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 18 204 18" name="Attrib2Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
|
||||
|
@ -128,7 +130,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 40 204 18" name="Attrib3Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 36 204 18" name="Attrib3Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
|
||||
|
@ -142,7 +144,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 58 204 18" name="Attrib4Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 54 204 18" name="Attrib4Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
|
||||
|
@ -156,7 +158,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 76 204 18" name="Attrib5Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 72 204 18" name="Attrib5Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
|
||||
|
@ -170,7 +172,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 94 204 18" name="Attrib6Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 90 204 18" name="Attrib6Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
|
||||
|
@ -184,7 +186,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 112 204 18" name="Attrib7Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 108 204 18" name="Attrib7Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
|
||||
|
@ -198,7 +200,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" skin="" position="4 130 204 18" name="Attrib8Box" align="Left Top HStretch">
|
||||
<Widget type="Button" skin="" position="0 126 204 18" name="Attrib8Box" align="Left Top HStretch">
|
||||
<UserString key="ToolTipType" value="Layout"/>
|
||||
<UserString key="ToolTipLayout" value="AttributeToolTip"/>
|
||||
<UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
|
||||
|
@ -212,6 +214,7 @@
|
|||
</Widget>
|
||||
</Widget>
|
||||
</Widget>
|
||||
</Widget>
|
||||
|
||||
</Widget>
|
||||
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<height>535</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>575</width>
|
||||
|
@ -55,6 +63,28 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>OpenMW version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
|
@ -63,6 +93,8 @@
|
|||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources>
|
||||
|
|
Loading…
Reference in a new issue