mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-30 03:06:43 +00:00
Add OpenMW commits up to 1 Dec 2019
# Conflicts: # CMakeLists.txt # apps/openmw/mwscript/aiextensions.cpp # apps/openmw/mwscript/statsextensions.cpp
This commit is contained in:
commit
9d6f3fdd09
35 changed files with 275 additions and 143 deletions
|
@ -74,6 +74,7 @@ Programmers
|
|||
Fil Krynicki (filkry)
|
||||
Finbar Crago (finbar-crago)
|
||||
Florian Weber (Florianjw)
|
||||
Gaëtan Dezeiraud (Brouilles)
|
||||
Gašper Sedej
|
||||
Gijsbert ter Horst (Ghostbird)
|
||||
Gohan1989
|
||||
|
|
|
@ -176,6 +176,9 @@
|
|||
Bug #5209: Spellcasting ignores race height
|
||||
Bug #5210: AiActivate allows actors to open dialogue and inventory windows
|
||||
Bug #5211: Screen fades in if the first loaded save is in interior cell
|
||||
Bug #5213: SameFaction script function is broken
|
||||
Bug #5218: Crash when disabling ToggleBorders
|
||||
Bug #5220: GetLOS crashes when actor isn't loaded
|
||||
Feature #1774: Handle AvoidNode
|
||||
Feature #2229: Improve pathfinding AI
|
||||
Feature #3025: Analogue gamepad movement controls
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
brew update
|
||||
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||
brew install cmake
|
||||
brew install qt
|
||||
brew install ccache
|
||||
|
||||
|
|
|
@ -332,6 +332,8 @@ IF(BOOST_STATIC)
|
|||
set(Boost_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
|
||||
set(Boost_NO_BOOST_CMAKE ON)
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
|
||||
include_directories("."
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTranslator>
|
||||
#include <QTextCodec>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
@ -26,6 +27,13 @@ int main(int argc, char *argv[])
|
|||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Internationalization
|
||||
QString locale = QLocale::system().name().section('_', 0, 0);
|
||||
|
||||
QTranslator appTranslator;
|
||||
appTranslator.load(":/translations/" + locale + ".qm");
|
||||
app.installTranslator(&appTranslator);
|
||||
|
||||
// Now we make sure the current dir is set to application path
|
||||
QDir dir(QCoreApplication::applicationDirPath());
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
|
|||
iconWidget->setFlow(QListView::LeftToRight);
|
||||
|
||||
QPushButton *playButton = new QPushButton(tr("Play"));
|
||||
buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
|
||||
buttonBox->addButton(playButton, QDialogButtonBox::AcceptRole);
|
||||
|
||||
connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
|
||||
|
|
|
@ -27,13 +27,13 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
|
|||
setupUi(this);
|
||||
|
||||
QStringList languages;
|
||||
languages << QLatin1String("English")
|
||||
<< QLatin1String("French")
|
||||
<< QLatin1String("German")
|
||||
<< QLatin1String("Italian")
|
||||
<< QLatin1String("Polish")
|
||||
<< QLatin1String("Russian")
|
||||
<< QLatin1String("Spanish");
|
||||
languages << tr("English")
|
||||
<< tr("French")
|
||||
<< tr("German")
|
||||
<< tr("Italian")
|
||||
<< tr("Polish")
|
||||
<< tr("Russian")
|
||||
<< tr("Spanish");
|
||||
|
||||
languageComboBox->addItems(languages);
|
||||
|
||||
|
|
|
@ -266,5 +266,5 @@ endif (MSVC)
|
|||
|
||||
|
||||
if(APPLE)
|
||||
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT BUNDLE)
|
||||
INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle)
|
||||
endif()
|
||||
|
|
16
apps/openmw/mwbase/rotationflags.hpp
Normal file
16
apps/openmw/mwbase/rotationflags.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef GAME_MWBASE_ROTATIONFLAGS_H
|
||||
#define GAME_MWBASE_ROTATIONFLAGS_H
|
||||
|
||||
namespace MWBase
|
||||
{
|
||||
using RotationFlags = unsigned short;
|
||||
|
||||
enum RotationFlag : RotationFlags
|
||||
{
|
||||
RotationFlag_none = 0,
|
||||
RotationFlag_adjust = 1,
|
||||
RotationFlag_inverseOrder = 1 << 1,
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef GAME_MWBASE_WORLD_H
|
||||
#define GAME_MWBASE_WORLD_H
|
||||
|
||||
#include "rotationflags.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
@ -408,7 +410,8 @@ namespace MWBase
|
|||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
|
||||
virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0;
|
||||
virtual void rotateObject(const MWWorld::Ptr& ptr, float x, float y, float z,
|
||||
RotationFlags flags = RotationFlag_inverseOrder) = 0;
|
||||
|
||||
virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0;
|
||||
///< Place an object. Makes a copy of the Ptr.
|
||||
|
|
|
@ -631,7 +631,8 @@ namespace MWGui
|
|||
|
||||
ESM::QuickKeys keys;
|
||||
|
||||
for (int i=0; i<10; ++i)
|
||||
// NB: The quick key with index 9 always has Hand-to-Hand type and must not be saved
|
||||
for (int i=0; i<9; ++i)
|
||||
{
|
||||
ItemWidget* button = mKey[i].button;
|
||||
|
||||
|
@ -680,7 +681,8 @@ namespace MWGui
|
|||
int i=0;
|
||||
for (ESM::QuickKeys::QuickKey& quickKey : keys.mKeys)
|
||||
{
|
||||
if (i >= 10)
|
||||
// NB: The quick key with index 9 always has Hand-to-Hand type and must not be loaded
|
||||
if (i >= 9)
|
||||
return;
|
||||
|
||||
mSelected = &mKey[i];
|
||||
|
|
|
@ -1917,23 +1917,25 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
||||
break;
|
||||
case UpperCharState_StartToMinAttack:
|
||||
{
|
||||
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
|
||||
// Happens if the player did not hold the attack button.
|
||||
// Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random.
|
||||
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
|
||||
{
|
||||
start = mAttackType+" min attack";
|
||||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
}
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
// Fall-through
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
{
|
||||
if (mUpperBodyState == UpperCharState_StartToMinAttack)
|
||||
{
|
||||
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
|
||||
// Happens if the player did not hold the attack button.
|
||||
// Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be random.
|
||||
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
|
||||
{
|
||||
start = mAttackType+" min attack";
|
||||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
}
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
|
||||
if(mAttackType == "shoot")
|
||||
{
|
||||
start = mAttackType+" min hit";
|
||||
|
@ -1946,6 +1948,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
}
|
||||
mUpperBodyState = UpperCharState_MinHitToHit;
|
||||
break;
|
||||
}
|
||||
case UpperCharState_MinHitToHit:
|
||||
if(mAttackType == "shoot")
|
||||
{
|
||||
|
|
|
@ -127,54 +127,39 @@ namespace MWMechanics
|
|||
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor);
|
||||
|
||||
const float distSameSpot = mDistSameSpot * duration;
|
||||
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
|
||||
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
|
||||
|
||||
mPrev = pos;
|
||||
|
||||
switch(mWalkState)
|
||||
if (mWalkState != State_Evade)
|
||||
{
|
||||
case State_Norm:
|
||||
if(!samePosition)
|
||||
{
|
||||
if(!samePosition)
|
||||
break;
|
||||
else
|
||||
mWalkState = State_CheckStuck;
|
||||
mWalkState = State_Norm;
|
||||
mStuckDuration = 0;
|
||||
mEvadeDuration = 0;
|
||||
return;
|
||||
}
|
||||
/* FALL THROUGH */
|
||||
case State_CheckStuck:
|
||||
|
||||
mWalkState = State_CheckStuck;
|
||||
mStuckDuration += duration;
|
||||
// consider stuck only if position unchanges for a period
|
||||
if(mStuckDuration < DURATION_SAME_SPOT)
|
||||
return; // still checking, note duration added to timer
|
||||
else
|
||||
{
|
||||
if(!samePosition)
|
||||
{
|
||||
mWalkState = State_Norm;
|
||||
mStuckDuration = 0;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
mStuckDuration += duration;
|
||||
// consider stuck only if position unchanges for a period
|
||||
if(mStuckDuration < DURATION_SAME_SPOT)
|
||||
break; // still checking, note duration added to timer
|
||||
else
|
||||
{
|
||||
mStuckDuration = 0;
|
||||
mWalkState = State_Evade;
|
||||
chooseEvasionDirection();
|
||||
}
|
||||
}
|
||||
mStuckDuration = 0;
|
||||
mWalkState = State_Evade;
|
||||
chooseEvasionDirection();
|
||||
}
|
||||
/* FALL THROUGH */
|
||||
case State_Evade:
|
||||
{
|
||||
mEvadeDuration += duration;
|
||||
if(mEvadeDuration >= DURATION_TO_EVADE)
|
||||
{
|
||||
// tried to evade, assume all is ok and start again
|
||||
mWalkState = State_Norm;
|
||||
mEvadeDuration = 0;
|
||||
}
|
||||
}
|
||||
/* NO DEFAULT CASE */
|
||||
}
|
||||
|
||||
mEvadeDuration += duration;
|
||||
if(mEvadeDuration >= DURATION_TO_EVADE)
|
||||
{
|
||||
// tried to evade, assume all is ok and start again
|
||||
mWalkState = State_Norm;
|
||||
mEvadeDuration = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -448,9 +448,11 @@ namespace MWScript
|
|||
std::string actorID = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true);
|
||||
MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false);
|
||||
|
||||
Interpreter::Type_Integer value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer);
|
||||
Interpreter::Type_Integer value = 0;
|
||||
if (!actor.isEmpty())
|
||||
value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer);
|
||||
|
||||
runtime.push (value);
|
||||
}
|
||||
|
@ -470,9 +472,9 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
|
||||
MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true);
|
||||
MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false);
|
||||
bool value = false;
|
||||
if(dest != MWWorld::Ptr() && source.getClass().isActor() && dest.getClass().isActor())
|
||||
if (!dest.isEmpty() && source.getClass().isActor() && dest.getClass().isActor())
|
||||
{
|
||||
value = MWBase::Environment::get().getWorld()->getLOS(source,dest);
|
||||
}
|
||||
|
@ -513,7 +515,7 @@ namespace MWScript
|
|||
std::string targetID = runtime.getStringLiteral (runtime[0].mInteger);
|
||||
runtime.pop();
|
||||
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(targetID, true);
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false);
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -521,12 +523,13 @@ namespace MWScript
|
|||
Track whether this actor is already in combat with its target, to ensure we don't
|
||||
send repetitive packets to the server
|
||||
*/
|
||||
bool alreadyInCombatWithTarget = actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(target);
|
||||
bool alreadyInCombatWithTarget = !target.isEmpty() ? actor.getClass().getCreatureStats(actor).getAiSequence().isInCombat(target) : false;
|
||||
/*
|
||||
End of tes3mp addition
|
||||
*/
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target);
|
||||
if (!target.isEmpty())
|
||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target);
|
||||
|
||||
/*
|
||||
Start of tes3mp addition
|
||||
|
@ -535,7 +538,7 @@ namespace MWScript
|
|||
cell authority or not; the server can decide if it wants to comply with them by
|
||||
forwarding them to the cell authority
|
||||
*/
|
||||
if (target && !alreadyInCombatWithTarget)
|
||||
if (!target.isEmpty() && !alreadyInCombatWithTarget)
|
||||
{
|
||||
mwmp::ActorList *actorList = mwmp::Main::get().getNetworking()->getActorList();
|
||||
actorList->reset();
|
||||
|
|
|
@ -269,7 +269,7 @@ namespace MWScript
|
|||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
|
||||
|
||||
player.getClass().getNpcStats (player).isInFaction(ptr.getClass().getPrimaryFaction(ptr));
|
||||
runtime.push(player.getClass().getNpcStats (player).isInFaction(ptr.getClass().getPrimaryFaction(ptr)));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1248,7 +1248,9 @@ namespace MWScript
|
|||
return;
|
||||
}
|
||||
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
|
||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetId, false, false);
|
||||
if (target.isEmpty())
|
||||
return;
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, target, false, true);
|
||||
cast.playSpellCastingEffects(spell->mId, false);
|
||||
|
|
|
@ -496,16 +496,6 @@ namespace MWScript
|
|||
{
|
||||
// Apply looping particles immediately for constant effects
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
||||
|
||||
// The spell may have an instant effect which must be handled immediately.
|
||||
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
|
||||
{
|
||||
if (effect.second.getMagnitude() <= 0)
|
||||
continue;
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
|
||||
creatureStats.getSpells().purgeEffect(effect.first.mId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -533,6 +523,16 @@ namespace MWScript
|
|||
|
||||
if (spells.hasSpell(id))
|
||||
{
|
||||
// The spell may have an instant effect which must be handled before the spell's removal.
|
||||
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
|
||||
{
|
||||
if (effect.second.getMagnitude() <= 0)
|
||||
continue;
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
|
||||
creatureStats.getSpells().purgeEffect(effect.first.mId);
|
||||
}
|
||||
|
||||
ptr.getClass().getCreatureStats(ptr).getSpells().remove(id);
|
||||
|
||||
if (ptr == MWMechanics::getPlayer())
|
||||
|
|
|
@ -344,6 +344,7 @@ namespace MWWorld
|
|||
ContainerStoreIteratorBase& operator++ ();
|
||||
ContainerStoreIteratorBase operator++ (int);
|
||||
ContainerStoreIteratorBase& operator= (const ContainerStoreIteratorBase& rhs);
|
||||
ContainerStoreIteratorBase (const ContainerStoreIteratorBase& rhs) = default;
|
||||
|
||||
int getType() const;
|
||||
const ContainerStore *getContainerStore() const;
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
using MWWorld::RotationOrder;
|
||||
|
||||
osg::Quat makeActorOsgQuat(const ESM::Position& position)
|
||||
{
|
||||
return osg::Quat(position.rot[2], osg::Vec3(0, 0, -1));
|
||||
|
@ -78,7 +80,7 @@ namespace
|
|||
* osg::Quat(xr, osg::Vec3(-1, 0, 0));
|
||||
}
|
||||
|
||||
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const bool inverseRotationOrder)
|
||||
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order)
|
||||
{
|
||||
if (!ptr.getRefData().getBaseNode())
|
||||
return;
|
||||
|
@ -86,7 +88,7 @@ namespace
|
|||
rendering.rotateObject(ptr,
|
||||
ptr.getClass().isActor()
|
||||
? makeActorOsgQuat(ptr.getRefData().getPosition())
|
||||
: (inverseRotationOrder
|
||||
: (order == RotationOrder::inverse
|
||||
? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition())
|
||||
: makeObjectOsgQuat(ptr.getRefData().getPosition()))
|
||||
);
|
||||
|
@ -111,7 +113,7 @@ namespace
|
|||
model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
||||
|
||||
ptr.getClass().insertObjectRendering(ptr, model, rendering);
|
||||
setNodeRotation(ptr, rendering, false);
|
||||
setNodeRotation(ptr, rendering, RotationOrder::direct);
|
||||
|
||||
ptr.getClass().insertObject (ptr, model, physics);
|
||||
|
||||
|
@ -190,9 +192,9 @@ namespace
|
|||
}
|
||||
|
||||
void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
||||
MWRender::RenderingManager& rendering, bool inverseRotationOrder)
|
||||
MWRender::RenderingManager& rendering, RotationOrder order)
|
||||
{
|
||||
setNodeRotation(ptr, rendering, inverseRotationOrder);
|
||||
setNodeRotation(ptr, rendering, order);
|
||||
physics.updateRotation(ptr);
|
||||
}
|
||||
|
||||
|
@ -289,9 +291,9 @@ namespace
|
|||
namespace MWWorld
|
||||
{
|
||||
|
||||
void Scene::updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder)
|
||||
void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order)
|
||||
{
|
||||
::updateObjectRotation(ptr, *mPhysics, mRendering, inverseRotationOrder);
|
||||
::updateObjectRotation(ptr, *mPhysics, mRendering, order);
|
||||
}
|
||||
|
||||
void Scene::updateObjectScale(const Ptr &ptr)
|
||||
|
|
|
@ -51,6 +51,12 @@ namespace MWWorld
|
|||
class CellStore;
|
||||
class CellPreloader;
|
||||
|
||||
enum class RotationOrder
|
||||
{
|
||||
direct,
|
||||
inverse
|
||||
};
|
||||
|
||||
class Scene
|
||||
{
|
||||
public:
|
||||
|
@ -137,7 +143,7 @@ namespace MWWorld
|
|||
void removeObjectFromScene (const Ptr& ptr);
|
||||
///< Remove an object from the scene, but not from the world model.
|
||||
|
||||
void updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder);
|
||||
void updateObjectRotation(const Ptr& ptr, RotationOrder order);
|
||||
void updateObjectScale(const Ptr& ptr);
|
||||
|
||||
bool isCellActive(const CellStore &cell);
|
||||
|
|
|
@ -92,6 +92,8 @@ namespace MWWorld
|
|||
: mIter(iter)
|
||||
{}
|
||||
|
||||
SharedIterator& operator=(const SharedIterator&) = default;
|
||||
|
||||
SharedIterator &operator++() {
|
||||
++mIter;
|
||||
return *this;
|
||||
|
|
|
@ -1550,13 +1550,13 @@ namespace MWWorld
|
|||
mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator;
|
||||
}
|
||||
|
||||
void World::rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust)
|
||||
void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags)
|
||||
{
|
||||
const float pi = static_cast<float>(osg::PI);
|
||||
|
||||
ESM::Position pos = ptr.getRefData().getPosition();
|
||||
float *objRot = pos.rot;
|
||||
if(adjust)
|
||||
if (flags & MWBase::RotationFlag_adjust)
|
||||
{
|
||||
objRot[0] += rot.x();
|
||||
objRot[1] += rot.y();
|
||||
|
@ -1588,7 +1588,9 @@ namespace MWWorld
|
|||
|
||||
if(ptr.getRefData().getBaseNode() != 0)
|
||||
{
|
||||
mWorldScene->updateObjectRotation(ptr, true);
|
||||
const auto order = flags & MWBase::RotationFlag_inverseOrder
|
||||
? RotationOrder::inverse : RotationOrder::direct;
|
||||
mWorldScene->updateObjectRotation(ptr, order);
|
||||
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
updateNavigatorObject(object);
|
||||
|
@ -1660,9 +1662,9 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust)
|
||||
void World::rotateObject (const Ptr& ptr, float x, float y, float z, MWBase::RotationFlags flags)
|
||||
{
|
||||
rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust);
|
||||
rotateObjectImp(ptr, osg::Vec3f(x, y, z), flags);
|
||||
}
|
||||
|
||||
void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate)
|
||||
|
@ -1868,7 +1870,7 @@ namespace MWWorld
|
|||
|
||||
float diff = duration * osg::DegreesToRadians(90.f);
|
||||
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == MWWorld::DoorState::Opening ? 1 : -1)), maxRot);
|
||||
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot);
|
||||
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none);
|
||||
|
||||
bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
|
||||
|
||||
|
@ -1912,11 +1914,9 @@ namespace MWWorld
|
|||
MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound);
|
||||
}
|
||||
|
||||
rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot);
|
||||
rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot, MWBase::RotationFlag_none);
|
||||
}
|
||||
|
||||
// the rotation order we want to use
|
||||
mWorldScene->updateObjectRotation(door, false);
|
||||
return reached;
|
||||
}
|
||||
|
||||
|
@ -2771,7 +2771,7 @@ namespace MWWorld
|
|||
player.getClass().getInventoryStore(player).setContListener(anim);
|
||||
|
||||
scaleObject(player, player.getCellRef().getScale()); // apply race height
|
||||
rotateObject(player, 0.f, 0.f, 0.f, true);
|
||||
rotateObject(player, 0.f, 0.f, 0.f, MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust);
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
|
||||
MWBase::Environment::get().getMechanicsManager()->watchActor(getPlayerPtr());
|
||||
|
|
|
@ -126,7 +126,7 @@ namespace MWWorld
|
|||
void updateWeather(float duration, bool paused = false);
|
||||
int getDaysPerMonth (int month) const;
|
||||
|
||||
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, bool adjust);
|
||||
void rotateObjectImp (const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags);
|
||||
|
||||
Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z, bool movePhysics=true, bool moveToActive=false);
|
||||
///< @return an updated Ptr in case the Ptr's cell changes
|
||||
|
@ -507,7 +507,8 @@ namespace MWWorld
|
|||
/// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This
|
||||
/// could be considered a bug, but is needed for MW compatibility.
|
||||
/// \param adjust indicates rotation should be set or adjusted
|
||||
void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false) override;
|
||||
void rotateObject (const Ptr& ptr, float x, float y, float z,
|
||||
MWBase::RotationFlags flags = MWBase::RotationFlag_inverseOrder) override;
|
||||
|
||||
MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) override;
|
||||
///< Place an object. Makes a copy of the Ptr.
|
||||
|
|
|
@ -361,9 +361,9 @@ namespace
|
|||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
0.707107067108154296875, 0, -3.535533905029296875,
|
||||
-0.70710659027099609375, 0, -3.535533905029296875,
|
||||
2.384185791015625e-07, 0, -4.24264049530029296875,
|
||||
1.41421353816986083984375, 0, 1.1920928955078125e-07,
|
||||
-1.41421353816986083984375, 0, -1.1920928955078125e-07,
|
||||
1.1920928955078125e-07, 0, -1.41421353816986083984375,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
|
|
38
components/bullethelpers/transformboundingbox.hpp
Normal file
38
components/bullethelpers/transformboundingbox.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef OPENMW_COMPONENTS_BULLETHELPERS_TRANSFORMBOUNDINGBOX_H
|
||||
#define OPENMW_COMPONENTS_BULLETHELPERS_TRANSFORMBOUNDINGBOX_H
|
||||
|
||||
#include <LinearMath/btVector3.h>
|
||||
#include <LinearMath/btTransform.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace BulletHelpers
|
||||
{
|
||||
inline btVector3 min(const btVector3& a, const btVector3& b)
|
||||
{
|
||||
return btVector3(std::min(a.x(), b.x()), std::min(a.y(), b.y()), std::min(a.z(), b.z()));
|
||||
}
|
||||
|
||||
inline btVector3 max(const btVector3& a, const btVector3& b)
|
||||
{
|
||||
return btVector3(std::max(a.x(), b.x()), std::max(a.y(), b.y()), std::max(a.z(), b.z()));
|
||||
}
|
||||
|
||||
// http://dev.theomader.com/transform-bounding-boxes/
|
||||
inline void transformBoundingBox(const btTransform& transform, btVector3& aabbMin, btVector3& aabbMax)
|
||||
{
|
||||
const btVector3 xa(transform.getBasis().getColumn(0) * aabbMin.x());
|
||||
const btVector3 xb(transform.getBasis().getColumn(0) * aabbMax.x());
|
||||
|
||||
const btVector3 ya(transform.getBasis().getColumn(1) * aabbMin.y());
|
||||
const btVector3 yb(transform.getBasis().getColumn(1) * aabbMax.y());
|
||||
|
||||
const btVector3 za(transform.getBasis().getColumn(2) * aabbMin.z());
|
||||
const btVector3 zb(transform.getBasis().getColumn(2) * aabbMax.z());
|
||||
|
||||
aabbMin = min(xa, xb) + min(ya, yb) + min(za, zb) + transform.getOrigin();
|
||||
aabbMax = max(xa, xb) + max(ya, yb) + max(za, zb) + transform.getOrigin();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -97,9 +97,13 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
|
|||
std::streambuf* cout_rdbuf = std::cout.rdbuf ();
|
||||
std::streambuf* cerr_rdbuf = std::cerr.rdbuf ();
|
||||
|
||||
#if !(defined(_WIN32) && defined(_DEBUG))
|
||||
#if defined(_WIN32) && defined(_DEBUG)
|
||||
boost::iostreams::stream_buffer<Debug::DebugOutput> sb;
|
||||
#else
|
||||
boost::iostreams::stream_buffer<Debug::Tee> coutsb;
|
||||
boost::iostreams::stream_buffer<Debug::Tee> cerrsb;
|
||||
std::ostream oldcout(cout_rdbuf);
|
||||
std::ostream oldcerr(cerr_rdbuf);
|
||||
#endif
|
||||
|
||||
const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log";
|
||||
|
@ -113,7 +117,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
|
|||
|
||||
#if defined(_WIN32) && defined(_DEBUG)
|
||||
// Redirect cout and cerr to VS debug output when running in debug mode
|
||||
boost::iostreams::stream_buffer<Debug::DebugOutput> sb;
|
||||
sb.open(Debug::DebugOutput());
|
||||
std::cout.rdbuf (&sb);
|
||||
std::cerr.rdbuf (&sb);
|
||||
|
@ -121,8 +124,6 @@ int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, c
|
|||
// Redirect cout and cerr to the log file
|
||||
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName));
|
||||
|
||||
std::ostream oldcout(cout_rdbuf);
|
||||
std::ostream oldcerr(cerr_rdbuf);
|
||||
coutsb.open (Debug::Tee(logfile, oldcout));
|
||||
cerrsb.open (Debug::Tee(logfile, oldcerr));
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ namespace DetourNavigator
|
|||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: ", e.what();
|
||||
Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what();
|
||||
}
|
||||
}
|
||||
Log(Debug::Debug) << "Stop navigator jobs processing";
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "settingsutils.hpp"
|
||||
#include "exceptions.hpp"
|
||||
|
||||
#include <components/bullethelpers/transformboundingbox.hpp>
|
||||
#include <components/bullethelpers/processtrianglecallback.hpp>
|
||||
#include <components/misc/convert.hpp>
|
||||
|
||||
|
@ -13,6 +14,7 @@
|
|||
#include <BulletCollision/CollisionShapes/btConcaveShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
#include <LinearMath/btTransform.h>
|
||||
#include <LinearMath/btAabbUtil2.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -57,7 +59,7 @@ namespace DetourNavigator
|
|||
return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int)
|
||||
{
|
||||
for (std::size_t i = 3; i > 0; --i)
|
||||
addTriangleVertex(transform(triangle[i - 1]));
|
||||
addTriangleVertex(triangle[i - 1]);
|
||||
mAreaTypes.push_back(areaType);
|
||||
}));
|
||||
}
|
||||
|
@ -68,7 +70,7 @@ namespace DetourNavigator
|
|||
return addObject(shape, transform, makeProcessTriangleCallback([&] (btVector3* triangle, int, int)
|
||||
{
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
addTriangleVertex(transform(triangle[i]));
|
||||
addTriangleVertex(triangle[i]);
|
||||
mAreaTypes.push_back(areaType);
|
||||
}));
|
||||
}
|
||||
|
@ -131,8 +133,34 @@ namespace DetourNavigator
|
|||
|
||||
shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
||||
|
||||
aabbMin = transform(aabbMin);
|
||||
aabbMax = transform(aabbMax);
|
||||
const btVector3 boundsMin(mBounds.mMin.x(), mBounds.mMin.y(),
|
||||
-std::numeric_limits<btScalar>::max() * std::numeric_limits<btScalar>::epsilon());
|
||||
const btVector3 boundsMax(mBounds.mMax.x(), mBounds.mMax.y(),
|
||||
std::numeric_limits<btScalar>::max() * std::numeric_limits<btScalar>::epsilon());
|
||||
|
||||
auto wrapper = makeProcessTriangleCallback([&] (btVector3* triangle, int partId, int triangleIndex)
|
||||
{
|
||||
std::array<btVector3, 3> transformed;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
transformed[i] = transform(triangle[i]);
|
||||
if (TestTriangleAgainstAabb2(transformed.data(), boundsMin, boundsMax))
|
||||
callback.processTriangle(transformed.data(), partId, triangleIndex);
|
||||
});
|
||||
|
||||
shape.processAllTriangles(&wrapper, aabbMin, aabbMax);
|
||||
}
|
||||
|
||||
void RecastMeshBuilder::addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform,
|
||||
btTriangleCallback&& callback)
|
||||
{
|
||||
using BulletHelpers::transformBoundingBox;
|
||||
|
||||
btVector3 aabbMin;
|
||||
btVector3 aabbMax;
|
||||
|
||||
shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
||||
|
||||
transformBoundingBox(transform, aabbMin, aabbMax);
|
||||
|
||||
aabbMin.setX(std::max(mBounds.mMin.x(), aabbMin.x()));
|
||||
aabbMin.setX(std::min(mBounds.mMax.x(), aabbMin.x()));
|
||||
|
@ -144,20 +172,17 @@ namespace DetourNavigator
|
|||
aabbMax.setY(std::max(mBounds.mMin.y(), aabbMax.y()));
|
||||
aabbMax.setY(std::min(mBounds.mMax.y(), aabbMax.y()));
|
||||
|
||||
const auto inversedTransform = transform.inverse();
|
||||
transformBoundingBox(transform.inverse(), aabbMin, aabbMax);
|
||||
|
||||
aabbMin = inversedTransform(aabbMin);
|
||||
aabbMax = inversedTransform(aabbMax);
|
||||
auto wrapper = makeProcessTriangleCallback([&] (btVector3* triangle, int partId, int triangleIndex)
|
||||
{
|
||||
std::array<btVector3, 3> transformed;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
transformed[i] = transform(triangle[i]);
|
||||
callback.processTriangle(transformed.data(), partId, triangleIndex);
|
||||
});
|
||||
|
||||
aabbMin.setX(std::min(aabbMin.x(), aabbMax.x()));
|
||||
aabbMin.setY(std::min(aabbMin.y(), aabbMax.y()));
|
||||
aabbMin.setZ(std::min(aabbMin.z(), aabbMax.z()));
|
||||
|
||||
aabbMax.setX(std::max(aabbMin.x(), aabbMax.x()));
|
||||
aabbMax.setY(std::max(aabbMin.y(), aabbMax.y()));
|
||||
aabbMax.setZ(std::max(aabbMin.z(), aabbMax.z()));
|
||||
|
||||
shape.processAllTriangles(&callback, aabbMin, aabbMax);
|
||||
shape.processAllTriangles(&wrapper, aabbMin, aabbMax);
|
||||
}
|
||||
|
||||
void RecastMeshBuilder::addTriangleVertex(const btVector3& worldPosition)
|
||||
|
|
|
@ -48,6 +48,8 @@ namespace DetourNavigator
|
|||
|
||||
void addObject(const btConcaveShape& shape, const btTransform& transform, btTriangleCallback&& callback);
|
||||
|
||||
void addObject(const btHeightfieldTerrainShape& shape, const btTransform& transform, btTriangleCallback&& callback);
|
||||
|
||||
void addTriangleVertex(const btVector3& worldPosition);
|
||||
|
||||
void addVertex(const btVector3& worldPosition);
|
||||
|
|
|
@ -76,7 +76,14 @@ ParticleShooter::ParticleShooter()
|
|||
ParticleShooter::ParticleShooter(const ParticleShooter ©, const osg::CopyOp ©op)
|
||||
: osgParticle::Shooter(copy, copyop)
|
||||
{
|
||||
*this = copy;
|
||||
mMinSpeed = copy.mMinSpeed;
|
||||
mMaxSpeed = copy.mMaxSpeed;
|
||||
mHorizontalDir = copy.mHorizontalDir;
|
||||
mHorizontalAngle = copy.mHorizontalAngle;
|
||||
mVerticalDir = copy.mVerticalDir;
|
||||
mVerticalAngle = copy.mVerticalAngle;
|
||||
mLifetime = copy.mLifetime;
|
||||
mLifetimeRandom = copy.mLifetimeRandom;
|
||||
}
|
||||
|
||||
void ParticleShooter::shoot(osgParticle::Particle *particle) const
|
||||
|
@ -112,7 +119,9 @@ GrowFadeAffector::GrowFadeAffector()
|
|||
GrowFadeAffector::GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop)
|
||||
: osgParticle::Operator(copy, copyop)
|
||||
{
|
||||
*this = copy;
|
||||
mGrowTime = copy.mGrowTime;
|
||||
mFadeTime = copy.mFadeTime;
|
||||
mCachedDefaultSize = copy.mCachedDefaultSize;
|
||||
}
|
||||
|
||||
void GrowFadeAffector::beginOperate(osgParticle::Program *program)
|
||||
|
@ -143,7 +152,7 @@ ParticleColorAffector::ParticleColorAffector()
|
|||
ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector ©, const osg::CopyOp ©op)
|
||||
: osgParticle::Operator(copy, copyop)
|
||||
{
|
||||
*this = copy;
|
||||
mData = copy.mData;
|
||||
}
|
||||
|
||||
void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */)
|
||||
|
@ -172,7 +181,13 @@ GravityAffector::GravityAffector()
|
|||
GravityAffector::GravityAffector(const GravityAffector ©, const osg::CopyOp ©op)
|
||||
: osgParticle::Operator(copy, copyop)
|
||||
{
|
||||
*this = copy;
|
||||
mForce = copy.mForce;
|
||||
mType = copy.mType;
|
||||
mPosition = copy.mPosition;
|
||||
mDirection = copy.mDirection;
|
||||
mDecay = copy.mDecay;
|
||||
mCachedWorldPosition = copy.mCachedWorldPosition;
|
||||
mCachedWorldDirection = copy.mCachedWorldDirection;
|
||||
}
|
||||
|
||||
void GravityAffector::beginOperate(osgParticle::Program* program)
|
||||
|
|
|
@ -78,6 +78,8 @@ namespace NifOsg
|
|||
ParticleShooter();
|
||||
ParticleShooter(const ParticleShooter& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
|
||||
|
||||
ParticleShooter& operator=(const ParticleShooter&) = delete;
|
||||
|
||||
META_Object(NifOsg, ParticleShooter)
|
||||
|
||||
virtual void shoot(osgParticle::Particle* particle) const;
|
||||
|
@ -135,6 +137,8 @@ namespace NifOsg
|
|||
GrowFadeAffector();
|
||||
GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
|
||||
|
||||
GrowFadeAffector& operator=(const GrowFadeAffector&) = delete;
|
||||
|
||||
META_Object(NifOsg, GrowFadeAffector)
|
||||
|
||||
virtual void beginOperate(osgParticle::Program* program);
|
||||
|
@ -155,6 +159,8 @@ namespace NifOsg
|
|||
ParticleColorAffector();
|
||||
ParticleColorAffector(const ParticleColorAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
|
||||
|
||||
ParticleColorAffector& operator=(const ParticleColorAffector&) = delete;
|
||||
|
||||
META_Object(NifOsg, ParticleColorAffector)
|
||||
|
||||
virtual void operate(osgParticle::Particle* particle, double dt);
|
||||
|
@ -170,6 +176,8 @@ namespace NifOsg
|
|||
GravityAffector();
|
||||
GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
|
||||
|
||||
GravityAffector& operator=(const GravityAffector&) = delete;
|
||||
|
||||
META_Object(NifOsg, GravityAffector)
|
||||
|
||||
virtual void operate(osgParticle::Particle* particle, double dt);
|
||||
|
|
|
@ -91,8 +91,9 @@ void CellBorder::destroyCellBorderGeometry(int x, int y)
|
|||
|
||||
void CellBorder::destroyCellBorderGeometry()
|
||||
{
|
||||
for (CellGrid::iterator it = mCellBorderNodes.begin(); it != mCellBorderNodes.end(); ++it)
|
||||
destroyCellBorderGeometry(it->first.first,it->first.second);
|
||||
for (const auto& v : mCellBorderNodes)
|
||||
mRoot->removeChild(v.second);
|
||||
mCellBorderNodes.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -176,12 +176,12 @@ recast scale factor
|
|||
|
||||
:Type: floating point
|
||||
:Range: > 0.0
|
||||
:Default: 0.023529411764705882
|
||||
:Default: 0.029411764705882353
|
||||
|
||||
Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry.
|
||||
Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size
|
||||
"recast scale factor" / "cell size". Default value calculates by this equation:
|
||||
sStepSizeUp * "recast scale factor" / "cell size" = 4 (max climb height should be equal to 4 voxels).
|
||||
sStepSizeUp * "recast scale factor" / "cell size" = 5 (max climb height should be equal to 4 voxels).
|
||||
Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures.
|
||||
Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency.
|
||||
|
||||
|
|
2
extern/oics/ICSInputControlSystem.cpp
vendored
2
extern/oics/ICSInputControlSystem.cpp
vendored
|
@ -148,7 +148,7 @@ namespace ICS
|
|||
float step = FromString<float>(xmlInterval->Attribute("step"));
|
||||
|
||||
ICS_LOG("Applying Bezier filter to channel [number="
|
||||
+ ToString<int>(ch) + ", startX="
|
||||
+ ToString<size_t>(ch) + ", startX="
|
||||
+ ToString<float>(startX) + ", startY="
|
||||
+ ToString<float>(startY) + ", midX="
|
||||
+ ToString<float>(midX) + ", midY="
|
||||
|
|
|
@ -642,8 +642,8 @@ enable = true
|
|||
# Scale of NavMesh coordinates to world coordinates (value > 0.0). Recastnavigation builds voxels for world geometry.
|
||||
# Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size
|
||||
# "recast scale factor" / "cell size". Default value calculates by this equation:
|
||||
# sStepSizeUp * "recast scale factor" / "cell size" = 4 (max climb height should be equal to 4 voxels)
|
||||
recast scale factor = 0.023529411764705882
|
||||
# sStepSizeUp * "recast scale factor" / "cell size" = 5 (max climb height should be equal to 4 voxels)
|
||||
recast scale factor = 0.029411764705882353
|
||||
|
||||
# The z-axis cell size to use for fields. (value > 0.0)
|
||||
# Defines voxel/grid/cell size. So their values have significant
|
||||
|
@ -671,7 +671,7 @@ detail sample max error = 1.0
|
|||
max simplification error = 1.3
|
||||
|
||||
# The width and height of each tile. (value > 0)
|
||||
tile size = 64
|
||||
tile size = 128
|
||||
|
||||
# The size of the non-navigable border around the heightfield. (value >= 0)
|
||||
border size = 16
|
||||
|
|
Loading…
Reference in a new issue