mirror of https://github.com/OpenMW/openmw.git
Merge branch 'bullet_object_tool' into 'master'
Add a tool to load and print information about all bullet objects in all cells See merge request OpenMW/openmw!1627C++20
commit
a55f1ad103
@ -0,0 +1,20 @@
|
||||
set(BULLETMESHTOOL
|
||||
main.cpp
|
||||
)
|
||||
source_group(apps\\bulletobjecttool FILES ${BULLETMESHTOOL})
|
||||
|
||||
openmw_add_executable(openmw-bulletobjecttool ${BULLETMESHTOOL})
|
||||
|
||||
target_link_libraries(openmw-bulletobjecttool
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
components
|
||||
)
|
||||
|
||||
if (BUILD_WITH_CODE_COVERAGE)
|
||||
add_definitions(--coverage)
|
||||
target_link_libraries(openmw-bulletobjecttool gcov)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".")
|
||||
endif()
|
@ -0,0 +1,203 @@
|
||||
#include <components/debug/debugging.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/loadcell.hpp>
|
||||
#include <components/esm3/variant.hpp>
|
||||
#include <components/esmloader/esmdata.hpp>
|
||||
#include <components/esmloader/load.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
#include <components/fallback/validate.hpp>
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/resource/foreachbulletobject.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/niffilemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/to_utf8/to_utf8.hpp>
|
||||
#include <components/version/version.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
#include <components/vfs/registerarchives.hpp>
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <charconv>
|
||||
#include <cstddef>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
using StringsVector = std::vector<std::string>;
|
||||
|
||||
bpo::options_description makeOptionsDescription()
|
||||
{
|
||||
using Fallback::FallbackMap;
|
||||
|
||||
bpo::options_description result;
|
||||
|
||||
result.add_options()
|
||||
("help", "print help message")
|
||||
|
||||
("version", "print version information and quit")
|
||||
|
||||
("data", bpo::value<Files::MaybeQuotedPathContainer>()->default_value(Files::MaybeQuotedPathContainer(), "data")
|
||||
->multitoken()->composing(), "set data directories (later directories have higher priority)")
|
||||
|
||||
("data-local", bpo::value<Files::MaybeQuotedPathContainer::value_type>()->default_value(Files::MaybeQuotedPathContainer::value_type(), ""),
|
||||
"set local data directory (highest priority)")
|
||||
|
||||
("fallback-archive", bpo::value<StringsVector>()->default_value(StringsVector(), "fallback-archive")
|
||||
->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)")
|
||||
|
||||
("resources", bpo::value<Files::MaybeQuotedPath>()->default_value(Files::MaybeQuotedPath(), "resources"),
|
||||
"set resources directory")
|
||||
|
||||
("content", bpo::value<StringsVector>()->default_value(StringsVector(), "")
|
||||
->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts")
|
||||
|
||||
("fs-strict", bpo::value<bool>()->implicit_value(true)
|
||||
->default_value(false), "strict file system handling (no case folding)")
|
||||
|
||||
("encoding", bpo::value<std::string>()->
|
||||
default_value("win1252"),
|
||||
"Character encoding used in OpenMW game messages:\n"
|
||||
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
|
||||
"\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n"
|
||||
"\n\twin1252 - Western European (Latin) alphabet, used by default")
|
||||
|
||||
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
|
||||
->multitoken()->composing(), "fallback values")
|
||||
;
|
||||
|
||||
Files::ConfigurationManager::addCommonOptions(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct WriteArray
|
||||
{
|
||||
const float (&mValue)[3];
|
||||
|
||||
friend std::ostream& operator <<(std::ostream& stream, const WriteArray& value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 2; ++i)
|
||||
stream << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.mValue[i] << ", ";
|
||||
return stream << std::setprecision(std::numeric_limits<float>::max_exponent10) << value.mValue[2];
|
||||
}
|
||||
};
|
||||
|
||||
std::string toHex(std::string_view value)
|
||||
{
|
||||
std::string buffer(value.size() * 2, '0');
|
||||
char* out = buffer.data();
|
||||
for (const char v : value)
|
||||
{
|
||||
const std::ptrdiff_t space = static_cast<std::ptrdiff_t>(static_cast<std::uint8_t>(v) <= 0xf);
|
||||
const auto [ptr, ec] = std::to_chars(out + space, out + space + 2, static_cast<std::uint8_t>(v), 16);
|
||||
if (ec != std::errc())
|
||||
throw std::system_error(std::make_error_code(ec));
|
||||
out += 2;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int runBulletObjectTool(int argc, char *argv[])
|
||||
{
|
||||
bpo::options_description desc = makeOptionsDescription();
|
||||
|
||||
bpo::parsed_options options = bpo::command_line_parser(argc, argv)
|
||||
.options(desc).allow_unregistered().run();
|
||||
bpo::variables_map variables;
|
||||
|
||||
bpo::store(options, variables);
|
||||
bpo::notify(variables);
|
||||
|
||||
if (variables.find("help") != variables.end())
|
||||
{
|
||||
getRawStdout() << desc << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Files::ConfigurationManager config;
|
||||
|
||||
bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc);
|
||||
config.readConfiguration(variables, desc);
|
||||
Files::mergeComposingVariables(variables, composingVariables, desc);
|
||||
|
||||
const std::string encoding(variables["encoding"].as<std::string>());
|
||||
Log(Debug::Info) << ToUTF8::encodingUsingMessage(encoding);
|
||||
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(encoding));
|
||||
|
||||
Files::PathContainer dataDirs(asPathContainer(variables["data"].as<Files::MaybeQuotedPathContainer>()));
|
||||
|
||||
auto local = variables["data-local"].as<Files::MaybeQuotedPathContainer::value_type>();
|
||||
if (!local.empty())
|
||||
dataDirs.push_back(std::move(local));
|
||||
|
||||
config.processPaths(dataDirs);
|
||||
|
||||
const auto fsStrict = variables["fs-strict"].as<bool>();
|
||||
const auto resDir = variables["resources"].as<Files::MaybeQuotedPath>();
|
||||
Version::Version v = Version::getOpenmwVersion(resDir.string());
|
||||
Log(Debug::Info) << v.describe();
|
||||
dataDirs.insert(dataDirs.begin(), resDir / "vfs");
|
||||
const auto fileCollections = Files::Collections(dataDirs, !fsStrict);
|
||||
const auto archives = variables["fallback-archive"].as<StringsVector>();
|
||||
const auto contentFiles = variables["content"].as<StringsVector>();
|
||||
|
||||
Fallback::Map::init(variables["fallback"].as<Fallback::FallbackMap>().mMap);
|
||||
|
||||
VFS::Manager vfs(fsStrict);
|
||||
|
||||
VFS::registerArchives(&vfs, fileCollections, archives, true);
|
||||
|
||||
Settings::Manager settings;
|
||||
settings.load(config);
|
||||
|
||||
std::vector<ESM::ESMReader> readers(contentFiles.size());
|
||||
EsmLoader::Query query;
|
||||
query.mLoadActivators = true;
|
||||
query.mLoadCells = true;
|
||||
query.mLoadContainers = true;
|
||||
query.mLoadDoors = true;
|
||||
query.mLoadGameSettings = true;
|
||||
query.mLoadLands = true;
|
||||
query.mLoadStatics = true;
|
||||
const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder);
|
||||
|
||||
Resource::ImageManager imageManager(&vfs);
|
||||
Resource::NifFileManager nifFileManager(&vfs);
|
||||
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager);
|
||||
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager);
|
||||
|
||||
Resource::forEachBulletObject(readers, vfs, bulletShapeManager, esmData,
|
||||
[] (const ESM::Cell& cell, const Resource::BulletObject& object)
|
||||
{
|
||||
Log(Debug::Verbose) << "Found bullet object in " << (cell.isExterior() ? "exterior" : "interior")
|
||||
<< " cell \"" << cell.getDescription() << "\":"
|
||||
<< " fileName=\"" << object.mShape->mFileName << '"'
|
||||
<< " fileHash=" << toHex(object.mShape->mFileHash)
|
||||
<< " collisionShape=" << std::boolalpha << (object.mShape->mCollisionShape == nullptr)
|
||||
<< " avoidCollisionShape=" << std::boolalpha << (object.mShape->mAvoidCollisionShape == nullptr)
|
||||
<< " position=(" << WriteArray {object.mPosition.pos} << ')'
|
||||
<< " rotation=(" << WriteArray {object.mPosition.rot} << ')'
|
||||
<< " scale=" << std::setprecision(std::numeric_limits<float>::max_exponent10) << object.mScale;
|
||||
});
|
||||
|
||||
Log(Debug::Info) << "Done";
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return wrapApplication(runBulletObjectTool, argc, argv, "BulletObjectTool");
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
#include "foreachbulletobject.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/cellref.hpp>
|
||||
#include <components/esm3/esmreader.hpp>
|
||||
#include <components/esm3/loadcell.hpp>
|
||||
#include <components/esm3/loadland.hpp>
|
||||
#include <components/esmloader/esmdata.hpp>
|
||||
#include <components/esmloader/lessbyid.hpp>
|
||||
#include <components/esmloader/record.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/misc/stringops.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct CellRef
|
||||
{
|
||||
ESM::RecNameInts mType;
|
||||
ESM::RefNum mRefNum;
|
||||
std::string mRefId;
|
||||
float mScale;
|
||||
ESM::Position mPos;
|
||||
|
||||
CellRef(ESM::RecNameInts type, ESM::RefNum refNum, std::string&& refId, float scale, const ESM::Position& pos)
|
||||
: mType(type), mRefNum(refNum), mRefId(std::move(refId)), mScale(scale), mPos(pos) {}
|
||||
};
|
||||
|
||||
ESM::RecNameInts getType(const EsmLoader::EsmData& esmData, std::string_view refId)
|
||||
{
|
||||
const auto it = std::lower_bound(esmData.mRefIdTypes.begin(), esmData.mRefIdTypes.end(),
|
||||
refId, EsmLoader::LessById {});
|
||||
if (it == esmData.mRefIdTypes.end() || it->mId != refId)
|
||||
return {};
|
||||
return it->mType;
|
||||
}
|
||||
|
||||
std::vector<CellRef> loadCellRefs(const ESM::Cell& cell, const EsmLoader::EsmData& esmData,
|
||||
std::vector<ESM::ESMReader>& readers)
|
||||
{
|
||||
std::vector<EsmLoader::Record<CellRef>> cellRefs;
|
||||
|
||||
for (std::size_t i = 0; i < cell.mContextList.size(); i++)
|
||||
{
|
||||
ESM::ESMReader& reader = readers[static_cast<std::size_t>(cell.mContextList[i].index)];
|
||||
cell.restore(reader, static_cast<int>(i));
|
||||
ESM::CellRef cellRef;
|
||||
bool deleted = false;
|
||||
while (ESM::Cell::getNextRef(reader, cellRef, deleted))
|
||||
{
|
||||
Misc::StringUtils::lowerCaseInPlace(cellRef.mRefID);
|
||||
const ESM::RecNameInts type = getType(esmData, cellRef.mRefID);
|
||||
if (type == ESM::RecNameInts {})
|
||||
continue;
|
||||
cellRefs.emplace_back(deleted, type, cellRef.mRefNum, std::move(cellRef.mRefID),
|
||||
cellRef.mScale, cellRef.mPos);
|
||||
}
|
||||
}
|
||||
|
||||
Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs";
|
||||
|
||||
const auto getKey = [] (const EsmLoader::Record<CellRef>& v) -> const ESM::RefNum& { return v.mValue.mRefNum; };
|
||||
std::vector<CellRef> result = prepareRecords(cellRefs, getKey);
|
||||
|
||||
Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void forEachObject(const ESM::Cell& cell, const EsmLoader::EsmData& esmData, const VFS::Manager& vfs,
|
||||
Resource::BulletShapeManager& bulletShapeManager, std::vector<ESM::ESMReader>& readers,
|
||||
F&& f)
|
||||
{
|
||||
std::vector<CellRef> cellRefs = loadCellRefs(cell, esmData, readers);
|
||||
|
||||
Log(Debug::Debug) << "Prepared " << cellRefs.size() << " unique cell refs";
|
||||
|
||||
for (CellRef& cellRef : cellRefs)
|
||||
{
|
||||
std::string model(getModel(esmData, cellRef.mRefId, cellRef.mType));
|
||||
if (model.empty())
|
||||
continue;
|
||||
|
||||
if (cellRef.mType != ESM::REC_STAT)
|
||||
model = Misc::ResourceHelpers::correctActorModelPath(model, &vfs);
|
||||
|
||||
osg::ref_ptr<const Resource::BulletShape> shape = [&]
|
||||
{
|
||||
try
|
||||
{
|
||||
return bulletShapeManager.getShape("meshes/" + model);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to load cell ref \"" << cellRef.mRefId << "\" model \"" << model << "\": " << e.what();
|
||||
return osg::ref_ptr<const Resource::BulletShape>();
|
||||
}
|
||||
} ();
|
||||
|
||||
if (shape == nullptr)
|
||||
continue;
|
||||
|
||||
switch (cellRef.mType)
|
||||
{
|
||||
case ESM::REC_ACTI:
|
||||
case ESM::REC_CONT:
|
||||
case ESM::REC_DOOR:
|
||||
case ESM::REC_STAT:
|
||||
f(BulletObject {std::move(shape), cellRef.mPos, cellRef.mScale});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void forEachBulletObject(std::vector<ESM::ESMReader>& readers, const VFS::Manager& vfs,
|
||||
Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
|
||||
std::function<void (const ESM::Cell& cell, const BulletObject& object)> callback)
|
||||
{
|
||||
Log(Debug::Info) << "Processing " << esmData.mCells.size() << " cells...";
|
||||
|
||||
for (std::size_t i = 0; i < esmData.mCells.size(); ++i)
|
||||
{
|
||||
const ESM::Cell& cell = esmData.mCells[i];
|
||||
const bool exterior = cell.isExterior();
|
||||
|
||||
Log(Debug::Debug) << "Processing " << (exterior ? "exterior" : "interior")
|
||||
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") \"" << cell.getDescription() << "\"";
|
||||
|
||||
std::size_t objects = 0;
|
||||
|
||||
forEachObject(cell, esmData, vfs, bulletShapeManager, readers,
|
||||
[&] (const BulletObject& object)
|
||||
{
|
||||
callback(cell, object);
|
||||
++objects;
|
||||
});
|
||||
|
||||
Log(Debug::Info) << "Processed " << (exterior ? "exterior" : "interior")
|
||||
<< " cell (" << (i + 1) << "/" << esmData.mCells.size() << ") " << cell.getDescription()
|
||||
<< " with " << objects << " objects";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
#ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H
|
||||
#define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H
|
||||
|
||||
#include <components/esm/defs.hpp>
|
||||
#include <components/misc/convert.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class ESMReader;
|
||||
struct Cell;
|
||||
}
|
||||
|
||||
namespace VFS
|
||||
{
|
||||
class Manager;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class BulletShapeManager;
|
||||
}
|
||||
|
||||
namespace EsmLoader
|
||||
{
|
||||
struct EsmData;
|
||||
}
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
struct BulletObject
|
||||
{
|
||||
osg::ref_ptr<const Resource::BulletShape> mShape;
|
||||
ESM::Position mPosition;
|
||||
float mScale;
|
||||
};
|
||||
|
||||
void forEachBulletObject(std::vector<ESM::ESMReader>& readers, const VFS::Manager& vfs,
|
||||
Resource::BulletShapeManager& bulletShapeManager, const EsmLoader::EsmData& esmData,
|
||||
std::function<void (const ESM::Cell&, const BulletObject& object)> callback);
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue