metaforce/hecl/lib/Blender/Connection.cpp

1753 lines
56 KiB
C++

#include <algorithm>
#include <cerrno>
#include <cfloat>
#include <chrono>
#include <cinttypes>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <system_error>
#include <thread>
#include <tuple>
#include "hecl/Blender/Connection.hpp"
#include "hecl/Blender/Token.hpp"
#include "hecl/Database.hpp"
#include "hecl/hecl.hpp"
#include "hecl/SteamFinder.hpp"
#include "MeshOptimizer.hpp"
#include <athena/MemoryWriter.hpp>
#include <logvisor/logvisor.hpp>
#if _WIN32
#include <io.h>
#include <fcntl.h>
#else
#include <sys/wait.h>
#endif
#undef min
#undef max
namespace std {
template <>
struct hash<std::pair<uint32_t, uint32_t>> {
std::size_t operator()(const std::pair<uint32_t, uint32_t>& val) const noexcept {
/* this will potentially truncate the second value if 32-bit size_t,
* however, its application here is intended to operate in 16-bit indices */
return val.first | (val.second << 16);
}
};
} // namespace std
using namespace std::literals;
namespace hecl::blender {
static const uint32_t MinBlenderMajorSearch = 2;
static const uint32_t MaxBlenderMajorSearch = 2;
static const uint32_t MinBlenderMinorSearch = 83;
static const uint32_t MaxBlenderMinorSearch = 92;
logvisor::Module BlenderLog("hecl::blender::Connection");
Token SharedBlenderToken;
#ifdef __APPLE__
#define DEFAULT_BLENDER_BIN "/Applications/Blender.app/Contents/MacOS/blender"
#else
#define DEFAULT_BLENDER_BIN "blender"
#endif
extern "C" uint8_t HECL_BLENDERSHELL[];
extern "C" size_t HECL_BLENDERSHELL_SZ;
extern "C" uint8_t HECL_ADDON[];
extern "C" size_t HECL_ADDON_SZ;
extern "C" uint8_t HECL_STARTUP[];
extern "C" size_t HECL_STARTUP_SZ;
static void InstallBlendershell(const SystemChar* path) {
auto fp = hecl::FopenUnique(path, _SYS_STR("w"));
if (fp == nullptr) {
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to open {} for writing")), path);
}
std::fwrite(HECL_BLENDERSHELL, 1, HECL_BLENDERSHELL_SZ, fp.get());
}
static void InstallAddon(const SystemChar* path) {
auto fp = hecl::FopenUnique(path, _SYS_STR("wb"));
if (fp == nullptr) {
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to install blender addon at '{}'")), path);
}
std::fwrite(HECL_ADDON, 1, HECL_ADDON_SZ, fp.get());
}
static int Read(int fd, void* buf, std::size_t size) {
int intrCount = 0;
do {
auto ret = read(fd, buf, size);
if (ret < 0) {
if (errno == EINTR)
++intrCount;
else
return -1;
} else
return ret;
} while (intrCount < 1000);
return -1;
}
static int Write(int fd, const void* buf, std::size_t size) {
int intrCount = 0;
do {
auto ret = write(fd, buf, size);
if (ret < 0) {
if (errno == EINTR)
++intrCount;
else
return -1;
} else
return ret;
} while (intrCount < 1000);
return -1;
}
static std::size_t BoundedStrLen(const char* buf, std::size_t maxLen) {
std::size_t ret;
for (ret = 0; ret < maxLen; ++ret)
if (buf[ret] == '\0')
break;
return ret;
}
uint32_t Connection::_readStr(char* buf, uint32_t bufSz) {
uint32_t readLen;
int ret = Read(m_readpipe[0], &readLen, sizeof(readLen));
if (ret < 4) {
BlenderLog.report(logvisor::Error, FMT_STRING("Pipe error {} {}"), ret, strerror(errno));
_blenderDied();
return 0;
}
if (readLen >= bufSz) {
BlenderLog.report(logvisor::Fatal, FMT_STRING("Pipe buffer overrun [{}/{}]"), readLen, bufSz);
*buf = '\0';
return 0;
}
ret = Read(m_readpipe[0], buf, readLen);
if (ret < 0) {
BlenderLog.report(logvisor::Fatal, FMT_STRING("{}"), strerror(errno));
return 0;
}
constexpr std::string_view exception_str{"EXCEPTION"};
const std::size_t readStrLen = BoundedStrLen(buf, readLen);
if (readStrLen >= exception_str.size()) {
if (exception_str.compare(0, exception_str.size(), std::string_view(buf, readStrLen)) == 0) {
_blenderDied();
return 0;
}
}
*(buf + readLen) = '\0';
return readLen;
}
uint32_t Connection::_writeStr(const char* buf, uint32_t len, int wpipe) {
const auto error = [this] {
_blenderDied();
return 0U;
};
const int nlerr = Write(wpipe, &len, 4);
if (nlerr < 4) {
return error();
}
const int ret = Write(wpipe, buf, len);
if (ret < 0) {
return error();
}
return static_cast<uint32_t>(ret);
}
std::size_t Connection::_readBuf(void* buf, std::size_t len) {
const auto error = [this] {
_blenderDied();
return 0U;
};
auto* cBuf = static_cast<uint8_t*>(buf);
std::size_t readLen = 0;
do {
const int ret = Read(m_readpipe[0], cBuf, len);
if (ret < 0) {
return error();
}
constexpr std::string_view exception_str{"EXCEPTION"};
const std::size_t readStrLen = BoundedStrLen(static_cast<char*>(buf), len);
if (readStrLen >= exception_str.size()) {
if (exception_str.compare(0, exception_str.size(), std::string_view(static_cast<char*>(buf), readStrLen)) == 0) {
_blenderDied();
}
}
readLen += ret;
cBuf += ret;
len -= ret;
} while (len != 0);
return readLen;
}
std::size_t Connection::_writeBuf(const void* buf, std::size_t len) {
const auto error = [this] {
_blenderDied();
return 0U;
};
const auto* cBuf = static_cast<const uint8_t*>(buf);
std::size_t writeLen = 0;
do {
const int ret = Write(m_writepipe[1], cBuf, len);
if (ret < 0) {
return error();
}
writeLen += ret;
cBuf += ret;
len -= ret;
} while (len != 0);
return writeLen;
}
ProjectPath Connection::_readPath() {
std::string path = _readStdString();
if (!path.empty()) {
SystemStringConv pathAbs(path);
SystemString meshPathRel =
getBlendPath().getProject().getProjectRootPath().getProjectRelativeFromAbsolute(pathAbs.sys_str());
return ProjectPath(getBlendPath().getProject().getProjectWorkingPath(), meshPathRel);
}
return {};
}
void Connection::_closePipe() {
close(m_readpipe[0]);
close(m_writepipe[1]);
#ifdef _WIN32
CloseHandle(m_pinfo.hProcess);
CloseHandle(m_pinfo.hThread);
m_consoleThreadRunning = false;
if (m_consoleThread.joinable())
m_consoleThread.join();
#endif
}
void Connection::_blenderDied() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto errFp = hecl::FopenUnique(m_errPath.c_str(), _SYS_STR("r"));
if (errFp != nullptr) {
std::fseek(errFp.get(), 0, SEEK_END);
const int64_t len = hecl::FTell(errFp.get());
if (len != 0) {
std::fseek(errFp.get(), 0, SEEK_SET);
const auto buf = std::make_unique<char[]>(len + 1);
std::fread(buf.get(), 1, len, errFp.get());
BlenderLog.report(logvisor::Fatal, FMT_STRING("\n{:.{}s}"), buf.get(), len);
}
}
BlenderLog.report(logvisor::Fatal, FMT_STRING("Blender Exception"));
}
static std::atomic_bool BlenderFirstInit(false);
#if _WIN32
static bool RegFileExists(const hecl::SystemChar* path) {
if (!path)
return false;
hecl::Sstat theStat;
return !hecl::Stat(path, &theStat) && S_ISREG(theStat.st_mode);
}
#endif
Connection::Connection(int verbosityLevel) {
#if !WINDOWS_STORE
if (hecl::VerbosityLevel >= 1)
BlenderLog.report(logvisor::Info, FMT_STRING("Establishing BlenderConnection..."));
/* Put hecl_blendershell.py in temp dir */
const SystemChar* TMPDIR = GetTmpDir();
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
hecl::SystemString blenderShellPath(TMPDIR);
blenderShellPath += _SYS_STR("/hecl_blendershell.py");
hecl::SystemString blenderAddonPath(TMPDIR);
blenderAddonPath += _SYS_STR("/hecl_blenderaddon.zip");
bool FalseCmp = false;
if (BlenderFirstInit.compare_exchange_strong(FalseCmp, true)) {
InstallBlendershell(blenderShellPath.c_str());
InstallAddon(blenderAddonPath.c_str());
}
int installAttempt = 0;
while (true) {
/* Construct communication pipes */
#if _WIN32
_pipe(m_readpipe.data(), 2048, _O_BINARY);
_pipe(m_writepipe.data(), 2048, _O_BINARY);
HANDLE writehandle = HANDLE(_get_osfhandle(m_writepipe[0]));
SetHandleInformation(writehandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
HANDLE readhandle = HANDLE(_get_osfhandle(m_readpipe[1]));
SetHandleInformation(readhandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
SECURITY_ATTRIBUTES sattrs = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE consoleOutReadTmp, consoleOutWrite, consoleErrWrite, consoleOutRead;
if (!CreatePipe(&consoleOutReadTmp, &consoleOutWrite, &sattrs, 1024))
BlenderLog.report(logvisor::Fatal, FMT_STRING("Error with CreatePipe"));
if (!DuplicateHandle(GetCurrentProcess(), consoleOutWrite, GetCurrentProcess(), &consoleErrWrite, 0, TRUE,
DUPLICATE_SAME_ACCESS))
BlenderLog.report(logvisor::Fatal, FMT_STRING("Error with DuplicateHandle"));
if (!DuplicateHandle(GetCurrentProcess(), consoleOutReadTmp, GetCurrentProcess(),
&consoleOutRead, // Address of new handle.
0, FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
BlenderLog.report(logvisor::Fatal, FMT_STRING("Error with DupliateHandle"));
if (!CloseHandle(consoleOutReadTmp))
BlenderLog.report(logvisor::Fatal, FMT_STRING("Error with CloseHandle"));
#else
pipe(m_readpipe.data());
pipe(m_writepipe.data());
#endif
/* User-specified blender path */
#if _WIN32
wchar_t BLENDER_BIN_BUF[2048];
std::wstring blenderBinBuf;
const wchar_t* blenderBin = _wgetenv(L"BLENDER_BIN");
#else
const char* blenderBin = getenv("BLENDER_BIN");
#endif
/* Steam blender */
hecl::SystemString steamBlender;
/* Child process of blender */
#if _WIN32
if (!blenderBin || !RegFileExists(blenderBin)) {
/* Environment not set; try steam */
steamBlender = hecl::FindCommonSteamApp(_SYS_STR("Blender"));
if (steamBlender.size()) {
steamBlender += _SYS_STR("\\blender.exe");
blenderBin = steamBlender.c_str();
}
if (!RegFileExists(blenderBin)) {
/* No steam; try default */
wchar_t progFiles[256];
if (GetEnvironmentVariableW(L"ProgramFiles", progFiles, 256)) {
for (size_t major = MaxBlenderMajorSearch; major >= MinBlenderMajorSearch; --major) {
bool found = false;
for (size_t minor = MaxBlenderMinorSearch; minor >= MinBlenderMinorSearch; --minor) {
_snwprintf(BLENDER_BIN_BUF, 2048, L"%s\\Blender Foundation\\Blender %i.%i\\blender.exe", progFiles, major,
minor);
if (RegFileExists(BLENDER_BIN_BUF)) {
blenderBin = BLENDER_BIN_BUF;
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
}
std::wstring cmdLine = fmt::format(FMT_STRING(L" --background -P \"{}\" -- {} {} {} \"{}\""), blenderShellPath,
uintptr_t(writehandle), uintptr_t(readhandle), verbosityLevel, blenderAddonPath);
STARTUPINFO sinfo = {sizeof(STARTUPINFO)};
HANDLE nulHandle = CreateFileW(L"nul", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sattrs, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
sinfo.dwFlags = STARTF_USESTDHANDLES;
sinfo.hStdInput = nulHandle;
if (verbosityLevel == 0) {
sinfo.hStdError = nulHandle;
sinfo.hStdOutput = nulHandle;
} else {
sinfo.hStdError = consoleErrWrite;
sinfo.hStdOutput = consoleOutWrite;
}
if (!CreateProcessW(blenderBin, cmdLine.data(), nullptr, nullptr, TRUE, NORMAL_PRIORITY_CLASS, nullptr, nullptr,
&sinfo, &m_pinfo)) {
LPWSTR messageBuffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0,
nullptr);
BlenderLog.report(logvisor::Fatal, FMT_STRING(L"unable to launch blender from {}: {}"), blenderBin,
messageBuffer);
}
close(m_writepipe[0]);
close(m_readpipe[1]);
CloseHandle(nulHandle);
CloseHandle(consoleErrWrite);
CloseHandle(consoleOutWrite);
m_consoleThreadRunning = true;
m_consoleThread = std::thread([=]() {
CHAR lpBuffer[1024];
DWORD nBytesRead;
DWORD nCharsWritten;
while (m_consoleThreadRunning) {
if (!ReadFile(consoleOutRead, lpBuffer, sizeof(lpBuffer), &nBytesRead, nullptr) || !nBytesRead) {
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE)
break; // pipe done - normal exit path.
else
BlenderLog.report(logvisor::Error, FMT_STRING("Error with ReadFile: {:08X}"),
err); // Something bad happened.
}
// Display the character read on the screen.
auto lk = logvisor::LockLog();
if (!WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, nBytesRead, &nCharsWritten, nullptr)) {
// BlenderLog.report(logvisor::Error, FMT_STRING("Error with WriteConsole: %08X"), GetLastError());
}
}
CloseHandle(consoleOutRead);
});
#else
pid_t pid = fork();
if (!pid) {
/* Close all file descriptors besides those this blender instance uses */
int upper_fd = std::max(m_writepipe[0], m_readpipe[1]);
for (int i = 3; i < upper_fd; ++i) {
if (i != m_writepipe[0] && i != m_readpipe[1])
close(i);
}
closefrom(upper_fd + 1);
if (verbosityLevel == 0) {
int devNull = open("/dev/null", O_WRONLY);
dup2(devNull, STDOUT_FILENO);
dup2(devNull, STDERR_FILENO);
close(devNull);
}
std::string errbuf;
std::string readfds = fmt::format(FMT_STRING("{}"), m_writepipe[0]);
std::string writefds = fmt::format(FMT_STRING("{}"), m_readpipe[1]);
std::string vLevel = fmt::format(FMT_STRING("{}"), verbosityLevel);
/* Try user-specified blender first */
if (blenderBin) {
execlp(blenderBin, blenderBin, "--background", "-P", blenderShellPath.c_str(), "--", readfds.c_str(),
writefds.c_str(), vLevel.c_str(), blenderAddonPath.c_str(), nullptr);
if (errno != ENOENT) {
errbuf = fmt::format(FMT_STRING("NOLAUNCH {}"), strerror(errno));
_writeStr(errbuf.c_str(), errbuf.size(), m_readpipe[1]);
exit(1);
}
}
/* Try steam */
steamBlender = hecl::FindCommonSteamApp(_SYS_STR("Blender"));
if (steamBlender.size()) {
#ifdef __APPLE__
steamBlender += "/blender.app/Contents/MacOS/blender";
#else
steamBlender += "/blender";
#endif
blenderBin = steamBlender.c_str();
execlp(blenderBin, blenderBin, "--background", "-P", blenderShellPath.c_str(), "--", readfds.c_str(),
writefds.c_str(), vLevel.c_str(), blenderAddonPath.c_str(), nullptr);
if (errno != ENOENT) {
errbuf = fmt::format(FMT_STRING("NOLAUNCH {}"), strerror(errno));
_writeStr(errbuf.c_str(), errbuf.size(), m_readpipe[1]);
exit(1);
}
}
/* Otherwise default blender */
execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, "--background", "-P", blenderShellPath.c_str(), "--",
readfds.c_str(), writefds.c_str(), vLevel.c_str(), blenderAddonPath.c_str(), nullptr);
if (errno != ENOENT) {
errbuf = fmt::format(FMT_STRING("NOLAUNCH {}"), strerror(errno));
_writeStr(errbuf.c_str(), errbuf.size(), m_readpipe[1]);
exit(1);
}
/* Unable to find blender */
_writeStr("NOBLENDER", 9, m_readpipe[1]);
exit(1);
}
close(m_writepipe[0]);
close(m_readpipe[1]);
m_blenderProc = pid;
#endif
/* Stash error path and unlink existing file */
#if _WIN32
m_errPath = hecl::SystemString(TMPDIR) +
fmt::format(FMT_STRING(_SYS_STR("/hecl_{:016X}.derp")), (unsigned long long)m_pinfo.dwProcessId);
#else
m_errPath = hecl::SystemString(TMPDIR) +
fmt::format(FMT_STRING(_SYS_STR("/hecl_{:016X}.derp")), (unsigned long long)m_blenderProc);
#endif
hecl::Unlink(m_errPath.c_str());
/* Handle first response */
std::string lineStr = _readStdString();
if (!lineStr.compare(0, 8, "NOLAUNCH")) {
_closePipe();
BlenderLog.report(logvisor::Fatal, FMT_STRING("Unable to launch blender: {}"), lineStr.c_str() + 9);
} else if (!lineStr.compare(0, 9, "NOBLENDER")) {
_closePipe();
#if _WIN32
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to find blender at '{}'")), blenderBin);
#else
if (blenderBin)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to find blender at '{}' or '{}'")), blenderBin,
DEFAULT_BLENDER_BIN);
else
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Unable to find blender at '{}'")), DEFAULT_BLENDER_BIN);
#endif
} else if (lineStr == "INVALIDBLENDERVER") {
_closePipe();
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("Installed blender version must be >= {}.{}")),
MinBlenderMajorSearch, MinBlenderMinorSearch);
} else if (lineStr == "NOADDON") {
_closePipe();
if (blenderAddonPath != _SYS_STR("SKIPINSTALL"))
InstallAddon(blenderAddonPath.c_str());
++installAttempt;
if (installAttempt >= 2)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to install blender addon using '{}'")),
blenderAddonPath.c_str());
#ifndef _WIN32
waitpid(pid, nullptr, 0);
#endif
continue;
} else if (lineStr == "ADDONINSTALLED") {
_closePipe();
blenderAddonPath = _SYS_STR("SKIPINSTALL");
#ifndef _WIN32
waitpid(pid, nullptr, 0);
#endif
continue;
} else if (lineStr != "READY") {
_closePipe();
BlenderLog.report(logvisor::Fatal, FMT_STRING("read '{}' from blender; expected 'READY'"), lineStr);
}
_writeStr("ACK");
break;
}
#else
BlenderLog.report(logvisor::Fatal, FMT_STRING("BlenderConnection not available on UWP"));
#endif
}
Connection::~Connection() { _closePipe(); }
void Vector2f::read(Connection& conn) { conn._readBuf(&val, 8); }
void Vector3f::read(Connection& conn) { conn._readBuf(&val, 12); }
void Vector4f::read(Connection& conn) { conn._readBuf(&val, 16); }
void Matrix4f::read(Connection& conn) { conn._readBuf(&val, 64); }
void Index::read(Connection& conn) { conn._readBuf(&val, 4); }
void Float::read(Connection& conn) { conn._readBuf(&val, 4); }
void Boolean::read(Connection& conn) { conn._readBuf(&val, 1); }
bool PyOutStream::StreamBuf::sendLine(std::string_view line) {
m_parent.m_parent->_writeStr(line);
if (!m_parent.m_parent->_isOk()) {
if (m_deleteOnError)
m_parent.m_parent->deleteBlend();
m_parent.m_parent->_blenderDied();
return false;
}
return true;
}
PyOutStream::StreamBuf::int_type PyOutStream::StreamBuf::overflow(int_type ch) {
if (!m_parent.m_parent || !m_parent.m_parent->m_lock)
BlenderLog.report(logvisor::Fatal, FMT_STRING("lock not held for PyOutStream writing"));
if (ch != traits_type::eof() && ch != '\n' && ch != '\0') {
m_lineBuf += char_type(ch);
return ch;
}
sendLine(m_lineBuf);
m_lineBuf.clear();
return ch;
}
std::streamsize PyOutStream::StreamBuf::xsputn(const char_type* __first, std::streamsize __n) {
if (!m_parent.m_parent || !m_parent.m_parent->m_lock)
BlenderLog.report(logvisor::Fatal, FMT_STRING("lock not held for PyOutStream writing"));
const char_type* __last = __first + __n;
const char_type* __s = __first;
for (const char_type* __e = __first; __e != __last; ++__e) {
if (*__e == '\n' || traits_type::to_int_type(*__e) == traits_type::eof()) {
std::string_view line(__s, __e - __s);
bool result;
if (!m_lineBuf.empty()) {
/* Complete line with incomplete line from previous call */
m_lineBuf += line;
result = sendLine(m_lineBuf);
m_lineBuf.clear();
} else {
/* Complete line (optimal case) */
result = sendLine(line);
}
if (!result || traits_type::to_int_type(*__e) == traits_type::eof())
return __e - __first; /* Error or eof, end now */
__s += line.size() + 1;
}
}
if (__s != __last) /* String ended with incomplete line (ideally this shouldn't happen for zero buffer overhead) */
m_lineBuf += std::string_view(__s, __last - __s);
return __n;
}
constexpr std::array<std::string_view, 12> BlendTypeStrs{"NONE"sv, "MESH"sv, "CMESH"sv, "ARMATURE"sv,
"ACTOR"sv, "AREA"sv, "WORLD"sv, "MAPAREA"sv,
"MAPUNIVERSE"sv, "FRAME"sv, "PATH"sv};
bool Connection::createBlend(const ProjectPath& path, BlendType type) {
if (m_lock) {
BlenderLog.report(logvisor::Fatal,
FMT_STRING("BlenderConnection::createBlend() musn't be called with stream active"));
return false;
}
_writeStr(fmt::format(FMT_STRING("CREATE \"{}\" {}"), path.getAbsolutePathUTF8(), BlendTypeStrs[int(type)]));
if (_isFinished()) {
/* Delete immediately in case save doesn't occur */
hecl::Unlink(path.getAbsolutePath().data());
m_loadedBlend = path;
m_loadedType = type;
return true;
}
return false;
}
bool Connection::openBlend(const ProjectPath& path, bool force) {
if (m_lock) {
BlenderLog.report(logvisor::Fatal,
FMT_STRING("BlenderConnection::openBlend() musn't be called with stream active"));
return false;
}
if (!force && path == m_loadedBlend)
return true;
_writeStr(fmt::format(FMT_STRING("OPEN \"{}\""), path.getAbsolutePathUTF8()));
if (_isFinished()) {
m_loadedBlend = path;
_writeStr("GETTYPE");
std::string typeStr = _readStdString();
m_loadedType = BlendType::None;
unsigned idx = 0;
for (const auto& type : BlendTypeStrs) {
if (type == typeStr) {
m_loadedType = BlendType(idx);
break;
}
++idx;
}
m_loadedRigged = false;
if (m_loadedType == BlendType::Mesh) {
_writeStr("GETMESHRIGGED");
if (_isTrue())
m_loadedRigged = true;
}
return true;
}
return false;
}
bool Connection::saveBlend() {
if (m_lock) {
BlenderLog.report(logvisor::Fatal,
FMT_STRING("BlenderConnection::saveBlend() musn't be called with stream active"));
return false;
}
_writeStr("SAVE");
return _isFinished();
}
void Connection::deleteBlend() {
if (m_loadedBlend) {
hecl::Unlink(m_loadedBlend.getAbsolutePath().data());
BlenderLog.report(logvisor::Info, FMT_STRING(_SYS_STR("Deleted '{}'")), m_loadedBlend.getAbsolutePath());
m_loadedBlend = ProjectPath();
}
}
PyOutStream::PyOutStream(Connection* parent, bool deleteOnError)
: std::ostream(&m_sbuf), m_parent(parent), m_sbuf(*this, deleteOnError) {
m_parent->m_pyStreamActive = true;
m_parent->_writeStr("PYBEGIN");
m_parent->_checkReady("unable to open PyOutStream with blender"sv);
}
void PyOutStream::close() {
if (m_parent && m_parent->m_lock) {
m_parent->_writeStr("PYEND");
m_parent->_checkDone("unable to close PyOutStream with blender"sv);
m_parent->m_pyStreamActive = false;
m_parent->m_lock = false;
}
}
void PyOutStream::linkBlend(std::string_view target, std::string_view objName, bool link) {
format(
FMT_STRING("if '{}' not in bpy.data.scenes:\n"
" with bpy.data.libraries.load('''{}''', link={}, relative=True) as (data_from, data_to):\n"
" data_to.scenes = data_from.scenes\n"
" obj_scene = None\n"
" for scene in data_to.scenes:\n"
" if scene.name == '{}':\n"
" obj_scene = scene\n"
" break\n"
" if not obj_scene:\n"
" raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n"
" obj = None\n"
" for object in obj_scene.objects:\n"
" if object.name == obj_scene.name:\n"
" obj = object\n"
"else:\n"
" obj = bpy.data.objects['{}']\n"
"\n"),
objName, target, link ? "True" : "False", objName, objName, target, objName);
}
void PyOutStream::linkArmature(std::string_view target, std::string_view armName) {
format(FMT_STRING(
"target_arm_name = '{}'\n"
"if target_arm_name not in bpy.data.armatures:\n"
" with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n"
" if target_arm_name not in data_from.armatures:\n"
" raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n"
" data_to.armatures.append(target_arm_name)\n"
" obj = bpy.data.objects.new(target_arm_name, bpy.data.armatures[target_arm_name])\n"
"else:\n"
" obj = bpy.data.objects[target_arm_name]\n"
"\n"),
armName, target, armName, target);
}
void PyOutStream::linkMesh(std::string_view target, std::string_view meshName) {
format(FMT_STRING(
"target_mesh_name = '{}'\n"
"if target_mesh_name not in bpy.data.objects:\n"
" with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n"
" if target_mesh_name not in data_from.objects:\n"
" raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n"
" data_to.objects.append(target_mesh_name)\n"
"obj = bpy.data.objects[target_mesh_name]\n"
"\n"),
meshName, target, meshName, target);
}
void PyOutStream::linkBackground(std::string_view target, std::string_view sceneName) {
if (sceneName.empty()) {
format(FMT_STRING("with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n"
" data_to.scenes = data_from.scenes\n"
"obj_scene = None\n"
"for scene in data_to.scenes:\n"
" obj_scene = scene\n"
" break\n"
"if not obj_scene:\n"
" raise RuntimeError('''unable to find {}. try deleting it and restart the extract.''')\n"
"\n"
"bpy.context.scene.background_set = obj_scene\n"),
target, target);
} else {
format(FMT_STRING(
"if '{}' not in bpy.data.scenes:\n"
" with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n"
" data_to.scenes = data_from.scenes\n"
" obj_scene = None\n"
" for scene in data_to.scenes:\n"
" if scene.name == '{}':\n"
" obj_scene = scene\n"
" break\n"
" if not obj_scene:\n"
" raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n"
"\n"
"bpy.context.scene.background_set = bpy.data.scenes['{}']\n"),
sceneName, target, sceneName, sceneName, target, sceneName);
}
}
void PyOutStream::AABBToBMesh(const atVec3f& min, const atVec3f& max) {
athena::simd_floats minf(min.simd);
athena::simd_floats maxf(max.simd);
format(FMT_STRING("bm = bmesh.new()\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.new(({},{},{}))\n"
"bm.verts.ensure_lookup_table()\n"
"bm.edges.new((bm.verts[0], bm.verts[1]))\n"
"bm.edges.new((bm.verts[0], bm.verts[2]))\n"
"bm.edges.new((bm.verts[0], bm.verts[4]))\n"
"bm.edges.new((bm.verts[3], bm.verts[1]))\n"
"bm.edges.new((bm.verts[3], bm.verts[2]))\n"
"bm.edges.new((bm.verts[3], bm.verts[7]))\n"
"bm.edges.new((bm.verts[5], bm.verts[1]))\n"
"bm.edges.new((bm.verts[5], bm.verts[4]))\n"
"bm.edges.new((bm.verts[5], bm.verts[7]))\n"
"bm.edges.new((bm.verts[6], bm.verts[2]))\n"
"bm.edges.new((bm.verts[6], bm.verts[4]))\n"
"bm.edges.new((bm.verts[6], bm.verts[7]))\n"),
minf[0], minf[1], minf[2], maxf[0], minf[1], minf[2], minf[0], maxf[1], minf[2], maxf[0], maxf[1], minf[2],
minf[0], minf[1], maxf[2], maxf[0], minf[1], maxf[2], minf[0], maxf[1], maxf[2], maxf[0], maxf[1], maxf[2]);
}
void PyOutStream::centerView() {
*this << "for obj in bpy.context.scene.objects:\n"
" if obj.type == 'CAMERA' or obj.type == 'LIGHT':\n"
" obj.hide_set(True)\n"
"\n"
"old_smooth_view = bpy.context.preferences.view.smooth_view\n"
"bpy.context.preferences.view.smooth_view = 0\n"
"for window in bpy.context.window_manager.windows:\n"
" screen = window.screen\n"
" for area in screen.areas:\n"
" if area.type == 'VIEW_3D':\n"
" for region in area.regions:\n"
" if region.type == 'WINDOW':\n"
" override = {'scene': bpy.context.scene, 'window': window, 'screen': screen, 'area': "
"area, 'region': region}\n"
" bpy.ops.view3d.view_all(override)\n"
" break\n"
"bpy.context.preferences.view.smooth_view = old_smooth_view\n"
"\n"
"for obj in bpy.context.scene.objects:\n"
" if obj.type == 'CAMERA' or obj.type == 'LIGHT':\n"
" obj.hide_set(True)\n";
}
ANIMOutStream::ANIMOutStream(Connection* parent) : m_parent(parent) {
m_parent->_writeStr("PYANIM");
m_parent->_checkAnimReady("unable to open ANIMOutStream"sv);
}
ANIMOutStream::~ANIMOutStream() {
char tp = -1;
m_parent->_writeBuf(&tp, 1);
m_parent->_checkAnimDone("unable to close ANIMOutStream"sv);
}
void ANIMOutStream::changeCurve(CurveType type, unsigned crvIdx, unsigned keyCount) {
if (m_curCount != m_totalCount)
BlenderLog.report(logvisor::Fatal, FMT_STRING("incomplete ANIMOutStream for change"));
m_curCount = 0;
m_totalCount = keyCount;
char tp = char(type);
m_parent->_writeBuf(&tp, 1);
struct {
uint32_t ci;
uint32_t kc;
} info = {uint32_t(crvIdx), uint32_t(keyCount)};
m_parent->_writeBuf(reinterpret_cast<const char*>(&info), 8);
m_inCurve = true;
}
void ANIMOutStream::write(unsigned frame, float val) {
if (!m_inCurve)
BlenderLog.report(logvisor::Fatal, FMT_STRING("changeCurve not called before write"));
if (m_curCount < m_totalCount) {
struct {
uint32_t frm;
float val;
} key = {uint32_t(frame), val};
m_parent->_writeBuf(reinterpret_cast<const char*>(&key), 8);
++m_curCount;
} else
BlenderLog.report(logvisor::Fatal, FMT_STRING("ANIMOutStream keyCount overflow"));
}
Mesh::SkinBind::SkinBind(Connection& conn) {
conn._readValue(vg_idx);
conn._readValue(weight);
}
void Mesh::normalizeSkinBinds() {
for (auto& skin : skins) {
float accum = 0.f;
for (const SkinBind& bind : skin)
if (bind.valid())
accum += bind.weight;
if (accum > FLT_EPSILON) {
for (SkinBind& bind : skin)
if (bind.valid())
bind.weight /= accum;
}
}
}
Mesh::Mesh(Connection& conn, HMDLTopology topologyIn, int skinSlotCount, bool useLuvs)
: topology(topologyIn), sceneXf(conn), aabbMin(conn), aabbMax(conn) {
conn._readVectorFunc(materialSets, [&]() { conn._readVector(materialSets.emplace_back()); });
MeshOptimizer opt(conn, materialSets[0], useLuvs);
opt.optimize(*this, skinSlotCount);
conn._readVector(boneNames);
if (boneNames.size())
for (Surface& s : surfaces)
s.skinBankIdx = skinBanks.addSurface(*this, s, skinSlotCount);
/* Custom properties */
uint32_t propCount;
conn._readValue(propCount);
std::string keyBuf;
std::string valBuf;
for (uint32_t i = 0; i < propCount; ++i) {
keyBuf = conn._readStdString();
valBuf = conn._readStdString();
customProps[keyBuf] = valBuf;
}
/* Connect skinned verts to bank slots */
if (boneNames.size()) {
for (Surface& surf : surfaces) {
SkinBanks::Bank& bank = skinBanks.banks[surf.skinBankIdx];
for (Surface::Vert& vert : surf.verts) {
if (vert.iPos == 0xffffffff)
continue;
for (uint32_t i = 0; i < bank.m_skinIdxs.size(); ++i) {
if (bank.m_skinIdxs[i] == vert.iSkin) {
vert.iBankSkin = i;
break;
}
}
}
}
}
}
Mesh Mesh::getContiguousSkinningVersion() const {
Mesh newMesh = *this;
newMesh.pos.clear();
newMesh.norm.clear();
newMesh.contiguousSkinVertCounts.clear();
newMesh.contiguousSkinVertCounts.reserve(skins.size());
for (std::size_t i = 0; i < skins.size(); ++i) {
std::unordered_map<std::pair<uint32_t, uint32_t>, uint32_t> contigMap;
std::size_t vertCount = 0;
for (Surface& surf : newMesh.surfaces) {
for (Surface::Vert& vert : surf.verts) {
if (vert.iPos == 0xffffffff)
continue;
if (vert.iSkin == i) {
auto key = std::make_pair(vert.iPos, vert.iNorm);
auto search = contigMap.find(key);
if (search != contigMap.end()) {
vert.iPos = search->second;
vert.iNorm = search->second;
} else {
uint32_t newIdx = newMesh.pos.size();
contigMap[key] = newIdx;
newMesh.pos.push_back(pos.at(vert.iPos));
newMesh.norm.push_back(norm.at(vert.iNorm));
vert.iPos = newIdx;
vert.iNorm = newIdx;
++vertCount;
}
}
}
}
newMesh.contiguousSkinVertCounts.push_back(vertCount);
}
return newMesh;
}
template <typename T>
static T SwapFourCC(T fcc) {
return T(hecl::SBig(std::underlying_type_t<T>(fcc)));
}
Material::PASS::PASS(Connection& conn) {
conn._readValue(type);
type = SwapFourCC(type);
tex = conn._readPath();
conn._readValue(source);
conn._readValue(uvAnimType);
uint32_t argCount;
conn._readValue(argCount);
for (uint32_t i = 0; i < argCount; ++i)
conn._readValue(uvAnimParms[i]);
conn._readValue(alpha);
}
Material::CLR::CLR(Connection& conn) {
conn._readValue(type);
type = SwapFourCC(type);
color.read(conn);
}
Material::Material(Connection& conn) {
name = conn._readStdString();
conn._readValue(passIndex);
conn._readValue(shaderType);
shaderType = SwapFourCC(shaderType);
conn._readVectorFunc(chunks, [&]() {
ChunkType type;
conn._readValue(type);
type = SwapFourCC(type);
chunks.push_back(Chunk::Build(type, conn));
});
uint32_t iPropCount;
conn._readValue(iPropCount);
iprops.reserve(iPropCount);
for (uint32_t i = 0; i < iPropCount; ++i) {
std::string readStr = conn._readStdString();
conn._readValue(iprops[readStr]);
}
conn._readValue(blendMode);
}
bool Mesh::Surface::Vert::operator==(const Vert& other) const {
return std::tie(iPos, iNorm, iColor, iUv, iSkin) ==
std::tie(other.iPos, other.iNorm, other.iColor, other.iUv, other.iSkin);
}
static bool VertInBank(const std::vector<uint32_t>& bank, uint32_t sIdx) {
return std::any_of(bank.cbegin(), bank.cend(), [sIdx](auto index) { return index == sIdx; });
}
void Mesh::SkinBanks::Bank::addSkins(const Mesh& parent, const std::vector<uint32_t>& skinIdxs) {
for (uint32_t sidx : skinIdxs) {
m_skinIdxs.push_back(sidx);
for (const SkinBind& bind : parent.skins[sidx]) {
if (!bind.valid())
break;
bool found = false;
for (uint32_t bidx : m_boneIdxs) {
if (bidx == bind.vg_idx) {
found = true;
break;
}
}
if (!found)
m_boneIdxs.push_back(bind.vg_idx);
}
}
}
std::vector<Mesh::SkinBanks::Bank>::iterator Mesh::SkinBanks::addSkinBank(int skinSlotCount) {
banks.emplace_back();
if (skinSlotCount > 0)
banks.back().m_skinIdxs.reserve(skinSlotCount);
return banks.end() - 1;
}
uint32_t Mesh::SkinBanks::addSurface(const Mesh& mesh, const Surface& surf, int skinSlotCount) {
if (banks.empty())
addSkinBank(skinSlotCount);
std::vector<uint32_t> toAdd;
if (skinSlotCount > 0)
toAdd.reserve(skinSlotCount);
std::vector<Bank>::iterator bankIt = banks.begin();
for (;;) {
bool done = true;
for (; bankIt != banks.end(); ++bankIt) {
Bank& bank = *bankIt;
done = true;
for (const Surface::Vert& v : surf.verts) {
if (v.iPos == 0xffffffff)
continue;
if (!VertInBank(bank.m_skinIdxs, v.iSkin) && !VertInBank(toAdd, v.iSkin)) {
toAdd.push_back(v.iSkin);
if (skinSlotCount > 0 && bank.m_skinIdxs.size() + toAdd.size() > std::size_t(skinSlotCount)) {
toAdd.clear();
done = false;
break;
}
}
}
if (toAdd.size()) {
bank.addSkins(mesh, toAdd);
toAdd.clear();
}
if (done)
return uint32_t(bankIt - banks.begin());
}
if (!done) {
bankIt = addSkinBank(skinSlotCount);
continue;
}
break;
}
return uint32_t(-1);
}
ColMesh::ColMesh(Connection& conn) {
conn._readVector(materials);
conn._readVector(verts);
conn._readVector(edges);
conn._readVector(trianges);
}
ColMesh::Material::Material(Connection& conn) {
name = conn._readStdString();
conn._readBuf(&unknown, 42);
}
ColMesh::Edge::Edge(Connection& conn) { conn._readBuf(this, 9); }
ColMesh::Triangle::Triangle(Connection& conn) { conn._readBuf(this, 17); }
World::Area::Dock::Dock(Connection& conn) {
verts[0].read(conn);
verts[1].read(conn);
verts[2].read(conn);
verts[3].read(conn);
targetArea.read(conn);
targetDock.read(conn);
}
World::Area::Area(Connection& conn) {
std::string name = conn._readStdString();
path.assign(conn.getBlendPath().getParentPath(), name);
aabb[0].read(conn);
aabb[1].read(conn);
transform.read(conn);
conn._readVector(docks);
}
World::World(Connection& conn) { conn._readVector(areas); }
Light::Light(Connection& conn) : sceneXf(conn), color(conn) {
conn._readBuf(&layer, 29);
name = conn._readStdString();
}
MapArea::Surface::Surface(Connection& conn) {
centerOfMass.read(conn);
normal.read(conn);
conn._readBuf(&start, 8);
conn._readVectorFunc(borders, [&]() { conn._readBuf(&borders.emplace_back(), 8); });
}
MapArea::POI::POI(Connection& conn) {
conn._readBuf(&type, 12);
xf.read(conn);
}
MapArea::MapArea(Connection& conn) {
conn._readValue(visType);
conn._readVector(verts);
uint8_t isIdx;
conn._readValue(isIdx);
while (isIdx) {
conn._readValue(indices.emplace_back());
conn._readValue(isIdx);
}
conn._readVector(surfaces);
conn._readVector(pois);
}
MapUniverse::World::World(Connection& conn) {
name = conn._readStdString();
xf.read(conn);
conn._readVector(hexagons);
color.read(conn);
std::string path = conn._readStdString();
if (!path.empty()) {
hecl::SystemStringConv sysPath(path);
worldPath.assign(conn.getBlendPath().getProject().getProjectWorkingPath(), sysPath.sys_str());
}
}
MapUniverse::MapUniverse(Connection& conn) {
hexagonPath = conn._readPath();
conn._readVector(worlds);
}
Actor::Actor(Connection& conn) {
conn._readVector(armatures);
conn._readVector(subtypes);
conn._readVector(attachments);
conn._readVector(actions);
}
PathMesh::PathMesh(Connection& conn) { conn._readVector(data); }
const Bone* Armature::lookupBone(const char* name) const {
for (const Bone& b : bones)
if (b.name == name)
return &b;
return nullptr;
}
const Bone* Armature::getParent(const Bone* bone) const {
if (bone->parent < 0)
return nullptr;
return &bones[bone->parent];
}
const Bone* Armature::getChild(const Bone* bone, std::size_t child) const {
if (child >= bone->children.size())
return nullptr;
int32_t cIdx = bone->children[child];
if (cIdx < 0)
return nullptr;
return &bones[cIdx];
}
const Bone* Armature::getRoot() const {
for (const Bone& b : bones)
if (b.parent < 0)
return &b;
return nullptr;
}
Armature::Armature(Connection& conn) { conn._readVector(bones); }
Bone::Bone(Connection& conn) {
name = conn._readStdString();
origin.read(conn);
conn._readValue(parent);
conn._readVector(children);
}
Actor::ActorArmature::ActorArmature(Connection& conn) {
name = conn._readStdString();
path = conn._readPath();
armature.emplace(conn);
}
Actor::Subtype::OverlayMesh::OverlayMesh(Connection& conn) {
name = conn._readStdString();
cskrId = conn._readStdString();
mesh = conn._readPath();
}
Actor::Subtype::Subtype(Connection& conn) {
name = conn._readStdString();
cskrId = conn._readStdString();
mesh = conn._readPath();
conn._readValue(armature);
conn._readVector(overlayMeshes);
}
Actor::Attachment::Attachment(Connection& conn) {
name = conn._readStdString();
cskrId = conn._readStdString();
mesh = conn._readPath();
conn._readValue(armature);
}
Action::Action(Connection& conn) {
name = conn._readStdString();
animId = conn._readStdString();
conn._readValue(interval);
conn._readValue(additive);
conn._readValue(looping);
conn._readVector(frames);
conn._readVector(channels);
conn._readVectorFunc(subtypeAABBs, [&]() {
auto& p = subtypeAABBs.emplace_back();
p.first.read(conn);
p.second.read(conn);
});
}
Action::Channel::Channel(Connection& conn) {
boneName = conn._readStdString();
conn._readValue(attrMask);
conn._readVector(keys, attrMask);
}
Action::Channel::Key::Key(Connection& conn, uint32_t attrMask) {
if (attrMask & 1)
rotation.read(conn);
if (attrMask & 2)
position.read(conn);
if (attrMask & 4)
scale.read(conn);
}
DataStream::DataStream(Connection* parent) : m_parent(parent) {
m_parent->m_dataStreamActive = true;
m_parent->_writeStr("DATABEGIN");
m_parent->_checkReady("unable to open DataStream with blender"sv);
}
void DataStream::close() {
if (m_parent && m_parent->m_lock) {
m_parent->_writeStr("DATAEND");
m_parent->_checkDone("unable to close DataStream with blender"sv);
m_parent->m_dataStreamActive = false;
m_parent->m_lock = false;
}
}
std::vector<std::string> DataStream::getMeshList() {
m_parent->_writeStr("MESHLIST");
std::vector<std::string> retval;
m_parent->_readVector(retval);
return retval;
}
std::vector<std::string> DataStream::getLightList() {
m_parent->_writeStr("LIGHTLIST");
std::vector<std::string> retval;
m_parent->_readVector(retval);
return retval;
}
std::pair<atVec3f, atVec3f> DataStream::getMeshAABB() {
if (m_parent->m_loadedType != BlendType::Mesh && m_parent->m_loadedType != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a MESH or ACTOR blend")),
m_parent->m_loadedBlend.getAbsolutePath());
m_parent->_writeStr("MESHAABB");
m_parent->_checkOk("unable get AABB"sv);
Vector3f minPt(*m_parent);
Vector3f maxPt(*m_parent);
return std::make_pair(minPt.val, maxPt.val);
}
const char* DataStream::MeshOutputModeString(HMDLTopology topology) {
static constexpr std::array<const char*, 2> STRS{"TRIANGLES", "TRISTRIPS"};
return STRS[int(topology)];
}
Mesh DataStream::compileMesh(HMDLTopology topology, int skinSlotCount) {
if (m_parent->getBlendType() != BlendType::Mesh)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a MESH blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("MESHCOMPILE");
m_parent->_checkOk("unable to cook mesh"sv);
return Mesh(*m_parent, topology, skinSlotCount);
}
Mesh DataStream::compileMesh(std::string_view name, HMDLTopology topology, int skinSlotCount, bool useLuv) {
if (m_parent->getBlendType() != BlendType::Area)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an AREA blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("MESHCOMPILENAME {} {}"), name, int(useLuv)));
m_parent->_checkOk("unable to cook mesh"sv);
return Mesh(*m_parent, topology, skinSlotCount, useLuv);
}
ColMesh DataStream::compileColMesh(std::string_view name) {
if (m_parent->getBlendType() != BlendType::Area)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an AREA blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("MESHCOMPILENAMECOLLISION {}"), name));
m_parent->_checkOk("unable to cook collision mesh"sv);
return ColMesh(*m_parent);
}
std::vector<ColMesh> DataStream::compileColMeshes() {
if (m_parent->getBlendType() != BlendType::ColMesh)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a CMESH blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("MESHCOMPILECOLLISIONALL");
m_parent->_checkOk("unable to cook collision meshes"sv);
std::vector<ColMesh> ret;
m_parent->_readVector(ret);
return ret;
}
std::vector<Light> DataStream::compileLights() {
if (m_parent->getBlendType() != BlendType::Area)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an AREA blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("LIGHTCOMPILEALL");
m_parent->_checkOk("unable to gather all lights"sv);
std::vector<Light> ret;
m_parent->_readVector(ret);
return ret;
}
PathMesh DataStream::compilePathMesh() {
if (m_parent->getBlendType() != BlendType::PathMesh)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a PATH blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("MESHCOMPILEPATH");
m_parent->_checkOk("unable to compile path mesh"sv);
return PathMesh(*m_parent);
}
std::vector<uint8_t> DataStream::compileGuiFrame(int version) {
if (m_parent->getBlendType() != BlendType::Frame)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a FRAME blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("FRAMECOMPILE {}"), version));
m_parent->_checkOk("unable to compile frame"sv);
while (true) {
std::string readStr = m_parent->_readStdString();
if (readStr == "FRAMEDONE")
break;
SystemStringConv absolute(readStr);
auto& proj = m_parent->getBlendPath().getProject();
SystemString relative;
if (PathRelative(absolute.c_str()))
relative = absolute.sys_str();
else
relative = proj.getProjectRootPath().getProjectRelativeFromAbsolute(absolute.sys_str());
hecl::ProjectPath path(proj.getProjectWorkingPath(), relative);
m_parent->_writeStr(fmt::format(FMT_STRING("{:08X}"), path.parsedHash32()));
}
std::vector<uint8_t> ret;
m_parent->_readVector(ret);
return ret;
}
std::vector<ProjectPath> DataStream::getTextures() {
m_parent->_writeStr("GETTEXTURES");
m_parent->_checkOk("unable to get textures"sv);
std::vector<ProjectPath> texs;
m_parent->_readVectorFunc(texs, [&]() { texs.push_back(m_parent->_readPath()); });
return texs;
}
Actor DataStream::compileActor() {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("ACTORCOMPILE");
m_parent->_checkOk("unable to compile actor"sv);
return Actor(*m_parent);
}
Actor DataStream::compileActorCharacterOnly() {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("ACTORCOMPILECHARACTERONLY");
m_parent->_checkOk("unable to compile actor"sv);
return Actor(*m_parent);
}
Armature DataStream::compileArmature() {
if (m_parent->getBlendType() != BlendType::Armature)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ARMATURE blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("ARMATURECOMPILE");
m_parent->_checkOk("unable to compile armature"sv);
return Armature(*m_parent);
}
Action DataStream::compileActionChannelsOnly(std::string_view name) {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("ACTIONCOMPILECHANNELSONLY {}"), name));
m_parent->_checkOk("unable to compile action"sv);
return Action(*m_parent);
}
World DataStream::compileWorld() {
if (m_parent->getBlendType() != BlendType::World)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an WORLD blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("WORLDCOMPILE");
m_parent->_checkOk("unable to compile world"sv);
return World(*m_parent);
}
std::vector<std::pair<std::string, std::string>> DataStream::getSubtypeNames() {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("GETSUBTYPENAMES");
m_parent->_checkOk("unable to get subtypes of actor"sv);
std::vector<std::pair<std::string, std::string>> ret;
m_parent->_readVectorFunc(ret, [&]() {
auto& [name, cskrId] = ret.emplace_back();
name = m_parent->_readStdString();
cskrId = m_parent->_readStdString();
});
return ret;
}
std::vector<std::pair<std::string, std::string>> DataStream::getActionNames() {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("GETACTIONNAMES");
m_parent->_checkOk("unable to get actions of actor"sv);
std::vector<std::pair<std::string, std::string>> ret;
m_parent->_readVectorFunc(ret, [&]() {
auto& [name, animId] = ret.emplace_back();
name = m_parent->_readStdString();
animId = m_parent->_readStdString();
});
return ret;
}
std::vector<std::pair<std::string, std::string>> DataStream::getSubtypeOverlayNames(std::string_view name) {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("GETSUBTYPEOVERLAYNAMES {}"), name));
m_parent->_checkOk("unable to get subtype overlays of actor"sv);
std::vector<std::pair<std::string, std::string>> ret;
m_parent->_readVectorFunc(ret, [&]() {
auto& [subtypeName, cskrId] = ret.emplace_back();
subtypeName = m_parent->_readStdString();
cskrId = m_parent->_readStdString();
});
return ret;
}
std::vector<std::pair<std::string, std::string>> DataStream::getAttachmentNames() {
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("GETATTACHMENTNAMES");
m_parent->_checkOk("unable to get attachments of actor"sv);
std::vector<std::pair<std::string, std::string>> ret;
m_parent->_readVectorFunc(ret, [&]() {
auto& [name, cskrId] = ret.emplace_back();
name = m_parent->_readStdString();
cskrId = m_parent->_readStdString();
});
return ret;
}
std::unordered_map<std::string, Matrix3f> DataStream::getBoneMatrices(std::string_view name) {
if (name.empty())
return {};
if (m_parent->getBlendType() != BlendType::Actor)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an ACTOR blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("GETBONEMATRICES {}"), name));
m_parent->_checkOk("unable to get matrices of armature"sv);
std::unordered_map<std::string, Matrix3f> ret;
uint32_t boneCount;
m_parent->_readValue(boneCount);
ret.reserve(boneCount);
for (uint32_t i = 0; i < boneCount; ++i) {
std::string mat_name = m_parent->_readStdString();
Matrix3f matOut;
for (int mat_i = 0; mat_i < 3; ++mat_i) {
for (int mat_j = 0; mat_j < 3; ++mat_j) {
float val;
m_parent->_readValue(val);
matOut[mat_i].simd[mat_j] = val;
}
matOut[mat_i].simd[3] = 0.f;
}
ret.emplace(std::move(mat_name), std::move(matOut));
}
return ret;
}
bool DataStream::renderPvs(std::string_view path, const atVec3f& location) {
if (path.empty())
return false;
if (m_parent->getBlendType() != BlendType::Area)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an AREA blend")),
m_parent->getBlendPath().getAbsolutePath());
athena::simd_floats f(location.simd);
m_parent->_writeStr(fmt::format(FMT_STRING("RENDERPVS {} {} {} {}"), path, f[0], f[1], f[2]));
m_parent->_checkOk("unable to render PVS"sv);
return true;
}
bool DataStream::renderPvsLight(std::string_view path, std::string_view lightName) {
if (path.empty())
return false;
if (m_parent->getBlendType() != BlendType::Area)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not an AREA blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr(fmt::format(FMT_STRING("RENDERPVSLIGHT {} {}"), path, lightName));
m_parent->_checkOk("unable to render PVS light"sv);
return true;
}
MapArea DataStream::compileMapArea() {
if (m_parent->getBlendType() != BlendType::MapArea)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a MAPAREA blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("MAPAREACOMPILE");
m_parent->_checkOk("unable to compile map area"sv);
return {*m_parent};
}
MapUniverse DataStream::compileMapUniverse() {
if (m_parent->getBlendType() != BlendType::MapUniverse)
BlenderLog.report(logvisor::Fatal, FMT_STRING(_SYS_STR("{} is not a MAPUNIVERSE blend")),
m_parent->getBlendPath().getAbsolutePath());
m_parent->_writeStr("MAPUNIVERSECOMPILE");
m_parent->_checkOk("unable to compile map universe"sv);
return {*m_parent};
}
void Connection::quitBlender() {
if (m_blenderQuit)
return;
m_blenderQuit = true;
char lineBuf[256];
if (m_lock) {
if (m_pyStreamActive) {
_writeStr("PYEND");
_readStr(lineBuf, sizeof(lineBuf));
m_pyStreamActive = false;
} else if (m_dataStreamActive) {
_writeStr("DATAEND");
_readStr(lineBuf, sizeof(lineBuf));
m_dataStreamActive = false;
}
m_lock = false;
}
_writeStr("QUIT");
_readStr(lineBuf, sizeof(lineBuf));
#ifndef _WIN32
waitpid(m_blenderProc, nullptr, 0);
#endif
}
Connection& Connection::SharedConnection() { return SharedBlenderToken.getBlenderConnection(); }
void Connection::Shutdown() { SharedBlenderToken.shutdown(); }
Connection& Token::getBlenderConnection() {
if (!m_conn)
m_conn = std::make_unique<Connection>(hecl::VerbosityLevel);
return *m_conn;
}
void Token::shutdown() {
if (m_conn) {
m_conn->quitBlender();
m_conn.reset();
if (hecl::VerbosityLevel >= 1)
BlenderLog.report(logvisor::Info, FMT_STRING("Blender Shutdown Successful"));
}
}
Token::~Token() { shutdown(); }
HMDLBuffers::HMDLBuffers(HMDLMeta&& meta, std::size_t vboSz, const std::vector<atUint32>& iboData,
std::vector<Surface>&& surfaces, const Mesh::SkinBanks& skinBanks)
: m_meta(std::move(meta))
, m_vboSz(vboSz)
, m_vboData(new uint8_t[vboSz])
, m_iboSz(iboData.size() * 4)
, m_iboData(new uint8_t[iboData.size() * 4])
, m_surfaces(std::move(surfaces))
, m_skinBanks(skinBanks) {
if (m_iboSz) {
athena::io::MemoryWriter w(m_iboData.get(), m_iboSz);
w.enumerateLittle(iboData);
}
}
} // namespace hecl::blender