Merge branch 'master' of gitlab.com:openmw/openmw into lua_record_services

macos_ci_fix
Zackhasacat 1 year ago
commit f287b2f436

@ -70,6 +70,7 @@
Bug #7472: Crash when enchanting last projectiles
Bug #7505: Distant terrain does not support sample size greater than cell size
Bug #7553: Faction reaction loading is incorrect
Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
@ -77,6 +78,7 @@
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
Feature #6726: Lua API for creating new objects
Feature #6864: Lua file access API
Feature #6922: Improve launcher appearance
Feature #6933: Support high-resolution cursor textures
Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData

@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 45)
set(OPENMW_LUA_API_REVISION 46)
set(OPENMW_VERSION_COMMITHASH "")
set(OPENMW_VERSION_TAGHASH "")

@ -61,7 +61,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal
worker magicbindings
)

@ -2,6 +2,7 @@
#include <MyGUI_TextIterator.h>
#include <cassert>
#include <memory>
#include <components/misc/constants.hpp>
@ -93,8 +94,10 @@ namespace
int level = creatureStats.getLevel();
for (const ESM::Attribute& attribute : attributes)
{
const ESM::Race::MaleFemale& value
= race->mData.mAttributeValues[static_cast<size_t>(ESM::Attribute::refIdToIndex(attribute.mId))];
auto index = ESM::Attribute::refIdToIndex(attribute.mId);
assert(index >= 0);
const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast<size_t>(index)];
creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale);
}

@ -44,6 +44,7 @@
#include "soundbindings.hpp"
#include "types/types.hpp"
#include "uibindings.hpp"
#include "vfsbindings.hpp"
namespace MWLua
{
@ -347,6 +348,7 @@ namespace MWLua
{ "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) },
{ "openmw.util", LuaUtil::initUtilPackage(lua) },
{ "openmw.vfs", initVFSPackage(context) },
};
}

@ -0,0 +1,346 @@
#include "vfsbindings.hpp"
#include <components/files/istreamptr.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp"
#include "context.hpp"
#include "luamanagerimp.hpp"
namespace MWLua
{
namespace
{
// Too many arguments may cause stack corruption and crash.
constexpr std::size_t sMaximumReadArguments = 20;
// Print a message if we read a large chunk of file to string.
constexpr std::size_t sFileSizeWarningThreshold = 1024 * 1024;
struct FileHandle
{
public:
FileHandle(Files::IStreamPtr stream, std::string_view fileName)
{
mFilePtr = std::move(stream);
mFileName = fileName;
}
Files::IStreamPtr mFilePtr;
std::string mFileName;
};
std::ios_base::seekdir getSeekDir(FileHandle& self, std::string_view whence)
{
if (whence == "cur")
return std::ios_base::cur;
if (whence == "set")
return std::ios_base::beg;
if (whence == "end")
return std::ios_base::end;
throw std::runtime_error(
"Error when handling '" + self.mFileName + "': invalid seek direction: '" + std::string(whence) + "'.");
}
size_t getBytesLeftInStream(Files::IStreamPtr& file)
{
auto oldPos = file->tellg();
file->seekg(0, std::ios_base::end);
auto newPos = file->tellg();
file->seekg(oldPos, std::ios_base::beg);
return newPos - oldPos;
}
void printLargeDataMessage(FileHandle& file, size_t size)
{
if (!file.mFilePtr || !Settings::lua().mLuaDebug || size < sFileSizeWarningThreshold)
return;
Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'.";
}
sol::object readFile(LuaUtil::LuaState* lua, FileHandle& file)
{
std::ostringstream os;
if (file.mFilePtr && file.mFilePtr->peek() != EOF)
os << file.mFilePtr->rdbuf();
auto result = os.str();
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), std::move(result));
}
sol::object readLineFromFile(LuaUtil::LuaState* lua, FileHandle& file)
{
std::string result;
if (file.mFilePtr && std::getline(*file.mFilePtr, result))
{
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), result);
}
return sol::nil;
}
sol::object readNumberFromFile(LuaUtil::LuaState* lua, Files::IStreamPtr& file)
{
double number = 0;
if (file && *file >> number)
return sol::make_object<double>(lua->sol(), number);
return sol::nil;
}
sol::object readCharactersFromFile(LuaUtil::LuaState* lua, FileHandle& file, size_t count)
{
if (count <= 0 && file.mFilePtr->peek() != EOF)
return sol::make_object<std::string>(lua->sol(), std::string());
auto bytesLeft = getBytesLeftInStream(file.mFilePtr);
if (bytesLeft <= 0)
return sol::nil;
if (count > bytesLeft)
count = bytesLeft;
std::string result(count, '\0');
if (file.mFilePtr->read(&result[0], count))
{
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), result);
}
return sol::nil;
}
void validateFile(const FileHandle& self)
{
if (self.mFilePtr)
return;
throw std::runtime_error("Error when handling '" + self.mFileName + "': attempt to use a closed file.");
}
sol::variadic_results seek(
LuaUtil::LuaState* lua, FileHandle& self, std::ios_base::seekdir dir, std::streamoff off)
{
sol::variadic_results values;
try
{
self.mFilePtr->seekg(off, dir);
if (self.mFilePtr->fail() || self.mFilePtr->bad())
{
auto msg = "Failed to seek in file '" + self.mFileName + "'";
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
else
values.push_back(sol::make_object<std::streampos>(lua->sol(), self.mFilePtr->tellg()));
}
catch (std::exception& e)
{
auto msg = "Failed to seek in file '" + self.mFileName + "': " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
}
}
sol::table initVFSPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
sol::usertype<FileHandle> handle = context.mLua->sol().new_usertype<FileHandle>("FileHandle");
handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; });
handle[sol::meta_function::to_string] = [](const FileHandle& self) {
return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}";
};
handle["seek"] = sol::overload(
[lua = context.mLua](FileHandle& self, std::string_view whence, sol::optional<long> offset) {
validateFile(self);
auto off = static_cast<std::streamoff>(offset.value_or(0));
auto dir = getSeekDir(self, whence);
return seek(lua, self, dir, off);
},
[lua = context.mLua](FileHandle& self, sol::optional<long> offset) {
validateFile(self);
auto off = static_cast<std::streamoff>(offset.value_or(0));
return seek(lua, self, std::ios_base::cur, off);
});
handle["lines"] = [lua = context.mLua](FileHandle& self) {
return sol::as_function([&lua, &self]() mutable {
validateFile(self);
return readLineFromFile(lua, self);
});
};
api["lines"] = [lua = context.mLua, vfs](std::string_view fileName) {
auto normalizedName = VFS::Path::normalizeFilename(fileName);
return sol::as_function(
[lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable {
validateFile(file);
auto result = readLineFromFile(lua, file);
if (result == sol::nil)
file.mFilePtr.reset();
return result;
});
};
handle["close"] = [lua = context.mLua](FileHandle& self) {
sol::variadic_results values;
try
{
self.mFilePtr.reset();
if (self.mFilePtr)
{
auto msg = "Can not close file '" + self.mFileName + "': file handle is still opened.";
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
else
values.push_back(sol::make_object<bool>(lua->sol(), true));
}
catch (std::exception& e)
{
auto msg = "Can not close file '" + self.mFileName + "': " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
handle["read"] = [lua = context.mLua](FileHandle& self, const sol::variadic_args args) {
validateFile(self);
if (args.size() > sMaximumReadArguments)
throw std::runtime_error(
"Error when handling '" + self.mFileName + "': too many arguments for 'read'.");
sol::variadic_results values;
// If there are no arguments, read a string
if (args.size() == 0)
{
values.push_back(readLineFromFile(lua, self));
return values;
}
bool success = true;
size_t i = 0;
for (i = 0; i < args.size() && success; i++)
{
if (args[i].is<std::string_view>())
{
auto format = args[i].as<std::string_view>();
if (format == "*a" || format == "*all")
{
values.push_back(readFile(lua, self));
continue;
}
if (format == "*n" || format == "*number")
{
auto result = readNumberFromFile(lua, self.mFilePtr);
values.push_back(result);
if (result == sol::nil)
success = false;
continue;
}
if (format == "*l" || format == "*line")
{
auto result = readLineFromFile(lua, self);
values.push_back(result);
if (result == sol::nil)
success = false;
continue;
}
throw std::runtime_error("Error when handling '" + self.mFileName + "': bad argument #"
+ std::to_string(i + 1) + " to 'read' (invalid format)");
}
else if (args[i].is<int>())
{
int number = args[i].as<int>();
auto result = readCharactersFromFile(lua, self, number);
values.push_back(result);
if (result == sol::nil)
success = false;
}
}
// We should return nil if we just reached the end of stream
if (!success && self.mFilePtr->eof())
return values;
if (!success && (self.mFilePtr->fail() || self.mFilePtr->bad()))
{
auto msg = "Error when handling '" + self.mFileName + "': can not read data for argument #"
+ std::to_string(i);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
api["open"] = [lua = context.mLua, vfs](std::string_view fileName) {
sol::variadic_results values;
try
{
auto normalizedName = VFS::Path::normalizeFilename(fileName);
auto handle = FileHandle(vfs->getNormalized(normalizedName), normalizedName);
values.push_back(sol::make_object<FileHandle>(lua->sol(), std::move(handle)));
}
catch (std::exception& e)
{
auto msg = "Can not open file: " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
api["type"] = sol::overload(
[](const FileHandle& handle) -> std::string {
if (handle.mFilePtr)
return "file";
return "closed file";
},
[](const sol::object&) -> sol::object { return sol::nil; });
api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); };
api["pathsWithPrefix"] = [vfs](std::string_view prefix) {
auto iterator = vfs->getRecursiveDirectoryIterator(prefix);
return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional<std::string> {
if (current != iterator.end())
{
const std::string& result = *current;
++current;
return result;
}
return sol::nullopt;
});
};
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,13 @@
#ifndef MWLUA_VFSBINDINGS_H
#define MWLUA_VFSBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initVFSPackage(const Context&);
}
#endif // MWLUA_VFSBINDINGS_H

@ -48,9 +48,9 @@ namespace MWMechanics
std::string EffectKey::toString() const
{
const auto& store = MWBase::Environment::get().getESMStore();
const ESM::MagicEffect* magicEffect = store->get<ESM::MagicEffect>().search(mId);
const ESM::MagicEffect* magicEffect = store->get<ESM::MagicEffect>().find(mId);
return getMagicEffectString(
*magicEffect, store->get<ESM::Attribute>().search(mArg), store->get<ESM::Skill>().search(mArg));
*magicEffect, store->get<ESM::Attribute>().find(mArg), store->get<ESM::Skill>().find(mArg));
}
bool operator<(const EffectKey& left, const EffectKey& right)

@ -1,5 +1,7 @@
#include "mechanicsmanagerimp.hpp"
#include <cassert>
#include <osg/Stats>
#include <components/misc/rng.hpp>
@ -150,9 +152,10 @@ namespace MWMechanics
for (const ESM::Attribute& attribute : esmStore.get<ESM::Attribute>())
{
const ESM::Race::MaleFemale& value
= race->mData.mAttributeValues[static_cast<size_t>(ESM::Attribute::refIdToIndex(attribute.mId))];
auto index = ESM::Attribute::refIdToIndex(attribute.mId);
assert(index >= 0);
const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast<size_t>(index)];
creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale);
}

@ -488,7 +488,12 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const
state.mSkillIncrease.fill(0);
for (const auto& [key, value] : mSkillIncreases)
state.mSkillIncrease[static_cast<size_t>(ESM::Attribute::refIdToIndex(key))] = value;
{
// TODO extend format
auto index = ESM::Attribute::refIdToIndex(key);
assert(index >= 0);
state.mSkillIncrease[static_cast<size_t>(index)] = value;
}
for (size_t i = 0; i < state.mSpecIncreases.size(); ++i)
state.mSpecIncreases[i] = mSpecIncreases[i];

@ -556,7 +556,7 @@ namespace MWWorld
return false;
}
CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers)
CellStore::CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers)
: mStore(esmStore)
, mReaders(readers)
, mCellVariant(std::move(cell))

@ -140,7 +140,7 @@ namespace MWWorld
}
/// @param readerList The readers to use for loading of the cell on-demand.
CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers);
CellStore(const CellStore&) = delete;

@ -159,9 +159,16 @@ MWWorld::ContainerStore::ContainerStore()
MWWorld::ContainerStore::~ContainerStore()
{
MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel();
for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter)
worldModel->deregisterPtr(*iter);
try
{
MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel();
for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter)
worldModel->deregisterPtr(*iter);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "Failed to deregister container store: " << e.what();
}
}
MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const

@ -834,9 +834,6 @@ namespace MWWorld
mPreloader = std::make_unique<CellPreloader>(rendering.getResourceSystem(), physics->getShapeManager(),
rendering.getTerrain(), rendering.getLandManager());
mPreloader->setWorkQueue(mRendering.getWorkQueue());
rendering.getResourceSystem()->setExpiryDelay(Settings::cells().mCacheExpiryDelay);
mPreloader->setExpiryDelay(Settings::cells().mPreloadCellExpiryDelay);
mPreloader->setMinCacheSize(Settings::cells().mPreloadCellCacheMin);
mPreloader->setMaxCacheSize(Settings::cells().mPreloadCellCacheMax);

@ -1017,11 +1017,12 @@ namespace MWWorld
void Store<ESM::GameSetting>::setUp()
{
auto addSetting = [&](const std::string& key, ESM::Variant value) {
auto id = ESM::RefId::stringRefId(key);
ESM::GameSetting setting;
setting.blank();
setting.mId = ESM::RefId::stringRefId(key);
setting.mId = id;
setting.mValue = std::move(value);
auto [iter, inserted] = mStatic.insert_or_assign(setting.mId, std::move(setting));
auto [iter, inserted] = mStatic.insert_or_assign(id, std::move(setting));
if (inserted)
mShared.push_back(&iter->second);
};

@ -13,7 +13,7 @@ namespace Nif::Testing
inline void init(Extra& value)
{
value.next = ExtraPtr(nullptr);
value.mNext = ExtraPtr(nullptr);
}
inline void init(Named& value)

@ -996,7 +996,7 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.string = "NCC__";
mNiStringExtraData.mData = "NCC__";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1024,8 +1024,8 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.string = "NCC__";
mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NCC__";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1052,7 +1052,7 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.string = "NC___";
mNiStringExtraData.mData = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1079,8 +1079,8 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.string = "NC___";
mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NC___";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1141,7 +1141,7 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape)
{
mNiStringExtraData.string = "MRK";
mNiStringExtraData.mData = "MRK";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1160,7 +1160,7 @@ namespace
TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers)
{
mNiIntegerExtraData.data = 32; // BSX flag "editor marker"
mNiIntegerExtraData.mData = 32; // BSX flag "editor marker"
mNiIntegerExtraData.recType = Nif::RC_BSXFlags;
mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData));
mNiTriShape.parents.push_back(&mNiNode);
@ -1181,7 +1181,7 @@ namespace
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes)
{
mNiStringExtraData.string = "MRK";
mNiStringExtraData.mData = "MRK";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode2);

@ -42,7 +42,7 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}")
# source files
add_component_dir (lua
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
shapes/box
)
@ -111,7 +111,7 @@ add_component_dir (sceneutil
)
add_component_dir (nif
controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics
base controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr texture
)
add_component_dir (nifosg

@ -12,6 +12,7 @@
#include <components/vfs/manager.hpp>
#include "scriptscontainer.hpp"
#include "utf8.hpp"
namespace LuaUtil
{
@ -51,7 +52,7 @@ namespace LuaUtil
static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select",
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
static const std::string safePackages[] = { "coroutine", "math", "string", "table" };
static const std::string safePackages[] = { "coroutine", "math", "string", "table", "utf8" };
static constexpr int64_t countHookStep = 1000;
@ -181,6 +182,8 @@ namespace LuaUtil
mSol["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
mSol["math"]["randomseed"] = [] {};
mSol["utf8"] = LuaUtf8::initUtf8Package(mSol);
mSol["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
mSol["setEnvironment"]

@ -0,0 +1,233 @@
#include <codecvt>
#include <components/misc/strings/format.hpp>
#include "utf8.hpp"
namespace
{
constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2
constexpr uint32_t MAXUTF = 0x7FFFFFFFu;
// constexpr uint32_t MAXUNICODE = 0x10FFFFu;
inline bool isNilOrNone(const sol::stack_proxy arg)
{
return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none);
}
inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name)
{
double integer;
if (!arg.is<double>())
throw std::runtime_error(Misc::StringUtils::format("bad argument #%i to '%s' (number expected, got %s)", n,
name, sol::type_name(arg.lua_state(), arg.get_type())));
if (std::modf(arg, &integer) != 0)
throw std::runtime_error(
Misc::StringUtils::format("bad argument #{} to '{}' (number has no integer representation)", n, name));
return integer;
}
// If the input 'pos' is negative, it is treated as counting from the end of the string,
// where -1 represents the last character position, -2 represents the second-to-last position,
// and so on. If 'pos' is non-negative, it is used as-is.
inline void relativePosition(int64_t& pos, const size_t len)
{
if (pos < 0)
pos = std::max<int64_t>(0, pos + len + 1);
}
// returns: first - character pos in bytes, second - character codepoint
std::pair<int64_t, int64_t> decodeNextUTF8Character(std::string_view s, std::vector<int64_t>& pos_byte)
{
const int64_t pos = pos_byte.back() - 1;
const unsigned char ch = static_cast<unsigned char>(s[pos]);
int64_t codepoint = -1;
size_t byteSize = 0;
if ((ch & 0b10000000) == 0)
{
codepoint = ch;
byteSize = 1;
}
else if ((ch & 0b11100000) == 0b11000000)
{
codepoint = ch & 0b00011111;
byteSize = 2;
}
else if ((ch & 0b11110000) == 0b11100000)
{
codepoint = ch & 0b00001111;
byteSize = 3;
}
else if ((ch & 0b11111000) == 0b11110000)
{
codepoint = ch & 0b00000111;
byteSize = 4;
}
// construct codepoint for non-ascii
for (size_t i = 1; i < byteSize; ++i)
{
// if not a continuation byte
if ((pos + i) >= s.size() || (static_cast<unsigned char>(s[pos + i]) & 0b11000000) != 0b10000000)
{
return std::make_pair(0, -1);
}
codepoint = (codepoint << 6) | (static_cast<unsigned char>(s[pos + i]) & 0b00111111);
}
std::pair<size_t, int64_t> res = std::make_pair(pos_byte.back(), codepoint);
pos_byte.push_back(pos_byte.back() + byteSize); /* the next character (if exists) starts at this byte */
return res;
}
}
namespace LuaUtf8
{
sol::table initUtf8Package(sol::state_view& lua)
{
sol::table utf8(lua, sol::create);
utf8["charpattern"] = UTF8PATT;
utf8["char"] = [](const sol::variadic_args args) -> std::string {
std::string result{};
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
for (size_t i = 0; i < args.size(); ++i)
{
int64_t codepoint = getInteger(args[i], (i + 1), "char");
if (codepoint < 0 || codepoint > MAXUTF)
throw std::runtime_error(
Misc::StringUtils::format("bad argument #{} to 'char' (value out of range)", (i + 1)));
result += converter.to_bytes(codepoint);
}
return result;
};
utf8["codes"] = [](std::string_view s) {
std::vector<int64_t> pos_byte{ 1 };
return sol::as_function([s, pos_byte]() mutable -> sol::optional<std::pair<int64_t, int64_t>> {
if (pos_byte.back() <= static_cast<int64_t>(s.size()))
{
const auto pair = decodeNextUTF8Character(s, pos_byte);
if (pair.second == -1)
throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size()));
return pair;
}
return sol::nullopt;
});
};
utf8["len"] = [](std::string_view s,
const sol::variadic_args args) -> std::variant<size_t, std::pair<sol::object, int64_t>> {
const size_t len = s.size();
int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len");
int64_t fv = isNilOrNone(args[1]) ? -1 : getInteger(args[1], 3, "len");
relativePosition(iv, len);
relativePosition(fv, len);
if (iv <= 0)
throw std::runtime_error("bad argument #2 to 'len' (initial position out of bounds)");
if (fv > static_cast<int64_t>(len))
throw std::runtime_error("bad argument #3 to 'len' (final position out of bounds)");
if (len == 0)
return len;
std::vector<int64_t> pos_byte = { iv };
while (pos_byte.back() <= fv)
{
if (decodeNextUTF8Character(s, pos_byte).second == -1)
return std::pair(sol::lua_nil, pos_byte.back());
}
return pos_byte.size() - 1;
};
utf8["codepoint"]
= [](std::string_view s, const sol::variadic_args args) -> sol::as_returns_t<std::vector<int64_t>> {
size_t len = s.size();
int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "codepoint");
int64_t fv = isNilOrNone(args[1]) ? iv : getInteger(args[1], 3, "codepoint");
relativePosition(iv, len);
relativePosition(fv, len);
if (iv <= 0)
throw std::runtime_error("bad argument #2 to 'codepoint' (initial position out of bounds)");
if (fv > static_cast<int64_t>(len))
throw std::runtime_error("bad argument #3 to 'codepoint' (final position out of bounds)");
if (iv > fv)
return sol::as_returns(std::vector<int64_t>{}); /* empty interval; return nothing */
std::vector<int64_t> pos_byte = { iv };
std::vector<int64_t> codepoints;
while (pos_byte.back() <= fv)
{
codepoints.push_back(decodeNextUTF8Character(s, pos_byte).second);
if (codepoints.back() == -1)
throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size()));
}
return sol::as_returns(std::move(codepoints));
};
utf8["offset"]
= [](std::string_view s, const int64_t n, const sol::variadic_args args) -> sol::optional<int64_t> {
size_t len = s.size();
int64_t iv;
if (isNilOrNone(args[0]))
{
if (n >= 0)
iv = 1;
else
iv = s.size() + 1;
}
else
iv = getInteger(args[0], 3, "offset");
std::vector<int64_t> pos_byte = { 1 };
relativePosition(iv, len);
if (iv > static_cast<int64_t>(len) + 1)
throw std::runtime_error("bad argument #3 to 'offset' (position out of bounds)");
while (pos_byte.back() <= static_cast<int64_t>(len))
decodeNextUTF8Character(s, pos_byte);
for (auto it = pos_byte.begin(); it != pos_byte.end(); ++it)
{
if (*it == iv)
{
if (n <= 0 && it + n >= pos_byte.begin())
return *(it + n);
if (n > 0 && it + n - 1 < pos_byte.end())
return *(it + n - 1);
break;
}
else if (*it > iv) /* a continuation byte */
{
if (n == 0)
return *(it - 1); /* special case */
else
throw std::runtime_error("initial position is a continuation byte");
}
}
return sol::nullopt;
};
return utf8;
}
}

@ -0,0 +1,11 @@
#ifndef COMPONENTS_LUA_UTF8_H
#define COMPONENTS_LUA_UTF8_H
#include <sol/sol.hpp>
namespace LuaUtf8
{
sol::table initUtf8Package(sol::state_view&);
}
#endif

@ -5,11 +5,11 @@ namespace Nif
void Extra::read(NIFStream* nif)
{
if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0))
name = nif->getString();
nif->read(mName);
else if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0))
{
next.read(nif);
recordSize = nif->getUInt();
mNext.read(nif);
nif->read(mRecordSize);
}
}

@ -13,12 +13,12 @@ namespace Nif
// An extra data record. All the extra data connected to an object form a linked list.
struct Extra : public Record
{
std::string name;
ExtraPtr next; // Next extra data record in the list
unsigned int recordSize{ 0u };
std::string mName;
ExtraPtr mNext; // Next extra data record in the list
uint32_t mRecordSize{ 0u };
void read(NIFStream* nif) override;
void post(Reader& nif) override { next.post(nif); }
void post(Reader& nif) override { mNext.post(nif); }
};
struct Controller : public Record

@ -1,140 +0,0 @@
#include "controlled.hpp"
#include "data.hpp"
namespace Nif
{
void NiSourceTexture::read(NIFStream* nif)
{
Named::read(nif);
external = nif->getChar() != 0;
bool internal = false;
if (external)
filename = nif->getString();
else
{
if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3))
internal = nif->getChar();
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
filename = nif->getString(); // Original file path of the internal texture
}
if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3))
{
if (!external && internal)
data.read(nif);
}
else
{
data.read(nif);
}
pixel = nif->getUInt();
mipmap = nif->getUInt();
alpha = nif->getUInt();
// Renderer hints, typically of no use for us
/* bool mIsStatic = */ nif->getChar();
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103))
/* bool mDirectRendering = */ nif->getBoolean();
if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4))
/* bool mPersistRenderData = */ nif->getBoolean();
}
void NiSourceTexture::post(Reader& nif)
{
Named::post(nif);
data.post(nif);
}
void BSShaderTextureSet::read(NIFStream* nif)
{
nif->getSizedStrings(textures, nif->getUInt());
}
void NiParticleModifier::read(NIFStream* nif)
{
next.read(nif);
controller.read(nif);
}
void NiParticleModifier::post(Reader& nif)
{
next.post(nif);
controller.post(nif);
}
void NiParticleGrowFade::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
growTime = nif->getFloat();
fadeTime = nif->getFloat();
}
void NiParticleColorModifier::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
data.read(nif);
}
void NiParticleColorModifier::post(Reader& nif)
{
NiParticleModifier::post(nif);
data.post(nif);
}
void NiGravity::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
mDecay = nif->getFloat();
mForce = nif->getFloat();
mType = nif->getUInt();
mPosition = nif->getVector3();
mDirection = nif->getVector3();
}
void NiParticleCollider::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
mBounceFactor = nif->getFloat();
if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2))
{
// Unused in NifSkope. Need to figure out what these do.
/*bool mSpawnOnCollision = */ nif->getBoolean();
/*bool mDieOnCollision = */ nif->getBoolean();
}
}
void NiPlanarCollider::read(NIFStream* nif)
{
NiParticleCollider::read(nif);
mExtents = nif->getVector2();
mPosition = nif->getVector3();
mXVector = nif->getVector3();
mYVector = nif->getVector3();
mPlaneNormal = nif->getVector3();
mPlaneDistance = nif->getFloat();
}
void NiParticleRotation::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
/* bool mRandomInitialAxis = */ nif->getChar();
/* osg::Vec3f mInitialAxis = */ nif->getVector3();
/* float mRotationSpeed = */ nif->getFloat();
}
void NiSphericalCollider::read(NIFStream* nif)
{
NiParticleCollider::read(nif);
mRadius = nif->getFloat();
mCenter = nif->getVector3();
}
}

@ -1,157 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: https://openmw.org/
This file (controlled.h) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
https://www.gnu.org/licenses/ .
*/
#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP
#include "base.hpp"
namespace Nif
{
struct NiSourceTexture : public Named
{
// Is this an external (references a separate texture file) or
// internal (data is inside the nif itself) texture?
bool external;
std::string filename; // In case of external textures
NiPixelDataPtr data; // In case of internal textures
/* Pixel layout
0 - Palettised
1 - High color 16
2 - True color 32
3 - Compressed
4 - Bumpmap
5 - Default */
unsigned int pixel;
/* Mipmap format
0 - no
1 - yes
2 - default */
unsigned int mipmap;
/* Alpha
0 - none
1 - binary
2 - smooth
3 - default (use material alpha, or multiply material with texture if present)
*/
unsigned int alpha;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct BSShaderTextureSet : public Record
{
enum TextureType
{
TextureType_Base = 0,
TextureType_Normal = 1,
TextureType_Glow = 2,
TextureType_Parallax = 3,
TextureType_Env = 4,
TextureType_EnvMask = 5,
TextureType_Subsurface = 6,
TextureType_BackLighting = 7
};
std::vector<std::string> textures;
void read(NIFStream* nif) override;
};
struct NiParticleModifier : public Record
{
NiParticleModifierPtr next;
ControllerPtr controller;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct NiParticleGrowFade : public NiParticleModifier
{
float growTime;
float fadeTime;
void read(NIFStream* nif) override;
};
struct NiParticleColorModifier : public NiParticleModifier
{
NiColorDataPtr data;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct NiGravity : public NiParticleModifier
{
float mForce;
/* 0 - Wind (fixed direction)
* 1 - Point (fixed origin)
*/
int mType;
float mDecay;
osg::Vec3f mPosition;
osg::Vec3f mDirection;
void read(NIFStream* nif) override;
};
struct NiParticleCollider : public NiParticleModifier
{
float mBounceFactor;
void read(NIFStream* nif) override;
};
// NiPinaColada
struct NiPlanarCollider : public NiParticleCollider
{
osg::Vec2f mExtents;
osg::Vec3f mPosition;
osg::Vec3f mXVector, mYVector;
osg::Vec3f mPlaneNormal;
float mPlaneDistance;
void read(NIFStream* nif) override;
};
struct NiSphericalCollider : public NiParticleCollider
{
float mRadius;
osg::Vec3f mCenter;
void read(NIFStream* nif) override;
};
struct NiParticleRotation : public NiParticleModifier
{
void read(NIFStream* nif) override;
};
} // Namespace
#endif

@ -1,9 +1,9 @@
#include "controller.hpp"
#include "controlled.hpp"
#include "data.hpp"
#include "node.hpp"
#include "recordptr.hpp"
#include "particle.hpp"
#include "texture.hpp"
namespace Nif
{

@ -1,7 +1,7 @@
#include "effect.hpp"
#include "controlled.hpp"
#include "node.hpp"
#include "texture.hpp"
namespace Nif
{
@ -9,55 +9,61 @@ namespace Nif
void NiDynamicEffect::read(NIFStream* nif)
{
Node::read(nif);
if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106)
&& nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)
nif->getBoolean(); // Switch state
if (nif->getVersion() <= NIFFile::VER_MW
|| (nif->getVersion() >= nif->generateVersion(10, 1, 0, 0)
&& nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4))
{
size_t numAffectedNodes = nif->get<uint32_t>();
nif->skip(numAffectedNodes * 4);
}
if (nif->getVersion() > NIFFile::VER_MW && nif->getVersion() < nif->generateVersion(10, 1, 0, 0))
return;
if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4)
return;
if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106))
nif->read(mSwitchState);
size_t numAffectedNodes = nif->get<uint32_t>();
nif->skip(numAffectedNodes * 4);
}
void NiLight::read(NIFStream* nif)
{
NiDynamicEffect::read(nif);
dimmer = nif->getFloat();
ambient = nif->getVector3();
diffuse = nif->getVector3();
specular = nif->getVector3();
mDimmer = nif->getFloat();
mAmbient = nif->getVector3();
mDiffuse = nif->getVector3();
mSpecular = nif->getVector3();
}
void NiTextureEffect::read(NIFStream* nif)
void NiPointLight::read(NIFStream* nif)
{
NiDynamicEffect::read(nif);
// Model Projection Matrix
nif->skip(3 * 3 * sizeof(float));
// Model Projection Transform
nif->skip(3 * sizeof(float));
// Texture Filtering
nif->skip(4);
// Max anisotropy samples
if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4))
nif->skip(2);
NiLight::read(nif);
clamp = nif->getUInt();
mConstantAttenuation = nif->getFloat();
mLinearAttenuation = nif->getFloat();
mQuadraticAttenuation = nif->getFloat();
}
textureType = (TextureType)nif->getUInt();
void NiSpotLight::read(NIFStream* nif)
{
NiPointLight::read(nif);
coordGenType = (CoordGenType)nif->getUInt();
mCutoff = nif->getFloat();
mExponent = nif->getFloat();
}
texture.read(nif);
void NiTextureEffect::read(NIFStream* nif)
{
NiDynamicEffect::read(nif);
nif->skip(1); // Use clipping plane
nif->skip(16); // Clipping plane dimensions vector
nif->read(mProjectionRotation);
nif->read(mProjectionPosition);
nif->read(mFilterMode);
if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4))
nif->read(mMaxAnisotropy);
nif->read(mClampMode);
mTextureType = static_cast<TextureType>(nif->get<uint32_t>());
mCoordGenType = static_cast<CoordGenType>(nif->get<uint32_t>());
mTexture.read(nif);
nif->read(mEnableClipPlane);
mClipPlane = osg::Plane(nif->get<osg::Vec4f>());
if (nif->getVersion() <= NIFStream::generateVersion(10, 2, 0, 0))
nif->skip(4); // PS2-specific shorts
if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 12))
@ -67,24 +73,8 @@ namespace Nif
void NiTextureEffect::post(Reader& nif)
{
NiDynamicEffect::post(nif);
texture.post(nif);
}
void NiPointLight::read(NIFStream* nif)
{
NiLight::read(nif);
constantAttenuation = nif->getFloat();
linearAttenuation = nif->getFloat();
quadraticAttenuation = nif->getFloat();
}
void NiSpotLight::read(NIFStream* nif)
{
NiPointLight::read(nif);
cutoff = nif->getFloat();
exponent = nif->getFloat();
mTexture.post(nif);
}
}

@ -29,67 +29,75 @@
namespace Nif
{
// Abstract
struct NiDynamicEffect : public Node
{
bool mSwitchState{ true };
void read(NIFStream* nif) override;
};
// Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight.
// Abstract light source
struct NiLight : NiDynamicEffect
{
float dimmer;
osg::Vec3f ambient;
osg::Vec3f diffuse;
osg::Vec3f specular;
float mDimmer;
osg::Vec3f mAmbient;
osg::Vec3f mDiffuse;
osg::Vec3f mSpecular;
void read(NIFStream* nif) override;
};
struct NiPointLight : public NiLight
{
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
float mConstantAttenuation;
float mLinearAttenuation;
float mQuadraticAttenuation;
void read(NIFStream* nif) override;
};
struct NiSpotLight : public NiPointLight
{
float cutoff;
float exponent;
float mCutoff;
float mExponent;
void read(NIFStream* nif) override;
};
struct NiTextureEffect : NiDynamicEffect
{
NiSourceTexturePtr texture;
unsigned int clamp;
enum TextureType
enum class TextureType : uint32_t
{
Projected_Light = 0,
Projected_Shadow = 1,
Environment_Map = 2,
Fog_Map = 3
ProjectedLight = 0,
ProjectedShadow = 1,
EnvironmentMap = 2,
FogMap = 3,
};
TextureType textureType;
enum CoordGenType
enum class CoordGenType : uint32_t
{
World_Parallel = 0,
World_Perspective,
Sphere_Map,
Specular_Cube_Map,
Diffuse_Cube_Map
WorldParallel = 0,
WorldPerspective = 1,
SphereMap = 2,
SpecularCubeMap = 3,
DiffuseCubeMap = 4,
};
CoordGenType coordGenType;
Matrix3 mProjectionRotation;
osg::Vec3f mProjectionPosition;
uint32_t mFilterMode;
NiSourceTexturePtr mTexture;
uint16_t mMaxAnisotropy{ 0 };
uint32_t mClampMode;
TextureType mTextureType;
CoordGenType mCoordGenType;
uint8_t mEnableClipPlane;
osg::Plane mClipPlane;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
bool wrapT() const { return clamp & 1; }
bool wrapS() const { return (clamp >> 1) & 1; }
bool wrapT() const { return mClampMode & 1; }
bool wrapS() const { return mClampMode & 2; }
};
} // Namespace

@ -6,25 +6,21 @@ namespace Nif
void NiExtraData::read(NIFStream* nif)
{
Extra::read(nif);
nif->readVector(data, recordSize);
}
void NiStringExtraData::read(NIFStream* nif)
{
Extra::read(nif);
string = nif->getString();
nif->readVector(mData, mRecordSize);
}
void NiTextKeyExtraData::read(NIFStream* nif)
{
Extra::read(nif);
int keynum = nif->getInt();
list.resize(keynum);
for (int i = 0; i < keynum; i++)
uint32_t numKeys;
nif->read(numKeys);
mList.resize(numKeys);
for (TextKey& key : mList)
{
list[i].time = nif->getFloat();
list[i].text = nif->getString();
nif->read(key.mTime);
nif->read(key.mText);
}
}
@ -32,81 +28,39 @@ namespace Nif
{
Extra::read(nif);
nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess
}
void NiIntegerExtraData::read(NIFStream* nif)
{
Extra::read(nif);
data = nif->getUInt();
}
void NiIntegersExtraData::read(NIFStream* nif)
{
Extra::read(nif);
nif->readVector(data, nif->getUInt());
}
void NiBinaryExtraData::read(NIFStream* nif)
{
Extra::read(nif);
nif->readVector(data, nif->getUInt());
}
void NiBooleanExtraData::read(NIFStream* nif)
{
Extra::read(nif);
data = nif->getBoolean();
}
void NiVectorExtraData::read(NIFStream* nif)
{
Extra::read(nif);
data = nif->getVector4();
}
void NiFloatExtraData::read(NIFStream* nif)
{
Extra::read(nif);
data = nif->getFloat();
}
void NiFloatsExtraData::read(NIFStream* nif)
{
Extra::read(nif);
nif->readVector(data, nif->getUInt());
nif->skip(nif->get<uint16_t>() * sizeof(float)); // vertex weights I guess
}
void BSBound::read(NIFStream* nif)
{
Extra::read(nif);
center = nif->getVector3();
halfExtents = nif->getVector3();
nif->read(mCenter);
nif->read(mExtents);
}
void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream* nif)
{
mOffset = nif->getVector3();
mOrientation = nif->getUShort();
mPositionRef = nif->getChar();
nif->read(mOffset);
nif->read(mOrientation);
nif->read(mPositionRef);
nif->skip(1); // Position ref 2
}
void BSFurnitureMarker::FurniturePosition::read(NIFStream* nif)
{
mOffset = nif->getVector3();
mHeading = nif->getFloat();
mType = nif->getUShort();
mEntryPoint = nif->getUShort();
nif->read(mOffset);
nif->read(mHeading);
nif->read(mType);
nif->read(mEntryPoint);
}
void BSFurnitureMarker::read(NIFStream* nif)
{
Extra::read(nif);
unsigned int num = nif->getUInt();
uint32_t num;
nif->read(num);
if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3)
{
mLegacyMarkers.resize(num);
@ -124,19 +78,20 @@ namespace Nif
void BSInvMarker::read(NIFStream* nif)
{
Extra::read(nif);
float rotX = nif->getUShort() / 1000.0;
float rotY = nif->getUShort() / 1000.0;
float rotZ = nif->getUShort() / 1000.0;
mScale = nif->getFloat();
float rotX = nif->get<uint16_t>() / 1000.f;
float rotY = nif->get<uint16_t>() / 1000.f;
float rotZ = nif->get<uint16_t>() / 1000.f;
mRotation = osg::Quat(rotX, osg::X_AXIS, rotY, osg::Y_AXIS, rotZ, osg::Z_AXIS);
nif->read(mScale);
}
void BSBehaviorGraphExtraData::read(NIFStream* nif)
{
Extra::read(nif);
mFile = nif->getString();
mControlsBaseSkeleton = nif->getBoolean();
nif->read(mFile);
nif->read(mControlsBaseSkeleton);
}
}

@ -1,26 +1,3 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008-2010 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: https://openmw.org/
This file (extra.h) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
https://www.gnu.org/licenses/ .
*/
#ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP
#define OPENMW_COMPONENTS_NIF_EXTRA_HPP
@ -29,94 +6,70 @@
namespace Nif
{
struct NiExtraData : public Extra
template <typename T>
struct TypedExtra : public Extra
{
std::vector<char> data;
void read(NIFStream* nif) override;
};
T mData;
struct NiVertWeightsExtraData : public Extra
{
void read(NIFStream* nif) override;
};
struct NiTextKeyExtraData : public Extra
{
struct TextKey
void read(NIFStream* nif) override
{
float time;
std::string text;
};
std::vector<TextKey> list;
void read(NIFStream* nif) override;
};
struct NiStringExtraData : public Extra
{
/* Known meanings:
"MRK" - marker, only visible in the editor, not rendered in-game
"NCC" - no collision except with the camera
Anything else starting with "NC" - no collision
*/
std::string string;
Extra::read(nif);
void read(NIFStream* nif) override;
nif->read(mData);
}
};
struct NiIntegerExtraData : public Extra
template <typename T>
struct TypedVectorExtra : public Extra
{
unsigned int data;
void read(NIFStream* nif) override;
};
std::vector<T> mData;
struct NiIntegersExtraData : public Extra
{
std::vector<unsigned int> data;
void read(NIFStream* nif) override
{
Extra::read(nif);
void read(NIFStream* nif) override;
nif->readVector(mData, nif->get<uint32_t>());
}
};
struct NiBinaryExtraData : public Extra
{
std::vector<char> data;
using NiBooleanExtraData = TypedExtra<bool>;
using NiFloatExtraData = TypedExtra<float>;
using NiIntegerExtraData = TypedExtra<uint32_t>;
using NiStringExtraData = TypedExtra<std::string>;
using NiVectorExtraData = TypedExtra<osg::Vec4f>;
void read(NIFStream* nif) override;
};
using NiBinaryExtraData = TypedVectorExtra<uint8_t>;
using NiFloatsExtraData = TypedVectorExtra<float>;
using NiIntegersExtraData = TypedVectorExtra<uint32_t>;
struct NiBooleanExtraData : public Extra
{
bool data;
void read(NIFStream* nif) override;
};
struct NiVectorExtraData : public Extra
// Distinct from NiBinaryExtraData, uses mRecordSize as its size
struct NiExtraData : public Extra
{
osg::Vec4f data;
std::vector<uint8_t> mData;
void read(NIFStream* nif) override;
};
struct NiFloatExtraData : public Extra
struct NiVertWeightsExtraData : public Extra
{
float data;
void read(NIFStream* nif) override;
};
struct NiFloatsExtraData : public Extra
struct NiTextKeyExtraData : public Extra
{
std::vector<float> data;
struct TextKey
{
float mTime;
std::string mText;
};
std::vector<TextKey> mList;
void read(NIFStream* nif) override;
};
struct BSBound : public Extra
{
osg::Vec3f center, halfExtents;
osg::Vec3f mCenter, mExtents;
void read(NIFStream* nif) override;
};
@ -149,7 +102,7 @@ namespace Nif
struct BSInvMarker : public Extra
{
osg::Quat mRotation;
float mScale = 1.0f;
float mScale;
void read(NIFStream* nif) override;
};
@ -162,5 +115,5 @@ namespace Nif
void read(NIFStream* nif) override;
};
} // Namespace
}
#endif

@ -10,15 +10,16 @@
#include <sstream>
#include <stdexcept>
#include "controlled.hpp"
#include "controller.hpp"
#include "data.hpp"
#include "effect.hpp"
#include "exception.hpp"
#include "extra.hpp"
#include "node.hpp"
#include "particle.hpp"
#include "physics.hpp"
#include "property.hpp"
#include "texture.hpp"
namespace Nif
{

@ -0,0 +1,95 @@
#include "particle.hpp"
#include "data.hpp"
namespace Nif
{
void NiParticleModifier::read(NIFStream* nif)
{
mNext.read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13))
mController.read(nif);
}
void NiParticleModifier::post(Reader& nif)
{
mNext.post(nif);
mController.post(nif);
}
void NiParticleGrowFade::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
nif->read(mGrowTime);
nif->read(mFadeTime);
}
void NiParticleColorModifier::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
mData.read(nif);
}
void NiParticleColorModifier::post(Reader& nif)
{
NiParticleModifier::post(nif);
mData.post(nif);
}
void NiGravity::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13))
nif->read(mDecay);
nif->read(mForce);
mType = static_cast<ForceType>(nif->get<uint32_t>());
nif->read(mPosition);
nif->read(mDirection);
}
void NiParticleCollider::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
nif->read(mBounceFactor);
if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2))
{
nif->read(mSpawnOnCollision);
nif->read(mDieOnCollision);
}
}
void NiPlanarCollider::read(NIFStream* nif)
{
NiParticleCollider::read(nif);
nif->read(mExtents);
nif->read(mPosition);
nif->read(mXVector);
nif->read(mYVector);
nif->read(mPlaneNormal);
nif->read(mPlaneDistance);
}
void NiSphericalCollider::read(NIFStream* nif)
{
NiParticleCollider::read(nif);
nif->read(mRadius);
nif->read(mCenter);
}
void NiParticleRotation::read(NIFStream* nif)
{
NiParticleModifier::read(nif);
nif->read(mRandomInitialAxis);
nif->read(mInitialAxis);
nif->read(mRotationSpeed);
}
}

@ -0,0 +1,90 @@
#ifndef OPENMW_COMPONENTS_NIF_PARTICLE_HPP
#define OPENMW_COMPONENTS_NIF_PARTICLE_HPP
#include "base.hpp"
namespace Nif
{
struct NiParticleModifier : public Record
{
NiParticleModifierPtr mNext;
ControllerPtr mController;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct NiParticleGrowFade : public NiParticleModifier
{
float mGrowTime;
float mFadeTime;
void read(NIFStream* nif) override;
};
struct NiParticleColorModifier : public NiParticleModifier
{
NiColorDataPtr mData;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct NiGravity : public NiParticleModifier
{
enum class ForceType : uint32_t
{
Wind = 0, // Fixed direction
Point = 1, // Fixed origin
};
float mDecay{ 0.f };
float mForce;
ForceType mType;
osg::Vec3f mPosition;
osg::Vec3f mDirection;
void read(NIFStream* nif) override;
};
struct NiParticleCollider : public NiParticleModifier
{
float mBounceFactor;
bool mSpawnOnCollision{ false };
bool mDieOnCollision{ false };
void read(NIFStream* nif) override;
};
// NiPinaColada
struct NiPlanarCollider : public NiParticleCollider
{
osg::Vec2f mExtents;
osg::Vec3f mPosition;
osg::Vec3f mXVector, mYVector;
osg::Vec3f mPlaneNormal;
float mPlaneDistance;
void read(NIFStream* nif) override;
};
struct NiSphericalCollider : public NiParticleCollider
{
float mRadius;
osg::Vec3f mCenter;
void read(NIFStream* nif) override;
};
struct NiParticleRotation : public NiParticleModifier
{
uint8_t mRandomInitialAxis;
osg::Vec3f mInitialAxis;
float mRotationSpeed;
void read(NIFStream* nif) override;
};
}
#endif

@ -12,15 +12,15 @@ namespace Nif
void bhkWorldObjCInfoProperty::read(NIFStream* nif)
{
mData = nif->getUInt();
mSize = nif->getUInt();
mCapacityAndFlags = nif->getUInt();
nif->read(mData);
nif->read(mSize);
nif->read(mCapacityAndFlags);
}
void bhkWorldObjectCInfo::read(NIFStream* nif)
{
nif->skip(4); // Unused
mPhaseType = static_cast<BroadPhaseType>(nif->getChar());
mPhaseType = static_cast<BroadPhaseType>(nif->get<uint8_t>());
nif->skip(3); // Unused
mProperty.read(nif);
}
@ -29,47 +29,47 @@ namespace Nif
{
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD)
nif->skip(4); // Unknown
mMaterial = nif->getUInt();
nif->read(mMaterial);
}
void HavokFilter::read(NIFStream* nif)
{
mLayer = nif->getChar();
mFlags = nif->getChar();
mGroup = nif->getUShort();
nif->read(mLayer);
nif->read(mFlags);
nif->read(mGroup);
}
void hkSubPartData::read(NIFStream* nif)
{
mHavokFilter.read(nif);
mNumVertices = nif->getUInt();
nif->read(mNumVertices);
mHavokMaterial.read(nif);
}
void hkpMoppCode::read(NIFStream* nif)
void bhkEntityCInfo::read(NIFStream* nif)
{
unsigned int size = nif->getUInt();
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
mOffset = nif->getVector4();
if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->getChar(); // MOPP data build type
nif->readVector(mData, size);
mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
nif->skip(1); // Unused
nif->read(mProcessContactDelay);
}
void bhkEntityCInfo::read(NIFStream* nif)
void hkpMoppCode::read(NIFStream* nif)
{
mResponseType = static_cast<hkResponseType>(nif->getChar());
nif->skip(1); // Unused
mProcessContactDelay = nif->getUShort();
uint32_t dataSize;
nif->read(dataSize);
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
nif->read(mOffset);
if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
nif->read(mBuildType);
nif->readVector(mData, dataSize);
}
void TriangleData::read(NIFStream* nif)
{
for (int i = 0; i < 3; i++)
mTriangle[i] = nif->getUShort();
mWeldingInfo = nif->getUShort();
nif->readArray(mTriangle);
nif->read(mWeldingInfo);
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
mNormal = nif->getVector3();
nif->read(mNormal);
}
void bhkMeshMaterial::read(NIFStream* nif)
@ -80,28 +80,27 @@ namespace Nif
void bhkQsTransform::read(NIFStream* nif)
{
mTranslation = nif->getVector4();
mRotation = nif->getQuaternion();
nif->read(mTranslation);
nif->read(mRotation);
}
void bhkCMSBigTri::read(NIFStream* nif)
{
for (int i = 0; i < 3; i++)
mTriangle[i] = nif->getUShort();
mMaterial = nif->getUInt();
mWeldingInfo = nif->getUShort();
nif->readArray(mTriangle);
nif->read(mMaterial);
nif->read(mWeldingInfo);
}
void bhkCMSChunk::read(NIFStream* nif)
{
mTranslation = nif->getVector4();
mMaterialIndex = nif->getUInt();
mReference = nif->getUShort();
mTransformIndex = nif->getUShort();
nif->readVector(mVertices, nif->getUInt());
nif->readVector(mIndices, nif->getUInt());
nif->readVector(mStrips, nif->getUInt());
nif->readVector(mWeldingInfos, nif->getUInt());
nif->read(mTranslation);
nif->read(mMaterialIndex);
nif->read(mReference);
nif->read(mTransformIndex);
nif->readVector(mVertices, nif->get<uint32_t>());
nif->readVector(mIndices, nif->get<uint32_t>());
nif->readVector(mStrips, nif->get<uint32_t>());
nif->readVector(mWeldingInfos, nif->get<uint32_t>());
}
void bhkRigidBodyCInfo::read(NIFStream* nif)
@ -115,64 +114,67 @@ namespace Nif
{
if (nif->getBethVersion() >= 83)
nif->skip(4); // Unused
mResponseType = static_cast<hkResponseType>(nif->getChar());
mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
nif->skip(1); // Unused
mProcessContactDelay = nif->getUShort();
nif->read(mProcessContactDelay);
}
}
if (nif->getBethVersion() < 83)
nif->skip(4); // Unused
mTranslation = nif->getVector4();
mRotation = nif->getQuaternion();
mLinearVelocity = nif->getVector4();
mAngularVelocity = nif->getVector4();
nif->read(mTranslation);
nif->read(mRotation);
nif->read(mLinearVelocity);
nif->read(mAngularVelocity);
// A bit hacky, but this is the only instance where a 3x3 matrix has padding.
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
mInertiaTensor[i][j] = nif->getFloat();
mCenter = nif->getVector4();
mMass = nif->getFloat();
mLinearDamping = nif->getFloat();
mAngularDamping = nif->getFloat();
{
nif->read(mInertiaTensor.mValues[i], 3);
nif->skip(4); // Padding
}
nif->read(mCenter);
nif->read(mMass);
nif->read(mLinearDamping);
nif->read(mAngularDamping);
if (nif->getBethVersion() >= 83)
{
if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4)
mTimeFactor = nif->getFloat();
mGravityFactor = nif->getFloat();
nif->read(mTimeFactor);
nif->read(mGravityFactor);
}
mFriction = nif->getFloat();
nif->read(mFriction);
if (nif->getBethVersion() >= 83)
mRollingFrictionMult = nif->getFloat();
mRestitution = nif->getFloat();
nif->read(mRollingFrictionMult);
nif->read(mRestitution);
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
{
mMaxLinearVelocity = nif->getFloat();
mMaxAngularVelocity = nif->getFloat();
nif->read(mMaxLinearVelocity);
nif->read(mMaxAngularVelocity);
if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4)
mPenetrationDepth = nif->getFloat();
nif->read(mPenetrationDepth);
}
mMotionType = static_cast<hkMotionType>(nif->getChar());
mMotionType = static_cast<hkMotionType>(nif->get<uint8_t>());
if (nif->getBethVersion() < 83)
mDeactivatorType = static_cast<hkDeactivatorType>(nif->getChar());
mDeactivatorType = static_cast<hkDeactivatorType>(nif->get<uint8_t>());
else
mEnableDeactivation = nif->getBoolean();
mSolverDeactivation = static_cast<hkSolverDeactivation>(nif->getChar());
nif->read(mEnableDeactivation);
mSolverDeactivation = static_cast<hkSolverDeactivation>(nif->get<uint8_t>());
if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4)
{
nif->skip(1);
mPenetrationDepth = nif->getFloat();
mTimeFactor = nif->getFloat();
nif->read(mPenetrationDepth);
nif->read(mTimeFactor);
nif->skip(4);
mResponseType = static_cast<hkResponseType>(nif->getChar());
mResponseType = static_cast<hkResponseType>(nif->get<uint8_t>());
nif->skip(1); // Unused
mProcessContactDelay = nif->getUShort();
nif->read(mProcessContactDelay);
}
mQualityType = static_cast<hkQualityType>(nif->getChar());
mQualityType = static_cast<hkQualityType>(nif->get<uint8_t>());
if (nif->getBethVersion() >= 83)
{
mAutoRemoveLevel = nif->getChar();
mResponseModifierFlags = nif->getChar();
mNumContactPointShapeKeys = nif->getChar();
mForceCollidedOntoPPU = nif->getBoolean();
nif->read(mAutoRemoveLevel);
nif->read(mResponseModifierFlags);
nif->read(mNumContactPointShapeKeys);
nif->read(mForceCollidedOntoPPU);
}
if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4)
nif->skip(3); // Unused
@ -182,7 +184,7 @@ namespace Nif
void bhkConstraintCInfo::read(NIFStream* nif)
{
nif->get<unsigned int>(); // Number of entities, unused
nif->get<uint32_t>(); // Number of entities, unused
mEntityA.read(nif);
mEntityB.read(nif);
@ -203,7 +205,7 @@ namespace Nif
nif->read(mDamping);
nif->read(mProportionalRecoveryVelocity);
nif->read(mConstantRecoveryVelocity);
mEnabled = nif->getBoolean();
nif->read(mEnabled);
}
void bhkVelocityConstraintMotor::read(NIFStream* nif)
@ -212,8 +214,8 @@ namespace Nif
nif->read(mMaxForce);
nif->read(mTau);
nif->read(mTargetVelocity);
mUseVelocityTarget = nif->getBoolean();
mEnabled = nif->getBoolean();
nif->read(mUseVelocityTarget);
nif->read(mEnabled);
}
void bhkSpringDamperConstraintMotor::read(NIFStream* nif)
@ -222,7 +224,7 @@ namespace Nif
nif->read(mMaxForce);
nif->read(mSpringConstant);
nif->read(mSpringDamping);
mEnabled = nif->getBoolean();
nif->read(mEnabled);
}
void bhkConstraintMotorCInfo::read(NIFStream* nif)
@ -335,7 +337,8 @@ namespace Nif
void bhkCollisionObject::read(NIFStream* nif)
{
NiCollisionObject::read(nif);
mFlags = nif->getUShort();
nif->read(mFlags);
mBody.read(nif);
}
@ -356,6 +359,7 @@ namespace Nif
void bhkEntity::read(NIFStream* nif)
{
bhkWorldObject::read(nif);
mInfo.read(nif);
}
@ -372,21 +376,26 @@ namespace Nif
void bhkMoppBvTreeShape::read(NIFStream* nif)
{
bhkBvTreeShape::read(nif);
nif->skip(12); // Unused
mScale = nif->getFloat();
nif->read(mScale);
mMopp.read(nif);
}
void bhkNiTriStripsShape::read(NIFStream* nif)
{
mHavokMaterial.read(nif);
mRadius = nif->getFloat();
nif->read(mRadius);
nif->skip(20); // Unused
mGrowBy = nif->getUInt();
nif->read(mGrowBy);
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
mScale = nif->getVector4();
nif->read(mScale);
readRecordList(nif, mData);
nif->readVector(mFilters, nif->getUInt());
uint32_t numFilters;
nif->read(numFilters);
mHavokFilters.resize(numFilters);
for (HavokFilter& filter : mHavokFilters)
filter.read(nif);
}
void bhkNiTriStripsShape::post(Reader& nif)
@ -398,15 +407,17 @@ namespace Nif
{
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
mSubshapes.resize(nif->getUShort());
uint16_t numSubshapes;
nif->read(numSubshapes);
mSubshapes.resize(numSubshapes);
for (hkSubPartData& subshape : mSubshapes)
subshape.read(nif);
}
mUserData = nif->getUInt();
nif->read(mUserData);
nif->skip(4); // Unused
mRadius = nif->getFloat();
nif->read(mRadius);
nif->skip(4); // Unused
mScale = nif->getVector4();
nif->read(mScale);
nif->skip(20); // Duplicates of the two previous fields
mData.read(nif);
}
@ -418,22 +429,26 @@ namespace Nif
void hkPackedNiTriStripsData::read(NIFStream* nif)
{
unsigned int numTriangles = nif->getUInt();
uint32_t numTriangles;
nif->read(numTriangles);
mTriangles.resize(numTriangles);
for (unsigned int i = 0; i < numTriangles; i++)
for (uint32_t i = 0; i < numTriangles; i++)
mTriangles[i].read(nif);
unsigned int numVertices = nif->getUInt();
uint32_t numVertices;
nif->read(numVertices);
bool compressed = false;
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
compressed = nif->getBoolean();
nif->read(compressed);
if (!compressed)
nif->readVector(mVertices, numVertices);
else
nif->skip(6 * numVertices); // Half-precision vectors are not currently supported
if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS)
{
mSubshapes.resize(nif->getUShort());
uint16_t numSubshapes;
nif->read(numSubshapes);
mSubshapes.resize(numSubshapes);
for (hkSubPartData& subshape : mSubshapes)
subshape.read(nif);
}
@ -447,23 +462,25 @@ namespace Nif
void bhkConvexShape::read(NIFStream* nif)
{
bhkSphereRepShape::read(nif);
mRadius = nif->getFloat();
nif->read(mRadius);
}
void bhkConvexVerticesShape::read(NIFStream* nif)
{
bhkConvexShape::read(nif);
mVerticesProperty.read(nif);
mNormalsProperty.read(nif);
nif->readVector(mVertices, nif->getUInt());
nif->readVector(mNormals, nif->getUInt());
nif->readVector(mVertices, nif->get<uint32_t>());
nif->readVector(mNormals, nif->get<uint32_t>());
}
void bhkConvexTransformShape::read(NIFStream* nif)
{
mShape.read(nif);
mHavokMaterial.read(nif);
mRadius = nif->getFloat();
nif->read(mRadius);
nif->skip(8); // Unused
std::array<float, 16> mat;
nif->readArray(mat);
@ -478,19 +495,21 @@ namespace Nif
void bhkBoxShape::read(NIFStream* nif)
{
bhkConvexShape::read(nif);
nif->skip(8); // Unused
mExtents = nif->getVector3();
nif->read(mExtents);
nif->skip(4); // Unused
}
void bhkCapsuleShape::read(NIFStream* nif)
{
bhkConvexShape::read(nif);
nif->skip(8); // Unused
mPoint1 = nif->getVector3();
mRadius1 = nif->getFloat();
mPoint2 = nif->getVector3();
mRadius2 = nif->getFloat();
nif->read(mPoint1);
nif->read(mRadius1);
nif->read(mPoint2);
nif->read(mRadius2);
}
void bhkListShape::read(NIFStream* nif)
@ -499,7 +518,8 @@ namespace Nif
mHavokMaterial.read(nif);
mChildShapeProperty.read(nif);
mChildFilterProperty.read(nif);
unsigned int numFilters = nif->getUInt();
uint32_t numFilters;
nif->read(numFilters);
mHavokFilters.resize(numFilters);
for (HavokFilter& filter : mHavokFilters)
filter.read(nif);
@ -508,12 +528,12 @@ namespace Nif
void bhkCompressedMeshShape::read(NIFStream* nif)
{
mTarget.read(nif);
mUserData = nif->getUInt();
mRadius = nif->getFloat();
nif->getFloat(); // Unknown
mScale = nif->getVector4();
nif->getFloat(); // Radius
nif->getVector4(); // Scale
nif->read(mUserData);
nif->read(mRadius);
nif->skip(4); // Unknown
nif->read(mScale);
nif->skip(4); // Radius
nif->skip(16); // Scale
mData.read(nif);
}
@ -525,60 +545,66 @@ namespace Nif
void bhkCompressedMeshShapeData::read(NIFStream* nif)
{
mBitsPerIndex = nif->getUInt();
mBitsPerWIndex = nif->getUInt();
mMaskWIndex = nif->getUInt();
mMaskIndex = nif->getUInt();
mError = nif->getFloat();
mAabbMin = nif->getVector4();
mAabbMax = nif->getVector4();
mWeldingType = nif->getChar();
mMaterialType = nif->getChar();
nif->skip(nif->getUInt() * 4); // Unused
nif->skip(nif->getUInt() * 4); // Unused
nif->skip(nif->getUInt() * 4); // Unused
size_t numMaterials = nif->getUInt();
nif->read(mBitsPerIndex);
nif->read(mBitsPerWIndex);
nif->read(mMaskWIndex);
nif->read(mMaskIndex);
nif->read(mError);
nif->read(mAabbMin);
nif->read(mAabbMax);
nif->read(mWeldingType);
nif->read(mMaterialType);
nif->skip(nif->get<uint32_t>() * 4); // Unused
nif->skip(nif->get<uint32_t>() * 4); // Unused
nif->skip(nif->get<uint32_t>() * 4); // Unused
uint32_t numMaterials;
nif->read(numMaterials);
mMaterials.resize(numMaterials);
for (bhkMeshMaterial& material : mMaterials)
material.read(nif);
nif->getUInt(); // Unused
size_t numTransforms = nif->getUInt();
nif->skip(4); // Unused
uint32_t numTransforms;
nif->read(numTransforms);
mChunkTransforms.resize(numTransforms);
for (bhkQsTransform& transform : mChunkTransforms)
transform.read(nif);
nif->readVector(mBigVerts, nif->getUInt());
nif->readVector(mBigVerts, nif->get<uint32_t>());
size_t numBigTriangles = nif->getUInt();
uint32_t numBigTriangles;
nif->read(numBigTriangles);
mBigTris.resize(numBigTriangles);
for (bhkCMSBigTri& tri : mBigTris)
tri.read(nif);
size_t numChunks = nif->getUInt();
uint32_t numChunks;
nif->read(numChunks);
mChunks.resize(numChunks);
for (bhkCMSChunk& chunk : mChunks)
chunk.read(nif);
nif->getUInt(); // Unused
nif->skip(4); // Unused
}
void bhkRigidBody::read(NIFStream* nif)
{
bhkEntity::read(nif);
mInfo.read(nif);
readRecordList(nif, mConstraints);
if (nif->getBethVersion() < 76)
mBodyFlags = nif->getUInt();
nif->read(mBodyFlags);
else
mBodyFlags = nif->getUShort();
mBodyFlags = nif->get<uint16_t>();
}
void bhkSimpleShapePhantom::read(NIFStream* nif)
{
bhkWorldObject::read(nif);
nif->skip(8); // Unused
std::array<float, 16> mat;
nif->readArray(mat);
@ -598,18 +624,21 @@ namespace Nif
void bhkRagdollConstraint::read(NIFStream* nif)
{
bhkConstraint::read(nif);
mConstraint.read(nif);
}
void bhkHingeConstraint::read(NIFStream* nif)
{
bhkConstraint::read(nif);
mConstraint.read(nif);
}
void bhkLimitedHingeConstraint::read(NIFStream* nif)
{
bhkConstraint::read(nif);
mConstraint.read(nif);
}

@ -1,6 +1,7 @@
#ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP
#define OPENMW_COMPONENTS_NIF_PHYSICS_HPP
#include "niftypes.hpp"
#include "record.hpp"
#include "recordptr.hpp"
@ -23,9 +24,10 @@ namespace Nif
struct bhkWorldObjCInfoProperty
{
unsigned int mData;
unsigned int mSize;
unsigned int mCapacityAndFlags;
uint32_t mData;
uint32_t mSize;
uint32_t mCapacityAndFlags;
void read(NIFStream* nif);
};
@ -41,28 +43,32 @@ namespace Nif
{
BroadPhaseType mPhaseType;
bhkWorldObjCInfoProperty mProperty;
void read(NIFStream* nif);
};
struct HavokMaterial
{
unsigned int mMaterial;
uint32_t mMaterial;
void read(NIFStream* nif);
};
struct HavokFilter
{
unsigned char mLayer;
unsigned char mFlags;
unsigned short mGroup;
uint8_t mLayer;
uint8_t mFlags;
uint16_t mGroup;
void read(NIFStream* nif);
};
struct hkSubPartData
{
HavokMaterial mHavokMaterial;
unsigned int mNumVertices;
uint32_t mNumVertices;
HavokFilter mHavokFilter;
void read(NIFStream* nif);
};
@ -77,22 +83,26 @@ namespace Nif
struct bhkEntityCInfo
{
hkResponseType mResponseType;
unsigned short mProcessContactDelay;
uint16_t mProcessContactDelay;
void read(NIFStream* nif);
};
struct hkpMoppCode
{
osg::Vec4f mOffset;
std::vector<char> mData;
uint8_t mBuildType;
std::vector<uint8_t> mData;
void read(NIFStream* nif);
};
struct TriangleData
{
unsigned short mTriangle[3];
unsigned short mWeldingInfo;
std::array<uint16_t, 3> mTriangle;
uint16_t mWeldingInfo;
osg::Vec3f mNormal;
void read(NIFStream* nif);
};
@ -100,6 +110,7 @@ namespace Nif
{
HavokMaterial mHavokMaterial;
HavokFilter mHavokFilter;
void read(NIFStream* nif);
};
@ -107,27 +118,30 @@ namespace Nif
{
osg::Vec4f mTranslation;
osg::Quat mRotation;
void read(NIFStream* nif);
};
struct bhkCMSBigTri
{
unsigned short mTriangle[3];
unsigned int mMaterial;
unsigned short mWeldingInfo;
std::array<uint16_t, 3> mTriangle;
uint32_t mMaterial;
uint16_t mWeldingInfo;
void read(NIFStream* nif);
};
struct bhkCMSChunk
{
osg::Vec4f mTranslation;
unsigned int mMaterialIndex;
unsigned short mReference;
unsigned short mTransformIndex;
std::vector<unsigned short> mVertices;
std::vector<unsigned short> mIndices;
std::vector<unsigned short> mStrips;
std::vector<unsigned short> mWeldingInfos;
uint32_t mMaterialIndex;
uint16_t mReference;
uint16_t mTransformIndex;
std::vector<uint16_t> mVertices;
std::vector<uint16_t> mIndices;
std::vector<uint16_t> mStrips;
std::vector<uint16_t> mWeldingInfos;
void read(NIFStream* nif);
};
@ -180,12 +194,12 @@ namespace Nif
{
HavokFilter mHavokFilter;
hkResponseType mResponseType;
unsigned short mProcessContactDelay;
uint16_t mProcessContactDelay;
osg::Vec4f mTranslation;
osg::Quat mRotation;
osg::Vec4f mLinearVelocity;
osg::Vec4f mAngularVelocity;
float mInertiaTensor[3][4];
Matrix3 mInertiaTensor;
osg::Vec4f mCenter;
float mMass;
float mLinearDamping;
@ -203,10 +217,11 @@ namespace Nif
bool mEnableDeactivation{ true };
hkSolverDeactivation mSolverDeactivation;
hkQualityType mQualityType;
unsigned char mAutoRemoveLevel;
unsigned char mResponseModifierFlags;
unsigned char mNumContactPointShapeKeys;
uint8_t mAutoRemoveLevel;
uint8_t mResponseModifierFlags;
uint8_t mNumContactPointShapeKeys;
bool mForceCollidedOntoPPU;
void read(NIFStream* nif);
};
@ -222,6 +237,7 @@ namespace Nif
bhkEntityPtr mEntityA;
bhkEntityPtr mEntityB;
ConstraintPriority mPriority;
void read(NIFStream* nif);
void post(Reader& nif);
};
@ -242,6 +258,7 @@ namespace Nif
float mProportionalRecoveryVelocity;
float mConstantRecoveryVelocity;
bool mEnabled;
void read(NIFStream* nif);
};
@ -252,6 +269,7 @@ namespace Nif
float mTargetVelocity;
bool mUseVelocityTarget;
bool mEnabled;
void read(NIFStream* nif);
};
@ -261,6 +279,7 @@ namespace Nif
float mSpringConstant;
float mSpringDamping;
bool mEnabled;
void read(NIFStream* nif);
};
@ -270,6 +289,7 @@ namespace Nif
bhkPositionConstraintMotor mPositionMotor;
bhkVelocityConstraintMotor mVelocityMotor;
bhkSpringDamperConstraintMotor mSpringDamperMotor;
void read(NIFStream* nif);
};
@ -289,6 +309,7 @@ namespace Nif
float mTwistMinAngle, mTwistMaxAngle;
float mMaxFriction;
bhkConstraintMotorCInfo mMotor;
void read(NIFStream* nif);
};
@ -301,8 +322,10 @@ namespace Nif
osg::Vec4f mPerpAxis1;
osg::Vec4f mPerpAxis2;
};
HingeData mDataA;
HingeData mDataB;
void read(NIFStream* nif);
};
@ -315,11 +338,13 @@ namespace Nif
osg::Vec4f mPerpAxis1;
osg::Vec4f mPerpAxis2;
};
HingeData mDataA;
HingeData mDataB;
float mMinAngle, mMaxAngle;
float mMaxFriction;
bhkConstraintMotorCInfo mMotor;
void read(NIFStream* nif);
};
@ -358,7 +383,7 @@ namespace Nif
// Bethesda Havok-specific collision object
struct bhkCollisionObject : public NiCollisionObject
{
unsigned short mFlags;
uint16_t mFlags;
bhkWorldObjectPtr mBody;
void read(NIFStream* nif) override;
@ -375,6 +400,7 @@ namespace Nif
bhkShapePtr mShape;
HavokFilter mHavokFilter;
bhkWorldObjectCInfo mWorldObjectInfo;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
@ -383,6 +409,7 @@ namespace Nif
struct bhkEntity : public bhkWorldObject
{
bhkEntityCInfo mInfo;
void read(NIFStream* nif) override;
};
@ -391,6 +418,7 @@ namespace Nif
struct bhkBvTreeShape : public bhkShape
{
bhkShapePtr mShape;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
@ -400,6 +428,7 @@ namespace Nif
{
float mScale;
hkpMoppCode mMopp;
void read(NIFStream* nif) override;
};
@ -408,10 +437,11 @@ namespace Nif
{
HavokMaterial mHavokMaterial;
float mRadius;
unsigned int mGrowBy;
uint32_t mGrowBy;
osg::Vec4f mScale{ 1.f, 1.f, 1.f, 0.f };
NiTriStripsDataList mData;
std::vector<unsigned int> mFilters;
std::vector<HavokFilter> mHavokFilters;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
@ -420,7 +450,7 @@ namespace Nif
struct bhkPackedNiTriStripsShape : public bhkShapeCollection
{
std::vector<hkSubPartData> mSubshapes;
unsigned int mUserData;
uint32_t mUserData;
float mRadius;
osg::Vec4f mScale;
hkPackedNiTriStripsDataPtr mData;
@ -435,6 +465,7 @@ namespace Nif
std::vector<TriangleData> mTriangles;
std::vector<osg::Vec3f> mVertices;
std::vector<hkSubPartData> mSubshapes;
void read(NIFStream* nif) override;
};
@ -442,6 +473,7 @@ namespace Nif
struct bhkSphereRepShape : public bhkShape
{
HavokMaterial mHavokMaterial;
void read(NIFStream* nif) override;
};
@ -449,6 +481,7 @@ namespace Nif
struct bhkConvexShape : public bhkSphereRepShape
{
float mRadius;
void read(NIFStream* nif) override;
};
@ -459,6 +492,7 @@ namespace Nif
bhkWorldObjCInfoProperty mNormalsProperty;
std::vector<osg::Vec4f> mVertices;
std::vector<osg::Vec4f> mNormals;
void read(NIFStream* nif) override;
};
@ -468,6 +502,7 @@ namespace Nif
HavokMaterial mHavokMaterial;
float mRadius;
osg::Matrixf mTransform;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
@ -476,6 +511,7 @@ namespace Nif
struct bhkBoxShape : public bhkConvexShape
{
osg::Vec3f mExtents;
void read(NIFStream* nif) override;
};
@ -499,28 +535,30 @@ namespace Nif
bhkWorldObjCInfoProperty mChildShapeProperty;
bhkWorldObjCInfoProperty mChildFilterProperty;
std::vector<HavokFilter> mHavokFilters;
void read(NIFStream* nif) override;
};
struct bhkCompressedMeshShape : public bhkShape
{
NodePtr mTarget;
unsigned int mUserData;
uint32_t mUserData;
float mRadius;
osg::Vec4f mScale;
bhkCompressedMeshShapeDataPtr mData;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct bhkCompressedMeshShapeData : public bhkRefObject
{
unsigned int mBitsPerIndex, mBitsPerWIndex;
unsigned int mMaskWIndex, mMaskIndex;
uint32_t mBitsPerIndex, mBitsPerWIndex;
uint32_t mMaskWIndex, mMaskIndex;
float mError;
osg::Vec4f mAabbMin, mAabbMax;
char mWeldingType;
char mMaterialType;
uint8_t mWeldingType;
uint8_t mMaterialType;
std::vector<bhkMeshMaterial> mMaterials;
std::vector<bhkQsTransform> mChunkTransforms;
std::vector<osg::Vec4f> mBigVerts;
@ -534,7 +572,7 @@ namespace Nif
{
bhkRigidBodyCInfo mInfo;
bhkSerializableList mConstraints;
unsigned int mBodyFlags;
uint32_t mBodyFlags;
void read(NIFStream* nif) override;
};
@ -542,6 +580,7 @@ namespace Nif
struct bhkSimpleShapePhantom : public bhkWorldObject
{
osg::Matrixf mTransform;
void read(NIFStream* nif) override;
};
@ -549,6 +588,7 @@ namespace Nif
struct bhkConstraint : public bhkSerializable
{
bhkConstraintCInfo mInfo;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
@ -556,18 +596,21 @@ namespace Nif
struct bhkRagdollConstraint : public bhkConstraint
{
bhkRagdollConstraintCInfo mConstraint;
void read(NIFStream* nif) override;
};
struct bhkHingeConstraint : public bhkConstraint
{
bhkHingeConstraintCInfo mConstraint;
void read(NIFStream* nif) override;
};
struct bhkLimitedHingeConstraint : public bhkConstraint
{
bhkLimitedHingeConstraintCInfo mConstraint;
void read(NIFStream* nif) override;
};

@ -1,7 +1,7 @@
#include "property.hpp"
#include "controlled.hpp"
#include "data.hpp"
#include "texture.hpp"
namespace Nif
{

@ -0,0 +1,47 @@
#include "texture.hpp"
#include "data.hpp"
namespace Nif
{
void NiSourceTexture::read(NIFStream* nif)
{
NiTexture::read(nif);
nif->read(mExternal);
if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0))
nif->read(mFile);
bool hasData = nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 4);
if (!hasData && !mExternal)
nif->read(hasData);
if (hasData)
mData.read(nif);
mPrefs.mPixelLayout = static_cast<PixelLayout>(nif->get<uint32_t>());
mPrefs.mUseMipMaps = static_cast<MipMapFormat>(nif->get<uint32_t>());
mPrefs.mAlphaFormat = static_cast<AlphaFormat>(nif->get<uint32_t>());
nif->read(mIsStatic);
if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103))
{
nif->read(mDirectRendering);
if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4))
nif->read(mPersistRenderData);
}
}
void NiSourceTexture::post(Reader& nif)
{
NiTexture::post(nif);
mData.post(nif);
}
void BSShaderTextureSet::read(NIFStream* nif)
{
nif->getSizedStrings(mTextures, nif->get<uint32_t>());
}
}

