forked from mirror/openmw-tes3mp
Merge master
This commit is contained in:
commit
a5397e5f2d
94 changed files with 1127 additions and 634 deletions
|
@ -34,6 +34,7 @@ Programmers
|
||||||
Ben Shealy (bentsherman)
|
Ben Shealy (bentsherman)
|
||||||
Bret Curtis (psi29a)
|
Bret Curtis (psi29a)
|
||||||
Britt Mathis (galdor557)
|
Britt Mathis (galdor557)
|
||||||
|
Capostrophic
|
||||||
cc9cii
|
cc9cii
|
||||||
Chris Boyce (slothlife)
|
Chris Boyce (slothlife)
|
||||||
Chris Robinson (KittyCat)
|
Chris Robinson (KittyCat)
|
||||||
|
@ -60,6 +61,7 @@ Programmers
|
||||||
Gašper Sedej
|
Gašper Sedej
|
||||||
gugus/gus
|
gugus/gus
|
||||||
Hallfaer Tuilinn
|
Hallfaer Tuilinn
|
||||||
|
Haoda Wang (h313)
|
||||||
hristoast
|
hristoast
|
||||||
Internecine
|
Internecine
|
||||||
Jacob Essex (Yacoby)
|
Jacob Essex (Yacoby)
|
||||||
|
|
|
@ -1,3 +1,40 @@
|
||||||
|
# Apps and tools
|
||||||
|
option(BUILD_OPENMW "build OpenMW" ON)
|
||||||
|
option(BUILD_BSATOOL "build BSA extractor" ON)
|
||||||
|
option(BUILD_ESMTOOL "build ESM inspector" ON)
|
||||||
|
option(BUILD_LAUNCHER "build Launcher" ON)
|
||||||
|
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
|
||||||
|
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
|
||||||
|
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
|
||||||
|
option(BUILD_WIZARD "build Installation Wizard" ON)
|
||||||
|
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
|
||||||
|
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
|
||||||
|
option(BUILD_NIFTEST "build nif file tester" OFF)
|
||||||
|
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
|
||||||
|
option(BUILD_DOCS "build documentation." OFF )
|
||||||
|
|
||||||
|
if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
|
||||||
|
set(USE_QT FALSE)
|
||||||
|
else()
|
||||||
|
set(USE_QT TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (USE_QT)
|
||||||
|
set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)")
|
||||||
|
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
# OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db
|
||||||
|
cmake_minimum_required(VERSION 3.1.0)
|
||||||
|
elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5)
|
||||||
|
# 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows.
|
||||||
|
cmake_minimum_required(VERSION 2.8.11)
|
||||||
|
else()
|
||||||
|
# We probably support older versions than this.
|
||||||
|
cmake_minimum_required(VERSION 2.6)
|
||||||
|
endif()
|
||||||
|
|
||||||
project(OpenMW)
|
project(OpenMW)
|
||||||
|
|
||||||
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them.
|
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them.
|
||||||
|
@ -59,21 +96,6 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE)
|
||||||
|
|
||||||
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
|
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
|
||||||
|
|
||||||
# Apps and tools
|
|
||||||
option(BUILD_OPENMW "build OpenMW" ON)
|
|
||||||
option(BUILD_BSATOOL "build BSA extractor" ON)
|
|
||||||
option(BUILD_ESMTOOL "build ESM inspector" ON)
|
|
||||||
option(BUILD_LAUNCHER "build Launcher" ON)
|
|
||||||
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
|
|
||||||
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
|
|
||||||
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
|
|
||||||
option(BUILD_WIZARD "build Installation Wizard" ON)
|
|
||||||
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
|
|
||||||
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
|
|
||||||
option(BUILD_NIFTEST "build nif file tester" OFF)
|
|
||||||
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
|
|
||||||
option(BUILD_DOCS "build documentation." OFF )
|
|
||||||
|
|
||||||
# what is necessary to build documentation
|
# what is necessary to build documentation
|
||||||
IF( BUILD_DOCS )
|
IF( BUILD_DOCS )
|
||||||
# Builds the documentation.
|
# Builds the documentation.
|
||||||
|
@ -120,16 +142,8 @@ if (WIN32)
|
||||||
option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON)
|
option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
|
|
||||||
set(USE_QT FALSE)
|
|
||||||
else()
|
|
||||||
set(USE_QT TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
if (USE_QT)
|
if (USE_QT)
|
||||||
set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)")
|
|
||||||
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
|
|
||||||
message(STATUS "Using Qt${DESIRED_QT_VERSION}")
|
message(STATUS "Using Qt${DESIRED_QT_VERSION}")
|
||||||
|
|
||||||
if (DESIRED_QT_VERSION MATCHES 4)
|
if (DESIRED_QT_VERSION MATCHES 4)
|
||||||
|
@ -144,17 +158,6 @@ if (USE_QT)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
|
||||||
# OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db
|
|
||||||
cmake_minimum_required(VERSION 3.1.0)
|
|
||||||
elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5)
|
|
||||||
# 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows.
|
|
||||||
cmake_minimum_required(VERSION 2.8.11)
|
|
||||||
else()
|
|
||||||
# We probably support older versions than this.
|
|
||||||
cmake_minimum_required(VERSION 2.6)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Sound setup
|
# Sound setup
|
||||||
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
|
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
|
||||||
# Required for building the FFmpeg headers
|
# Required for building the FFmpeg headers
|
||||||
|
@ -206,8 +209,8 @@ if(NOT HAVE_STDINT_H)
|
||||||
message(FATAL_ERROR "stdint.h was not found" )
|
message(FATAL_ERROR "stdint.h was not found" )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX osgShadow)
|
|
||||||
|
|
||||||
|
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX)
|
||||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
|
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
|
||||||
|
|
||||||
set(USED_OSG_PLUGINS
|
set(USED_OSG_PLUGINS
|
||||||
|
|
|
@ -4,7 +4,7 @@ set(BSATOOL
|
||||||
source_group(apps\\bsatool FILES ${BSATOOL})
|
source_group(apps\\bsatool FILES ${BSATOOL})
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
add_executable(bsatool
|
openmw_add_executable(bsatool
|
||||||
${BSATOOL}
|
${BSATOOL}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ set(ESMTOOL
|
||||||
source_group(apps\\esmtool FILES ${ESMTOOL})
|
source_group(apps\\esmtool FILES ${ESMTOOL})
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
add_executable(esmtool
|
openmw_add_executable(esmtool
|
||||||
${ESMTOOL}
|
${ESMTOOL}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ set(ESSIMPORTER_FILES
|
||||||
convertplayer.cpp
|
convertplayer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(openmw-essimporter
|
openmw_add_executable(openmw-essimporter
|
||||||
${ESSIMPORTER_FILES}
|
${ESSIMPORTER_FILES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ if(NOT WIN32)
|
||||||
endif(NOT WIN32)
|
endif(NOT WIN32)
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
add_executable(openmw-launcher
|
openmw_add_executable(openmw-launcher
|
||||||
${GUI_TYPE}
|
${GUI_TYPE}
|
||||||
${LAUNCHER}
|
${LAUNCHER}
|
||||||
${LAUNCHER_HEADER}
|
${LAUNCHER_HEADER}
|
||||||
|
|
|
@ -9,7 +9,7 @@ set(MWINIIMPORT_HEADER
|
||||||
|
|
||||||
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
|
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
|
||||||
|
|
||||||
add_executable(openmw-iniimporter
|
openmw_add_executable(openmw-iniimporter
|
||||||
${MWINIIMPORT}
|
${MWINIIMPORT}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ set(NIFTEST
|
||||||
source_group(components\\nif\\tests FILES ${NIFTEST})
|
source_group(components\\nif\\tests FILES ${NIFTEST})
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
add_executable(niftest
|
openmw_add_executable(niftest
|
||||||
${NIFTEST}
|
${NIFTEST}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ else()
|
||||||
set (OPENCS_OPENMW_CFG "")
|
set (OPENCS_OPENMW_CFG "")
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
|
||||||
add_executable(openmw-cs
|
openmw_add_executable(openmw-cs
|
||||||
MACOSX_BUNDLE
|
MACOSX_BUNDLE
|
||||||
${OPENCS_SRC}
|
${OPENCS_SRC}
|
||||||
${OPENCS_UI_HDR}
|
${OPENCS_UI_HDR}
|
||||||
|
@ -199,7 +199,7 @@ if(APPLE)
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
|
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
|
||||||
OUTPUT_NAME ${OPENCS_BUNDLE_NAME}
|
OUTPUT_NAME ${OPENCS_BUNDLE_NAME}
|
||||||
MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns"
|
MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns"
|
||||||
MACOSX_BUNDLE_BUNDLE_NAME "OpenCS"
|
MACOSX_BUNDLE_BUNDLE_NAME "OpenMW-CS"
|
||||||
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
|
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
|
||||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
|
||||||
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
|
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
|
||||||
|
|
|
@ -132,7 +132,7 @@ namespace CSMPrefs
|
||||||
if (mods && i == 0)
|
if (mods && i == 0)
|
||||||
{
|
{
|
||||||
if (mods & Qt::ControlModifier)
|
if (mods & Qt::ControlModifier)
|
||||||
result.append("Ctl+");
|
result.append("Ctrl+");
|
||||||
if (mods & Qt::ShiftModifier)
|
if (mods & Qt::ShiftModifier)
|
||||||
result.append("Shift+");
|
result.append("Shift+");
|
||||||
if (mods & Qt::AltModifier)
|
if (mods & Qt::AltModifier)
|
||||||
|
@ -196,7 +196,7 @@ namespace CSMPrefs
|
||||||
|
|
||||||
std::string name = value.substr(start, end - start);
|
std::string name = value.substr(start, end - start);
|
||||||
|
|
||||||
if (name == "Ctl")
|
if (name == "Ctrl")
|
||||||
{
|
{
|
||||||
mods |= Qt::ControlModifier;
|
mods |= Qt::ControlModifier;
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,11 +275,13 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
|
||||||
|
|
||||||
void CSVRender::Cell::pathgridModified()
|
void CSVRender::Cell::pathgridModified()
|
||||||
{
|
{
|
||||||
|
if (mPathgrid)
|
||||||
mPathgrid->recreateGeometry();
|
mPathgrid->recreateGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVRender::Cell::pathgridRemoved()
|
void CSVRender::Cell::pathgridRemoved()
|
||||||
{
|
{
|
||||||
|
if (mPathgrid)
|
||||||
mPathgrid->removeGeometry();
|
mPathgrid->removeGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +322,7 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode)
|
||||||
iter->second->setSelected (selected);
|
iter->second->setSelected (selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (elementMask & Mask_Pathgrid)
|
if (mPathgrid && elementMask & Mask_Pathgrid)
|
||||||
{
|
{
|
||||||
// Only one pathgrid may be selected, so some operations will only have an effect
|
// Only one pathgrid may be selected, so some operations will only have an effect
|
||||||
// if the pathgrid is already focused
|
// if the pathgrid is already focused
|
||||||
|
@ -420,7 +422,7 @@ std::vector<osg::ref_ptr<CSVRender::TagBase> > CSVRender::Cell::getSelection (un
|
||||||
iter!=mObjects.end(); ++iter)
|
iter!=mObjects.end(); ++iter)
|
||||||
if (iter->second->getSelected())
|
if (iter->second->getSelected())
|
||||||
result.push_back (iter->second->getTag());
|
result.push_back (iter->second->getTag());
|
||||||
if (elementMask & Mask_Pathgrid)
|
if (mPathgrid && elementMask & Mask_Pathgrid)
|
||||||
if (mPathgrid->isSelected())
|
if (mPathgrid->isSelected())
|
||||||
result.push_back(mPathgrid->getTag());
|
result.push_back(mPathgrid->getTag());
|
||||||
|
|
||||||
|
@ -457,6 +459,6 @@ void CSVRender::Cell::reset (unsigned int elementMask)
|
||||||
for (std::map<std::string, Object *>::const_iterator iter (mObjects.begin());
|
for (std::map<std::string, Object *>::const_iterator iter (mObjects.begin());
|
||||||
iter!=mObjects.end(); ++iter)
|
iter!=mObjects.end(); ++iter)
|
||||||
iter->second->reset();
|
iter->second->reset();
|
||||||
if (elementMask & Mask_Pathgrid)
|
if (mPathgrid && elementMask & Mask_Pathgrid)
|
||||||
mPathgrid->resetIndicators();
|
mPathgrid->resetIndicators();
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,8 @@ namespace CSVRender
|
||||||
primarySelectPressed(hitResult);
|
primarySelectPressed(hitResult);
|
||||||
}
|
}
|
||||||
else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos))
|
else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos))
|
||||||
|
{
|
||||||
|
if (cell->getPathgrid())
|
||||||
{
|
{
|
||||||
// Add node
|
// Add node
|
||||||
QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack();
|
QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack();
|
||||||
|
@ -80,6 +82,7 @@ namespace CSVRender
|
||||||
cell->getPathgrid()->applyPoint(macro, hitResult.worldPos);
|
cell->getPathgrid()->applyPoint(macro, hitResult.worldPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit)
|
void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit)
|
||||||
{
|
{
|
||||||
|
@ -205,7 +208,7 @@ namespace CSVRender
|
||||||
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
||||||
|
|
||||||
Cell* cell = getWorldspaceWidget().getCell(hit.worldPos);
|
Cell* cell = getWorldspaceWidget().getCell(hit.worldPos);
|
||||||
if (cell)
|
if (cell && cell->getPathgrid())
|
||||||
{
|
{
|
||||||
PathgridTag* tag = 0;
|
PathgridTag* tag = 0;
|
||||||
if (hit.tag && (tag = dynamic_cast<PathgridTag*>(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId)
|
if (hit.tag && (tag = dynamic_cast<PathgridTag*>(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId)
|
||||||
|
|
|
@ -221,8 +221,8 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
|
||||||
|
|
||||||
SceneWidget::~SceneWidget()
|
SceneWidget::~SceneWidget()
|
||||||
{
|
{
|
||||||
// Since we're holding on to the scene templates past the existence of this graphics context, we'll need to manually release the created objects
|
// Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects
|
||||||
mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
|
mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneWidget::setLighting(Lighting *lighting)
|
void SceneWidget::setLighting(Lighting *lighting)
|
||||||
|
|
|
@ -32,13 +32,19 @@ std::string CSVWorld::InfoCreator::getId() const
|
||||||
|
|
||||||
void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
|
void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const
|
||||||
{
|
{
|
||||||
int index =
|
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId()));
|
||||||
dynamic_cast<CSMWorld::IdTable&> (*getData().getTableModel (getCollectionId())).
|
|
||||||
findColumnIndex (
|
|
||||||
getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ?
|
|
||||||
CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal);
|
|
||||||
|
|
||||||
command.addValue (index, mTopic->text());
|
if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos)
|
||||||
|
{
|
||||||
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text());
|
||||||
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1);
|
||||||
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1);
|
||||||
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
|
CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack,
|
||||||
|
|
|
@ -99,7 +99,7 @@ add_openmw_dir (mwbase
|
||||||
# Main executable
|
# Main executable
|
||||||
|
|
||||||
if (NOT ANDROID)
|
if (NOT ANDROID)
|
||||||
add_executable(openmw
|
openmw_add_executable(openmw
|
||||||
${OPENMW_FILES}
|
${OPENMW_FILES}
|
||||||
${GAME} ${GAME_HEADER}
|
${GAME} ${GAME_HEADER}
|
||||||
${APPLE_BUNDLE_RESOURCES}
|
${APPLE_BUNDLE_RESOURCES}
|
||||||
|
|
|
@ -84,7 +84,6 @@ void OMW::Engine::frame(float frametime)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mStartTick = mViewer->getStartTick();
|
mStartTick = mViewer->getStartTick();
|
||||||
mEnvironment.setFrameDuration (frametime);
|
|
||||||
|
|
||||||
// update input
|
// update input
|
||||||
mEnvironment.getInputManager()->update(frametime, false);
|
mEnvironment.getInputManager()->update(frametime, false);
|
||||||
|
@ -651,6 +650,8 @@ void OMW::Engine::go()
|
||||||
Settings::Manager::getString("screenshot format", "General")));
|
Settings::Manager::getString("screenshot format", "General")));
|
||||||
mViewer->addEventHandler(mScreenCaptureHandler);
|
mViewer->addEventHandler(mScreenCaptureHandler);
|
||||||
|
|
||||||
|
mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video"));
|
||||||
|
|
||||||
// Create encoder
|
// Create encoder
|
||||||
ToUTF8::Utf8Encoder encoder (mEncoding);
|
ToUTF8::Utf8Encoder encoder (mEncoding);
|
||||||
mEncoder = &encoder;
|
mEncoder = &encoder;
|
||||||
|
@ -684,7 +685,6 @@ void OMW::Engine::go()
|
||||||
// Start the main rendering loop
|
// Start the main rendering loop
|
||||||
osg::Timer frameTimer;
|
osg::Timer frameTimer;
|
||||||
double simulationTime = 0.0;
|
double simulationTime = 0.0;
|
||||||
float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video");
|
|
||||||
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
|
while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest())
|
||||||
{
|
{
|
||||||
double dt = frameTimer.time_s();
|
double dt = frameTimer.time_s();
|
||||||
|
@ -697,6 +697,8 @@ void OMW::Engine::go()
|
||||||
|
|
||||||
mViewer->advance(simulationTime);
|
mViewer->advance(simulationTime);
|
||||||
|
|
||||||
|
mEnvironment.setFrameDuration(dt);
|
||||||
|
|
||||||
frame(dt);
|
frame(dt);
|
||||||
|
|
||||||
if (!mEnvironment.getInputManager()->isWindowVisible())
|
if (!mEnvironment.getInputManager()->isWindowVisible())
|
||||||
|
@ -714,15 +716,7 @@ void OMW::Engine::go()
|
||||||
mViewer->renderingTraversals();
|
mViewer->renderingTraversals();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (framerateLimit > 0.f)
|
mEnvironment.limitFrameRate(frameTimer.time_s());
|
||||||
{
|
|
||||||
double thisFrameTime = frameTimer.time_s();
|
|
||||||
double minFrameTime = 1.0 / framerateLimit;
|
|
||||||
if (thisFrameTime < minFrameTime)
|
|
||||||
{
|
|
||||||
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save user settings
|
// Save user settings
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <OpenThreads/Thread>
|
||||||
|
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "scriptmanager.hpp"
|
#include "scriptmanager.hpp"
|
||||||
#include "dialoguemanager.hpp"
|
#include "dialoguemanager.hpp"
|
||||||
|
@ -17,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = 0;
|
||||||
MWBase::Environment::Environment()
|
MWBase::Environment::Environment()
|
||||||
: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
|
: mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0),
|
||||||
mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0),
|
mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0),
|
||||||
mFrameDuration (0)
|
mFrameDuration (0), mFrameRateLimit(0.f)
|
||||||
{
|
{
|
||||||
assert (!sThis);
|
assert (!sThis);
|
||||||
sThis = this;
|
sThis = this;
|
||||||
|
@ -79,6 +81,29 @@ void MWBase::Environment::setFrameDuration (float duration)
|
||||||
mFrameDuration = duration;
|
mFrameDuration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MWBase::Environment::setFrameRateLimit(float limit)
|
||||||
|
{
|
||||||
|
mFrameRateLimit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
float MWBase::Environment::getFrameRateLimit() const
|
||||||
|
{
|
||||||
|
return mFrameRateLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MWBase::Environment::limitFrameRate(double dt) const
|
||||||
|
{
|
||||||
|
if (mFrameRateLimit > 0.f)
|
||||||
|
{
|
||||||
|
double thisFrameTime = dt;
|
||||||
|
double minFrameTime = 1.0 / static_cast<double>(mFrameRateLimit);
|
||||||
|
if (thisFrameTime < minFrameTime)
|
||||||
|
{
|
||||||
|
OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MWBase::World *MWBase::Environment::getWorld() const
|
MWBase::World *MWBase::Environment::getWorld() const
|
||||||
{
|
{
|
||||||
assert (mWorld);
|
assert (mWorld);
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace MWBase
|
||||||
InputManager *mInputManager;
|
InputManager *mInputManager;
|
||||||
StateManager *mStateManager;
|
StateManager *mStateManager;
|
||||||
float mFrameDuration;
|
float mFrameDuration;
|
||||||
|
float mFrameRateLimit;
|
||||||
|
|
||||||
Environment (const Environment&);
|
Environment (const Environment&);
|
||||||
///< not implemented
|
///< not implemented
|
||||||
|
@ -67,6 +68,10 @@ namespace MWBase
|
||||||
void setFrameDuration (float duration);
|
void setFrameDuration (float duration);
|
||||||
///< Set length of current frame in seconds.
|
///< Set length of current frame in seconds.
|
||||||
|
|
||||||
|
void setFrameRateLimit(float frameRateLimit);
|
||||||
|
float getFrameRateLimit() const;
|
||||||
|
void limitFrameRate(double dt) const;
|
||||||
|
|
||||||
World *getWorld() const;
|
World *getWorld() const;
|
||||||
|
|
||||||
SoundManager *getSoundManager() const;
|
SoundManager *getSoundManager() const;
|
||||||
|
|
|
@ -223,6 +223,7 @@ namespace MWBase
|
||||||
virtual void keepPlayerAlive() = 0;
|
virtual void keepPlayerAlive() = 0;
|
||||||
|
|
||||||
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
|
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
|
||||||
|
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0;
|
||||||
|
|
||||||
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0;
|
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0;
|
||||||
|
|
||||||
|
@ -246,6 +247,7 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
|
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
|
||||||
|
|
||||||
|
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
|
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
|
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -212,7 +212,10 @@ namespace MWBase
|
||||||
virtual void setSpellVisibility(bool visible) = 0;
|
virtual void setSpellVisibility(bool visible) = 0;
|
||||||
virtual void setSneakVisibility(bool visible) = 0;
|
virtual void setSneakVisibility(bool visible) = 0;
|
||||||
|
|
||||||
|
/// activate selected quick key
|
||||||
virtual void activateQuickKey (int index) = 0;
|
virtual void activateQuickKey (int index) = 0;
|
||||||
|
/// update activated quick key state (if action executing was delayed for some reason)
|
||||||
|
virtual void updateActivatedQuickKey () = 0;
|
||||||
|
|
||||||
virtual std::string getSelectedSpell() = 0;
|
virtual std::string getSelectedSpell() = 0;
|
||||||
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0;
|
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0;
|
||||||
|
@ -357,7 +360,7 @@ namespace MWBase
|
||||||
|
|
||||||
// In WindowManager for now since there isn't a VFS singleton
|
// In WindowManager for now since there isn't a VFS singleton
|
||||||
virtual std::string correctIconPath(const std::string& path) = 0;
|
virtual std::string correctIconPath(const std::string& path) = 0;
|
||||||
virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0;
|
virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0;
|
||||||
virtual std::string correctTexturePath(const std::string& path) = 0;
|
virtual std::string correctTexturePath(const std::string& path) = 0;
|
||||||
virtual bool textureExists(const std::string& path) = 0;
|
virtual bool textureExists(const std::string& path) = 0;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <components/esm/loadlock.hpp>
|
#include <components/esm/loadlock.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
@ -155,6 +156,16 @@ namespace MWClass
|
||||||
return MWWorld::Ptr(cell.insert(ref), &cell);
|
return MWWorld::Ptr(cell.insert(ref), &cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<int, std::string> Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||||
|
{
|
||||||
|
// Do not allow equip tools from inventory during attack
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
|
||||||
|
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||||
|
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
||||||
|
|
||||||
|
return std::make_pair(1, "");
|
||||||
|
}
|
||||||
|
|
||||||
bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const
|
bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const
|
||||||
{
|
{
|
||||||
return (npcServices & ESM::NPC::Picks) != 0;
|
return (npcServices & ESM::NPC::Picks) != 0;
|
||||||
|
|
|
@ -51,6 +51,8 @@ namespace MWClass
|
||||||
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
|
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
|
||||||
///< Return name of inventory icon.
|
///< Return name of inventory icon.
|
||||||
|
|
||||||
|
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
|
||||||
|
|
||||||
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
|
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
|
||||||
const;
|
const;
|
||||||
///< Generate action for using via inventory menu
|
///< Generate action for using via inventory menu
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <components/esm/loadprob.hpp>
|
#include <components/esm/loadprob.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
@ -155,6 +156,16 @@ namespace MWClass
|
||||||
return MWWorld::Ptr(cell.insert(ref), &cell);
|
return MWWorld::Ptr(cell.insert(ref), &cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<int, std::string> Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||||
|
{
|
||||||
|
// Do not allow equip tools from inventory during attack
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
|
||||||
|
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||||
|
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
||||||
|
|
||||||
|
return std::make_pair(1, "");
|
||||||
|
}
|
||||||
|
|
||||||
bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const
|
bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const
|
||||||
{
|
{
|
||||||
return (npcServices & ESM::NPC::Probes) != 0;
|
return (npcServices & ESM::NPC::Probes) != 0;
|
||||||
|
|
|
@ -51,6 +51,8 @@ namespace MWClass
|
||||||
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
|
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
|
||||||
///< Return name of inventory icon.
|
///< Return name of inventory icon.
|
||||||
|
|
||||||
|
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
|
||||||
|
|
||||||
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
|
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
|
||||||
const;
|
const;
|
||||||
///< Generate action for using via inventory menu
|
///< Generate action for using via inventory menu
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
@ -373,6 +374,11 @@ namespace MWClass
|
||||||
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
||||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||||
|
|
||||||
|
// Do not allow equip weapons from inventory during attack
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
|
||||||
|
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||||
|
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
||||||
|
|
||||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
||||||
|
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
|
|
|
@ -634,7 +634,7 @@ namespace MWGui
|
||||||
|
|
||||||
void DialogueWindow::onFrame()
|
void DialogueWindow::onFrame()
|
||||||
{
|
{
|
||||||
if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
|
if(mMainWidget->getVisible() && mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||||
{
|
{
|
||||||
int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr);
|
int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr);
|
||||||
mDispositionBar->setProgressRange(100);
|
mDispositionBar->setProgressRange(100);
|
||||||
|
|
|
@ -275,8 +275,6 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
case BookTextParser::Event_ImgTag:
|
case BookTextParser::Event_ImgTag:
|
||||||
{
|
{
|
||||||
pag.setIgnoreLeadingEmptyLines(false);
|
|
||||||
|
|
||||||
const BookTextParser::Attributes & attr = parser.getAttributes();
|
const BookTextParser::Attributes & attr = parser.getAttributes();
|
||||||
|
|
||||||
if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end())
|
if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end())
|
||||||
|
@ -286,8 +284,19 @@ namespace MWGui
|
||||||
int width = MyGUI::utility::parseInt(attr.at("width"));
|
int width = MyGUI::utility::parseInt(attr.at("width"));
|
||||||
int height = MyGUI::utility::parseInt(attr.at("height"));
|
int height = MyGUI::utility::parseInt(attr.at("height"));
|
||||||
|
|
||||||
|
bool exists;
|
||||||
|
std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists);
|
||||||
|
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
std::cerr << "Warning: Could not find \"" << src << "\" referenced by an <img> tag." << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pag.setIgnoreLeadingEmptyLines(false);
|
||||||
|
|
||||||
ImageElement elem(paper, pag, mBlockStyle,
|
ImageElement elem(paper, pag, mBlockStyle,
|
||||||
src, width, height);
|
correctedSrc, width, height);
|
||||||
elem.paginate();
|
elem.paginate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -471,8 +480,7 @@ namespace MWGui
|
||||||
MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,
|
MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,
|
||||||
parent->getName() + MyGUI::utility::toString(parent->getChildCount()));
|
parent->getName() + MyGUI::utility::toString(parent->getChildCount()));
|
||||||
|
|
||||||
std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight);
|
mImageBox->setImageTexture(src);
|
||||||
mImageBox->setImageTexture(image);
|
|
||||||
mImageBox->setProperty("NeedMouse", "false");
|
mImageBox->setProperty("NeedMouse", "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,29 +179,33 @@ namespace MWGui
|
||||||
|
|
||||||
void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
||||||
{
|
{
|
||||||
int current = std::max(0, static_cast<int>(value.getCurrent()));
|
int current = static_cast<int>(value.getCurrent());
|
||||||
int modified = static_cast<int>(value.getModified());
|
int modified = static_cast<int>(value.getModified());
|
||||||
|
|
||||||
|
// Fatigue can be negative
|
||||||
|
if (id != "FBar")
|
||||||
|
current = std::max(0, current);
|
||||||
|
|
||||||
MyGUI::Widget* w;
|
MyGUI::Widget* w;
|
||||||
std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
|
std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified);
|
||||||
if (id == "HBar")
|
if (id == "HBar")
|
||||||
{
|
{
|
||||||
mHealth->setProgressRange(modified);
|
mHealth->setProgressRange(std::max(0, modified));
|
||||||
mHealth->setProgressPosition(current);
|
mHealth->setProgressPosition(std::max(0, current));
|
||||||
getWidget(w, "HealthFrame");
|
getWidget(w, "HealthFrame");
|
||||||
w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
|
w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr);
|
||||||
}
|
}
|
||||||
else if (id == "MBar")
|
else if (id == "MBar")
|
||||||
{
|
{
|
||||||
mMagicka->setProgressRange (modified);
|
mMagicka->setProgressRange(std::max(0, modified));
|
||||||
mMagicka->setProgressPosition (current);
|
mMagicka->setProgressPosition(std::max(0, current));
|
||||||
getWidget(w, "MagickaFrame");
|
getWidget(w, "MagickaFrame");
|
||||||
w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr);
|
w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr);
|
||||||
}
|
}
|
||||||
else if (id == "FBar")
|
else if (id == "FBar")
|
||||||
{
|
{
|
||||||
mStamina->setProgressRange (modified);
|
mStamina->setProgressRange(std::max(0, modified));
|
||||||
mStamina->setProgressPosition (current);
|
mStamina->setProgressPosition(std::max(0, current));
|
||||||
getWidget(w, "FatigueFrame");
|
getWidget(w, "FatigueFrame");
|
||||||
w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
|
w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "../mwrender/characterpreview.hpp"
|
#include "../mwrender/characterpreview.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
|
||||||
#include "itemview.hpp"
|
#include "itemview.hpp"
|
||||||
#include "inventoryitemmodel.hpp"
|
#include "inventoryitemmodel.hpp"
|
||||||
|
@ -254,6 +255,19 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we unequip weapon during attack, it can lead to unexpected behaviour
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr))
|
||||||
|
{
|
||||||
|
bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name();
|
||||||
|
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
|
||||||
|
|
||||||
|
if (isWeapon && invStore.isEquipped(item.mBase))
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 1 && !shift)
|
if (count > 1 && !shift)
|
||||||
{
|
{
|
||||||
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
|
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();
|
||||||
|
@ -660,9 +674,18 @@ namespace MWGui
|
||||||
|
|
||||||
void InventoryWindow::cycle(bool next)
|
void InventoryWindow::cycle(bool next)
|
||||||
{
|
{
|
||||||
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
|
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player);
|
||||||
|
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
|
||||||
|
return;
|
||||||
|
|
||||||
ItemModel::ModelIndex selected = -1;
|
ItemModel::ModelIndex selected = -1;
|
||||||
// not using mSortFilterModel as we only need sorting, not filtering
|
// not using mSortFilterModel as we only need sorting, not filtering
|
||||||
SortFilterItemModel model(new InventoryItemModel(MWMechanics::getPlayer()));
|
SortFilterItemModel model(new InventoryItemModel(player));
|
||||||
model.setSortByType(false);
|
model.setSortByType(false);
|
||||||
model.update();
|
model.update();
|
||||||
if (model.getItemCount() == 0)
|
if (model.getItemCount() == 0)
|
||||||
|
|
|
@ -143,11 +143,11 @@ namespace MWGui
|
||||||
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));
|
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));
|
||||||
|
|
||||||
std::string levelupdescription;
|
std::string levelupdescription;
|
||||||
if(level > 20)
|
|
||||||
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default");
|
|
||||||
else
|
|
||||||
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level));
|
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level));
|
||||||
|
|
||||||
|
if (levelupdescription == "")
|
||||||
|
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default");
|
||||||
|
|
||||||
mLevelDescription->setCaption (levelupdescription);
|
mLevelDescription->setCaption (levelupdescription);
|
||||||
|
|
||||||
unsigned int availableAttributes = 0;
|
unsigned int availableAttributes = 0;
|
||||||
|
|
|
@ -102,6 +102,15 @@ namespace MWGui
|
||||||
mBackgroundImage->setVisible(visible);
|
mBackgroundImage->setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double LoadingScreen::getTargetFrameRate() const
|
||||||
|
{
|
||||||
|
double frameRateLimit = MWBase::Environment::get().getFrameRateLimit();
|
||||||
|
if (frameRateLimit > 0)
|
||||||
|
return std::min(frameRateLimit, mTargetFrameRate);
|
||||||
|
else
|
||||||
|
return mTargetFrameRate;
|
||||||
|
}
|
||||||
|
|
||||||
class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback
|
class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -141,7 +150,7 @@ namespace MWGui
|
||||||
if (mViewer->getIncrementalCompileOperation())
|
if (mViewer->getIncrementalCompileOperation())
|
||||||
{
|
{
|
||||||
mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100);
|
mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100);
|
||||||
mViewer->getIncrementalCompileOperation()->setTargetFrameRate(mTargetFrameRate);
|
mViewer->getIncrementalCompileOperation()->setTargetFrameRate(getTargetFrameRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading
|
// Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading
|
||||||
|
@ -210,7 +219,7 @@ namespace MWGui
|
||||||
void LoadingScreen::setProgress (size_t value)
|
void LoadingScreen::setProgress (size_t value)
|
||||||
{
|
{
|
||||||
// skip expensive update if there isn't enough visible progress
|
// skip expensive update if there isn't enough visible progress
|
||||||
if (value - mProgress < mProgressBar->getScrollRange()/200.f)
|
if (mProgressBar->getWidth() <= 0 || value - mProgress < mProgressBar->getScrollRange()/mProgressBar->getWidth())
|
||||||
return;
|
return;
|
||||||
value = std::min(value, mProgressBar->getScrollRange()-1);
|
value = std::min(value, mProgressBar->getScrollRange()-1);
|
||||||
mProgress = value;
|
mProgress = value;
|
||||||
|
@ -231,7 +240,7 @@ namespace MWGui
|
||||||
|
|
||||||
bool LoadingScreen::needToDrawLoadingScreen()
|
bool LoadingScreen::needToDrawLoadingScreen()
|
||||||
{
|
{
|
||||||
if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0)
|
if ( mTimer.time_m() <= mLastRenderTime + (1.0/getTargetFrameRate()) * 1000.0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// the minimal delay before a loading screen shows
|
// the minimal delay before a loading screen shows
|
||||||
|
|
|
@ -43,6 +43,8 @@ namespace MWGui
|
||||||
|
|
||||||
virtual void setVisible(bool visible);
|
virtual void setVisible(bool visible);
|
||||||
|
|
||||||
|
double getTargetFrameRate() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void findSplashScreens();
|
void findSplashScreens();
|
||||||
bool needToDrawLoadingScreen();
|
bool needToDrawLoadingScreen();
|
||||||
|
@ -73,8 +75,6 @@ namespace MWGui
|
||||||
|
|
||||||
std::vector<std::string> mSplashScreens;
|
std::vector<std::string> mSplashScreens;
|
||||||
|
|
||||||
// TODO: add releaseGLObjects() for mTexture
|
|
||||||
|
|
||||||
osg::ref_ptr<osg::Texture2D> mTexture;
|
osg::ref_ptr<osg::Texture2D> mTexture;
|
||||||
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
|
std::unique_ptr<MyGUI::ITexture> mGuiTexture;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ namespace MWGui
|
||||||
, mItemSelectionDialog(0)
|
, mItemSelectionDialog(0)
|
||||||
, mMagicSelectionDialog(0)
|
, mMagicSelectionDialog(0)
|
||||||
, mSelectedIndex(-1)
|
, mSelectedIndex(-1)
|
||||||
|
, mActivatedIndex(-1)
|
||||||
{
|
{
|
||||||
getWidget(mOkButton, "OKButton");
|
getWidget(mOkButton, "OKButton");
|
||||||
getWidget(mInstructionLabel, "InstructionLabel");
|
getWidget(mInstructionLabel, "InstructionLabel");
|
||||||
|
@ -69,6 +71,8 @@ namespace MWGui
|
||||||
|
|
||||||
void QuickKeysMenu::clear()
|
void QuickKeysMenu::clear()
|
||||||
{
|
{
|
||||||
|
mActivatedIndex = -1;
|
||||||
|
|
||||||
for (int i=0; i<10; ++i)
|
for (int i=0; i<10; ++i)
|
||||||
{
|
{
|
||||||
unassign(mQuickKeyButtons[i], i);
|
unassign(mQuickKeyButtons[i], i);
|
||||||
|
@ -254,6 +258,15 @@ namespace MWGui
|
||||||
mMagicSelectionDialog->setVisible(false);
|
mMagicSelectionDialog->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuickKeysMenu::updateActivatedQuickKey()
|
||||||
|
{
|
||||||
|
// there is no delayed action, nothing to do.
|
||||||
|
if (mActivatedIndex < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activateQuickKey(mActivatedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void QuickKeysMenu::activateQuickKey(int index)
|
void QuickKeysMenu::activateQuickKey(int index)
|
||||||
{
|
{
|
||||||
assert (index-1 >= 0);
|
assert (index-1 >= 0);
|
||||||
|
@ -263,6 +276,27 @@ namespace MWGui
|
||||||
|
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
|
MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player);
|
||||||
|
const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player);
|
||||||
|
|
||||||
|
// Delay action executing,
|
||||||
|
// if player is busy for now (casting a spell, attacking someone, etc.)
|
||||||
|
bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)
|
||||||
|
|| playerStats.getKnockedDown()
|
||||||
|
|| playerStats.getHitRecovery();
|
||||||
|
|
||||||
|
bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead();
|
||||||
|
if (isReturnNeeded && type != Type_Item)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDelayNeeded && type != Type_Item)
|
||||||
|
{
|
||||||
|
mActivatedIndex = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mActivatedIndex = -1;
|
||||||
|
|
||||||
if (type == Type_Item || type == Type_MagicItem)
|
if (type == Type_Item || type == Type_MagicItem)
|
||||||
{
|
{
|
||||||
|
@ -309,6 +343,22 @@ namespace MWGui
|
||||||
else if (type == Type_Item)
|
else if (type == Type_Item)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
|
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
|
||||||
|
bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name();
|
||||||
|
bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name();
|
||||||
|
|
||||||
|
// delay weapon switching if player is busy
|
||||||
|
if (isDelayNeeded && (isWeapon || isTool))
|
||||||
|
{
|
||||||
|
mActivatedIndex = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable weapon switching if player is dead or paralyzed
|
||||||
|
if (isReturnNeeded && (isWeapon || isTool))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->useItem(item);
|
MWBase::Environment::get().getWindowManager()->useItem(item);
|
||||||
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
// change draw state only if the item is in player's right hand
|
// change draw state only if the item is in player's right hand
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace MWGui
|
||||||
void onAssignMagicCancel ();
|
void onAssignMagicCancel ();
|
||||||
|
|
||||||
void activateQuickKey(int index);
|
void activateQuickKey(int index);
|
||||||
|
void updateActivatedQuickKey();
|
||||||
|
|
||||||
/// @note This enum is serialized, so don't move the items around!
|
/// @note This enum is serialized, so don't move the items around!
|
||||||
enum QuickKeyType
|
enum QuickKeyType
|
||||||
|
@ -64,7 +65,7 @@ namespace MWGui
|
||||||
MagicSelectionDialog* mMagicSelectionDialog;
|
MagicSelectionDialog* mMagicSelectionDialog;
|
||||||
|
|
||||||
int mSelectedIndex;
|
int mSelectedIndex;
|
||||||
|
int mActivatedIndex;
|
||||||
|
|
||||||
void onQuickKeyButtonClicked(MyGUI::Widget* sender);
|
void onQuickKeyButtonClicked(MyGUI::Widget* sender);
|
||||||
void onOkButtonClicked(MyGUI::Widget* sender);
|
void onOkButtonClicked(MyGUI::Widget* sender);
|
||||||
|
|
|
@ -180,7 +180,7 @@ namespace MWGui
|
||||||
|
|
||||||
void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value)
|
void ReviewDialog::setFatigue(const MWMechanics::DynamicStat<float>& value)
|
||||||
{
|
{
|
||||||
int current = std::max(0, static_cast<int>(value.getCurrent()));
|
int current = static_cast<int>(value.getCurrent());
|
||||||
int modified = static_cast<int>(value.getModified());
|
int modified = static_cast<int>(value.getModified());
|
||||||
|
|
||||||
mFatigue->setValue(current, modified);
|
mFatigue->setValue(current, modified);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
@ -195,6 +196,15 @@ namespace MWGui
|
||||||
|
|
||||||
void SpellWindow::cycle(bool next)
|
void SpellWindow::cycle(bool next)
|
||||||
{
|
{
|
||||||
|
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||||
|
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player);
|
||||||
|
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery())
|
||||||
|
return;
|
||||||
|
|
||||||
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
|
mSpellView->setModel(new SpellModel(MWMechanics::getPlayer()));
|
||||||
|
|
||||||
SpellModel::ModelIndex selected = 0;
|
SpellModel::ModelIndex selected = 0;
|
||||||
|
|
|
@ -102,12 +102,13 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
MyGUI::ProgressBar* pt;
|
MyGUI::ProgressBar* pt;
|
||||||
getWidget(pt, name);
|
getWidget(pt, name);
|
||||||
pt->setProgressRange(max);
|
|
||||||
pt->setProgressPosition(val);
|
|
||||||
|
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
out << val << "/" << max;
|
out << val << "/" << max;
|
||||||
setText(tname, out.str().c_str());
|
setText(tname, out.str().c_str());
|
||||||
|
|
||||||
|
pt->setProgressRange(std::max(0, max));
|
||||||
|
pt->setProgressPosition(std::max(0, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsWindow::setPlayerName(const std::string& playerName)
|
void StatsWindow::setPlayerName(const std::string& playerName)
|
||||||
|
@ -147,9 +148,13 @@ namespace MWGui
|
||||||
|
|
||||||
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
||||||
{
|
{
|
||||||
int current = std::max(0, static_cast<int>(value.getCurrent()));
|
int current = static_cast<int>(value.getCurrent());
|
||||||
int modified = static_cast<int>(value.getModified());
|
int modified = static_cast<int>(value.getModified());
|
||||||
|
|
||||||
|
// Fatigue can be negative
|
||||||
|
if (id != "FBar")
|
||||||
|
current = std::max(0, current);
|
||||||
|
|
||||||
setBar (id, id + "T", current, modified);
|
setBar (id, id + "T", current, modified);
|
||||||
|
|
||||||
// health, magicka, fatigue tooltip
|
// health, magicka, fatigue tooltip
|
||||||
|
|
|
@ -502,11 +502,10 @@ namespace MWGui
|
||||||
|
|
||||||
if (mBarWidget)
|
if (mBarWidget)
|
||||||
{
|
{
|
||||||
mBarWidget->setProgressRange(mMax);
|
mBarWidget->setProgressRange(std::max(0, mMax));
|
||||||
mBarWidget->setProgressPosition(mValue);
|
mBarWidget->setProgressPosition(std::max(0, mValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (mBarTextWidget)
|
if (mBarTextWidget)
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
|
|
|
@ -518,6 +518,8 @@ namespace MWGui
|
||||||
cleanupGarbage();
|
cleanupGarbage();
|
||||||
|
|
||||||
mHud->update();
|
mHud->update();
|
||||||
|
|
||||||
|
updateActivatedQuickKey ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowManager::updateVisible()
|
void WindowManager::updateVisible()
|
||||||
|
@ -904,19 +906,30 @@ namespace MWGui
|
||||||
|
|
||||||
if (block)
|
if (block)
|
||||||
{
|
{
|
||||||
|
osg::Timer frameTimer;
|
||||||
while (mMessageBoxManager->readPressedButton(false) == -1
|
while (mMessageBoxManager->readPressedButton(false) == -1
|
||||||
&& !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
&& !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
||||||
{
|
{
|
||||||
mMessageBoxManager->onFrame(0.f);
|
double dt = frameTimer.time_s();
|
||||||
MWBase::Environment::get().getInputManager()->update(0, true, false);
|
frameTimer.setStartTick();
|
||||||
|
|
||||||
// at the time this function is called we are in the middle of a frame,
|
mMessageBoxManager->onFrame(dt);
|
||||||
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
MWBase::Environment::get().getInputManager()->update(dt, true, false);
|
||||||
// refer to the advance() and frame() order in Engine::go()
|
|
||||||
|
if (!MWBase::Environment::get().getInputManager()->isWindowVisible())
|
||||||
|
OpenThreads::Thread::microSleep(5000);
|
||||||
|
else
|
||||||
|
{
|
||||||
mViewer->eventTraversal();
|
mViewer->eventTraversal();
|
||||||
mViewer->updateTraversal();
|
mViewer->updateTraversal();
|
||||||
mViewer->renderingTraversals();
|
mViewer->renderingTraversals();
|
||||||
|
}
|
||||||
|
// at the time this function is called we are in the middle of a frame,
|
||||||
|
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
||||||
|
// refer to the advance() and frame() order in Engine::go()
|
||||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||||
|
|
||||||
|
MWBase::Environment::get().limitFrameRate(frameTimer.time_s());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1528,6 +1541,11 @@ namespace MWGui
|
||||||
mHud->setCrosshairVisible (show && mCrosshairEnabled);
|
mHud->setCrosshairVisible (show && mCrosshairEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowManager::updateActivatedQuickKey ()
|
||||||
|
{
|
||||||
|
mQuickKeysMenu->updateActivatedQuickKey();
|
||||||
|
}
|
||||||
|
|
||||||
void WindowManager::activateQuickKey (int index)
|
void WindowManager::activateQuickKey (int index)
|
||||||
{
|
{
|
||||||
mQuickKeysMenu->activateQuickKey(index);
|
mQuickKeysMenu->activateQuickKey(index);
|
||||||
|
@ -1831,18 +1849,28 @@ namespace MWGui
|
||||||
if (mVideoWidget->hasAudioStream())
|
if (mVideoWidget->hasAudioStream())
|
||||||
MWBase::Environment::get().getSoundManager()->pauseSounds(
|
MWBase::Environment::get().getSoundManager()->pauseSounds(
|
||||||
MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie));
|
MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie));
|
||||||
|
osg::Timer frameTimer;
|
||||||
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest())
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getInputManager()->update(0, true, false);
|
double dt = frameTimer.time_s();
|
||||||
|
frameTimer.setStartTick();
|
||||||
|
|
||||||
// at the time this function is called we are in the middle of a frame,
|
MWBase::Environment::get().getInputManager()->update(dt, true, false);
|
||||||
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
|
||||||
// refer to the advance() and frame() order in Engine::go()
|
if (!MWBase::Environment::get().getInputManager()->isWindowVisible())
|
||||||
|
OpenThreads::Thread::microSleep(5000);
|
||||||
|
else
|
||||||
|
{
|
||||||
mViewer->eventTraversal();
|
mViewer->eventTraversal();
|
||||||
mViewer->updateTraversal();
|
mViewer->updateTraversal();
|
||||||
mViewer->renderingTraversals();
|
mViewer->renderingTraversals();
|
||||||
|
}
|
||||||
|
// at the time this function is called we are in the middle of a frame,
|
||||||
|
// so out of order calls are necessary to get a correct frameNumber for the next frame.
|
||||||
|
// refer to the advance() and frame() order in Engine::go()
|
||||||
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
|
||||||
|
|
||||||
|
MWBase::Environment::get().limitFrameRate(frameTimer.time_s());
|
||||||
}
|
}
|
||||||
mVideoWidget->stop();
|
mVideoWidget->stop();
|
||||||
|
|
||||||
|
@ -2062,9 +2090,12 @@ namespace MWGui
|
||||||
return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS());
|
return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WindowManager::correctBookartPath(const std::string& path, int width, int height)
|
std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists)
|
||||||
{
|
{
|
||||||
return Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS());
|
std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS());
|
||||||
|
if (exists)
|
||||||
|
*exists = mResourceSystem->getVFS()->exists(corrected);
|
||||||
|
return corrected;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WindowManager::correctTexturePath(const std::string& path)
|
std::string WindowManager::correctTexturePath(const std::string& path)
|
||||||
|
|
|
@ -241,7 +241,10 @@ namespace MWGui
|
||||||
virtual void setSpellVisibility(bool visible);
|
virtual void setSpellVisibility(bool visible);
|
||||||
virtual void setSneakVisibility(bool visible);
|
virtual void setSneakVisibility(bool visible);
|
||||||
|
|
||||||
|
/// activate selected quick key
|
||||||
virtual void activateQuickKey (int index);
|
virtual void activateQuickKey (int index);
|
||||||
|
/// update activated quick key state (if action executing was delayed for some reason)
|
||||||
|
virtual void updateActivatedQuickKey ();
|
||||||
|
|
||||||
virtual std::string getSelectedSpell() { return mSelectedSpell; }
|
virtual std::string getSelectedSpell() { return mSelectedSpell; }
|
||||||
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent);
|
virtual void setSelectedSpell(const std::string& spellId, int successChancePercent);
|
||||||
|
@ -385,7 +388,7 @@ namespace MWGui
|
||||||
|
|
||||||
// In WindowManager for now since there isn't a VFS singleton
|
// In WindowManager for now since there isn't a VFS singleton
|
||||||
virtual std::string correctIconPath(const std::string& path);
|
virtual std::string correctIconPath(const std::string& path);
|
||||||
virtual std::string correctBookartPath(const std::string& path, int width, int height);
|
virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr);
|
||||||
virtual std::string correctTexturePath(const std::string& path);
|
virtual std::string correctTexturePath(const std::string& path);
|
||||||
virtual bool textureExists(const std::string& path);
|
virtual bool textureExists(const std::string& path);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/statemanager.hpp"
|
#include "../mwbase/statemanager.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
@ -929,6 +930,9 @@ namespace MWInput
|
||||||
inventory.getSelectedEnchantItem() == inventory.end())
|
inventory.getSelectedEnchantItem() == inventory.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
|
||||||
|
return;
|
||||||
|
|
||||||
MWMechanics::DrawState_ state = mPlayer->getDrawState();
|
MWMechanics::DrawState_ state = mPlayer->getDrawState();
|
||||||
if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
|
if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
|
||||||
mPlayer->setDrawState(MWMechanics::DrawState_Spell);
|
mPlayer->setDrawState(MWMechanics::DrawState_Spell);
|
||||||
|
@ -944,6 +948,13 @@ namespace MWInput
|
||||||
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
|
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// We want to interrupt animation only if attack is prepairing, but still is not triggered
|
||||||
|
// Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice
|
||||||
|
if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer()))
|
||||||
|
mPlayer->setAttackingOrSpell(false);
|
||||||
|
else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
|
||||||
|
return;
|
||||||
|
|
||||||
MWMechanics::DrawState_ state = mPlayer->getDrawState();
|
MWMechanics::DrawState_ state = mPlayer->getDrawState();
|
||||||
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
||||||
mPlayer->setDrawState(MWMechanics::DrawState_Weapon);
|
mPlayer->setDrawState(MWMechanics::DrawState_Weapon);
|
||||||
|
|
|
@ -398,6 +398,11 @@ namespace MWMechanics
|
||||||
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
|
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
|
||||||
if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc())
|
if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc())
|
||||||
{
|
{
|
||||||
|
// Check if the creature is too far
|
||||||
|
static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fAlarmRadius")->getFloat();
|
||||||
|
if (sqrDist > fAlarmRadius * fAlarmRadius)
|
||||||
|
return;
|
||||||
|
|
||||||
bool followerOrEscorter = false;
|
bool followerOrEscorter = false;
|
||||||
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it)
|
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -802,6 +807,16 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
||||||
|
{
|
||||||
|
PtrActorMap::iterator it = mActors.find(ptr);
|
||||||
|
if (it == mActors.end())
|
||||||
|
return false;
|
||||||
|
CharacterController* ctrl = it->second->getCharacterController();
|
||||||
|
|
||||||
|
return ctrl->isAttackPrepairing();
|
||||||
|
}
|
||||||
|
|
||||||
bool Actors::isRunning(const MWWorld::Ptr& ptr)
|
bool Actors::isRunning(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
PtrActorMap::iterator it = mActors.find(ptr);
|
PtrActorMap::iterator it = mActors.find(ptr);
|
||||||
|
@ -1788,6 +1803,16 @@ namespace MWMechanics
|
||||||
return it->second->getCharacterController()->isReadyToBlock();
|
return it->second->getCharacterController()->isReadyToBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const
|
||||||
|
{
|
||||||
|
PtrActorMap::const_iterator it = mActors.find(ptr);
|
||||||
|
if (it == mActors.end())
|
||||||
|
return false;
|
||||||
|
CharacterController* ctrl = it->second->getCharacterController();
|
||||||
|
|
||||||
|
return ctrl->isAttackingOrSpell();
|
||||||
|
}
|
||||||
|
|
||||||
void Actors::fastForwardAi()
|
void Actors::fastForwardAi()
|
||||||
{
|
{
|
||||||
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
||||||
|
|
|
@ -107,6 +107,7 @@ namespace MWMechanics
|
||||||
int countDeaths (const std::string& id) const;
|
int countDeaths (const std::string& id) const;
|
||||||
///< Return the number of deaths for actors with the given ID.
|
///< Return the number of deaths for actors with the given ID.
|
||||||
|
|
||||||
|
bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
||||||
bool isRunning(const MWWorld::Ptr& ptr);
|
bool isRunning(const MWWorld::Ptr& ptr);
|
||||||
bool isSneaking(const MWWorld::Ptr& ptr);
|
bool isSneaking(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
|
@ -150,6 +151,7 @@ namespace MWMechanics
|
||||||
void clear(); // Clear death counter
|
void clear(); // Clear death counter
|
||||||
|
|
||||||
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
|
bool isReadyToBlock(const MWWorld::Ptr& ptr) const;
|
||||||
|
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PtrActorMap mActors;
|
PtrActorMap mActors;
|
||||||
|
|
|
@ -262,21 +262,15 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co
|
||||||
|
|
||||||
void MWMechanics::Alchemy::removeIngredients()
|
void MWMechanics::Alchemy::removeIngredients()
|
||||||
{
|
{
|
||||||
bool needsUpdate = false;
|
|
||||||
|
|
||||||
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
|
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
|
||||||
if (!iter->isEmpty())
|
if (!iter->isEmpty())
|
||||||
{
|
{
|
||||||
iter->getContainerStore()->remove(*iter, 1, mAlchemist);
|
iter->getContainerStore()->remove(*iter, 1, mAlchemist);
|
||||||
|
|
||||||
if (iter->getRefData().getCount()<1)
|
if (iter->getRefData().getCount()<1)
|
||||||
{
|
|
||||||
needsUpdate = true;
|
|
||||||
*iter = MWWorld::Ptr();
|
*iter = MWWorld::Ptr();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (needsUpdate)
|
|
||||||
updateEffects();
|
updateEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2218,6 +2218,12 @@ void CharacterController::setAttackTypeBasedOnMovement()
|
||||||
mAttackType = "chop";
|
mAttackType = "chop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CharacterController::isAttackPrepairing() const
|
||||||
|
{
|
||||||
|
return mUpperBodyState == UpperCharState_StartToMinAttack ||
|
||||||
|
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
||||||
|
}
|
||||||
|
|
||||||
bool CharacterController::isReadyToBlock() const
|
bool CharacterController::isReadyToBlock() const
|
||||||
{
|
{
|
||||||
return updateCarriedLeftVisible(mWeaponType);
|
return updateCarriedLeftVisible(mWeaponType);
|
||||||
|
@ -2228,6 +2234,12 @@ bool CharacterController::isKnockedOut() const
|
||||||
return mHitState == CharState_KnockOut;
|
return mHitState == CharState_KnockOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CharacterController::isAttackingOrSpell() const
|
||||||
|
{
|
||||||
|
return mUpperBodyState != UpperCharState_Nothing &&
|
||||||
|
mUpperBodyState != UpperCharState_WeapEquiped;
|
||||||
|
}
|
||||||
|
|
||||||
bool CharacterController::isSneaking() const
|
bool CharacterController::isSneaking() const
|
||||||
{
|
{
|
||||||
return mIdleState == CharState_IdleSneak ||
|
return mIdleState == CharState_IdleSneak ||
|
||||||
|
|
|
@ -263,10 +263,12 @@ public:
|
||||||
|
|
||||||
void forceStateUpdate();
|
void forceStateUpdate();
|
||||||
|
|
||||||
|
bool isAttackPrepairing() const;
|
||||||
bool isReadyToBlock() const;
|
bool isReadyToBlock() const;
|
||||||
bool isKnockedOut() const;
|
bool isKnockedOut() const;
|
||||||
bool isSneaking() const;
|
bool isSneaking() const;
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
|
bool isAttackingOrSpell() const;
|
||||||
|
|
||||||
void setAttackingOrSpell(bool attackingOrSpell);
|
void setAttackingOrSpell(bool attackingOrSpell);
|
||||||
void setAIAttackType(const std::string& attackType);
|
void setAIAttackType(const std::string& attackType);
|
||||||
|
|
|
@ -423,6 +423,11 @@ namespace MWMechanics
|
||||||
mObjects.update(duration, paused);
|
mObjects.update(duration, paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
||||||
|
{
|
||||||
|
return mActors.isAttackPrepairing(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
|
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
return mActors.isRunning(ptr);
|
return mActors.isRunning(ptr);
|
||||||
|
@ -1591,6 +1596,11 @@ namespace MWMechanics
|
||||||
return mActors.isReadyToBlock(ptr);
|
return mActors.isReadyToBlock(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr &ptr) const
|
||||||
|
{
|
||||||
|
return mActors.isAttackingOrSpell(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf)
|
void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf)
|
||||||
{
|
{
|
||||||
MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor);
|
MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor);
|
||||||
|
|
|
@ -188,6 +188,8 @@ namespace MWMechanics
|
||||||
virtual void keepPlayerAlive();
|
virtual void keepPlayerAlive();
|
||||||
|
|
||||||
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
|
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
|
||||||
|
/// Is \a ptr casting spell or using weapon now?
|
||||||
|
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const;
|
||||||
|
|
||||||
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
|
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
|
||||||
|
|
||||||
|
@ -208,8 +210,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
||||||
|
|
||||||
|
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
||||||
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
||||||
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
|
||||||
OffenseType type, int arg=0);
|
OffenseType type, int arg=0);
|
||||||
|
|
|
@ -380,7 +380,7 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int
|
||||||
for (int i=0; i<7; ++i)
|
for (int i=0; i<7; ++i)
|
||||||
{
|
{
|
||||||
if (faction.mData.mSkills[i] != -1)
|
if (faction.mData.mSkills[i] != -1)
|
||||||
skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getModified()));
|
skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getBase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skills.empty())
|
if (skills.empty())
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "aicombataction.hpp"
|
#include "aicombataction.hpp"
|
||||||
#include "spellpriority.hpp"
|
#include "spellpriority.hpp"
|
||||||
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -90,11 +91,14 @@ namespace MWMechanics
|
||||||
if (!weapon->mEnchant.empty())
|
if (!weapon->mEnchant.empty())
|
||||||
{
|
{
|
||||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(weapon->mEnchant);
|
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(weapon->mEnchant);
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes
|
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||||
&& (item.getCellRef().getEnchantmentCharge() == -1
|
{
|
||||||
|| item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost))
|
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), actor);
|
||||||
|
|
||||||
|
if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost)
|
||||||
rating += rateEffects(enchantment->mEffects, actor, enemy);
|
rating += rateEffects(enchantment->mEffects, actor, enemy);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int skill = item.getClass().getEquipmentSkill(item);
|
int skill = item.getClass().getEquipmentSkill(item);
|
||||||
if (skill != -1)
|
if (skill != -1)
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world)
|
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world)
|
||||||
: mCanWaterWalk(false), mWalkingOnWater(false)
|
: mCanWaterWalk(false), mWalkingOnWater(false)
|
||||||
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(false), mOnSlope(false)
|
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
|
||||||
, mInternalCollisionMode(true)
|
, mInternalCollisionMode(true)
|
||||||
, mExternalCollisionMode(true)
|
, mExternalCollisionMode(true)
|
||||||
, mCollisionWorld(world)
|
, mCollisionWorld(world)
|
||||||
|
|
|
@ -182,7 +182,10 @@ namespace
|
||||||
void remove()
|
void remove()
|
||||||
{
|
{
|
||||||
for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it)
|
for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it)
|
||||||
it->second->removeChild(it->first);
|
{
|
||||||
|
if (!it->second->removeChild(it->first))
|
||||||
|
std::cerr << "error removing " << it->first->getName() << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -1192,6 +1195,9 @@ namespace MWRender
|
||||||
mObjectRoot->addChild(created);
|
mObjectRoot->addChild(created);
|
||||||
mInsert->addChild(mObjectRoot);
|
mInsert->addChild(mObjectRoot);
|
||||||
}
|
}
|
||||||
|
osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(mObjectRoot.get());
|
||||||
|
if (skel)
|
||||||
|
mSkeleton = skel.get();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -118,7 +118,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
|
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
|
||||||
|
|
||||||
const NodeMap& nodeMap = getNodeMap();
|
const NodeMap& nodeMap = getNodeMap();
|
||||||
NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename));
|
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||||
if (found == nodeMap.end())
|
if (found == nodeMap.end())
|
||||||
throw std::runtime_error("Can't find attachment node " + bonename);
|
throw std::runtime_error("Can't find attachment node " + bonename);
|
||||||
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get());
|
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get());
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
@ -919,6 +920,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
removeIndividualPart(ESM::PRT_Weapon);
|
removeIndividualPart(ESM::PRT_Weapon);
|
||||||
|
// If we remove/hide weapon from player, we should reset attack animation as well
|
||||||
|
if (mPtr == MWMechanics::getPlayer())
|
||||||
|
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1189,6 +1189,14 @@ namespace MWScript
|
||||||
if (mNegativeEffect != -1)
|
if (mNegativeEffect != -1)
|
||||||
currentValue -= effects.get(mNegativeEffect).getMagnitude();
|
currentValue -= effects.get(mNegativeEffect).getMagnitude();
|
||||||
|
|
||||||
|
// GetResist* should take in account elemental shields
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistFire)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude();
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistShock)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude();
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistFrost)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude();
|
||||||
|
|
||||||
int ret = static_cast<int>(currentValue);
|
int ret = static_cast<int>(currentValue);
|
||||||
runtime.push(ret);
|
runtime.push(ret);
|
||||||
}
|
}
|
||||||
|
@ -1215,6 +1223,14 @@ namespace MWScript
|
||||||
if (mNegativeEffect != -1)
|
if (mNegativeEffect != -1)
|
||||||
currentValue -= effects.get(mNegativeEffect).getMagnitude();
|
currentValue -= effects.get(mNegativeEffect).getMagnitude();
|
||||||
|
|
||||||
|
// SetResist* should take in account elemental shields
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistFire)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude();
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistShock)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude();
|
||||||
|
if (mPositiveEffect == ESM::MagicEffect::ResistFrost)
|
||||||
|
currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude();
|
||||||
|
|
||||||
int arg = runtime[0].mInteger;
|
int arg = runtime[0].mInteger;
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
effects.modifyBase(mPositiveEffect, (arg - static_cast<int>(currentValue)));
|
effects.modifyBase(mPositiveEffect, (arg - static_cast<int>(currentValue)));
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
#include <osg/Matrixf>
|
#include <osg/Matrixf>
|
||||||
|
|
||||||
|
@ -271,7 +272,6 @@ namespace MWSound
|
||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Gets the combined volume settings for the given sound type
|
// Gets the combined volume settings for the given sound type
|
||||||
float SoundManager::volumeFromType(PlayType type) const
|
float SoundManager::volumeFromType(PlayType type) const
|
||||||
{
|
{
|
||||||
|
@ -298,7 +298,6 @@ namespace MWSound
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void SoundManager::stopMusic()
|
void SoundManager::stopMusic()
|
||||||
{
|
{
|
||||||
if(mMusic)
|
if(mMusic)
|
||||||
|
@ -349,6 +348,7 @@ namespace MWSound
|
||||||
void SoundManager::startRandomTitle()
|
void SoundManager::startRandomTitle()
|
||||||
{
|
{
|
||||||
std::vector<std::string> filelist;
|
std::vector<std::string> filelist;
|
||||||
|
auto &tracklist = mMusicToPlay[mCurrentPlaylist];
|
||||||
if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end())
|
if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end())
|
||||||
{
|
{
|
||||||
const std::map<std::string, VFS::File*>& index = mVFS->getIndex();
|
const std::map<std::string, VFS::File*>& index = mVFS->getIndex();
|
||||||
|
@ -367,7 +367,6 @@ namespace MWSound
|
||||||
}
|
}
|
||||||
|
|
||||||
mMusicFiles[mCurrentPlaylist] = filelist;
|
mMusicFiles[mCurrentPlaylist] = filelist;
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
filelist = mMusicFiles[mCurrentPlaylist];
|
filelist = mMusicFiles[mCurrentPlaylist];
|
||||||
|
@ -375,15 +374,25 @@ namespace MWSound
|
||||||
if(filelist.empty())
|
if(filelist.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int i = Misc::Rng::rollDice(filelist.size());
|
// Do a Fisher-Yates shuffle
|
||||||
|
|
||||||
// Don't play the same music track twice in a row
|
// Repopulate if playlist is empty
|
||||||
if (filelist[i] == mLastPlayedMusic)
|
if(tracklist.empty())
|
||||||
{
|
{
|
||||||
i = (i+1) % filelist.size();
|
tracklist.resize(filelist.size());
|
||||||
|
std::iota(tracklist.begin(), tracklist.end(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
advanceMusic(filelist[i]);
|
int i = Misc::Rng::rollDice(tracklist.size());
|
||||||
|
|
||||||
|
// Reshuffle if last played music is the same after a repopulation
|
||||||
|
if(filelist[tracklist[i]] == mLastPlayedMusic)
|
||||||
|
i = (i+1) % tracklist.size();
|
||||||
|
|
||||||
|
// Remove music from list after advancing music
|
||||||
|
advanceMusic(filelist[tracklist[i]]);
|
||||||
|
tracklist[i] = tracklist.back();
|
||||||
|
tracklist.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SoundManager::isMusicPlaying()
|
bool SoundManager::isMusicPlaying()
|
||||||
|
@ -570,6 +579,9 @@ namespace MWSound
|
||||||
if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000)
|
if((mode&Play_RemoveAtDistance) && (mListenerPos-objpos).length2() > 2000*2000)
|
||||||
return MWBase::SoundPtr();
|
return MWBase::SoundPtr();
|
||||||
|
|
||||||
|
// Only one copy of given sound can be played at time on ptr, so stop previous copy
|
||||||
|
stopSound3D(ptr, soundId);
|
||||||
|
|
||||||
if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer())
|
if(!(mode&Play_NoPlayerLocal) && ptr == MWMechanics::getPlayer())
|
||||||
{
|
{
|
||||||
sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D));
|
sound.reset(new Sound(volume * sfx->mVolume, basevol, pitch, mode|type|Play_2D));
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ namespace MWSound
|
||||||
|
|
||||||
// Caches available music tracks by <playlist name, (sound files) >
|
// Caches available music tracks by <playlist name, (sound files) >
|
||||||
std::map<std::string, std::vector<std::string> > mMusicFiles;
|
std::map<std::string, std::vector<std::string> > mMusicFiles;
|
||||||
|
std::unordered_map<std::string, std::vector<int>> mMusicToPlay; // A list with music files not yet played
|
||||||
std::string mLastPlayedMusic; // The music file that was last played
|
std::string mLastPlayedMusic; // The music file that was last played
|
||||||
|
|
||||||
float mMasterVolume;
|
float mMasterVolume;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <MyGUI_TextIterator.h>
|
||||||
|
|
||||||
#include "components/loadinglistener/loadinglistener.hpp"
|
#include "components/loadinglistener/loadinglistener.hpp"
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ struct ContentLoader
|
||||||
virtual void load(const boost::filesystem::path& filepath, int& index)
|
virtual void load(const boost::filesystem::path& filepath, int& index)
|
||||||
{
|
{
|
||||||
std::cout << "Loading content file " << filepath.string() << std::endl;
|
std::cout << "Loading content file " << filepath.string() << std::endl;
|
||||||
mListener.setLabel(filepath.string());
|
mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -17,7 +17,7 @@ if (GTEST_FOUND)
|
||||||
|
|
||||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||||
|
|
||||||
add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||||
|
|
||||||
target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components)
|
target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components)
|
||||||
# Fix for not visible pthreads functions for linker with glibc 2.15
|
# Fix for not visible pthreads functions for linker with glibc 2.15
|
||||||
|
|
|
@ -96,7 +96,7 @@ if (OPENMW_USE_UNSHIELD)
|
||||||
include_directories(${LIBUNSHIELD_INCLUDE_DIRS})
|
include_directories(${LIBUNSHIELD_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(openmw-wizard
|
openmw_add_executable(openmw-wizard
|
||||||
${GUI_TYPE}
|
${GUI_TYPE}
|
||||||
${WIZARD}
|
${WIZARD}
|
||||||
${WIZARD_HEADER}
|
${WIZARD_HEADER}
|
||||||
|
|
|
@ -142,3 +142,32 @@ foreach (u ${ARGN})
|
||||||
add_hdr (OPENCS ${dir} ${u})
|
add_hdr (OPENCS ${dir} ${u})
|
||||||
endforeach (u)
|
endforeach (u)
|
||||||
endmacro (opencs_hdrs_noqt)
|
endmacro (opencs_hdrs_noqt)
|
||||||
|
|
||||||
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
|
macro (openmw_add_executable target)
|
||||||
|
set(OMW_ADD_EXE_OPTIONS WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL)
|
||||||
|
set(OMW_ADD_EXE_VALUES)
|
||||||
|
set(OMW_ADD_EXE_MULTI_VALUES)
|
||||||
|
cmake_parse_arguments(OMW_ADD_EXE "${OMW_ADD_EXE_OPTIONS}" "${OMW_ADD_EXE_VALUES}" "${OMW_ADD_EXE_MULTI_VALUES}" ${ARGN})
|
||||||
|
|
||||||
|
if (OMW_ADD_EXE_WIN32)
|
||||||
|
set(OMW_ADD_EXE_WIN32_VALUE WIN32)
|
||||||
|
endif (OMW_ADD_EXE_WIN32)
|
||||||
|
|
||||||
|
if (OMW_ADD_EXE_MACOSX_BUNDLE)
|
||||||
|
set(OMW_ADD_EXE_MACOSX_BUNDLE_VALUE MACOSX_BUNDLE)
|
||||||
|
endif (OMW_ADD_EXE_MACOSX_BUNDLE)
|
||||||
|
|
||||||
|
if (OMW_ADD_EXE_EXCLUDE_FROM_ALL)
|
||||||
|
set(OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE EXCLUDE_FROM_ALL)
|
||||||
|
endif (OMW_ADD_EXE_EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
add_executable(${target} ${OMW_ADD_EXE_WIN32_VALUE} ${OMW_ADD_EXE_MACOSX_BUNDLE_VALUE} ${OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE} ${OMW_ADD_EXE_UNPARSED_ARGUMENTS})
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
|
||||||
|
set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)")
|
||||||
|
endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
|
||||||
|
endif (MSVC)
|
||||||
|
endmacro (openmw_add_executable)
|
||||||
|
|
|
@ -49,7 +49,7 @@ add_component_dir (shader
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (sceneutil
|
add_component_dir (sceneutil
|
||||||
clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller
|
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||||
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,10 @@
|
||||||
#include <osg/Texture2D>
|
#include <osg/Texture2D>
|
||||||
#include <osg/UserDataContainer>
|
#include <osg/UserDataContainer>
|
||||||
|
|
||||||
#include <osgAnimation/MorphGeometry>
|
|
||||||
|
|
||||||
#include <osgParticle/Emitter>
|
#include <osgParticle/Emitter>
|
||||||
|
|
||||||
#include <components/nif/data.hpp>
|
#include <components/nif/data.hpp>
|
||||||
|
#include <components/sceneutil/morphgeometry.hpp>
|
||||||
|
|
||||||
#include "userdata.hpp"
|
#include "userdata.hpp"
|
||||||
|
|
||||||
|
@ -188,7 +187,7 @@ GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data)
|
||||||
|
|
||||||
void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable)
|
void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable)
|
||||||
{
|
{
|
||||||
osgAnimation::MorphGeometry* morphGeom = static_cast<osgAnimation::MorphGeometry*>(drawable);
|
SceneUtil::MorphGeometry* morphGeom = static_cast<SceneUtil::MorphGeometry*>(drawable);
|
||||||
if (hasInput())
|
if (hasInput())
|
||||||
{
|
{
|
||||||
if (mKeyFrames.size() <= 1)
|
if (mKeyFrames.size() <= 1)
|
||||||
|
@ -202,7 +201,7 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable
|
||||||
val = it->interpKey(input);
|
val = it->interpKey(input);
|
||||||
val = std::max(0.f, std::min(1.f, val));
|
val = std::max(0.f, std::min(1.f, val));
|
||||||
|
|
||||||
osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i);
|
SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i);
|
||||||
if (target.getWeight() != val)
|
if (target.getWeight() != val)
|
||||||
{
|
{
|
||||||
target.setWeight(val);
|
target.setWeight(val);
|
||||||
|
@ -210,8 +209,6 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UVController::UVController()
|
UVController::UVController()
|
||||||
|
|
|
@ -31,11 +31,6 @@ namespace osgParticle
|
||||||
class Emitter;
|
class Emitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace osgAnimation
|
|
||||||
{
|
|
||||||
class MorphGeometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace NifOsg
|
namespace NifOsg
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -172,7 +167,7 @@ namespace NifOsg
|
||||||
virtual float getMaximum() const;
|
virtual float getMaximum() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Must be set on an osgAnimation::MorphGeometry.
|
/// Must be set on a SceneUtil::MorphGeometry.
|
||||||
class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
|
class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -13,9 +13,6 @@
|
||||||
#include <components/misc/resourcehelpers.hpp>
|
#include <components/misc/resourcehelpers.hpp>
|
||||||
#include <components/resource/imagemanager.hpp>
|
#include <components/resource/imagemanager.hpp>
|
||||||
|
|
||||||
// skel
|
|
||||||
#include <osgAnimation/MorphGeometry>
|
|
||||||
|
|
||||||
// particle
|
// particle
|
||||||
#include <osgParticle/ParticleSystem>
|
#include <osgParticle/ParticleSystem>
|
||||||
#include <osgParticle/ParticleSystemUpdater>
|
#include <osgParticle/ParticleSystemUpdater>
|
||||||
|
@ -39,6 +36,7 @@
|
||||||
#include <components/nif/effect.hpp>
|
#include <components/nif/effect.hpp>
|
||||||
#include <components/sceneutil/skeleton.hpp>
|
#include <components/sceneutil/skeleton.hpp>
|
||||||
#include <components/sceneutil/riggeometry.hpp>
|
#include <components/sceneutil/riggeometry.hpp>
|
||||||
|
#include <components/sceneutil/morphgeometry.hpp>
|
||||||
|
|
||||||
#include "particle.hpp"
|
#include "particle.hpp"
|
||||||
#include "userdata.hpp"
|
#include "userdata.hpp"
|
||||||
|
@ -83,35 +81,6 @@ namespace
|
||||||
collectDrawableProperties(nifNode->parent, out);
|
collectDrawableProperties(nifNode->parent, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FrameSwitch : public osg::Group
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FrameSwitch()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameSwitch(const FrameSwitch& copy, const osg::CopyOp& copyop)
|
|
||||||
: osg::Group(copy, copyop)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
META_Object(NifOsg, FrameSwitch)
|
|
||||||
|
|
||||||
virtual void traverse(osg::NodeVisitor& nv)
|
|
||||||
{
|
|
||||||
if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR)
|
|
||||||
osg::Group::traverse(nv);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (unsigned int i=0; i<getNumChildren(); ++i)
|
|
||||||
{
|
|
||||||
if (i%2 == nv.getTraversalNumber()%2)
|
|
||||||
getChild(i)->accept(nv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
|
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
|
||||||
// set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera.
|
// set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera.
|
||||||
// Must be set as a cull callback.
|
// Must be set as a cull callback.
|
||||||
|
@ -154,70 +123,6 @@ namespace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UpdateMorphGeometry : public osg::Drawable::CullCallback
|
|
||||||
{
|
|
||||||
UpdateMorphGeometry()
|
|
||||||
: mLastFrameNumber(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop)
|
|
||||||
: osg::Drawable::CullCallback(copy, copyop)
|
|
||||||
, mLastFrameNumber(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
META_Object(NifOsg, UpdateMorphGeometry)
|
|
||||||
|
|
||||||
virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const
|
|
||||||
{
|
|
||||||
osgAnimation::MorphGeometry* geom = static_cast<osgAnimation::MorphGeometry*>(drw);
|
|
||||||
if (!geom)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mLastFrameNumber == nv->getTraversalNumber())
|
|
||||||
return false;
|
|
||||||
mLastFrameNumber = nv->getTraversalNumber();
|
|
||||||
|
|
||||||
geom->transformSoftwareMethod();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable unsigned int mLastFrameNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box
|
|
||||||
// every time the morph weights change. To do so we return a maximum containing box that is big enough for all possible combinations of morph targets.
|
|
||||||
class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
StaticBoundingBoxCallback()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticBoundingBoxCallback(const osg::BoundingBox& bounds)
|
|
||||||
: mBoundingBox(bounds)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticBoundingBoxCallback(const StaticBoundingBoxCallback& copy, const osg::CopyOp& copyop)
|
|
||||||
: osg::Drawable::ComputeBoundingBoxCallback(copy, copyop)
|
|
||||||
, mBoundingBox(copy.mBoundingBox)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
META_Object(NifOsg, StaticBoundingBoxCallback)
|
|
||||||
|
|
||||||
virtual osg::BoundingBox computeBound(const osg::Drawable&) const
|
|
||||||
{
|
|
||||||
return mBoundingBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
osg::BoundingBox mBoundingBox;
|
|
||||||
};
|
|
||||||
|
|
||||||
void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys)
|
void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys)
|
||||||
{
|
{
|
||||||
for(size_t i = 0;i < tk->list.size();i++)
|
for(size_t i = 0;i < tk->list.size();i++)
|
||||||
|
@ -1107,106 +1012,49 @@ namespace NifOsg
|
||||||
|
|
||||||
void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Geometry> geometry;
|
osg::ref_ptr<osg::Drawable> drawable;
|
||||||
for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next)
|
for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next)
|
||||||
{
|
{
|
||||||
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
||||||
continue;
|
continue;
|
||||||
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
|
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
|
||||||
{
|
{
|
||||||
geometry = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
|
drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
|
||||||
|
|
||||||
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
|
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
|
||||||
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
||||||
setupController(ctrl.getPtr(), morphctrl, animflags);
|
setupController(ctrl.getPtr(), morphctrl, animflags);
|
||||||
geometry->setUpdateCallback(morphctrl);
|
drawable->setUpdateCallback(morphctrl);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!geometry.get())
|
if (!drawable.get())
|
||||||
{
|
{
|
||||||
geometry = new osg::Geometry;
|
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
|
||||||
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags);
|
drawable = geom;
|
||||||
|
triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (geometry->getDataVariance() == osg::Object::DYNAMIC)
|
drawable->setName(triShape->name);
|
||||||
{
|
|
||||||
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch
|
|
||||||
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
|
|
||||||
geometry->setDataVariance(osg::Object::STATIC);
|
|
||||||
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
|
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
|
parentNode->addChild(drawable);
|
||||||
frameswitch->addChild(geometry);
|
|
||||||
frameswitch->addChild(geom2);
|
|
||||||
|
|
||||||
parentNode->addChild(frameswitch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
parentNode->addChild(geometry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osgAnimation::MorphGeometry> morphGeom = new osgAnimation::MorphGeometry;
|
osg::ref_ptr<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
|
||||||
morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE);
|
|
||||||
// No normals available in the MorphData
|
|
||||||
morphGeom->setMorphNormals(false);
|
|
||||||
|
|
||||||
morphGeom->setUpdateCallback(NULL);
|
osg::ref_ptr<osg::Geometry> sourceGeometry (new osg::Geometry);
|
||||||
morphGeom->setCullCallback(new UpdateMorphGeometry);
|
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
|
||||||
morphGeom->setUseVertexBufferObjects(true);
|
morphGeom->setSourceGeometry(sourceGeometry);
|
||||||
|
|
||||||
triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags);
|
|
||||||
|
|
||||||
morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB);
|
|
||||||
|
|
||||||
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
||||||
if (morphs.empty())
|
if (morphs.empty())
|
||||||
return morphGeom;
|
return morphGeom;
|
||||||
// Note we are not interested in morph 0, which just contains the original vertices
|
// Note we are not interested in morph 0, which just contains the original vertices
|
||||||
for (unsigned int i = 1; i < morphs.size(); ++i)
|
for (unsigned int i = 1; i < morphs.size(); ++i)
|
||||||
{
|
morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f);
|
||||||
osg::ref_ptr<osg::Geometry> morphTarget = new osg::Geometry;
|
|
||||||
morphTarget->setVertexArray(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]));
|
|
||||||
morphGeom->addMorphTarget(morphTarget, 0.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the bounding box containing all possible morph combinations
|
|
||||||
|
|
||||||
std::vector<osg::BoundingBox> vertBounds(morphs[0].mVertices.size());
|
|
||||||
|
|
||||||
// Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex.
|
|
||||||
// The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position.
|
|
||||||
|
|
||||||
// Start with zero offsets which will happen when no morphs are applied.
|
|
||||||
for (unsigned int i=0; i<vertBounds.size(); ++i)
|
|
||||||
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
|
|
||||||
|
|
||||||
for (unsigned int i = 1; i < morphs.size(); ++i)
|
|
||||||
{
|
|
||||||
for (unsigned int j=0; j<morphs[i].mVertices.size() && vertBounds.size(); ++j)
|
|
||||||
{
|
|
||||||
osg::BoundingBox& bounds = vertBounds[j];
|
|
||||||
bounds.expandBy(bounds._max + morphs[i].mVertices[j]);
|
|
||||||
bounds.expandBy(bounds._min + morphs[i].mVertices[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::BoundingBox box;
|
|
||||||
for (unsigned int i=0; i<vertBounds.size(); ++i)
|
|
||||||
{
|
|
||||||
vertBounds[i]._max += morphs[0].mVertices[i];
|
|
||||||
vertBounds[i]._min += morphs[0].mVertices[i];
|
|
||||||
box.expandBy(vertBounds[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box
|
|
||||||
morphGeom->getBound();
|
|
||||||
|
|
||||||
// Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating
|
|
||||||
morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box));
|
|
||||||
|
|
||||||
return morphGeom;
|
return morphGeom;
|
||||||
}
|
}
|
||||||
|
@ -1219,6 +1067,7 @@ namespace NifOsg
|
||||||
|
|
||||||
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
||||||
rig->setSourceGeometry(geometry);
|
rig->setSourceGeometry(geometry);
|
||||||
|
rig->setName(triShape->name);
|
||||||
|
|
||||||
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
||||||
|
|
||||||
|
@ -1233,7 +1082,6 @@ namespace NifOsg
|
||||||
|
|
||||||
SceneUtil::RigGeometry::BoneInfluence influence;
|
SceneUtil::RigGeometry::BoneInfluence influence;
|
||||||
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
||||||
//influence.mWeights.reserve(weights.size());
|
|
||||||
for(size_t j = 0;j < weights.size();j++)
|
for(size_t j = 0;j < weights.size();j++)
|
||||||
{
|
{
|
||||||
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
|
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
|
||||||
|
@ -1246,17 +1094,7 @@ namespace NifOsg
|
||||||
}
|
}
|
||||||
rig->setInfluenceMap(map);
|
rig->setInfluenceMap(map);
|
||||||
|
|
||||||
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch
|
parentNode->addChild(rig);
|
||||||
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
|
|
||||||
rig->setDataVariance(osg::Object::STATIC);
|
|
||||||
|
|
||||||
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
|
|
||||||
|
|
||||||
SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
|
|
||||||
frameswitch->addChild(rig);
|
|
||||||
frameswitch->addChild(rig2);
|
|
||||||
|
|
||||||
parentNode->addChild(frameswitch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::BlendFunc::BlendFuncMode getBlendMode(int mode)
|
osg::BlendFunc::BlendFuncMode getBlendMode(int mode)
|
||||||
|
@ -1674,6 +1512,8 @@ namespace NifOsg
|
||||||
|
|
||||||
bool hasMatCtrl = false;
|
bool hasMatCtrl = false;
|
||||||
|
|
||||||
|
int lightmode = 1;
|
||||||
|
|
||||||
for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it)
|
for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it)
|
||||||
{
|
{
|
||||||
const Nif::Property* property = *it;
|
const Nif::Property* property = *it;
|
||||||
|
@ -1706,9 +1546,9 @@ namespace NifOsg
|
||||||
case Nif::RC_NiVertexColorProperty:
|
case Nif::RC_NiVertexColorProperty:
|
||||||
{
|
{
|
||||||
const Nif::NiVertexColorProperty* vertprop = static_cast<const Nif::NiVertexColorProperty*>(property);
|
const Nif::NiVertexColorProperty* vertprop = static_cast<const Nif::NiVertexColorProperty*>(property);
|
||||||
if (!hasVertexColors)
|
lightmode = vertprop->data.lightmode;
|
||||||
break;
|
|
||||||
switch (vertprop->flags)
|
switch (vertprop->data.vertmode)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
mat->setColorMode(osg::Material::OFF);
|
mat->setColorMode(osg::Material::OFF);
|
||||||
|
@ -1717,7 +1557,10 @@ namespace NifOsg
|
||||||
mat->setColorMode(osg::Material::EMISSION);
|
mat->setColorMode(osg::Material::EMISSION);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
if (lightmode != 0)
|
||||||
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
||||||
|
else
|
||||||
|
mat->setColorMode(osg::Material::OFF);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1772,6 +1615,35 @@ namespace NifOsg
|
||||||
mat->setColorMode(osg::Material::AMBIENT);
|
mat->setColorMode(osg::Material::AMBIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lightmode == 0)
|
||||||
|
{
|
||||||
|
osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK);
|
||||||
|
diffuse = osg::Vec4f(0,0,0,diffuse.a());
|
||||||
|
mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse);
|
||||||
|
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're told to use vertex colors but there are none to use, use a default color instead.
|
||||||
|
if (!hasVertexColors)
|
||||||
|
{
|
||||||
|
switch (mat->getColorMode())
|
||||||
|
{
|
||||||
|
case osg::Material::AMBIENT:
|
||||||
|
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||||
|
break;
|
||||||
|
case osg::Material::AMBIENT_AND_DIFFUSE:
|
||||||
|
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||||
|
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||||
|
break;
|
||||||
|
case osg::Material::EMISSION:
|
||||||
|
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mat->setColorMode(osg::Material::OFF);
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF
|
if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF
|
||||||
&& mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1)
|
&& mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1)
|
||||||
&& mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1)
|
&& mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1)
|
||||||
|
|
|
@ -38,4 +38,9 @@ namespace Resource
|
||||||
return mVFS;
|
return mVFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceManager::releaseGLObjects(osg::State *state)
|
||||||
|
{
|
||||||
|
mCache->releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace VFS
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
class Stats;
|
class Stats;
|
||||||
|
class State;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Resource
|
namespace Resource
|
||||||
|
@ -38,6 +39,8 @@ namespace Resource
|
||||||
|
|
||||||
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {}
|
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {}
|
||||||
|
|
||||||
|
virtual void releaseGLObjects(osg::State* state);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const VFS::Manager* mVFS;
|
const VFS::Manager* mVFS;
|
||||||
osg::ref_ptr<Resource::ObjectCache> mCache;
|
osg::ref_ptr<Resource::ObjectCache> mCache;
|
||||||
|
|
|
@ -97,4 +97,10 @@ namespace Resource
|
||||||
(*it)->reportStats(frameNumber, stats);
|
(*it)->reportStats(frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResourceSystem::releaseGLObjects(osg::State *state)
|
||||||
|
{
|
||||||
|
for (std::vector<ResourceManager*>::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it)
|
||||||
|
(*it)->releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace VFS
|
||||||
namespace osg
|
namespace osg
|
||||||
{
|
{
|
||||||
class Stats;
|
class Stats;
|
||||||
|
class State;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Resource
|
namespace Resource
|
||||||
|
@ -60,6 +61,9 @@ namespace Resource
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
|
void reportStats(unsigned int frameNumber, osg::Stats* stats) const;
|
||||||
|
|
||||||
|
/// Call releaseGLObjects for each resource manager.
|
||||||
|
void releaseGLObjects(osg::State* state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<SceneManager> mSceneManager;
|
std::unique_ptr<SceneManager> mSceneManager;
|
||||||
std::unique_ptr<ImageManager> mImageManager;
|
std::unique_ptr<ImageManager> mImageManager;
|
||||||
|
|
|
@ -628,6 +628,11 @@ namespace Resource
|
||||||
{
|
{
|
||||||
mCache->releaseGLObjects(state);
|
mCache->releaseGLObjects(state);
|
||||||
mInstanceCache->releaseGLObjects(state);
|
mInstanceCache->releaseGLObjects(state);
|
||||||
|
|
||||||
|
mShaderManager->releaseGLObjects(state);
|
||||||
|
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSharedStateMutex);
|
||||||
|
mSharedStateManager->releaseGLObjects(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico)
|
void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico)
|
||||||
|
|
|
@ -116,7 +116,7 @@ namespace Resource
|
||||||
|
|
||||||
/// Manually release created OpenGL objects for the given graphics context. This may be required
|
/// Manually release created OpenGL objects for the given graphics context. This may be required
|
||||||
/// in cases where multiple contexts are used over the lifetime of the application.
|
/// in cases where multiple contexts are used over the lifetime of the application.
|
||||||
void releaseGLObjects(osg::State* state);
|
void releaseGLObjects(osg::State* state) override;
|
||||||
|
|
||||||
/// Set up an IncrementalCompileOperation for background compiling of loaded scenes.
|
/// Set up an IncrementalCompileOperation for background compiling of loaded scenes.
|
||||||
void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico);
|
void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico);
|
||||||
|
|
|
@ -32,29 +32,29 @@ namespace SceneUtil
|
||||||
|
|
||||||
virtual void apply(osg::MatrixTransform& node)
|
virtual void apply(osg::MatrixTransform& node)
|
||||||
{
|
{
|
||||||
applyNode(node);
|
traverse(node);
|
||||||
}
|
|
||||||
virtual void apply(osg::Geometry& node)
|
|
||||||
{
|
|
||||||
applyNode(node);
|
|
||||||
}
|
}
|
||||||
virtual void apply(osg::Node& node)
|
virtual void apply(osg::Node& node)
|
||||||
{
|
{
|
||||||
applyNode(node);
|
traverse(node);
|
||||||
}
|
}
|
||||||
virtual void apply(osg::Group& node)
|
virtual void apply(osg::Group& node)
|
||||||
{
|
{
|
||||||
applyNode(node);
|
traverse(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyNode(osg::Node& node)
|
virtual void apply(osg::Drawable& drawable)
|
||||||
{
|
{
|
||||||
std::string lowerName = Misc::StringUtils::lowerCase(node.getName());
|
std::string lowerName = Misc::StringUtils::lowerCase(drawable.getName());
|
||||||
if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0)
|
if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0)
|
||||||
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0))
|
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0))
|
||||||
mToCopy.push_back(&node);
|
{
|
||||||
else
|
osg::Node* node = &drawable;
|
||||||
traverse(node);
|
while (node && node->getNumParents() && !node->getStateSet())
|
||||||
|
node = node->getParent(0);
|
||||||
|
if (node)
|
||||||
|
mToCopy.push_back(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void doCopy()
|
void doCopy()
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include <osgParticle/Emitter>
|
#include <osgParticle/Emitter>
|
||||||
#include <osgParticle/Program>
|
#include <osgParticle/Program>
|
||||||
|
|
||||||
#include <osgAnimation/MorphGeometry>
|
#include <components/sceneutil/morphgeometry.hpp>
|
||||||
|
|
||||||
#include <components/sceneutil/riggeometry.hpp>
|
#include <components/sceneutil/riggeometry.hpp>
|
||||||
|
|
||||||
|
@ -49,46 +49,12 @@ namespace SceneUtil
|
||||||
{
|
{
|
||||||
if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
|
if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
|
||||||
return operator()(partsys);
|
return operator()(partsys);
|
||||||
if (dynamic_cast<const osgAnimation::MorphGeometry*>(drawable))
|
|
||||||
{
|
|
||||||
osg::CopyOp copyop = *this;
|
|
||||||
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
||||||
|
|
||||||
#if OSG_VERSION_LESS_THAN(3,5,0)
|
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
|
||||||
/*
|
|
||||||
|
|
||||||
Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor:
|
|
||||||
|
|
||||||
if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS))
|
|
||||||
{
|
|
||||||
if (_useVertexBufferObjects)
|
|
||||||
{
|
|
||||||
// copying of arrays doesn't set up buffer objects so we'll need to force
|
|
||||||
// Geometry to assign these, we'll do this by switching off VBO's then renabling them.
|
|
||||||
setUseVertexBufferObjects(false);
|
|
||||||
setUseVertexBufferObjects(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from,
|
|
||||||
causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation.
|
|
||||||
|
|
||||||
Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ).
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
osg::Drawable* cloned = osg::clone(drawable, copyop);
|
|
||||||
return cloned;
|
|
||||||
}
|
|
||||||
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
|
|
||||||
{
|
{
|
||||||
return osg::clone(drawable, *this);
|
return osg::clone(drawable, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return osg::CopyOp::operator()(drawable);
|
return osg::CopyOp::operator()(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
190
components/sceneutil/morphgeometry.cpp
Normal file
190
components/sceneutil/morphgeometry.cpp
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
#include "morphgeometry.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
|
||||||
|
MorphGeometry::MorphGeometry()
|
||||||
|
: mLastFrameNumber(0)
|
||||||
|
, mDirty(true)
|
||||||
|
, mMorphedBoundingBox(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MorphGeometry::MorphGeometry(const MorphGeometry ©, const osg::CopyOp ©op)
|
||||||
|
: osg::Drawable(copy, copyop)
|
||||||
|
, mMorphTargets(copy.mMorphTargets)
|
||||||
|
, mLastFrameNumber(0)
|
||||||
|
, mDirty(true)
|
||||||
|
, mMorphedBoundingBox(false)
|
||||||
|
{
|
||||||
|
setSourceGeometry(copy.getSourceGeometry());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom)
|
||||||
|
{
|
||||||
|
mSourceGeometry = sourceGeom;
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
|
mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY);
|
||||||
|
|
||||||
|
const osg::Geometry& from = *mSourceGeometry;
|
||||||
|
osg::Geometry& to = *mGeometry[i];
|
||||||
|
to.setSupportsDisplayList(false);
|
||||||
|
to.setUseVertexBufferObjects(true);
|
||||||
|
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
|
||||||
|
|
||||||
|
// vertices are modified every frame, so we need to deep copy them.
|
||||||
|
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
||||||
|
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
|
||||||
|
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
|
||||||
|
if (vertexArray)
|
||||||
|
{
|
||||||
|
vertexArray->setVertexBufferObject(vbo);
|
||||||
|
to.setVertexArray(vertexArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::addMorphTarget(osg::Vec3Array *offsets, float weight)
|
||||||
|
{
|
||||||
|
mMorphTargets.push_back(MorphTarget(offsets, weight));
|
||||||
|
mMorphedBoundingBox = false;
|
||||||
|
dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::dirty()
|
||||||
|
{
|
||||||
|
mDirty = true;
|
||||||
|
if (!mMorphedBoundingBox)
|
||||||
|
dirtyBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> MorphGeometry::getSourceGeometry() const
|
||||||
|
{
|
||||||
|
return mSourceGeometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::accept(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (!nv.validNodeMask(*this))
|
||||||
|
return;
|
||||||
|
|
||||||
|
nv.pushOntoNodePath(this);
|
||||||
|
|
||||||
|
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||||
|
cull(&nv);
|
||||||
|
else
|
||||||
|
nv.apply(*this);
|
||||||
|
|
||||||
|
nv.popFromNodePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::accept(osg::PrimitiveFunctor& func) const
|
||||||
|
{
|
||||||
|
getGeometry(mLastFrameNumber)->accept(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::BoundingBox MorphGeometry::computeBoundingBox() const
|
||||||
|
{
|
||||||
|
bool anyMorphTarget = false;
|
||||||
|
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
|
||||||
|
if (mMorphTargets[i].getWeight() > 0)
|
||||||
|
{
|
||||||
|
anyMorphTarget = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// before the MorphGeometry has started animating, we will use a regular bounding box (this is required
|
||||||
|
// for correct object placements, which uses the bounding box)
|
||||||
|
if (!mMorphedBoundingBox && !anyMorphTarget)
|
||||||
|
{
|
||||||
|
return mSourceGeometry->getBoundingBox();
|
||||||
|
}
|
||||||
|
// once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMorphedBoundingBox = true;
|
||||||
|
|
||||||
|
osg::Vec3Array& sourceVerts = *static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||||
|
std::vector<osg::BoundingBox> vertBounds(sourceVerts.size());
|
||||||
|
|
||||||
|
// Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex.
|
||||||
|
// The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position.
|
||||||
|
|
||||||
|
// Start with zero offsets which will happen when no morphs are applied.
|
||||||
|
for (unsigned int i=0; i<vertBounds.size(); ++i)
|
||||||
|
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < mMorphTargets.size(); ++i)
|
||||||
|
{
|
||||||
|
const osg::Vec3Array& offsets = *mMorphTargets[i].getOffsets();
|
||||||
|
for (unsigned int j=0; j<offsets.size() && j<vertBounds.size(); ++j)
|
||||||
|
{
|
||||||
|
osg::BoundingBox& bounds = vertBounds[j];
|
||||||
|
bounds.expandBy(bounds._max + offsets[j]);
|
||||||
|
bounds.expandBy(bounds._min + offsets[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::BoundingBox box;
|
||||||
|
for (unsigned int i=0; i<vertBounds.size(); ++i)
|
||||||
|
{
|
||||||
|
vertBounds[i]._max += sourceVerts[i];
|
||||||
|
vertBounds[i]._min += sourceVerts[i];
|
||||||
|
box.expandBy(vertBounds[i]);
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MorphGeometry::cull(osg::NodeVisitor *nv)
|
||||||
|
{
|
||||||
|
if (mLastFrameNumber == nv->getTraversalNumber() || !mDirty)
|
||||||
|
{
|
||||||
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
|
nv->pushOntoNodePath(&geom);
|
||||||
|
nv->apply(geom);
|
||||||
|
nv->popFromNodePath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDirty = false;
|
||||||
|
mLastFrameNumber = nv->getTraversalNumber();
|
||||||
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
|
|
||||||
|
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||||
|
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
|
||||||
|
assert(positionSrc->size() == positionDst->size());
|
||||||
|
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
|
||||||
|
(*positionDst)[vertex] = (*positionSrc)[vertex];
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
|
||||||
|
{
|
||||||
|
float weight = mMorphTargets[i].getWeight();
|
||||||
|
if (weight == 0.f)
|
||||||
|
continue;
|
||||||
|
const osg::Vec3Array* offsets = mMorphTargets[i].getOffsets();
|
||||||
|
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
|
||||||
|
(*positionDst)[vertex] += (*offsets)[vertex] * weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
positionDst->dirty();
|
||||||
|
|
||||||
|
nv->pushOntoNodePath(&geom);
|
||||||
|
nv->apply(geom);
|
||||||
|
nv->popFromNodePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const
|
||||||
|
{
|
||||||
|
return mGeometry[frame%2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
83
components/sceneutil/morphgeometry.hpp
Normal file
83
components/sceneutil/morphgeometry.hpp
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H
|
||||||
|
#define OPENMW_COMPONENTS_MORPHGEOMETRY_H
|
||||||
|
|
||||||
|
#include <osg/Geometry>
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
|
||||||
|
/// @brief Vertex morphing implementation.
|
||||||
|
/// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
|
||||||
|
/// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
|
||||||
|
class MorphGeometry : public osg::Drawable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MorphGeometry();
|
||||||
|
MorphGeometry(const MorphGeometry& copy, const osg::CopyOp& copyop);
|
||||||
|
|
||||||
|
META_Object(SceneUtil, MorphGeometry)
|
||||||
|
|
||||||
|
/// Initialize this geometry from the source geometry.
|
||||||
|
/// @note The source geometry will not be modified.
|
||||||
|
void setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom);
|
||||||
|
|
||||||
|
class MorphTarget
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
osg::ref_ptr<osg::Vec3Array> mOffsets;
|
||||||
|
float mWeight;
|
||||||
|
public:
|
||||||
|
MorphTarget(osg::Vec3Array* offsets, float w = 1.0) : mOffsets(offsets), mWeight(w) {}
|
||||||
|
void setWeight(float weight) { mWeight = weight; }
|
||||||
|
float getWeight() const { return mWeight; }
|
||||||
|
osg::Vec3Array* getOffsets() { return mOffsets.get(); }
|
||||||
|
const osg::Vec3Array* getOffsets() const { return mOffsets.get(); }
|
||||||
|
void setOffsets(osg::Vec3Array* offsets) { mOffsets = offsets; }
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<MorphTarget> MorphTargetList;
|
||||||
|
|
||||||
|
virtual void addMorphTarget( osg::Vec3Array* offsets, float weight = 1.0 );
|
||||||
|
|
||||||
|
/** Set the MorphGeometry dirty.*/
|
||||||
|
void dirty();
|
||||||
|
|
||||||
|
/** Get the list of MorphTargets.*/
|
||||||
|
const MorphTargetList& getMorphTargetList() const { return mMorphTargets; }
|
||||||
|
|
||||||
|
/** Get the list of MorphTargets. Warning if you modify this array you will have to call dirty() */
|
||||||
|
MorphTargetList& getMorphTargetList() { return mMorphTargets; }
|
||||||
|
|
||||||
|
/** Return the \c MorphTarget at position \c i.*/
|
||||||
|
inline const MorphTarget& getMorphTarget( unsigned int i ) const { return mMorphTargets[i]; }
|
||||||
|
|
||||||
|
/** Return the \c MorphTarget at position \c i.*/
|
||||||
|
inline MorphTarget& getMorphTarget( unsigned int i ) { return mMorphTargets[i]; }
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> getSourceGeometry() const;
|
||||||
|
|
||||||
|
virtual void accept(osg::NodeVisitor &nv);
|
||||||
|
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
|
||||||
|
virtual void accept(osg::PrimitiveFunctor&) const;
|
||||||
|
|
||||||
|
virtual osg::BoundingBox computeBoundingBox() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cull(osg::NodeVisitor* nv);
|
||||||
|
|
||||||
|
MorphTargetList mMorphTargets;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> mSourceGeometry;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> mGeometry[2];
|
||||||
|
osg::Geometry* getGeometry(unsigned int frame) const;
|
||||||
|
|
||||||
|
unsigned int mLastFrameNumber;
|
||||||
|
bool mDirty; // Have any morph targets changed?
|
||||||
|
|
||||||
|
mutable bool mMorphedBoundingBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -10,73 +10,17 @@
|
||||||
namespace SceneUtil
|
namespace SceneUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
class UpdateRigBounds : public osg::Drawable::UpdateCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UpdateRigBounds()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop)
|
|
||||||
: osg::Drawable::UpdateCallback(copy, copyop)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
META_Object(SceneUtil, UpdateRigBounds)
|
|
||||||
|
|
||||||
void update(osg::NodeVisitor* nv, osg::Drawable* drw)
|
|
||||||
{
|
|
||||||
RigGeometry* rig = static_cast<RigGeometry*>(drw);
|
|
||||||
|
|
||||||
rig->updateBounds(nv);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: make threadsafe for multiple cull threads
|
|
||||||
class UpdateRigGeometry : public osg::Drawable::CullCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UpdateRigGeometry()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop)
|
|
||||||
: osg::Drawable::CullCallback(copy, copyop)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
META_Object(SceneUtil, UpdateRigGeometry)
|
|
||||||
|
|
||||||
virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const
|
|
||||||
{
|
|
||||||
RigGeometry* geom = static_cast<RigGeometry*>(drw);
|
|
||||||
geom->update(nv);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix.
|
|
||||||
// So we return nothing. Bounds are updated every frame in the UpdateCallback.
|
|
||||||
class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
RigGeometry::RigGeometry()
|
RigGeometry::RigGeometry()
|
||||||
: mSkeleton(NULL)
|
: mSkeleton(NULL)
|
||||||
, mLastFrameNumber(0)
|
, mLastFrameNumber(0)
|
||||||
, mBoundsFirstFrame(true)
|
, mBoundsFirstFrame(true)
|
||||||
{
|
{
|
||||||
setCullCallback(new UpdateRigGeometry);
|
setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct
|
||||||
setUpdateCallback(new UpdateRigBounds);
|
// update done in accept(NodeVisitor&)
|
||||||
setSupportsDisplayList(false);
|
|
||||||
setUseVertexBufferObjects(true);
|
|
||||||
setComputeBoundingBoxCallback(new DummyComputeBoundCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op)
|
RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op)
|
||||||
: osg::Geometry(copy, copyop)
|
: Drawable(copy, copyop)
|
||||||
, mSkeleton(NULL)
|
, mSkeleton(NULL)
|
||||||
, mInfluenceMap(copy.mInfluenceMap)
|
, mInfluenceMap(copy.mInfluenceMap)
|
||||||
, mLastFrameNumber(0)
|
, mLastFrameNumber(0)
|
||||||
|
@ -89,24 +33,14 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
|
||||||
{
|
{
|
||||||
mSourceGeometry = sourceGeometry;
|
mSourceGeometry = sourceGeometry;
|
||||||
|
|
||||||
osg::Geometry& from = *sourceGeometry;
|
for (unsigned int i=0; i<2; ++i)
|
||||||
|
{
|
||||||
if (from.getStateSet())
|
const osg::Geometry& from = *sourceGeometry;
|
||||||
setStateSet(from.getStateSet());
|
mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY);
|
||||||
|
osg::Geometry& to = *mGeometry[i];
|
||||||
// shallow copy primitive sets & vertex attributes that we will not modify
|
to.setSupportsDisplayList(false);
|
||||||
setPrimitiveSetList(from.getPrimitiveSetList());
|
to.setUseVertexBufferObjects(true);
|
||||||
setColorArray(from.getColorArray());
|
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
|
||||||
setSecondaryColorArray(from.getSecondaryColorArray());
|
|
||||||
setFogCoordArray(from.getFogCoordArray());
|
|
||||||
|
|
||||||
// need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5
|
|
||||||
osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList();
|
|
||||||
for (unsigned int i=0; i<texCoordList.size(); ++i)
|
|
||||||
if (texCoordList[i])
|
|
||||||
setTexCoordArray(i, texCoordList[i], osg::Array::BIND_PER_VERTEX);
|
|
||||||
|
|
||||||
setVertexAttribArrayList(from.getVertexAttribArrayList());
|
|
||||||
|
|
||||||
// vertices and normals are modified every frame, so we need to deep copy them.
|
// vertices and normals are modified every frame, so we need to deep copy them.
|
||||||
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
||||||
|
@ -117,30 +51,30 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
|
||||||
if (vertexArray)
|
if (vertexArray)
|
||||||
{
|
{
|
||||||
vertexArray->setVertexBufferObject(vbo);
|
vertexArray->setVertexBufferObject(vbo);
|
||||||
setVertexArray(vertexArray);
|
to.setVertexArray(vertexArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (osg::Array* normals = from.getNormalArray())
|
if (const osg::Array* normals = from.getNormalArray())
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
|
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
|
||||||
if (normalArray)
|
if (normalArray)
|
||||||
{
|
{
|
||||||
normalArray->setVertexBufferObject(vbo);
|
normalArray->setVertexBufferObject(vbo);
|
||||||
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
|
to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const osg::Vec4Array* tangents = dynamic_cast<const osg::Vec4Array*>(from.getTexCoordArray(7)))
|
||||||
if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7)))
|
|
||||||
{
|
{
|
||||||
mSourceTangents = tangents;
|
mSourceTangents = tangents;
|
||||||
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
|
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
|
||||||
tangentArray->setVertexBufferObject(vbo);
|
tangentArray->setVertexBufferObject(vbo);
|
||||||
setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
|
to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
mSourceTangents = NULL;
|
mSourceTangents = NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry()
|
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry()
|
||||||
{
|
{
|
||||||
|
@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat
|
||||||
ptrresult[14] += ptr[14] * weight;
|
ptrresult[14] += ptr[14] * weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RigGeometry::update(osg::NodeVisitor* nv)
|
void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||||
{
|
{
|
||||||
if (!mSkeleton)
|
if (!mSkeleton)
|
||||||
{
|
{
|
||||||
|
@ -238,23 +172,27 @@ void RigGeometry::update(osg::NodeVisitor* nv)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mSkeleton->getActive() && mLastFrameNumber != 0)
|
if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber())
|
||||||
return;
|
{
|
||||||
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
if (mLastFrameNumber == nv->getTraversalNumber())
|
nv->pushOntoNodePath(&geom);
|
||||||
|
nv->apply(geom);
|
||||||
|
nv->popFromNodePath();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
mLastFrameNumber = nv->getTraversalNumber();
|
mLastFrameNumber = nv->getTraversalNumber();
|
||||||
|
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||||
|
|
||||||
mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
|
mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
|
||||||
|
|
||||||
// skinning
|
// skinning
|
||||||
osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||||
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
|
const osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
|
||||||
osg::Vec4Array* tangentSrc = mSourceTangents;
|
const osg::Vec4Array* tangentSrc = mSourceTangents;
|
||||||
|
|
||||||
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray());
|
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
|
||||||
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray());
|
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
|
||||||
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(getTexCoordArray(7));
|
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
|
||||||
|
|
||||||
for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it)
|
for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -294,6 +232,10 @@ void RigGeometry::update(osg::NodeVisitor* nv)
|
||||||
normalDst->dirty();
|
normalDst->dirty();
|
||||||
if (tangentDst)
|
if (tangentDst)
|
||||||
tangentDst->dirty();
|
tangentDst->dirty();
|
||||||
|
|
||||||
|
nv->pushOntoNodePath(&geom);
|
||||||
|
nv->apply(geom);
|
||||||
|
nv->popFromNodePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RigGeometry::updateBounds(osg::NodeVisitor *nv)
|
void RigGeometry::updateBounds(osg::NodeVisitor *nv)
|
||||||
|
@ -365,5 +307,32 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap)
|
||||||
mInfluenceMap = influenceMap;
|
mInfluenceMap = influenceMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RigGeometry::accept(osg::NodeVisitor &nv)
|
||||||
|
{
|
||||||
|
if (!nv.validNodeMask(*this))
|
||||||
|
return;
|
||||||
|
|
||||||
|
nv.pushOntoNodePath(this);
|
||||||
|
|
||||||
|
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||||
|
cull(&nv);
|
||||||
|
else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
|
||||||
|
updateBounds(&nv);
|
||||||
|
else
|
||||||
|
nv.apply(*this);
|
||||||
|
|
||||||
|
nv.popFromNodePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RigGeometry::accept(osg::PrimitiveFunctor& func) const
|
||||||
|
{
|
||||||
|
getGeometry(mLastFrameNumber)->accept(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const
|
||||||
|
{
|
||||||
|
return mGeometry[frame%2].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,9 @@ namespace SceneUtil
|
||||||
/// @brief Mesh skinning implementation.
|
/// @brief Mesh skinning implementation.
|
||||||
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton.
|
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton.
|
||||||
/// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important.
|
/// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important.
|
||||||
/// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done
|
/// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
|
||||||
/// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing
|
/// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
|
||||||
/// the double buffering inside RigGeometry.
|
class RigGeometry : public osg::Drawable
|
||||||
class RigGeometry : public osg::Geometry
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RigGeometry();
|
RigGeometry();
|
||||||
|
@ -24,6 +23,9 @@ namespace SceneUtil
|
||||||
|
|
||||||
META_Object(SceneUtil, RigGeometry)
|
META_Object(SceneUtil, RigGeometry)
|
||||||
|
|
||||||
|
// At this point compileGLObjects() remains unimplemented, hard to avoid race conditions
|
||||||
|
// and there is limited value in compiling anyway since the data will change again for the next frame
|
||||||
|
|
||||||
struct BoneInfluence
|
struct BoneInfluence
|
||||||
{
|
{
|
||||||
osg::Matrixf mInvBindMatrix;
|
osg::Matrixf mInvBindMatrix;
|
||||||
|
@ -45,15 +47,19 @@ namespace SceneUtil
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> getSourceGeometry();
|
osg::ref_ptr<osg::Geometry> getSourceGeometry();
|
||||||
|
|
||||||
// Called automatically by our CullCallback
|
virtual void accept(osg::NodeVisitor &nv);
|
||||||
void update(osg::NodeVisitor* nv);
|
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
|
||||||
|
virtual void accept(osg::PrimitiveFunctor&) const;
|
||||||
// Called automatically by our UpdateCallback
|
|
||||||
void updateBounds(osg::NodeVisitor* nv);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void cull(osg::NodeVisitor* nv);
|
||||||
|
void updateBounds(osg::NodeVisitor* nv);
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geometry> mGeometry[2];
|
||||||
|
osg::Geometry* getGeometry(unsigned int frame) const;
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> mSourceGeometry;
|
osg::ref_ptr<osg::Geometry> mSourceGeometry;
|
||||||
osg::ref_ptr<osg::Vec4Array> mSourceTangents;
|
osg::ref_ptr<const osg::Vec4Array> mSourceTangents;
|
||||||
Skeleton* mSkeleton;
|
Skeleton* mSkeleton;
|
||||||
|
|
||||||
osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix;
|
osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
#include <components/sceneutil/skeleton.hpp>
|
#include <components/sceneutil/skeleton.hpp>
|
||||||
#include <components/sceneutil/riggeometry.hpp>
|
#include <components/sceneutil/riggeometry.hpp>
|
||||||
|
#include <components/sceneutil/morphgeometry.hpp>
|
||||||
|
|
||||||
namespace SceneUtil
|
namespace SceneUtil
|
||||||
{
|
{
|
||||||
|
@ -37,20 +38,20 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class FrameSwitchSerializer : public osgDB::ObjectWrapper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FrameSwitchSerializer()
|
|
||||||
: osgDB::ObjectWrapper(createInstanceFunc<osg::Group>, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RigGeometrySerializer : public osgDB::ObjectWrapper
|
class RigGeometrySerializer : public osgDB::ObjectWrapper
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RigGeometrySerializer()
|
RigGeometrySerializer()
|
||||||
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry")
|
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MorphGeometrySerializer : public osgDB::ObjectWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MorphGeometrySerializer()
|
||||||
|
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::MorphGeometry>, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -95,8 +96,8 @@ void registerSerializers()
|
||||||
osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager();
|
osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager();
|
||||||
mgr->addWrapper(new PositionAttitudeTransformSerializer);
|
mgr->addWrapper(new PositionAttitudeTransformSerializer);
|
||||||
mgr->addWrapper(new SkeletonSerializer);
|
mgr->addWrapper(new SkeletonSerializer);
|
||||||
mgr->addWrapper(new FrameSwitchSerializer);
|
|
||||||
mgr->addWrapper(new RigGeometrySerializer);
|
mgr->addWrapper(new RigGeometrySerializer);
|
||||||
|
mgr->addWrapper(new MorphGeometrySerializer);
|
||||||
mgr->addWrapper(new LightManagerSerializer);
|
mgr->addWrapper(new LightManagerSerializer);
|
||||||
mgr->addWrapper(new CameraRelativeTransformSerializer);
|
mgr->addWrapper(new CameraRelativeTransformSerializer);
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,6 @@ Skeleton::Skeleton()
|
||||||
, mNeedToUpdateBoneMatrices(true)
|
, mNeedToUpdateBoneMatrices(true)
|
||||||
, mActive(true)
|
, mActive(true)
|
||||||
, mLastFrameNumber(0)
|
, mLastFrameNumber(0)
|
||||||
, mTraversedEvenFrame(false)
|
|
||||||
, mTraversedOddFrame(false)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op)
|
||||||
, mNeedToUpdateBoneMatrices(true)
|
, mNeedToUpdateBoneMatrices(true)
|
||||||
, mActive(copy.mActive)
|
, mActive(copy.mActive)
|
||||||
, mLastFrameNumber(0)
|
, mLastFrameNumber(0)
|
||||||
, mTraversedEvenFrame(false)
|
|
||||||
, mTraversedOddFrame(false)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber)
|
||||||
|
|
||||||
mLastFrameNumber = traversalNumber;
|
mLastFrameNumber = traversalNumber;
|
||||||
|
|
||||||
if (mLastFrameNumber % 2 == 0)
|
|
||||||
mTraversedEvenFrame = true;
|
|
||||||
else
|
|
||||||
mTraversedOddFrame = true;
|
|
||||||
|
|
||||||
if (mNeedToUpdateBoneMatrices)
|
if (mNeedToUpdateBoneMatrices)
|
||||||
{
|
{
|
||||||
if (mRootBone.get())
|
if (mRootBone.get())
|
||||||
|
@ -144,18 +135,14 @@ bool Skeleton::getActive() const
|
||||||
|
|
||||||
void Skeleton::markDirty()
|
void Skeleton::markDirty()
|
||||||
{
|
{
|
||||||
mTraversedEvenFrame = false;
|
mLastFrameNumber = 0;
|
||||||
mTraversedOddFrame = false;
|
|
||||||
mBoneCache.clear();
|
mBoneCache.clear();
|
||||||
mBoneCacheInit = false;
|
mBoneCacheInit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skeleton::traverse(osg::NodeVisitor& nv)
|
void Skeleton::traverse(osg::NodeVisitor& nv)
|
||||||
{
|
{
|
||||||
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR
|
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0)
|
||||||
// need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized
|
|
||||||
// this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node
|
|
||||||
&& mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame)
|
|
||||||
return;
|
return;
|
||||||
osg::Group::traverse(nv);
|
osg::Group::traverse(nv);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,6 @@ namespace SceneUtil
|
||||||
bool mActive;
|
bool mActive;
|
||||||
|
|
||||||
unsigned int mLastFrameNumber;
|
unsigned int mLastFrameNumber;
|
||||||
bool mTraversedEvenFrame;
|
|
||||||
bool mTraversedOddFrame;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,4 +158,13 @@ namespace Shader
|
||||||
return found->second;
|
return found->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShaderManager::releaseGLObjects(osg::State *state)
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mMutex);
|
||||||
|
for (auto shader : mShaders)
|
||||||
|
shader.second->releaseGLObjects(state);
|
||||||
|
for (auto program : mPrograms)
|
||||||
|
program.second->releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ namespace Shader
|
||||||
|
|
||||||
osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader);
|
osg::ref_ptr<osg::Program> getProgram(osg::ref_ptr<osg::Shader> vertexShader, osg::ref_ptr<osg::Shader> fragmentShader);
|
||||||
|
|
||||||
|
void releaseGLObjects(osg::State* state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mPath;
|
std::string mPath;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <components/resource/imagemanager.hpp>
|
#include <components/resource/imagemanager.hpp>
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
#include <components/sceneutil/riggeometry.hpp>
|
#include <components/sceneutil/riggeometry.hpp>
|
||||||
|
#include <components/sceneutil/morphgeometry.hpp>
|
||||||
|
|
||||||
#include "shadermanager.hpp"
|
#include "shadermanager.hpp"
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ namespace Shader
|
||||||
, mMaterialOverridden(false)
|
, mMaterialOverridden(false)
|
||||||
, mNormalHeight(false)
|
, mNormalHeight(false)
|
||||||
, mTexStageRequiringTangents(-1)
|
, mTexStageRequiringTangents(-1)
|
||||||
|
, mNode(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ namespace Shader
|
||||||
{
|
{
|
||||||
if (node.getStateSet())
|
if (node.getStateSet())
|
||||||
{
|
{
|
||||||
pushRequirements();
|
pushRequirements(node);
|
||||||
applyStateSet(node.getStateSet(), node);
|
applyStateSet(node.getStateSet(), node);
|
||||||
traverse(node);
|
traverse(node);
|
||||||
popRequirements();
|
popRequirements();
|
||||||
|
@ -234,9 +236,10 @@ namespace Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderVisitor::pushRequirements()
|
void ShaderVisitor::pushRequirements(osg::Node& node)
|
||||||
{
|
{
|
||||||
mRequirements.push_back(mRequirements.back());
|
mRequirements.push_back(mRequirements.back());
|
||||||
|
mRequirements.back().mNode = &node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderVisitor::popRequirements()
|
void ShaderVisitor::popRequirements()
|
||||||
|
@ -244,8 +247,12 @@ namespace Shader
|
||||||
mRequirements.pop_back();
|
mRequirements.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node)
|
void ShaderVisitor::createProgram(const ShaderRequirements &reqs)
|
||||||
{
|
{
|
||||||
|
if (!reqs.mShaderRequired && !mForceShaders)
|
||||||
|
return;
|
||||||
|
|
||||||
|
osg::Node& node = *reqs.mNode;
|
||||||
osg::StateSet* writableStateSet = NULL;
|
osg::StateSet* writableStateSet = NULL;
|
||||||
if (mAllowedToModifyStateSets)
|
if (mAllowedToModifyStateSets)
|
||||||
writableStateSet = node.getOrCreateStateSet();
|
writableStateSet = node.getOrCreateStateSet();
|
||||||
|
@ -302,12 +309,42 @@ namespace Shader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs)
|
||||||
|
{
|
||||||
|
bool useShader = reqs.mShaderRequired || mForceShaders;
|
||||||
|
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (mAllowedToModifyStateSets && (useShader || generateTangents))
|
||||||
|
{
|
||||||
|
// make sure that all UV sets are there
|
||||||
|
for (std::map<int, std::string>::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it)
|
||||||
|
{
|
||||||
|
if (sourceGeometry.getTexCoordArray(it->first) == NULL)
|
||||||
|
{
|
||||||
|
sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generateTangents)
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator (new osgUtil::TangentSpaceGenerator);
|
||||||
|
generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents);
|
||||||
|
|
||||||
|
sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
void ShaderVisitor::apply(osg::Geometry& geometry)
|
void ShaderVisitor::apply(osg::Geometry& geometry)
|
||||||
{
|
{
|
||||||
bool needPop = (geometry.getStateSet() != NULL);
|
bool needPop = (geometry.getStateSet() != NULL);
|
||||||
if (geometry.getStateSet())
|
if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it
|
||||||
{
|
{
|
||||||
pushRequirements();
|
pushRequirements(geometry);
|
||||||
applyStateSet(geometry.getStateSet(), geometry);
|
applyStateSet(geometry.getStateSet(), geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,44 +352,9 @@ namespace Shader
|
||||||
{
|
{
|
||||||
const ShaderRequirements& reqs = mRequirements.back();
|
const ShaderRequirements& reqs = mRequirements.back();
|
||||||
|
|
||||||
bool useShader = reqs.mShaderRequired || mForceShaders;
|
adjustGeometry(geometry, reqs);
|
||||||
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
|
|
||||||
|
|
||||||
if (mAllowedToModifyStateSets && (useShader || generateTangents))
|
createProgram(reqs);
|
||||||
{
|
|
||||||
osg::ref_ptr<osg::Geometry> sourceGeometry = &geometry;
|
|
||||||
SceneUtil::RigGeometry* rig = dynamic_cast<SceneUtil::RigGeometry*>(&geometry);
|
|
||||||
if (rig)
|
|
||||||
sourceGeometry = rig->getSourceGeometry();
|
|
||||||
|
|
||||||
bool requiresSetGeometry = false;
|
|
||||||
|
|
||||||
// make sure that all UV sets are there
|
|
||||||
for (std::map<int, std::string>::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it)
|
|
||||||
{
|
|
||||||
if (sourceGeometry->getTexCoordArray(it->first) == NULL)
|
|
||||||
{
|
|
||||||
sourceGeometry->setTexCoordArray(it->first, sourceGeometry->getTexCoordArray(0));
|
|
||||||
requiresSetGeometry = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (generateTangents)
|
|
||||||
{
|
|
||||||
osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator (new osgUtil::TangentSpaceGenerator);
|
|
||||||
generator->generate(sourceGeometry, reqs.mTexStageRequiringTangents);
|
|
||||||
|
|
||||||
sourceGeometry->setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX);
|
|
||||||
requiresSetGeometry = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rig && requiresSetGeometry)
|
|
||||||
rig->setSourceGeometry(sourceGeometry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: find a better place for the stateset
|
|
||||||
if (useShader)
|
|
||||||
createProgram(reqs, geometry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needPop)
|
if (needPop)
|
||||||
|
@ -366,16 +368,27 @@ namespace Shader
|
||||||
|
|
||||||
if (drawable.getStateSet())
|
if (drawable.getStateSet())
|
||||||
{
|
{
|
||||||
pushRequirements();
|
pushRequirements(drawable);
|
||||||
applyStateSet(drawable.getStateSet(), drawable);
|
applyStateSet(drawable.getStateSet(), drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mRequirements.empty())
|
if (!mRequirements.empty())
|
||||||
{
|
{
|
||||||
const ShaderRequirements& reqs = mRequirements.back();
|
const ShaderRequirements& reqs = mRequirements.back();
|
||||||
// TODO: find a better place for the stateset
|
createProgram(reqs);
|
||||||
if (reqs.mShaderRequired || mForceShaders)
|
|
||||||
createProgram(reqs, drawable);
|
if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&drawable))
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Geometry> sourceGeometry = rig->getSourceGeometry();
|
||||||
|
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
|
||||||
|
rig->setSourceGeometry(sourceGeometry);
|
||||||
|
}
|
||||||
|
else if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable))
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Geometry> sourceGeometry = morph->getSourceGeometry();
|
||||||
|
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
|
||||||
|
morph->setSourceGeometry(sourceGeometry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needPop)
|
if (needPop)
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Shader
|
||||||
|
|
||||||
void applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node);
|
void applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node);
|
||||||
|
|
||||||
void pushRequirements();
|
void pushRequirements(osg::Node& node);
|
||||||
void popRequirements();
|
void popRequirements();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -89,13 +89,17 @@ namespace Shader
|
||||||
|
|
||||||
// -1 == no tangents required
|
// -1 == no tangents required
|
||||||
int mTexStageRequiringTangents;
|
int mTexStageRequiringTangents;
|
||||||
|
|
||||||
|
// the Node that requested these requirements
|
||||||
|
osg::Node* mNode;
|
||||||
};
|
};
|
||||||
std::vector<ShaderRequirements> mRequirements;
|
std::vector<ShaderRequirements> mRequirements;
|
||||||
|
|
||||||
std::string mDefaultVsTemplate;
|
std::string mDefaultVsTemplate;
|
||||||
std::string mDefaultFsTemplate;
|
std::string mDefaultFsTemplate;
|
||||||
|
|
||||||
void createProgram(const ShaderRequirements& reqs, osg::Node& node);
|
void createProgram(const ShaderRequirements& reqs);
|
||||||
|
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,4 +243,18 @@ namespace Terrain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferCache::releaseGLObjects(osg::State *state)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mIndexBufferMutex);
|
||||||
|
for (auto indexbuffer : mIndexBufferMap)
|
||||||
|
indexbuffer.second->releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mUvBufferMutex);
|
||||||
|
for (auto uvbuffer : mUvBufferMap)
|
||||||
|
uvbuffer.second->releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Terrain
|
||||||
|
|
||||||
void clearCache();
|
void clearCache();
|
||||||
|
|
||||||
// TODO: add releaseGLObjects() for our vertex/element buffer objects
|
void releaseGLObjects(osg::State* state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
|
||||||
|
|
|
@ -62,6 +62,12 @@ void ChunkManager::clearCache()
|
||||||
mBufferCache.clearCache();
|
mBufferCache.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChunkManager::releaseGLObjects(osg::State *state)
|
||||||
|
{
|
||||||
|
ResourceManager::releaseGLObjects(state);
|
||||||
|
mBufferCache.releaseGLObjects(state);
|
||||||
|
}
|
||||||
|
|
||||||
void ChunkManager::setCullingActive(bool active)
|
void ChunkManager::setCullingActive(bool active)
|
||||||
{
|
{
|
||||||
mCullingActive = active;
|
mCullingActive = active;
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace Terrain
|
||||||
|
|
||||||
virtual void clearCache();
|
virtual void clearCache();
|
||||||
|
|
||||||
|
void releaseGLObjects(osg::State* state) override;
|
||||||
|
|
||||||
void setCullingActive(bool active);
|
void setCullingActive(bool active);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace Gui
|
||||||
private:
|
private:
|
||||||
MyGUI::Widget* mLeft;
|
MyGUI::Widget* mLeft;
|
||||||
MyGUI::Widget* mRight;
|
MyGUI::Widget* mRight;
|
||||||
|
MyGUI::Widget* mClient;
|
||||||
|
|
||||||
void align();
|
void align();
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,22 +14,22 @@ Basics
|
||||||
Directories
|
Directories
|
||||||
===========
|
===========
|
||||||
|
|
||||||
OpenMW and OpenMW CS us multiple directories on the file system. First of all
|
OpenMW and OpenMW CS use multiple directories on the file system. First of all
|
||||||
there is a *user directory* that holds configuration files and a number of
|
there is a *user directory* that holds configuration files and a number of
|
||||||
different sub-directories. The location of the user directory is hard-coded
|
different sub-directories. The location of the user directory is hard-coded
|
||||||
into the CS and depends on your operating system.
|
into the CS and depends on your operating system.
|
||||||
|
|
||||||
================ =========================================
|
================ =========================================
|
||||||
Operating System User Dircetory
|
Operating System User Directory
|
||||||
================ =========================================
|
================ =========================================
|
||||||
GNU/Linux ``<whatever>``
|
GNU/Linux ``~/.config/openmw/``
|
||||||
OS X ``~/Library/Application Support/openmw/``
|
OS X ``~/Library/Application Support/openmw/``
|
||||||
Windows ``<whatever>``
|
Windows ``C:\Users\ *Username* \Documents\my games\OpenMW``
|
||||||
================ =========================================
|
================ =========================================
|
||||||
|
|
||||||
In addition to to this single hard-coded directory both OpenMW and OpenMW CS
|
In addition to to this single hard-coded directory both OpenMW and OpenMW CS
|
||||||
need a place to seek for a actuals data files of the game: textures, 3D models,
|
need a place to search for actual data files of the game: textures, 3D models,
|
||||||
sounds and record files that store objects in game; dialogues an so one. These
|
sounds and record files that store objects in game; dialogues and so on. These
|
||||||
files are called *content files*. We support multiple such paths (we call them
|
files are called *content files*. We support multiple such paths (we call them
|
||||||
*data paths*) as specified in the configuration. Usually one data path points
|
*data paths*) as specified in the configuration. Usually one data path points
|
||||||
to the directory where the original Morrowind game is either installed or
|
to the directory where the original Morrowind game is either installed or
|
||||||
|
@ -42,12 +42,12 @@ Content files
|
||||||
=============
|
=============
|
||||||
|
|
||||||
The original Morrowind engine by Bethesda Softworks uses two types of content
|
The original Morrowind engine by Bethesda Softworks uses two types of content
|
||||||
files: `esm` (master) and `esp` (plugin). The distinction between those two is
|
files: `ESM` (master) and `ESP` (plugin). The distinction between those two is
|
||||||
not clear, and often confusing. One would expect the `esm` (master) file to be
|
not clear, and often confusing. One would expect the `ESM` (master) file to be
|
||||||
used to specify one master, which is then modified by the `esp` plugins. And
|
used to specify one master, which is then modified by the `ESP` plugins. And
|
||||||
indeed: this is the basic idea. However, the official expansions were also made
|
indeed: this is the basic idea. However, the official expansions were also made
|
||||||
as ESM files, even though they could essentially be described as really large
|
as ESM files, even though they could essentially be described as really large
|
||||||
plugins, and therefore would rather use `esp` files. There were technical
|
plugins, and therefore should have been `ESP` files. There were technical
|
||||||
reasons behind this decision – somewhat valid in the case of the original
|
reasons behind this decision – somewhat valid in the case of the original
|
||||||
engine, but clearly it is better to create a system that can be used in a more
|
engine, but clearly it is better to create a system that can be used in a more
|
||||||
sensible way. OpenMW achieves this with our own content file types.
|
sensible way. OpenMW achieves this with our own content file types.
|
||||||
|
@ -62,7 +62,7 @@ OpenMW content files
|
||||||
|
|
||||||
The concepts of *Game* and *Addon* files are somewhat similar to the old
|
The concepts of *Game* and *Addon* files are somewhat similar to the old
|
||||||
concept of *ESM* and *ESP*, but more strictly enforced. It is quite
|
concept of *ESM* and *ESP*, but more strictly enforced. It is quite
|
||||||
straight-formward: If you want to make new game using OpenMW as the engine (a
|
straight-forward: If you want to make new game using OpenMW as the engine (a
|
||||||
so called *total conversion*) you should create a game file. If you want to
|
so called *total conversion*) you should create a game file. If you want to
|
||||||
create an addon for an existing game file create an addon file. Nothing else
|
create an addon for an existing game file create an addon file. Nothing else
|
||||||
matters; the only distinction you should consider is if your project is about
|
matters; the only distinction you should consider is if your project is about
|
||||||
|
@ -75,21 +75,21 @@ Another simple thing about content files are the extensions: we are using
|
||||||
Morrowind content files
|
Morrowind content files
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Using our content files is recommended for projects that are intended to used
|
Using our content files is recommended for projects that are intended to use
|
||||||
with the OpenMW engine. However, some players might wish to still use the
|
the OpenMW engine. However, some players might wish to still use the
|
||||||
original Morrowind engine. In addition thousands of *ESP*/*ESM* files were
|
original Morrowind engine. In addition thousands of *ESP*/*ESM* files were
|
||||||
created since 2002, some of them with really outstanding content. Because of
|
created since 2002, some of them with really outstanding content. Because of
|
||||||
this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If
|
this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If
|
||||||
you decid to choose *ESP*/*ESM* file instead of using our own content file
|
you decide to choose *ESP*/*ESM* file instead of using our own content file
|
||||||
types you are most likely aimng at compatibility with the original engine. This
|
types you are most likely aiming at compatibility with the original engine. This
|
||||||
subject is covered in it own chapter of this manual.
|
subject is covered in its own chapter of this manual.
|
||||||
|
|
||||||
|
|
||||||
.. TODO This paragraph sounds weird
|
.. TODO This paragraph sounds weird
|
||||||
|
|
||||||
The actual creation of new files is described in the next chapter. Here we are
|
The actual creation of new files is described in the next chapter. Here we are
|
||||||
going to focus only on the details you need to know in order to create your
|
going to focus only on the details you need to know in order to create your
|
||||||
first OpenMW CS file while fully understanding your needs. For now let’s jut
|
first OpenMW CS file while fully understanding your needs. For now let’s just
|
||||||
remember that content files are created inside the user directory in the the
|
remember that content files are created inside the user directory in the the
|
||||||
``data`` subdirectory (that is the one special data directory mentioned
|
``data`` subdirectory (that is the one special data directory mentioned
|
||||||
earlier).
|
earlier).
|
||||||
|
@ -99,8 +99,8 @@ Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Since an addon is supposed to change the game it follows that it also depends
|
Since an addon is supposed to change the game it follows that it also depends
|
||||||
on the said game to work. We can conceptualise this with an examples: your
|
on the said game to work. We can conceptualise this with an example: your
|
||||||
modification is the changing prize of an iron sword, but what if there is no
|
modification is changing the price of an iron sword, but what if there is no
|
||||||
iron sword in game? That's right: we get nonsense. What you want to do is tie
|
iron sword in game? That's right: we get nonsense. What you want to do is tie
|
||||||
your addon to the files you are changing. Those can be either game files (for
|
your addon to the files you are changing. Those can be either game files (for
|
||||||
example when making an expansion island for a game) or other addon files
|
example when making an expansion island for a game) or other addon files
|
||||||
|
@ -112,9 +112,9 @@ files – it is only a theoretical introduction to the subject. For now just kee
|
||||||
in mind that dependencies exist, and is up to you to decide whether your
|
in mind that dependencies exist, and is up to you to decide whether your
|
||||||
content file should depend on other content files.
|
content file should depend on other content files.
|
||||||
|
|
||||||
Game files are not intend to have any dependencies for a very simple reasons:
|
Game files are not intended to have any dependencies for a very simple reasons:
|
||||||
the player is using only one game file (excluding original and the dirty
|
the player is using only one game file (excluding original and the dirty
|
||||||
ESP/ESM system) at a time and therefore no game file can depend on other game
|
ESP/ESM system) at a time and therefore no game file can depend on another game
|
||||||
file, and since a game file makes the base for addon files it can not depend on
|
file, and since a game file makes the base for addon files it can not depend on
|
||||||
addon files.
|
addon files.
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ Project files
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Project files act as containers for data not used by the OpenMW game engine
|
Project files act as containers for data not used by the OpenMW game engine
|
||||||
itself, but still useful for OpenMW CS. The shining example of this data
|
itself, but still useful for OpenMW CS. The shining examples of this data
|
||||||
category are without doubt record filters (described in a later chapter of the
|
category are without doubt record filters (described in a later chapter of the
|
||||||
manual). As a mod author you probably do not need or want to distribute project
|
manual). As a mod author you probably do not need or want to distribute project
|
||||||
files at all, they are meant to be used only by you and your team.
|
files at all, they are meant to be used only by you and your team.
|
||||||
|
@ -132,7 +132,7 @@ files at all, they are meant to be used only by you and your team.
|
||||||
|
|
||||||
As you would imagine, project files make sense only in combination with actual
|
As you would imagine, project files make sense only in combination with actual
|
||||||
content files. In fact, each time you start to work on new content file and a
|
content files. In fact, each time you start to work on new content file and a
|
||||||
project file was not found, one will be created. The extensio of project files
|
project file was not found, one will be created. The extension of project files
|
||||||
is ``.project``. The whole name of the project file is the whole name of the
|
is ``.project``. The whole name of the project file is the whole name of the
|
||||||
content file with appended extension. For instance a ``swords.omwaddon`` file
|
content file with appended extension. For instance a ``swords.omwaddon`` file
|
||||||
is associated with a ``swords.omwaddon.project`` file.
|
is associated with a ``swords.omwaddon.project`` file.
|
||||||
|
|
|
@ -3,7 +3,7 @@ OpenMW CS Starting Dialog
|
||||||
|
|
||||||
In this chapter we will cover starting up OpenMW CS and the starting interface.
|
In this chapter we will cover starting up OpenMW CS and the starting interface.
|
||||||
Start the CS the way intended for your operating system and you will be
|
Start the CS the way intended for your operating system and you will be
|
||||||
presented with window and three main buttons and a small button with a
|
presented with a window and three main buttons and a small button with a
|
||||||
wrench-icon. The wrench will open the configuration dialog which we will cover
|
wrench-icon. The wrench will open the configuration dialog which we will cover
|
||||||
later. The three main buttons are the following:
|
later. The three main buttons are the following:
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ choose exactly one game and you can choose an arbitrary amount of addon
|
||||||
dependencies. For the sake of simplicity and maintainability choose only the
|
dependencies. For the sake of simplicity and maintainability choose only the
|
||||||
addons you actually want to depend on. Also keep in mind that your dependencies
|
addons you actually want to depend on. Also keep in mind that your dependencies
|
||||||
might have dependencies of their own, you have to depend on those as well. If
|
might have dependencies of their own, you have to depend on those as well. If
|
||||||
one of your dependencies nees something it will be indicated by a warning sign
|
one of your dependencies needs something it will be indicated by a warning sign
|
||||||
and automatically include its dependencies when you choose it.
|
and automatically include its dependencies when you choose it.
|
||||||
|
|
||||||
If you want to edit an existing content file you will be presented with a
|
If you want to edit an existing content file you will be presented with a
|
||||||
|
|
|
@ -48,7 +48,7 @@ Once the addon has been created you will be presented with a table. If you see
|
||||||
a blank window rather than a table choose *World* → *Objects* from the menu.
|
a blank window rather than a table choose *World* → *Objects* from the menu.
|
||||||
|
|
||||||
.. figure:: _static/images/chapter-1/objects.png
|
.. figure:: _static/images/chapter-1/objects.png
|
||||||
:alt: The table showing all objet records in the game.
|
:alt: The table showing all object records in the game.
|
||||||
|
|
||||||
Let's talk about the interface for a second. Every window in OpenMW CS has
|
Let's talk about the interface for a second. Every window in OpenMW CS has
|
||||||
*panels*, these are often but not always tables. You can close a panel by
|
*panels*, these are often but not always tables. You can close a panel by
|
||||||
|
@ -139,7 +139,7 @@ the first character. Type the following into the field:
|
||||||
|
|
||||||
A filter is defined by a number of *queries* which can be logically linked. For
|
A filter is defined by a number of *queries* which can be logically linked. For
|
||||||
now all that matters is that the `string(<property>, <pattern>)` query will check
|
now all that matters is that the `string(<property>, <pattern>)` query will check
|
||||||
whether `<propery>` matches `<pattern>`. The pattern is a regular expression,
|
whether `<property>` matches `<pattern>`. The pattern is a regular expression,
|
||||||
if you don't know about them you should learn their syntax. For now all that
|
if you don't know about them you should learn their syntax. For now all that
|
||||||
matters is that `.` stands for any character and `*` stands for any amount,
|
matters is that `.` stands for any character and `*` stands for any amount,
|
||||||
even zero. In other words, we are looking for all entries which have an ID that
|
even zero. In other words, we are looking for all entries which have an ID that
|
||||||
|
|
|
@ -176,7 +176,7 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions –
|
||||||
#. Open up each of the models in NifSkope and look for these certain blocks_:
|
#. Open up each of the models in NifSkope and look for these certain blocks_:
|
||||||
- NiTextureEffect
|
- NiTextureEffect
|
||||||
- NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*.
|
- NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*.
|
||||||
#. Remove all these tags by selecting them one at a time and press right click>Block>Remove.
|
#. Remove all these tags by selecting them one at a time and press right click>Block>Remove Branch. (Ctrl-Del)
|
||||||
#. Repeat this on all the affected models.
|
#. Repeat this on all the affected models.
|
||||||
#. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here.
|
#. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here.
|
||||||
#. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question.
|
#. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question.
|
||||||
|
|
|
@ -56,7 +56,7 @@ Enabling this feature results in better visuals, and a marginally lower frame ra
|
||||||
|
|
||||||
This setting has no effect if the shader setting is false.
|
This setting has no effect if the shader setting is false.
|
||||||
|
|
||||||
This setting can be toggled with the Refraction button in the Water tab of the Video panel of the Options menu.
|
This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu.
|
||||||
|
|
||||||
reflect actors
|
reflect actors
|
||||||
--------------
|
--------------
|
||||||
|
@ -68,6 +68,8 @@ reflect actors
|
||||||
This setting controls whether or not NPCs and creatures are drawn in water reflections.
|
This setting controls whether or not NPCs and creatures are drawn in water reflections.
|
||||||
Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance.
|
Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance.
|
||||||
|
|
||||||
|
This setting can be toggled with the 'Reflect actors' button in the Water tab of the Video panel of the Options menu.
|
||||||
|
|
||||||
small feature culling pixel size
|
small feature culling pixel size
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue