metaforce/visigen/VISIBuilder.cpp

328 lines
12 KiB
C++

#include "VISIBuilder.hpp"
#include "logvisor/logvisor.hpp"
#ifndef _WIN32
#include <unistd.h>
#include <signal.h>
#endif
#define VISI_MAX_LEVEL 10
#define VISI_MIN_LENGTH 8.0
static logvisor::Module Log("VISIBuilder");
VISIBuilder::PVSRenderCache::PVSRenderCache(VISIRenderer& renderer) : m_renderer(renderer) { m_cache.reserve(1000); }
static std::unique_ptr<VISIRenderer::RGBA8[]> RGBABuf(new VISIRenderer::RGBA8[256 * 256 * 6]);
const VISIBuilder::Leaf& VISIBuilder::PVSRenderCache::GetLeaf(const zeus::CVector3f& vec) {
auto search = m_cache.find(vec);
if (search != m_cache.cend()) {
// Log.report(logvisor::Info, FMT_STRING("Cache hit"));
return *search->second;
}
// Log.report(logvisor::Info, FMT_STRING("Rendering"));
bool needsTransparent = false;
m_renderer.RenderPVSOpaque(RGBABuf.get(), vec, needsTransparent);
std::unique_ptr<Leaf> leafOut = std::make_unique<Leaf>();
for (unsigned i = 0; i < 768 * 512; ++i) {
const VISIRenderer::RGBA8& pixel = RGBABuf[i];
uint32_t id = (pixel.b << 16) | (pixel.g << 8) | pixel.r;
if (id != 0)
leafOut->setBit(id - 1);
}
auto setBitLambda = [&](int idx) { leafOut->setBit(idx); };
auto setLightLambda = [&](int idx, EPVSVisSetState state) {
if (state != EPVSVisSetState::EndOfTree)
leafOut->setLightEnum(m_lightMetaBit + idx * 2, state);
};
if (needsTransparent)
m_renderer.RenderPVSTransparent(setBitLambda, vec);
m_renderer.RenderPVSEntitiesAndLights(setBitLambda, setLightLambda, vec);
return *m_cache.emplace(std::make_pair(vec, std::move(leafOut))).first->second;
}
void VISIBuilder::Progress::report(int divisions) {
m_prog += 1.f / divisions;
// printf(" %g%% \r", m_prog * 100.f);
// fflush(stdout);
if (m_updatePercent)
m_updatePercent(m_prog);
}
void VISIBuilder::Node::buildChildren(int level, int divisions, const zeus::CAABox& curAabb, PVSRenderCache& rc,
Progress& prog, const std::function<bool()>& terminate) {
if (terminate())
return;
// Recurse in while building node structure
if (level < VISI_MAX_LEVEL) {
// Heuristic split
int splits[3];
splits[0] = (curAabb.max.x() - curAabb.min.x() >= VISI_MIN_LENGTH) ? 2 : 1;
splits[1] = (curAabb.max.y() - curAabb.min.y() >= VISI_MIN_LENGTH) ? 2 : 1;
splits[2] = (curAabb.max.z() - curAabb.min.z() >= VISI_MIN_LENGTH) ? 2 : 1;
if (splits[0] == 2)
flags |= 0x1;
if (splits[1] == 2)
flags |= 0x2;
if (splits[2] == 2)
flags |= 0x4;
int thisdiv = splits[0] * splits[1] * splits[2] * divisions;
if (flags) {
childNodes.resize(8);
// Inward subdivide
zeus::CAABox Z[2];
if (flags & 0x4)
curAabb.splitZ(Z[0], Z[1]);
else
Z[0] = curAabb;
for (int i = 0; i < splits[2]; ++i) {
zeus::CAABox Y[2];
if (flags & 0x2)
Z[i].splitY(Y[0], Y[1]);
else
Y[0] = Z[i];
for (int j = 0; j < splits[1]; ++j) {
zeus::CAABox X[2];
if (flags & 0x1)
Y[j].splitX(X[0], X[1]);
else
X[0] = Y[j];
for (int k = 0; k < splits[0]; ++k) {
childNodes[i * 4 + j * 2 + k].buildChildren(level + 1, thisdiv, X[k], rc, prog, terminate);
}
}
}
// Outward unsubdivide for like-leaves
for (int i = 0; i < 3; ++i) {
if (flags & 0x4 && childNodes[0] == childNodes[4] && (!(flags & 0x1) || childNodes[1] == childNodes[5]) &&
(!(flags & 0x2) || childNodes[2] == childNodes[6]) && (!(flags & 0x3) || childNodes[3] == childNodes[7])) {
flags &= ~0x4;
// Log.report(logvisor::Info, FMT_STRING("Unsub Z"));
continue;
}
if (flags & 0x2 && childNodes[0] == childNodes[2] && (!(flags & 0x1) || childNodes[1] == childNodes[3]) &&
(!(flags & 0x4) || childNodes[4] == childNodes[6]) && (!(flags & 0x5) || childNodes[5] == childNodes[7])) {
flags &= ~0x2;
// Log.report(logvisor::Info, FMT_STRING("Unsub Y"));
continue;
}
if (flags & 0x1 && childNodes[0] == childNodes[1] && (!(flags & 0x2) || childNodes[2] == childNodes[3]) &&
(!(flags & 0x4) || childNodes[4] == childNodes[5]) && (!(flags & 0x6) || childNodes[6] == childNodes[7])) {
flags &= ~0x1;
// Log.report(logvisor::Info, FMT_STRING("Unsub X"));
continue;
}
break;
}
if (!flags) {
// This is now a leaf node
for (int i = 0; i < 8; ++i)
leaf |= childNodes[i].leaf;
// Log.report(logvisor::Info, FMT_STRING("Leaf Promote"));
return;
}
}
}
if (!flags) {
// This is a child node
zeus::CVector3f center = curAabb.center();
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.min.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.min.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.min.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), center.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), center.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), center.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.max.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.max.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.max.y(), curAabb.min.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.min.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.min.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.min.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), center.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), center.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), center.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.max.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.max.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.max.y(), center.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.min.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.min.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.min.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), center.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), center.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), center.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.min.x(), curAabb.max.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(center.x(), curAabb.max.y(), curAabb.max.z()));
leaf |= rc.GetLeaf(zeus::CVector3f(curAabb.max.x(), curAabb.max.y(), curAabb.max.z()));
prog.report(divisions);
}
}
static const int NumChildTable[] = {0, 2, 2, 4, 2, 4, 4, 8};
void VISIBuilder::Node::calculateSizesAndOffs(size_t& cur, size_t leafSz) {
cur += 1;
flags |= 0x18;
if (flags & 0x7) {
int splits[3];
splits[0] = (flags & 0x1) ? 2 : 1;
splits[1] = (flags & 0x2) ? 2 : 1;
splits[2] = (flags & 0x4) ? 2 : 1;
// Inward accumulate
const size_t startCur = cur;
size_t maxDelta = 0;
for (int i = 0; i < splits[2]; ++i)
for (int j = 0; j < splits[1]; ++j)
for (int k = 0; k < splits[0]; ++k) {
const size_t nodeSel = i * 4 + j * 2 + k;
const size_t delta = cur - startCur;
if (delta > maxDelta)
maxDelta = delta;
childRelOffs[nodeSel] = delta;
childNodes[nodeSel].calculateSizesAndOffs(cur, leafSz);
}
const int numChildren = NumChildTable[flags & 0x7];
if (maxDelta > 0xffff) {
cur += (numChildren - 1) * 3;
flags |= 0x40;
} else if (maxDelta > 0xff) {
cur += (numChildren - 1) * 2;
} else {
cur += numChildren - 1;
flags |= 0x20;
}
} else {
if (!leaf)
flags &= ~0x8;
else
cur += leafSz;
}
}
void VISIBuilder::Node::writeNodes(athena::io::MemoryWriter& w, size_t leafBytes) const {
w.writeUByte(flags);
if (flags & 0x7) {
int splits[3];
splits[0] = (flags & 0x1) ? 2 : 1;
splits[1] = (flags & 0x2) ? 2 : 1;
splits[2] = (flags & 0x4) ? 2 : 1;
// Write offsets
for (int i = 0; i < splits[2]; ++i)
for (int j = 0; j < splits[1]; ++j)
for (int k = 0; k < splits[0]; ++k) {
const size_t nodeSel = i * 4 + j * 2 + k;
if (nodeSel == 0)
continue;
const size_t offset = childRelOffs[nodeSel];
if (flags & 0x40) {
w.writeUByte((offset >> 16) & 0xff);
w.writeUByte((offset >> 8) & 0xff);
w.writeUByte(offset & 0xff);
} else if (flags & 0x20) {
w.writeUByte(offset & 0xff);
} else {
w.writeUint16Big(offset);
}
}
// Inward iterate
for (int i = 0; i < splits[2]; ++i)
for (int j = 0; j < splits[1]; ++j)
for (int k = 0; k < splits[0]; ++k) {
const size_t nodeSel = i * 4 + j * 2 + k;
childNodes[nodeSel].writeNodes(w, leafBytes);
}
} else if (leaf) {
leaf.write(w, leafBytes);
}
}
std::vector<uint8_t> VISIBuilder::build(const zeus::CAABox& fullAabb, size_t modelCount,
const std::vector<VISIRenderer::Entity>& entities,
const std::vector<VISIRenderer::Light>& lights, size_t layer2LightCount,
FPercent updatePercent, ProcessType parentPid) {
// Log.report(logvisor::Info, FMT_STRING("Started!"));
size_t featureCount = modelCount + entities.size();
renderCache.m_lightMetaBit = featureCount;
Progress prog(updatePercent);
#ifndef _WIN32
auto terminate = [this, parentPid]() {
return renderCache.m_renderer.m_terminate || (parentPid ? kill(parentPid, 0) : false);
};
#else
auto terminate = [this, parentPid]() {
DWORD exitCode = 0;
if (!GetExitCodeProcess(parentPid, &exitCode))
return renderCache.m_renderer.m_terminate;
return renderCache.m_renderer.m_terminate || (parentPid ? exitCode != STILL_ACTIVE : false);
};
#endif
rootNode.buildChildren(0, 1, fullAabb, renderCache, prog, terminate);
if (terminate())
return {};
// Lights cache their CPVSVisSet result enum as 2 bits
size_t leafBitsCount = featureCount + lights.size() * 2;
size_t leafBytesCount = ROUND_UP_8(leafBitsCount) / 8;
// Calculate octree size and store relative offsets
size_t octreeSz = 0;
rootNode.calculateSizesAndOffs(octreeSz, leafBytesCount);
size_t visiSz = 34 + entities.size() * 4 + lights.size() * leafBytesCount + 36 + octreeSz;
size_t roundedVisiSz = ROUND_UP_32(visiSz);
std::vector<uint8_t> dataOut(roundedVisiSz, 0);
athena::io::MemoryWriter w(dataOut.data(), roundedVisiSz);
w.writeUint32Big('VISI');
w.writeUint32Big(2);
w.writeBool(true);
w.writeBool(true);
w.writeUint32Big(featureCount);
w.writeUint32Big(lights.size());
w.writeUint32Big(layer2LightCount);
w.writeUint32Big(entities.size());
w.writeUint32Big(leafBytesCount);
w.writeUint32Big(lights.size());
for (const VISIRenderer::Entity& e : entities) {
w.writeUint32Big(e.entityId);
}
for (const VISIRenderer::Light& l : lights) {
const VISIBuilder::Leaf& leaf = renderCache.GetLeaf(l.point);
leaf.write(w, leafBytesCount);
}
w.writeVec3fBig(fullAabb.min);
w.writeVec3fBig(fullAabb.max);
w.writeUint32Big(featureCount + lights.size());
w.writeUint32Big(lights.size());
w.writeUint32Big(octreeSz);
rootNode.writeNodes(w, leafBytesCount);
w.seekAlign32();
// Log.report(logvisor::Info, FMT_STRING("Finished!"));
return dataOut;
}