@ -0,0 +1,81 @@
#ifndef OPENMW_COMPONENTS_NIF_TEXTURE_HPP
#define OPENMW_COMPONENTS_NIF_TEXTURE_HPP
#include "base.hpp"
namespace Nif
{
struct NiTexture : public Named
{
};
struct NiSourceTexture : public NiTexture
{
enum class PixelLayout : uint32_t
{
Palette = 0,
HighColor = 1,
TrueColor = 2,
Compressed = 3,
BumpMap = 4,
Default = 5,
};
enum class MipMapFormat : uint32_t
{
No = 0,
Yes = 1,
Default = 2,
};
enum class AlphaFormat : uint32_t
{
None = 0,
Binary = 1,
Smooth = 2,
Default = 3,
};
struct FormatPrefs
{
PixelLayout mPixelLayout;
MipMapFormat mUseMipMaps;
AlphaFormat mAlphaFormat;
};
char mExternal; // References external file
std::string mFile;
NiPixelDataPtr mData;
FormatPrefs mPrefs;
char mIsStatic{ 1 };
bool mDirectRendering{ true };
bool mPersistRenderData{ false };
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct BSShaderTextureSet : public Record
{
enum class TextureType : uint32_t
{
Base = 0,
Normal = 1,
Glow = 2,
Parallax = 3,
Environment = 4,
EnvironmentMask = 5,
Subsurface = 6,
BackLighting = 7,
};
std::vector<std::string> mTextures;
void read(NIFStream* nif) override;
};
}
#endif

@ -305,7 +305,7 @@ namespace NifBullet
// Check for extra data
std::vector<Nif::ExtraPtr> extraCollection;
for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next)
for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->mNext)
extraCollection.emplace_back(e);
for (const auto& extraNode : node.extralist)
if (!extraNode.empty())
@ -316,29 +316,30 @@ namespace NifBullet
{
// String markers may contain important information
// affecting the entire subtree of this node
Nif::NiStringExtraData* sd = (Nif::NiStringExtraData*)e.getPtr();
auto sd = static_cast<const Nif::NiStringExtraData*>(e.getPtr());
if (Misc::StringUtils::ciStartsWith(sd->string, "NC"))
if (Misc::StringUtils::ciStartsWith(sd->mData, "NC"))
{
// NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be
// uppercase
if (sd->string.length() > 2 && sd->string[2] == 'C')
if (sd->mData.length() > 2 && sd->mData[2] == 'C')
// Collide only with camera.
visualCollisionType = Resource::VisualCollisionType::Camera;
else
// No collision.
visualCollisionType = Resource::VisualCollisionType::Default;
}
else if (sd->string == "MRK" && args.mAutogenerated)
// Don't autogenerate collision if MRK is set.
// FIXME: verify if this covers the entire subtree
else if (sd->mData == "MRK" && args.mAutogenerated)
{
// Marker can still have collision if the model explicitely specifies it via a RootCollisionNode.
return;
}
}
else if (e->recType == Nif::RC_BSXFlags)
{
auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
if (bsxFlags->data & 32) // Editor marker flag
if (bsxFlags->mData & 32) // Editor marker flag
args.mHasMarkers = true;
}
}

@ -42,13 +42,14 @@
#include <osg/TexEnvCombine>
#include <osg/Texture2D>
#include <components/nif/controlled.hpp>
#include <components/nif/effect.hpp>
#include <components/nif/exception.hpp>
#include <components/nif/extra.hpp>
#include <components/nif/niffile.hpp>
#include <components/nif/node.hpp>
#include <components/nif/particle.hpp>
#include <components/nif/property.hpp>
#include <components/nif/texture.hpp>
#include <components/sceneutil/depth.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
@ -174,16 +175,16 @@ namespace
void extractTextKeys(const Nif::NiTextKeyExtraData* tk, SceneUtil::TextKeyMap& textkeys)
{
for (size_t i = 0; i < tk->list.size(); i++)
for (const Nif::NiTextKeyExtraData::TextKey& key : tk->mList)
{
std::vector<std::string> results;
Misc::StringUtils::split(tk->list[i].text, results, "\r\n");
Misc::StringUtils::split(key.mText, results, "\r\n");
for (std::string& result : results)
{
Misc::StringUtils::trim(result);
Misc::StringUtils::lowerCaseInPlace(result);
if (!result.empty())
textkeys.emplace(tk->list[i].time, std::move(result));
textkeys.emplace(key.mTime, std::move(result));
}
}
}
@ -285,9 +286,9 @@ namespace NifOsg
extractTextKeys(static_cast<const Nif::NiTextKeyExtraData*>(extra.getPtr()), target.mTextKeys);
extra = extra->next;
extra = extra->mNext;
Nif::ControllerPtr ctrl = seq->controller;
for (; !extra.empty() && !ctrl.empty(); (extra = extra->next), (ctrl = ctrl->next))
for (; !extra.empty() && !ctrl.empty(); (extra = extra->mNext), (ctrl = ctrl->next))
{
if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController)
{
@ -315,8 +316,8 @@ namespace NifOsg
osg::ref_ptr<SceneUtil::KeyframeController> callback = new NifOsg::KeyframeController(key);
setupController(key, callback, /*animflags*/ 0);
if (!target.mKeyframeControllers.emplace(strdata->string, callback).second)
Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in "
if (!target.mKeyframeControllers.emplace(strdata->mData, callback).second)
Log(Debug::Verbose) << "Controller " << strdata->mData << " present more than once in "
<< nif.getFilename() << ", ignoring later version";
}
}
@ -509,14 +510,14 @@ namespace NifOsg
return nullptr;
osg::ref_ptr<osg::Image> image;
if (!st->external && !st->data.empty())
if (st->mExternal)
{
image = handleInternalTexture(st->data.getPtr());
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS());
image = imageManager->getImage(filename);
}
else
else if (!st->mData.empty())
{
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS());
image = imageManager->getImage(filename);
image = handleInternalTexture(st->mData.getPtr());
}
return image;
}
@ -536,38 +537,41 @@ namespace NifOsg
}
const Nif::NiTextureEffect* textureEffect = static_cast<const Nif::NiTextureEffect*>(nifNode);
if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map)
if (!textureEffect->mSwitchState)
return false;
if (textureEffect->mTextureType != Nif::NiTextureEffect::TextureType::EnvironmentMap)
{
Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in "
<< mFilename;
Log(Debug::Info) << "Unhandled NiTextureEffect type "
<< static_cast<uint32_t>(textureEffect->mTextureType) << " in " << mFilename;
return false;
}
if (textureEffect->texture.empty())
if (textureEffect->mTexture.empty())
{
Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename;
return false;
}
osg::ref_ptr<osg::TexGen> texGen(new osg::TexGen);
switch (textureEffect->coordGenType)
switch (textureEffect->mCoordGenType)
{
case Nif::NiTextureEffect::World_Parallel:
case Nif::NiTextureEffect::CoordGenType::WorldParallel:
texGen->setMode(osg::TexGen::OBJECT_LINEAR);
break;
case Nif::NiTextureEffect::World_Perspective:
case Nif::NiTextureEffect::CoordGenType::WorldPerspective:
texGen->setMode(osg::TexGen::EYE_LINEAR);
break;
case Nif::NiTextureEffect::Sphere_Map:
case Nif::NiTextureEffect::CoordGenType::SphereMap:
texGen->setMode(osg::TexGen::SPHERE_MAP);
break;
default:
Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType
<< " in " << mFilename;
Log(Debug::Info) << "Unhandled NiTextureEffect CoordGenType "
<< static_cast<uint32_t>(textureEffect->mCoordGenType) << " in " << mFilename;
return false;
}
osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->texture.getPtr(), imageManager));
osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager));
osg::ref_ptr<osg::Texture2D> texture2d(new osg::Texture2D(image));
if (image)
texture2d->setTextureSize(image->s(), image->t());
@ -644,7 +648,7 @@ namespace NifOsg
std::vector<Nif::ExtraPtr> extraCollection;
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next)
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->mNext)
extraCollection.emplace_back(e);
for (const auto& extraNode : nifNode->extralist)
@ -666,25 +670,25 @@ namespace NifOsg
// String markers may contain important information
// affecting the entire subtree of this obj
if (sd->string == "MRK" && !Loader::getShowMarkers())
if (sd->mData == "MRK" && !Loader::getShowMarkers())
{
// Marker objects. These meshes are only visible in the editor.
args.mHasMarkers = true;
}
else if (sd->string == "BONE")
else if (sd->mData == "BONE")
{
node->getOrCreateUserDataContainer()->addDescription("CustomBone");
}
else if (sd->string.rfind(extraDataIdentifer, 0) == 0)
else if (sd->mData.rfind(extraDataIdentifer, 0) == 0)
{
node->setUserValue(
Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length()));
Misc::OsgUserValues::sExtraData, sd->mData.substr(extraDataIdentifer.length()));
}
}
else if (e->recType == Nif::RC_BSXFlags)
{
auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
if (bsxFlags->data & 32) // Editor marker flag
if (bsxFlags->mData & 32) // Editor marker flag
args.mHasMarkers = true;
}
}
@ -895,7 +899,7 @@ namespace NifOsg
if (!key->mInterpolator.empty() && key->mInterpolator->recType != Nif::RC_NiTransformInterpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiKeyframeController " << key->recIndex
<< " in " << mFilename;
<< " in " << mFilename << ": " << key->mInterpolator->recName;
continue;
}
osg::ref_ptr<KeyframeController> callback = new KeyframeController(key);
@ -922,7 +926,7 @@ namespace NifOsg
&& visctrl->mInterpolator->recType != Nif::RC_NiBoolInterpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiVisController " << visctrl->recIndex
<< " in " << mFilename;
<< " in " << mFilename << ": " << visctrl->mInterpolator->recName;
continue;
}
osg::ref_ptr<VisController> callback(new VisController(visctrl, Loader::getHiddenNodeMask()));
@ -938,7 +942,7 @@ namespace NifOsg
&& rollctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiRollController " << rollctrl->recIndex
<< " in " << mFilename;
<< " in " << mFilename << ": " << rollctrl->mInterpolator->recName;
continue;
}
osg::ref_ptr<RollController> callback = new RollController(rollctrl);
@ -973,8 +977,9 @@ namespace NifOsg
if (!alphactrl->mInterpolator.empty()
&& alphactrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiAlphaController "
<< alphactrl->recIndex << " in " << mFilename;
Log(Debug::Error)
<< "Unsupported interpolator type for NiAlphaController " << alphactrl->recIndex << " in "
<< mFilename << ": " << alphactrl->mInterpolator->recName;
continue;
}
osg::ref_ptr<AlphaController> osgctrl = new AlphaController(alphactrl, baseMaterial);
@ -994,8 +999,9 @@ namespace NifOsg
if (!matctrl->mInterpolator.empty()
&& matctrl->mInterpolator->recType != Nif::RC_NiPoint3Interpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController "
<< matctrl->recIndex << " in " << mFilename;
Log(Debug::Error)
<< "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex
<< " in " << mFilename << ": " << matctrl->mInterpolator->recName;
continue;
}
osg::ref_ptr<MaterialColorController> osgctrl = new MaterialColorController(matctrl, baseMaterial);
@ -1021,7 +1027,7 @@ namespace NifOsg
&& flipctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator)
{
Log(Debug::Error) << "Unsupported interpolator type for NiFlipController " << flipctrl->recIndex
<< " in " << mFilename;
<< " in " << mFilename << ": " << flipctrl->mInterpolator->recName;
continue;
}
std::vector<osg::ref_ptr<osg::Texture2D>> textures;
@ -1067,12 +1073,12 @@ namespace NifOsg
attachTo->addChild(program);
program->setParticleSystem(partsys);
program->setReferenceFrame(rf);
for (; !affectors.empty(); affectors = affectors->next)
for (; !affectors.empty(); affectors = affectors->mNext)
{
if (affectors->recType == Nif::RC_NiParticleGrowFade)
{
const Nif::NiParticleGrowFade* gf = static_cast<const Nif::NiParticleGrowFade*>(affectors.getPtr());
program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime));
program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime));
}
else if (affectors->recType == Nif::RC_NiGravity)
{
@ -1083,9 +1089,9 @@ namespace NifOsg
{
const Nif::NiParticleColorModifier* cl
= static_cast<const Nif::NiParticleColorModifier*>(affectors.getPtr());
if (cl->data.empty())
if (cl->mData.empty())
continue;
const Nif::NiColorData* clrdata = cl->data.getPtr();
const Nif::NiColorData* clrdata = cl->mData.getPtr();
program->addOperator(new ParticleColorAffector(clrdata));
}
else if (affectors->recType == Nif::RC_NiParticleRotation)
@ -1095,7 +1101,7 @@ namespace NifOsg
else
Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename;
}
for (; !colliders.empty(); colliders = colliders->next)
for (; !colliders.empty(); colliders = colliders->mNext)
{
if (colliders->recType == Nif::RC_NiPlanarCollider)
{
@ -2008,15 +2014,15 @@ namespace NifOsg
const unsigned int uvSet = 0;
for (size_t i = 0; i < textureSet->textures.size(); ++i)
for (size_t i = 0; i < textureSet->mTextures.size(); ++i)
{
if (textureSet->textures[i].empty())
if (textureSet->mTextures[i].empty())
continue;
switch (i)
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{
case Nif::BSShaderTextureSet::TextureType_Base:
case Nif::BSShaderTextureSet::TextureType_Normal:
case Nif::BSShaderTextureSet::TextureType_Glow:
case Nif::BSShaderTextureSet::TextureType::Base:
case Nif::BSShaderTextureSet::TextureType::Normal:
case Nif::BSShaderTextureSet::TextureType::Glow:
break;
default:
{
@ -2026,7 +2032,7 @@ namespace NifOsg
}
}
std::string filename
= Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS());
= Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
@ -2035,17 +2041,19 @@ namespace NifOsg
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
// BSShaderTextureSet presence means there's no need for FFP support for the affected node
switch (i)
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{
case Nif::BSShaderTextureSet::TextureType_Base:
case Nif::BSShaderTextureSet::TextureType::Base:
texture2d->setName("diffuseMap");
break;
case Nif::BSShaderTextureSet::TextureType_Normal:
case Nif::BSShaderTextureSet::TextureType::Normal:
texture2d->setName("normalMap");
break;
case Nif::BSShaderTextureSet::TextureType_Glow:
case Nif::BSShaderTextureSet::TextureType::Glow:
texture2d->setName("emissiveMap");
break;
default:
break;
}
boundTextures.emplace_back(uvSet);
}

@ -9,7 +9,6 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp>
#include <components/nif/controlled.hpp>
#include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
@ -281,20 +280,13 @@ namespace NifOsg
GravityAffector::GravityAffector(const Nif::NiGravity* gravity)
: mForce(gravity->mForce)
, mType(static_cast<ForceType>(gravity->mType))
, mType(gravity->mType)
, mPosition(gravity->mPosition)
, mDirection(gravity->mDirection)
, mDecay(gravity->mDecay)
{
}
GravityAffector::GravityAffector()
: mForce(0)
, mType(Type_Wind)
, mDecay(0.f)
{
}
GravityAffector::GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop)
: osgParticle::Operator(copy, copyop)
{
@ -311,8 +303,8 @@ namespace NifOsg
{
bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF);
if (mType == Type_Point
|| mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied
// We don't need the position for Wind gravity, except if decay is being applied
if (mType == Nif::NiGravity::ForceType::Point || mDecay != 0.f)
mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition;
mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection;
@ -324,7 +316,7 @@ namespace NifOsg
const float magic = 1.6f;
switch (mType)
{
case Type_Wind:
case Nif::NiGravity::ForceType::Wind:
{
float decayFactor = 1.f;
if (mDecay != 0.f)
@ -338,7 +330,7 @@ namespace NifOsg
break;
}
case Type_Point:
case Nif::NiGravity::ForceType::Point:
{
osg::Vec3f diff = mCachedWorldPosition - particle->getPosition();

@ -10,15 +10,14 @@
#include <osgParticle/Placer>
#include <osgParticle/Shooter>
#include <components/nif/particle.hpp> // NiGravity::ForceType
#include <components/sceneutil/nodecallback.hpp>
#include "controller.hpp" // ValueInterpolator
namespace Nif
{
struct NiGravity;
struct NiPlanarCollider;
struct NiSphericalCollider;
struct NiColorData;
}
@ -180,7 +179,7 @@ namespace NifOsg
{
public:
GravityAffector(const Nif::NiGravity* gravity);
GravityAffector();
GravityAffector() = default;
GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
GravityAffector& operator=(const GravityAffector&) = delete;
@ -191,16 +190,11 @@ namespace NifOsg
void beginOperate(osgParticle::Program*) override;
private:
float mForce;
enum ForceType
{
Type_Wind,
Type_Point
};
ForceType mType;
float mForce{ 0.f };
Nif::NiGravity::ForceType mType{ Nif::NiGravity::ForceType::Wind };
osg::Vec3f mPosition;
osg::Vec3f mDirection;
float mDecay;
float mDecay{ 0.f };
osg::Vec3f mCachedWorldPosition;
osg::Vec3f mCachedWorldDirection;
};

@ -32,7 +32,9 @@ namespace Resource
};
NifFileManager::NifFileManager(const VFS::Manager* vfs)
: ResourceManager(vfs)
// NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager,
// so no point in using an expiry delay.
: ResourceManager(vfs, 0)
{
}

@ -62,9 +62,9 @@ namespace Resource
{
// If ref count is greater than 1, the object has an external reference.
// If the timestamp is yet to be initialized, it needs to be updated too.
if ((itr->second.first != nullptr && itr->second.first->referenceCount() > 1)
|| itr->second.second == 0.0)
itr->second.second = referenceTime;
if ((itr->second.mValue != nullptr && itr->second.mValue->referenceCount() > 1)
|| itr->second.mLastUsage == 0.0)
itr->second.mLastUsage = referenceTime;
}
}
@ -81,10 +81,10 @@ namespace Resource
typename ObjectCacheMap::iterator oitr = _objectCache.begin();
while (oitr != _objectCache.end())
{
if (oitr->second.second <= expiryTime)
if (oitr->second.mLastUsage <= expiryTime)
{
if (oitr->second.first != nullptr)
objectsToRemove.push_back(oitr->second.first);
if (oitr->second.mValue != nullptr)
objectsToRemove.push_back(std::move(oitr->second.mValue));
_objectCache.erase(oitr++);
}
else
@ -106,7 +106,7 @@ namespace Resource
void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0)
{
std::lock_guard<std::mutex> lock(_objectCacheMutex);
_objectCache[key] = ObjectTimeStampPair(object, timestamp);
_objectCache[key] = Item{ object, timestamp };
}
/** Remove Object from cache.*/
@ -124,7 +124,7 @@ namespace Resource
std::lock_guard<std::mutex> lock(_objectCacheMutex);
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr != _objectCache.end())
return itr->second.first;
return itr->second.mValue;
else
return nullptr;
}
@ -135,7 +135,7 @@ namespace Resource
const auto it = _objectCache.find(key);
if (it == _objectCache.end())
return std::nullopt;
return it->second.first;
return it->second.mValue;
}
/** Check if an object is in the cache, and if it is, update its usage time stamp. */
@ -145,7 +145,7 @@ namespace Resource
typename ObjectCacheMap::iterator itr = _objectCache.find(key);
if (itr != _objectCache.end())
{
itr->second.second = timeStamp;
itr->second.mLastUsage = timeStamp;
return true;
}
else
@ -158,7 +158,7 @@ namespace Resource
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
osg::Object* object = itr->second.mValue.get();
object->releaseGLObjects(state);
}
}
@ -169,8 +169,7 @@ namespace Resource
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr)
{
osg::Object* object = itr->second.first.get();
if (object)
if (osg::Object* object = itr->second.mValue.get())
{
osg::Node* node = dynamic_cast<osg::Node*>(object);
if (node)
@ -185,7 +184,7 @@ namespace Resource
{
std::lock_guard<std::mutex> lock(_objectCacheMutex);
for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->first, it->second.first.get());
f(it->first, it->second.mValue.get());
}
/** Get the number of objects in the cache. */
@ -195,11 +194,26 @@ namespace Resource
return _objectCache.size();
}
template <class K>
std::optional<std::pair<KeyType, osg::ref_ptr<osg::Object>>> lowerBound(K&& key)
{
const std::lock_guard<std::mutex> lock(_objectCacheMutex);
const auto it = _objectCache.lower_bound(std::forward<K>(key));
if (it == _objectCache.end())
return std::nullopt;
return std::pair(it->first, it->second.mValue);
}
protected:
struct Item
{
osg::ref_ptr<osg::Object> mValue;
double mLastUsage;
};
virtual ~GenericObjectCache() {}
typedef std::pair<osg::ref_ptr<osg::Object>, double> ObjectTimeStampPair;
typedef std::map<KeyType, ObjectTimeStampPair> ObjectCacheMap;
using ObjectCacheMap = std::map<KeyType, Item, std::less<>>;
ObjectCacheMap _objectCache;
mutable std::mutex _objectCacheMutex;

@ -3,6 +3,8 @@
#include <osg/ref_ptr>
#include <components/settings/values.hpp>
#include "objectcache.hpp"
namespace VFS
@ -23,11 +25,11 @@ namespace Resource
{
public:
virtual ~BaseResourceManager() = default;
virtual void updateCache(double referenceTime) {}
virtual void clearCache() {}
virtual void setExpiryDelay(double expiryDelay) {}
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {}
virtual void releaseGLObjects(osg::State* state) {}
virtual void updateCache(double referenceTime) = 0;
virtual void clearCache() = 0;
virtual void setExpiryDelay(double expiryDelay) = 0;
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const = 0;
virtual void releaseGLObjects(osg::State* state) = 0;
};
/// @brief Base class for managers that require a virtual file system and object cache.
@ -39,10 +41,11 @@ namespace Resource
public:
typedef GenericObjectCache<KeyType> CacheType;
GenericResourceManager(const VFS::Manager* vfs)
explicit GenericResourceManager(
const VFS::Manager* vfs, double expiryDelay = Settings::cells().mCacheExpiryDelay)
: mVFS(vfs)
, mCache(new CacheType)
, mExpiryDelay(0.0)
, mExpiryDelay(expiryDelay)
{
}
@ -59,7 +62,7 @@ namespace Resource
void clearCache() override { mCache->clear(); }
/// How long to keep objects in cache after no longer being referenced.
void setExpiryDelay(double expiryDelay) override { mExpiryDelay = expiryDelay; }
void setExpiryDelay(double expiryDelay) final { mExpiryDelay = expiryDelay; }
double getExpiryDelay() const { return mExpiryDelay; }
const VFS::Manager* getVFS() const { return mVFS; }
@ -77,10 +80,15 @@ namespace Resource
class ResourceManager : public GenericResourceManager<std::string>
{
public:
ResourceManager(const VFS::Manager* vfs)
explicit ResourceManager(const VFS::Manager* vfs)
: GenericResourceManager<std::string>(vfs)
{
}
explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay)
: GenericResourceManager<std::string>(vfs, expiryDelay)
{
}
};
}

@ -21,7 +21,7 @@ namespace Terrain
ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager,
CompositeMapRenderer* renderer, ESM::RefId worldspace)
: GenericResourceManager<ChunkId>(nullptr)
: GenericResourceManager<ChunkKey>(nullptr)
, QuadTreeWorld::ChunkManager(worldspace)
, mStorage(storage)
, mSceneManager(sceneMgr)
@ -39,38 +39,26 @@ namespace Terrain
mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON);
}
struct FindChunkTemplate
{
void operator()(ChunkId id, osg::Object* obj)
{
if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId))
mFoundTemplate = obj;
}
ChunkId mId;
osg::ref_ptr<osg::Object> mFoundTemplate;
};
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod,
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
{
// Override lod with the vertexLodMod adjusted value.
// TODO: maybe we can refactor this code by moving all vertexLodMod code into this class.
lod = static_cast<unsigned char>(lodFlags >> (4 * 4));
ChunkId id = std::make_tuple(center, lod, lodFlags);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
if (obj)
const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags };
if (osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(key))
return static_cast<osg::Node*>(obj.get());
else
{
FindChunkTemplate find;
find.mId = id;
mCache->call(find);
TerrainDrawable* templateGeometry
= find.mFoundTemplate ? static_cast<TerrainDrawable*>(find.mFoundTemplate.get()) : nullptr;
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags, compile, templateGeometry);
mCache->addEntryToObjectCache(id, node.get());
return node;
}
const TerrainDrawable* templateGeometry = nullptr;
const TemplateKey templateKey{ .mCenter = center, .mLod = lod };
const auto pair = mCache->lowerBound(templateKey);
if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod })
templateGeometry = static_cast<const TerrainDrawable*>(pair->second.get());
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags, compile, templateGeometry);
mCache->addEntryToObjectCache(key, node.get());
return node;
}
void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
@ -80,14 +68,14 @@ namespace Terrain
void ChunkManager::clearCache()
{
GenericResourceManager<ChunkId>::clearCache();
GenericResourceManager<ChunkKey>::clearCache();
mBufferCache.clearCache();
}
void ChunkManager::releaseGLObjects(osg::State* state)
{
GenericResourceManager<ChunkId>::releaseGLObjects(state);
GenericResourceManager<ChunkKey>::releaseGLObjects(state);
mBufferCache.releaseGLObjects(state);
}
@ -202,7 +190,7 @@ namespace Terrain
}
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod,
unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry)
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry)
{
osg::ref_ptr<TerrainDrawable> geometry(new TerrainDrawable);

@ -28,10 +28,51 @@ namespace Terrain
class CompositeMap;
class TerrainDrawable;
typedef std::tuple<osg::Vec2f, unsigned char, unsigned int> ChunkId; // Center, Lod, Lod Flags
struct TemplateKey
{
osg::Vec2f mCenter;
unsigned char mLod;
};
inline auto tie(const TemplateKey& v)
{
return std::tie(v.mCenter, v.mLod);
}
inline bool operator<(const TemplateKey& l, const TemplateKey& r)
{
return tie(l) < tie(r);
}
inline bool operator==(const TemplateKey& l, const TemplateKey& r)
{
return tie(l) == tie(r);
}
struct ChunkKey
{
osg::Vec2f mCenter;
unsigned char mLod;
unsigned mLodFlags;
};
inline auto tie(const ChunkKey& v)
{
return std::tie(v.mCenter, v.mLod, v.mLodFlags);
}
inline bool operator<(const ChunkKey& l, const ChunkKey& r)
{
return tie(l) < tie(r);
}
inline bool operator<(const ChunkKey& l, const TemplateKey& r)
{
return TemplateKey{ .mCenter = l.mCenter, .mLod = l.mLod } < r;
}
/// @brief Handles loading and caching of terrain chunks
class ChunkManager : public Resource::GenericResourceManager<ChunkId>, public QuadTreeWorld::ChunkManager
class ChunkManager : public Resource::GenericResourceManager<ChunkKey>, public QuadTreeWorld::ChunkManager
{
public:
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager,
@ -55,7 +96,7 @@ namespace Terrain
private:
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod,
unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry);
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry);
osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();

@ -544,37 +544,16 @@ namespace Terrain
vd->setViewPoint(viewPoint);
vd->setActiveGrid(grid);
for (unsigned int pass = 0; pass < 3; ++pass)
{
unsigned int startEntry = vd->getNumEntries();
float distanceModifier = 0.f;
if (pass == 1)
distanceModifier = 1024;
else if (pass == 2)
distanceModifier = -1024;
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize, distanceModifier);
mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
if (pass == 0)
{
std::size_t progressTotal = 0;
for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i)
progressTotal += vd->getEntry(i).mNode->getSize();
reporter.addTotal(progressTotal);
}
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize);
mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
for (unsigned int i = startEntry; i < vd->getNumEntries() && !abort; ++i)
{
ViewDataEntry& entry = vd->getEntry(i);
reporter.addTotal(vd->getNumEntries());
loadRenderingNode(entry, vd, cellWorldSize, grid, true);
if (pass == 0)
reporter.addProgress(entry.mNode->getSize());
vd->removeNodeFromIndex(entry.mNode);
entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass
}
for (unsigned int i = 0, n = vd->getNumEntries(); i < n && !abort; ++i)
{
ViewDataEntry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, cellWorldSize, grid, true);
reporter.addProgress(1);
}
}

@ -60,7 +60,7 @@ namespace Terrain
const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; }
void setCompositeMap(CompositeMap* map) { mCompositeMap = map; }
CompositeMap* getCompositeMap() { return mCompositeMap; }
CompositeMap* getCompositeMap() const { return mCompositeMap; }
void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; }
private:

@ -17,6 +17,7 @@ Lua API reference
openmw_core
openmw_types
openmw_async
openmw_vfs
openmw_world
openmw_self
openmw_nearby

@ -0,0 +1,7 @@
Package openmw.vfs
==================
.. include:: version.rst
.. raw:: html
:file: generated_html/openmw_vfs.html

@ -6,7 +6,7 @@ Overview of Lua scripting
Language and sandboxing
=======================
OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2.
OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2 and Lua 5.3.
There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT.
.. note::
@ -40,6 +40,10 @@ Supported Lua 5.2 features:
- ``__pairs`` and ``__ipairs`` metamethods;
- Function ``table.unpack`` (alias to Lua 5.1 ``unpack``).
Supported Lua 5.3 features:
- All functions in the `UTF-8 Library <https://www.lua.org/manual/5.3/manual.html#6.5>`__
Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way:
1. If `library_name` is one of the standard libraries, then return the library.

@ -15,6 +15,8 @@
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.vfs <Package openmw.vfs>` | everywhere | | Read-only access to data directories via VFS. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |

@ -22,6 +22,7 @@ local env = {
storage = require('openmw.storage'),
core = require('openmw.core'),
types = require('openmw.types'),
vfs = require('openmw.vfs'),
async = require('openmw.async'),
world = require('openmw.world'),
aux_util = require('openmw_aux.util'),

@ -24,6 +24,7 @@ local env = {
storage = require('openmw.storage'),
core = require('openmw.core'),
types = require('openmw.types'),
vfs = require('openmw.vfs'),
async = require('openmw.async'),
nearby = require('openmw.nearby'),
self = require('openmw.self'),

@ -71,6 +71,8 @@ local env = {
storage = require('openmw.storage'),
core = require('openmw.core'),
types = require('openmw.types'),
vfs = require('openmw.vfs'),
ambient = require('openmw.ambient'),
async = require('openmw.async'),
nearby = require('openmw.nearby'),
self = require('openmw.self'),

@ -12,14 +12,15 @@ set(LUA_API_FILES
openmw/ambient.lua
openmw/async.lua
openmw/core.lua
openmw/debug.lua
openmw/nearby.lua
openmw/postprocessing.lua
openmw/self.lua
openmw/types.lua
openmw/ui.lua
openmw/util.lua
openmw/vfs.lua
openmw/world.lua
openmw/types.lua
openmw/postprocessing.lua
openmw/debug.lua
)
foreach (f ${LUA_API_FILES})

@ -0,0 +1,159 @@
---
-- `openmw.vfs` provides read-only access to data directories via VFS.
-- Interface is very similar to "io" library.
-- @module vfs
-- @usage local vfs = require('openmw.vfs')
---
-- @type FileHandle
-- @field #string fileName VFS path to related file
---
-- Close a file handle
-- @function [parent=#FileHandle] close
-- @param self
-- @return #boolean true if a call succeeds without errors.
-- @return #nil, #string nil plus the error message in case of any error.
---
-- Get an iterator function to fetch the next line from given file.
-- Throws an exception if file is closed.
--
-- Hint: since garbage collection works once per frame,
-- you will get the whole file in RAM if you read it in one frame.
-- So if you need to read a really large file, it is better to split reading
-- between different frames (e.g. by keeping a current position in file
-- and using a "seek" to read from saved position).
-- @function [parent=#FileHandle] lines
-- @param self
-- @return #function Iterator function to get next line
-- @usage f = vfs.open("Test\\test.txt");
-- for line in f:lines() do
-- print(line);
-- end
---
-- Set new position in file.
-- Throws an exception if file is closed or seek base is incorrect.
-- @function [parent=#FileHandle] seek
-- @param self
-- @param #string whence Seek base (optional, "cur" by default). Can be:
--
-- * "set" - seek from beginning of file;
-- * "cur" - seek from current position;
-- * "end" - seek from end of file (offset needs to be <= 0);
-- @param #number offset Offset from given base (optional, 0 by default)
-- @return #number new position in file if a call succeeds without errors.
-- @return #nil, #string nil plus the error message in case of any error.
-- @usage -- set pointer to beginning of file
-- f = vfs.open("Test\\test.txt");
-- f:seek("set");
-- @usage -- print current position in file
-- f = vfs.open("Test\\test.txt");
-- print(f:seek());
-- @usage -- print file size
-- f = vfs.open("Test\\test.txt");
-- print(f:seek("end"));
---
-- Read data from file to strings.
-- Throws an exception if file is closed, if there is too many arguments or if an invalid format encountered.
--
-- Hint: since garbage collection works once per frame,
-- you will get the whole file in RAM if you read it in one frame.
-- So if you need to read a really large file, it is better to split reading
-- between different frames (e.g. by keeping a current position in file
-- and using a "seek" to read from saved position).
-- @function [parent=#FileHandle] read
-- @param self
-- @param ... Read formats (up to 20 arguments, default value is one "*l"). Can be:
--
-- * "\*a" (or "*all") - reads the whole file, starting at the current position as #string. On end of file, it returns the empty string.
-- * "\*l" (or "*line") - reads the next line (skipping the end of line), returning nil on end of file (nil and error message if error occured);
-- * "\*n" (or "*number") - read a floating point value as #number (nil and error message if error occured);
-- * number - reads a #string with up to this number of characters, returning nil on end of file (nil and error message if error occured). If number is 0 and end of file is not reached, it reads nothing and returns an empty string;
-- @return #string One #string for every format if a call succeeds without errors. One #string for every successfully handled format, nil for first failed format.
-- @usage -- read three numbers from file
-- f = vfs.open("Test\\test.txt");
-- local n1, n2, n3 = f:read("*number", "*number", "*number");
-- @usage -- read 10 bytes from file
-- f = vfs.open("Test\\test.txt");
-- local n4 = f:read(10);
-- @usage -- read until end of file
-- f = vfs.open("Test\\test.txt");
-- local n5 = f:read("*all");
-- @usage -- read a line from file
-- f = vfs.open("Test\\test.txt");
-- local n6 = f:read();
-- @usage -- try to read three numbers from file with "1" content
-- f = vfs.open("one.txt");
-- print(f:read("*number", "*number", "*number"));
-- -- prints(1, nil)
---
-- Check if file exists in VFS
-- @function [parent=#vfs] fileExists
-- @param #string fileName Path to file in VFS
-- @return #boolean (true - exists, false - does not exist)
-- @usage local exists = vfs.fileExists("Test\\test.txt");
---
-- Open a file
-- @function [parent=#vfs] open
-- @param #string fileName Path to file in VFS
-- @return #FileHandle Opened file handle if a call succeeds without errors.
-- @return #nil, #string nil plus the error message in case of any error.
-- @usage f, msg = vfs.open("Test\\test.txt");
-- -- print file name or error message
-- if (f == nil)
-- print(msg);
-- else
-- print(f.fileName);
-- end
---
-- Get an iterator function to fetch the next line from file with given path.
-- Throws an exception if file is closed or file with given path does not exist.
-- Closes file automatically when it fails to read any more bytes.
--
-- Hint: since garbage collection works once per frame,
-- you will get the whole file in RAM if you read it in one frame.
-- So if you need to read a really large file, it is better to split reading
-- between different frames (e.g. by keeping a current position in file
-- and using a "seek" to read from saved position).
-- @function [parent=#vfs] lines
-- @param #string fileName Path to file in VFS
-- @return #function Iterator function to get next line
-- @usage for line in vfs.lines("Test\\test.txt") do
-- print(line);
-- end
---
-- Get iterator function to fetch file names with given path prefix from VFS
-- @function [parent=#vfs] pathsWithPrefix
-- @param #string path Path prefix
-- @return #function Function to get next file name
-- @usage -- get all files with given prefix from VFS index
-- for fileName in vfs.pathsWithPrefix("Music\\Explore") do
-- print(fileName);
-- end
-- @usage -- get some first files
-- local getNextFile = vfs.pathsWithPrefix("Music\\Explore");
-- local firstFile = getNextFile();
-- local secondFile = getNextFile();
---
-- Detect a file handle type
-- @function [parent=#vfs] type
-- @param #any handle Object to check
-- @return #string File handle type. Can be:
--
-- * "file" - an argument is a valid opened @{openmw.vfs#FileHandle};
-- * "closed file" - an argument is a valid closed @{openmw.vfs#FileHandle};
-- * nil - an argument is not a @{openmw.vfs#FileHandle};
-- @usage f = vfs.open("Test\\test.txt");
-- print(vfs.type(f));
return nil

@ -0,0 +1,77 @@
-------------------------------------------------------------------------------
-- UTF-8 Support.
-- This library provides basic support for UTF-8 encoding.
-- It provides all its functions inside the table utf8.
-- This library does not provide any support for Unicode other than the handling of the encoding.
-- Any operation that needs the meaning of a character, such as character classification, is outside its scope.
--
-- Unless stated otherwise, all functions that expect a byte position as a parameter assume that
-- the given position is either the start of a byte sequence or one plus the length of the subject string.
-- As in the string library, negative indices count from the end of the string.
-- @module utf8
-------------------------------------------------------------------------------
-- Receives zero or more integers, converts each one to its
-- corresponding UTF-8 byte sequence, and returns a string with the concatenation
-- of all these sequences.
-- @function [parent=#utf8] char
-- @param ... zero or more integers.
-- @return #string
-------------------------------------------------------------------------------
-- The pattern which matches exactly one UTF-8 byte sequence, assuming that
-- the subject is a valid UTF-8 string.
-- @function [parent=#utf8] charpattern
-- @return #string
-------------------------------------------------------------------------------
-- Returns values so that the construction
--
-- for p, c in utf8.codes(s) do body end
--
-- will iterate over all characters in string s, with p being the position (in bytes)
-- and c the code point of each character.
-- It raises an error if it meets any invalid byte sequence.
-- @function [parent=#utf8] codes
-- @param #string s string to handle.
-------------------------------------------------------------------------------
-- Returns the codepoints (as integers) from all characters in s that start
-- between byte position i and j (both included). The default for i is 1 and for j is i.
-- It raises an error if it meets any invalid byte sequence.
-- @function [parent=#utf8] codepoint
-- @param #string s string to handle
-- @param #number i the initial position (default value is 1)
-- @param #number j the final position (default value is i)
-- @return #number the codepoints of each character in s
-------------------------------------------------------------------------------
-- Returns the number of UTF-8 characters in string s that start
-- between positions i and j (both inclusive).
-- The default for i is 1 and for j is -1.
-- If it finds any invalid byte sequence,
-- returns a false value plus the position of the first invalid byte.
-- @function [parent=#utf8] len
-- @param #string s string to handle
-- @param #number i the initial position (default value is 1)
-- @param #number j the final position (default value is -1)
-- @return #number the number of utf8 characters in s
-------------------------------------------------------------------------------
-- Returns the position (in bytes) where the encoding of the n-th character of s
-- (counting from position i) starts. A negative n gets characters before position i.
-- The default for i is 1 when n is non-negative and #s + 1 otherwise,
-- so that utf8.offset(s, -n) gets the offset of the n-th character from the end of the string.
-- If the specified character is neither in the subject nor right after its end, the function returns nil.
--
-- As a special case, when n is 0 the function returns the
-- start of the encoding of the character that contains the i-th byte of s.
--
-- This function assumes that s is a valid UTF-8 string.
-- @function [parent=#utf8] offset
-- @param #string s string to handle
-- @param #number n the n-th character
-- @param #number i the initial position (default value is 1 if n is is non-negative and #s + 1 otherwise)
-- @return #number
return nil
Loading…
Cancel
Save