diff --git a/CMakeLists.txt b/CMakeLists.txt index b595851af..de6c8f2d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ set(CLIENT_SOURCES add_subdirectory(Runtime) add_subdirectory(mpcksum) add_subdirectory(gbalink) +add_subdirectory(visigen) unset(GIT_EXECUTABLE CACHE) find_package(Git) diff --git a/DataSpec/DNAMP1/MREA.cpp b/DataSpec/DNAMP1/MREA.cpp index f82aa2ed4..68192d98d 100644 --- a/DataSpec/DNAMP1/MREA.cpp +++ b/DataSpec/DNAMP1/MREA.cpp @@ -5,6 +5,9 @@ #include "zeus/Math.hpp" #include "zeus/CAABox.hpp" #include "DataSpec/DNACommon/AROTBuilder.hpp" +#include "ScriptObjects/ScriptTypes.hpp" + +extern const hecl::SystemString ExeDir; namespace DataSpec { @@ -162,6 +165,26 @@ bool MREA::Extract(const SpecBase& dataSpec, ReadBabeDeadToBlender_1_2(os, rs); rs.seek(secStart + head.secSizes[curSec++], athena::Begin); + /* Dump VISI entities */ + if (head.secSizes[curSec] && rs.readUint32Big() == 'VISI') + { + athena::io::YAMLDocWriter visiWriter("VISI"); + if (auto __vec = visiWriter.enterSubVector("entities")) + { + rs.seek(18, athena::Current); + uint32_t entityCount = rs.readUint32Big(); + rs.seek(8, athena::Current); + for (int i=0 ; i& meshes, const ColMesh& cMesh, - const std::vector& lights) + const std::vector& lights, + hecl::BlenderToken& btok) { /* Discover area layers */ hecl::ProjectPath areaDirPath = inPath.getParentPath(); @@ -322,13 +346,13 @@ bool MREA::PCCook(const hecl::ProjectPath& outPath, /* AROT */ { - AROTBuilder builder; - builder.build(secs, fullAabb, meshAabbs, meshes); + AROTBuilder arotBuilder; + arotBuilder.build(secs, fullAabb, meshAabbs, meshes); } /* SCLY */ + DNAMP1::SCLY sclyData = {}; { - DNAMP1::SCLY sclyData = {}; sclyData.fourCC = 'SCLY'; sclyData.version = 1; for (const hecl::ProjectPath& layer : layerScriptPaths) @@ -373,13 +397,13 @@ bool MREA::PCCook(const hecl::ProjectPath& outPath, } /* Lights */ + std::vector lightsVisi; { int actualCount = 0; for (const Light& l : lights) - { if (l.layer == 0 || l.layer == 1) ++actualCount; - } + lightsVisi.reserve(actualCount); secs.emplace_back(12 + 65 * actualCount, 0); athena::io::MemoryWriter w(secs.back().data(), secs.back().size()); @@ -401,15 +425,100 @@ bool MREA::PCCook(const hecl::ProjectPath& outPath, BabeDeadLight light = {}; WriteBabeDeadLightFromBlender(light, l); light.write(w); + lightsVisi.push_back(light.position); } } } } /* VISI */ + hecl::ProjectPath visiMetadataPath(areaDirPath, _S("!visi.yaml")); + if (visiMetadataPath.isFile()) { - /* Empty (for now) */ - secs.emplace_back(0, 0); + bool good = false; + athena::io::FileReader visiReader(visiMetadataPath.getAbsolutePath()); + athena::io::YAMLDocReader r; + if (r.parse(&visiReader)) + { + size_t entityCount; + std::vector> entities; + if (auto __vec = r.enterSubVector("entities", entityCount)) + { + entities.reserve(entityCount); + uint16_t entityId = r.readUint16(nullptr); + for (const SCLY::ScriptLayer& layer : sclyData.layers) + { + for (const std::unique_ptr& obj : layer.objects) + { + if ((obj->id & 0xffff) == entityId) + { + zeus::CAABox entAABB = obj->getVISIAABB(btok); + if (entAABB.min.x < entAABB.max.x) + entities.emplace_back(entityId, entAABB); + } + } + } + } + + hecl::ProjectPath visiIntOut = outPath.getWithExtension(_S(".visiint")); + hecl::ProjectPath visiIn = outPath.getWithExtension(_S(".visi")); + athena::io::FileWriter w(visiIntOut.getAbsolutePath()); + w.writeUint32Big(meshes.size()); + for (const DNACMDL::Mesh& mesh : meshes) + { + w.writeUint32Big(uint32_t(mesh.topology)); + + w.writeUint32Big(mesh.pos.size()); + for (const auto& v : mesh.pos) + { + atVec3f xfPos = hecl::BlenderConnection::DataStream::MtxVecMul4RM(mesh.sceneXf, v); + w.writeVec3fBig(xfPos); + } + + w.writeUint32Big(mesh.surfaces.size()); + for (const DNACMDL::Mesh::Surface& surf : mesh.surfaces) + { + w.writeUint32Big(surf.verts.size()); + for (const DNACMDL::Mesh::Surface::Vert& vert : surf.verts) + w.writeUint32Big(vert.iPos); + const DNACMDL::Mesh::Material& mat = mesh.materialSets[0][surf.materialIdx]; + w.writeBool(mat.transparent); + } + } + + w.writeUint32Big(entities.size()); + for (const auto& ent : entities) + { + w.writeUint32Big(ent.first); + w.writeVec3fBig(ent.second.min); + w.writeVec3fBig(ent.second.max); + } + + w.writeUint32Big(lightsVisi.size()); + for (const auto& light : lightsVisi) + w.writeVec3fBig(light); + + w.close(); + + hecl::SystemString VisiGenPath = ExeDir + _S("/visigen"); +#if _WIN32 + VisiGenPath += _S(".exe"); +#endif + const hecl::SystemChar* args[] = {VisiGenPath.c_str(), + visiIntOut.getAbsolutePath().c_str(), + visiIn.getAbsolutePath().c_str(), + nullptr}; + if (0 == hecl::RunProcess(VisiGenPath.c_str(), args)) + { + athena::io::FileReader r(visiIn.getAbsolutePath()); + size_t length = r.length(); + secs.emplace_back(length, 0); + r.readBytesToBuf(secs.back().data(), length); + good = true; + } + } + if (!good) + secs.emplace_back(0, 0); } /* PATH */ diff --git a/DataSpec/DNAMP1/MREA.hpp b/DataSpec/DNAMP1/MREA.hpp index 3d5e36e29..f11da16ae 100644 --- a/DataSpec/DNAMP1/MREA.hpp +++ b/DataSpec/DNAMP1/MREA.hpp @@ -135,7 +135,8 @@ struct MREA const hecl::ProjectPath& inPath, const std::vector& meshes, const ColMesh& cMesh, - const std::vector& lights); + const std::vector& lights, + hecl::BlenderToken& btok); }; } diff --git a/DataSpec/DNAMP1/ScriptObjects/Actor.hpp b/DataSpec/DNAMP1/ScriptObjects/Actor.hpp index f34c0eab9..ee350ac5f 100644 --- a/DataSpec/DNAMP1/ScriptObjects/Actor.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/Actor.hpp @@ -16,7 +16,7 @@ struct Actor : IScriptObject Value orientation; Value scale; Value collisionExtent; - Value centroid; + Value collisionOffset; Value unknown2; Value unknown3; HealthInfo healthInfo; @@ -64,6 +64,36 @@ struct Actor : IScriptObject { actorParameters.scanIDs(scansOut); } + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + hecl::BlenderConnection& conn = btok.getBlenderConnection(); + zeus::CAABox aabbOut; + + if (model) + { + hecl::ProjectPath path = UniqueIDBridge::TranslatePakIdToPath(model); + conn.openBlend(path); + hecl::BlenderConnection::DataStream ds = conn.beginData(); + auto aabb = ds.getMeshAABB(); + aabbOut = zeus::CAABox(aabb.first, aabb.second); + } + else if (animationParameters.animationCharacterSet) + { + hecl::ProjectPath path = UniqueIDBridge::TranslatePakIdToPath( + animationParameters.animationCharacterSet); + conn.openBlend(path.getWithExtension(_S(".blend"), true)); + hecl::BlenderConnection::DataStream ds = conn.beginData(); + auto aabb = ds.getMeshAABB(); + aabbOut = zeus::CAABox(aabb.first, aabb.second); + } + + if (aabbOut.min.x > aabbOut.max.x) + return {}; + + zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location); + return aabbOut.getTransformedAABox(xf); + } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/DamageableTrigger.hpp b/DataSpec/DNAMP1/ScriptObjects/DamageableTrigger.hpp index 5838d5161..9eba8406f 100644 --- a/DataSpec/DNAMP1/ScriptObjects/DamageableTrigger.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/DamageableTrigger.hpp @@ -50,6 +50,13 @@ struct DamageableTrigger : IScriptObject g_curSpec->flattenDependencies(texture2, pathsOut); g_curSpec->flattenDependencies(texture3, pathsOut); } + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + zeus::CVector3f halfExtent = zeus::CVector3f(volume) / 2.f; + zeus::CVector3f loc(location); + return zeus::CAABox(loc - halfExtent, loc + halfExtent); + } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/DoorArea.hpp b/DataSpec/DNAMP1/ScriptObjects/DoorArea.hpp index 597241438..894fa603e 100644 --- a/DataSpec/DNAMP1/ScriptObjects/DoorArea.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/DoorArea.hpp @@ -19,8 +19,8 @@ struct DoorArea : IScriptObject AnimationParameters animationParameters; ActorParameters actorParameters; Value unknown1; - Value unknown2; - Value unknown3; + Value collisionExtent; + Value collisionOffset; Value unknown4; Value unknown5; Value unknown6; @@ -49,6 +49,28 @@ struct DoorArea : IScriptObject { actorParameters.scanIDs(scansOut); } + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + hecl::BlenderConnection& conn = btok.getBlenderConnection(); + zeus::CAABox aabbOut; + + if (animationParameters.animationCharacterSet) + { + hecl::ProjectPath path = UniqueIDBridge::TranslatePakIdToPath( + animationParameters.animationCharacterSet); + conn.openBlend(path.getWithExtension(_S(".blend"), true)); + hecl::BlenderConnection::DataStream ds = conn.beginData(); + auto aabb = ds.getMeshAABB(); + aabbOut = zeus::CAABox(aabb.first, aabb.second); + } + + if (aabbOut.min.x > aabbOut.max.x) + return {}; + + zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location); + return aabbOut.getTransformedAABox(xf); + } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/IScriptObject.cpp b/DataSpec/DNAMP1/ScriptObjects/IScriptObject.cpp index d64d2a46d..87d0151e4 100644 --- a/DataSpec/DNAMP1/ScriptObjects/IScriptObject.cpp +++ b/DataSpec/DNAMP1/ScriptObjects/IScriptObject.cpp @@ -267,5 +267,16 @@ const std::vector SCRIPT_OBJECT_DB = &priv::WorldTeleporterx62Ent, }; +zeus::CTransform ConvertEditorEulerToTransform4f(const zeus::CVector3f& scale, + const zeus::CVector3f& orientation, + const zeus::CVector3f& position) +{ + return zeus::CTransform::RotateZ(zeus::degToRad(orientation.z)) * + zeus::CTransform::RotateY(zeus::degToRad(orientation.y)) * + zeus::CTransform::RotateX(zeus::degToRad(orientation.x)) * + zeus::CTransform::Scale(scale) + + position; +} + } } diff --git a/DataSpec/DNAMP1/ScriptObjects/IScriptObject.hpp b/DataSpec/DNAMP1/ScriptObjects/IScriptObject.hpp index 4a42ab67d..7f81aba7d 100644 --- a/DataSpec/DNAMP1/ScriptObjects/IScriptObject.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/IScriptObject.hpp @@ -3,12 +3,18 @@ #include "../../DNACommon/DNACommon.hpp" #include "../DNAMP1.hpp" #include "../SAVW.hpp" +#include "zeus/CAABox.hpp" #include namespace DataSpec { namespace DNAMP1 { + +zeus::CTransform ConvertEditorEulerToTransform4f(const zeus::CVector3f& scale, + const zeus::CVector3f& orientation, + const zeus::CVector3f& position); + struct IScriptObject : BigYAML { DECL_YAML @@ -32,6 +38,7 @@ struct IScriptObject : BigYAML virtual void nameIDs(PAKRouter& pakRouter) const {} virtual void gatherDependencies(std::vector& pathsOut) const {} virtual void gatherScans(std::vector& scansOut) const {} + virtual zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const { return {}; } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/Platform.hpp b/DataSpec/DNAMP1/ScriptObjects/Platform.hpp index abac02808..d353d70c3 100644 --- a/DataSpec/DNAMP1/ScriptObjects/Platform.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/Platform.hpp @@ -66,6 +66,36 @@ struct Platform : IScriptObject { actorParameters.scanIDs(scansOut); } + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + hecl::BlenderConnection& conn = btok.getBlenderConnection(); + zeus::CAABox aabbOut; + + if (model) + { + hecl::ProjectPath path = UniqueIDBridge::TranslatePakIdToPath(model); + conn.openBlend(path); + hecl::BlenderConnection::DataStream ds = conn.beginData(); + auto aabb = ds.getMeshAABB(); + aabbOut = zeus::CAABox(aabb.first, aabb.second); + } + else if (animationParameters.animationCharacterSet) + { + hecl::ProjectPath path = UniqueIDBridge::TranslatePakIdToPath( + animationParameters.animationCharacterSet); + conn.openBlend(path.getWithExtension(_S(".blend"), true)); + hecl::BlenderConnection::DataStream ds = conn.beginData(); + auto aabb = ds.getMeshAABB(); + aabbOut = zeus::CAABox(aabb.first, aabb.second); + } + + if (aabbOut.min.x > aabbOut.max.x) + return {}; + + zeus::CTransform xf = ConvertEditorEulerToTransform4f(scale, orientation, location); + return aabbOut.getTransformedAABox(xf); + } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/Trigger.hpp b/DataSpec/DNAMP1/ScriptObjects/Trigger.hpp index f0f83ba6f..1477f8eac 100644 --- a/DataSpec/DNAMP1/ScriptObjects/Trigger.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/Trigger.hpp @@ -21,6 +21,13 @@ struct Trigger : IScriptObject Value active; Value unknown2; Value unknown3; + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + zeus::CVector3f halfExtent = zeus::CVector3f(volume) / 2.f; + zeus::CVector3f loc(location); + return zeus::CAABox(loc - halfExtent, loc + halfExtent); + } }; } } diff --git a/DataSpec/DNAMP1/ScriptObjects/Water.hpp b/DataSpec/DNAMP1/ScriptObjects/Water.hpp index eab8f5ca0..8aa5f87fc 100644 --- a/DataSpec/DNAMP1/ScriptObjects/Water.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/Water.hpp @@ -173,6 +173,13 @@ struct Water : IScriptObject g_curSpec->flattenDependencies(particle4, pathsOut); g_curSpec->flattenDependencies(particle5, pathsOut); } + + zeus::CAABox getVISIAABB(hecl::BlenderToken& btok) const + { + zeus::CVector3f halfExtent = zeus::CVector3f(volume) / 2.f; + zeus::CVector3f loc(location); + return zeus::CAABox(loc - halfExtent, loc + halfExtent); + } }; } } diff --git a/DataSpec/SpecMP1.cpp b/DataSpec/SpecMP1.cpp index e44560e30..276808900 100644 --- a/DataSpec/SpecMP1.cpp +++ b/DataSpec/SpecMP1.cpp @@ -381,6 +381,7 @@ struct SpecMP1 : SpecBase std::unique_lock lk(msgLock); progress(sysName.c_str(), _S(""), compIdx, 0.0); } + hecl::SystemString pakName = sysName.sys_str(); process.addLambdaTransaction([&, pakName](hecl::BlenderToken& btok) { m_pakRouter.extractResources(pak, force, btok, [&](const hecl::SystemChar* substr, float factor) { @@ -737,8 +738,10 @@ struct SpecMP1 : SpecBase std::vector lights = ds.compileLights(); + ds.close(); + if (m_pc) - DNAMP1::MREA::PCCook(out, in, meshCompiles, *colMesh, lights); + DNAMP1::MREA::PCCook(out, in, meshCompiles, *colMesh, lights, btok); else DNAMP1::MREA::Cook(out, in, meshCompiles, *colMesh, lights); } diff --git a/Editor/main.cpp b/Editor/main.cpp index 7c60d21a3..7924cde51 100644 --- a/Editor/main.cpp +++ b/Editor/main.cpp @@ -123,6 +123,9 @@ struct Application : boo::IApplicationCallback } +static hecl::SystemChar CwdBuf[1024]; +hecl::SystemString ExeDir; + #if _WIN32 int wmain(int argc, const boo::SystemChar** argv) #else @@ -132,6 +135,17 @@ int main(int argc, const boo::SystemChar** argv) logvisor::RegisterStandardExceptions(); logvisor::RegisterConsoleLogger(); atSetExceptionHandler(AthenaExc); + + if (hecl::SystemChar* cwd = hecl::Getcwd(CwdBuf, 1024)) + { + if (argv[0][0] != _S('/') && argv[0][0] != _S('\\')) + ExeDir = hecl::SystemString(cwd) + _S('/'); + hecl::SystemString Argv0(argv[0]); + hecl::SystemString::size_type lastIdx = Argv0.find_last_of(_S("/\\")); + if (lastIdx != hecl::SystemString::npos) + ExeDir.insert(ExeDir.end(), Argv0.begin(), Argv0.begin() + lastIdx); + } + urde::Application appCb; int ret = boo::ApplicationRun(boo::IApplication::EPlatformType::Auto, appCb, _S("urde"), _S("URDE"), argc, argv, false); diff --git a/hecl b/hecl index f407c84e7..1a9260afd 160000 --- a/hecl +++ b/hecl @@ -1 +1 @@ -Subproject commit f407c84e78721e6e682bfc7790bfb0c68a886453 +Subproject commit 1a9260afd0a5bcf9e77203bd4b5cde4334eb1b3f diff --git a/specter b/specter index 03d074af1..67a7efea5 160000 --- a/specter +++ b/specter @@ -1 +1 @@ -Subproject commit 03d074af13e902cebdf05d6f5ec5e4c2cbf569ee +Subproject commit 67a7efea539964b2f2a816135ee5719e5ae104a1 diff --git a/visigen/CMakeLists.txt b/visigen/CMakeLists.txt new file mode 100644 index 000000000..47b9ba479 --- /dev/null +++ b/visigen/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.0) +project(visigen) + +if(MSVC) +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}\ + -std=c++14 -Wno-multichar -fno-exceptions -Wno-narrowing -Wno-nullability-completeness -Werror=return-type") +endif() + +if(APPLE) + set(PLAT_SRCS MainMac.mm) + set_source_files_properties(MainMac.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) +endif() + +add_executable(visigen ${PLAT_SRCS} + VISIRenderer.cpp VISIRenderer.hpp + VISIBuilder.cpp VISIBuilder.hpp) +target_link_libraries(visigen logvisor athena-core zeus glew xxhash ${BOO_SYS_LIBS}) + +add_custom_command(TARGET visigen POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $) diff --git a/visigen/MainMac.mm b/visigen/MainMac.mm new file mode 100644 index 000000000..b8cb79390 --- /dev/null +++ b/visigen/MainMac.mm @@ -0,0 +1,122 @@ +#include "VISIRenderer.hpp" +#include +#include "athena/Global.hpp" +#include "logvisor/logvisor.hpp" +#include + +#if !__has_feature(objc_arc) +#error ARC Required +#endif + +static std::thread s_task; + +static const NSOpenGLPixelFormatAttribute PF_RGBA8_Z24_ATTRS[] = +{ + NSOpenGLPFAAccelerated, + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + //NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 24, + 0, 0 +}; + +@interface OpenGLView : NSOpenGLView +{ + VISIRenderer* m_renderer; +} +- (id)initWithFrame:(NSRect)frame renderer:(VISIRenderer*)renderer; +@end + +static NSWindow* s_Window; +static void UpdatePercent(float percent) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + s_Window.title = [NSString stringWithFormat:@"VISIGen [%g%%]", percent * 100.f]; + }); +} + +@implementation OpenGLView +- (id)initWithFrame:(NSRect)frame renderer:(VISIRenderer*)renderer; +{ + NSOpenGLPixelFormat* pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:PF_RGBA8_Z24_ATTRS]; + self = [super initWithFrame:frame pixelFormat:pf]; + m_renderer = renderer; + return self; +} +- (void)prepareOpenGL +{ + s_task = std::thread([self](){ + [[self openGLContext] makeCurrentContext]; + m_renderer->Run(UpdatePercent); + [NSApp terminate:nil]; + }); +} +@end + +@interface AppDelegate : NSObject +{ + VISIRenderer* m_renderer; + NSWindow* m_window; + NSOpenGLView* m_glView; +} +- (id)initWithRenderer:(VISIRenderer*)renderer; +@end + +@implementation AppDelegate +- (id)initWithRenderer:(VISIRenderer*)renderer +{ + self = [super init]; + m_renderer = renderer; + return self; +} +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + NSRect cRect = NSMakeRect(100, 100, 768, 512); + m_window = [[NSWindow alloc] initWithContentRect:cRect + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable + backing:NSBackingStoreBuffered + defer:NO]; + m_window.releasedWhenClosed = NO; + m_window.title = @"VISIGen"; + s_Window = m_window; + m_glView = [[OpenGLView alloc] initWithFrame:cRect renderer:m_renderer]; + m_window.contentView = m_glView; + [m_window makeKeyAndOrderFront:nil]; +} +- (void)applicationWillTerminate:(NSNotification*)notification +{ + m_renderer->Terminate(); + if (s_task.joinable()) + s_task.join(); + exit(m_renderer->ReturnVal()); +} +@end + +static logvisor::Module AthenaLog("Athena"); +static void AthenaExc(athena::error::Level level, const char* file, + const char*, int line, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + AthenaLog.report(logvisor::Level(level), fmt, ap); + va_end(ap); +} + +int main(int argc, const char** argv) +{ + logvisor::RegisterStandardExceptions(); + logvisor::RegisterConsoleLogger(); + atSetExceptionHandler(AthenaExc); + VISIRenderer renderer(argc, argv); + @autoreleasepool + { + [[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular]; + + /* Delegate (OS X callbacks) */ + AppDelegate* appDelegate = [[AppDelegate alloc] initWithRenderer:&renderer]; + [[NSApplication sharedApplication] setDelegate:appDelegate]; + [[NSApplication sharedApplication] run]; + } + return renderer.ReturnVal(); +} diff --git a/visigen/VISIBuilder.cpp b/visigen/VISIBuilder.cpp new file mode 100644 index 000000000..c9a681cec --- /dev/null +++ b/visigen/VISIBuilder.cpp @@ -0,0 +1,369 @@ +#include "VISIBuilder.hpp" + +#define VISI_MAX_LEVEL 10 +#define VISI_MIN_LENGTH 8.0 + +static logvisor::Module Log("VISIBuilder"); +const VISIBuilder::Leaf VISIBuilder::NullLeaf = {}; + +VISIBuilder::PVSRenderCache::PVSRenderCache(VISIRenderer& renderer) +: m_renderer(renderer) +{ + m_cache.reserve(1000); +} + +static std::unique_ptr 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, "Cache hit"); + return *search->second; + } + + //Log.report(logvisor::Info, "Rendering"); + bool needsTransparent = false; + m_renderer.RenderPVSOpaque(RGBABuf.get(), vec, needsTransparent); + std::unique_ptr leafOut = std::make_unique(); + 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 != 0xffffff) + leafOut->setBit(id); + } + + 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 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[1], Z[0]); + else + Z[0] = curAabb; + for (int i=0 ; i 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 + { + cur += leafSz; + flags |= 0x18; + } +} + +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> 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 VISIBuilder::build(const zeus::CAABox& fullAabb, + size_t modelCount, + const std::vector& entities, + const std::vector& lights, + FPercent updatePercent) +{ + Log.report(logvisor::Info, "Started!"); + + size_t featureCount = modelCount + entities.size(); + renderCache.m_lightMetaBit = featureCount + lights.size(); + + Progress prog(updatePercent); + bool& terminate = renderCache.m_renderer.m_terminate; + 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() * 3; + size_t leafBytesCount = ROUND_UP_8(leafBitsCount) / 8; + + // Calculate octree size and store relative offsets + size_t octreeSz = 0; + rootNode.calculateSizesAndOffs(octreeSz, leafBytesCount); + octreeSz += 1; // Terminator node + + size_t visiSz = 34 + entities.size() * 4 + lights.size() * leafBytesCount + 36 + octreeSz; + size_t roundedVisiSz = ROUND_UP_32(visiSz); + + std::vector 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(0); + 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(leafBitsCount); + w.writeUint32Big(lights.size()); + w.writeUint32Big(octreeSz); + rootNode.writeNodes(w, leafBytesCount); + w.writeUByte(0x10); + + w.seekAlign32(); + + printf("\n"); + Log.report(logvisor::Info, "Finished!"); + return dataOut; +} diff --git a/visigen/VISIBuilder.hpp b/visigen/VISIBuilder.hpp new file mode 100644 index 000000000..1694b12f5 --- /dev/null +++ b/visigen/VISIBuilder.hpp @@ -0,0 +1,131 @@ +#ifndef _DNACOMMON_VISIBUILDER_HPP_ +#define _DNACOMMON_VISIBUILDER_HPP_ + +#include "VISIRenderer.hpp" +#include "zeus/CAABox.hpp" +#include "hecl/extern/xxhash/xxhash.h" +#include "athena/MemoryWriter.hpp" +#include "hecl/hecl.hpp" +#include + +namespace std +{ +template <> struct hash +{ + size_t operator()(const zeus::CVector3f& val) const noexcept + { + return XXH64(val.v, 12, 0); + } +}; +} + +struct VISIBuilder +{ + struct Leaf + { + std::vector bits; + void setBit(size_t bit) + { + size_t byte = bit / 8; + if (byte >= bits.size()) + bits.resize(byte + 1); + bits[byte] |= 1 << (bit & 0x7); + } + void setLightEnum(size_t bit, EPVSVisSetState state) + { + size_t byte0 = bit / 8; + size_t byte1 = (bit + 1) / 8; + if (byte1 >= bits.size()) + bits.resize(byte1 + 1); + + if (byte0 == byte1) + { + bits[byte0] |= int(state) << (bit & 0x7); + } + else + { + bits[byte0] |= (int(state) << 7) & 0x1; + bits[byte1] |= (int(state) >> 1) & 0x1; + } + } + bool operator==(const Leaf& other) const + { + if (bits.size() != other.bits.size()) + return false; + if (memcmp(bits.data(), other.bits.data(), bits.size())) + return false; + return true; + } + Leaf& operator|=(const Leaf& other) + { + if (bits.size() < other.bits.size()) + bits.resize(other.bits.size()); + for (int i=0 ; i> m_cache; + size_t m_lightMetaBit; + public: + PVSRenderCache(VISIRenderer& renderer); + const Leaf& GetLeaf(const zeus::CVector3f& vec); + } renderCache; + + class Progress + { + float m_prog = 0.f; + FPercent m_updatePercent; + public: + void report(int divisions); + Progress(FPercent updatePercent) : m_updatePercent(updatePercent) {} + }; + + struct Node + { + std::vector childNodes; + size_t childRelOffs[8] = {}; + Leaf leaf; + uint8_t flags = 0; + + void buildChildren(int level, int divisions, const zeus::CAABox& curAabb, + PVSRenderCache& rc, Progress& prog, const bool& terminate); + void calculateSizesAndOffs(size_t& cur, size_t leafSz); + void writeNodes(athena::io::MemoryWriter& w, size_t leafBytes) const; + + bool operator==(const Node& other) const + { + if (!leaf || !other.leaf) + return false; + return leaf == other.leaf; + } + } rootNode; + + std::vector build(const zeus::CAABox& fullAabb, + size_t modelCount, + const std::vector& entities, + const std::vector& lights, + FPercent updatePercent); + + VISIBuilder(VISIRenderer& renderer) : renderCache(renderer) {} +}; + +#endif // _DNACOMMON_VISIBUILDER_HPP_ diff --git a/visigen/VISIRenderer.cpp b/visigen/VISIRenderer.cpp new file mode 100644 index 000000000..7161137cc --- /dev/null +++ b/visigen/VISIRenderer.cpp @@ -0,0 +1,562 @@ +#include "VISIRenderer.hpp" +#include "athena/FileReader.hpp" +#include "zeus/CAABox.hpp" +#include "VISIBuilder.hpp" +#include "zeus/CFrustum.hpp" + +static logvisor::Module Log("visigen"); + +static const char* VS = +"#version 330\n" +"layout(location=0) in vec4 posIn;\n" +"layout(location=1) in vec4 colorIn;\n" +"\n" +"uniform UniformBlock\n" +"{\n" +" mat4 xf;\n" +"};\n" +"\n" +"struct VertToFrag\n" +"{\n" +" vec4 color;\n" +"};\n" +"\n" +"out VertToFrag vtf;\n" +"void main()\n" +"{\n" +" vtf.color = colorIn;\n" +" gl_Position = xf * vec4(posIn.xyz, 1.0);\n" +"}\n"; + +static const char* FS = +"#version 330\n" +"struct VertToFrag\n" +"{\n" +" vec4 color;\n" +"};\n" +"\n" +"in VertToFrag vtf;\n" +"layout(location=0) out vec4 colorOut;\n" +"void main()\n" +"{\n" +" colorOut = vtf.color;\n" +"}\n"; + +static const uint32_t AABBIdxs[19] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 7, 3, 5, 5, 0, 0, 2, 6, 4 +}; + +bool VISIRenderer::SetupShaders() +{ + m_vtxShader = glCreateShader(GL_VERTEX_SHADER); + m_fragShader = glCreateShader(GL_FRAGMENT_SHADER); + m_program = glCreateProgram(); + + glShaderSource(m_vtxShader, 1, &VS, nullptr); + glCompileShader(m_vtxShader); + GLint status; + glGetShaderiv(m_vtxShader, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) + { + GLint logLen; + glGetShaderiv(m_vtxShader, GL_INFO_LOG_LENGTH, &logLen); + char* log = (char*)malloc(logLen); + glGetShaderInfoLog(m_vtxShader, logLen, nullptr, log); + Log.report(logvisor::Error, "unable to compile vert source\n%s\n%s\n", log, VS); + free(log); + return false; + } + + glShaderSource(m_fragShader, 1, &FS, nullptr); + glCompileShader(m_fragShader); + glGetShaderiv(m_fragShader, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) + { + GLint logLen; + glGetShaderiv(m_fragShader, GL_INFO_LOG_LENGTH, &logLen); + char* log = (char*)malloc(logLen); + glGetShaderInfoLog(m_fragShader, logLen, nullptr, log); + Log.report(logvisor::Error, "unable to compile frag source\n%s\n%s\n", log, FS); + free(log); + return false; + } + + glAttachShader(m_program, m_vtxShader); + glAttachShader(m_program, m_fragShader); + + glLinkProgram(m_program); + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + if (status != GL_TRUE) + { + GLint logLen; + glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLen); + char* log = (char*)malloc(logLen); + glGetProgramInfoLog(m_program, logLen, nullptr, log); + Log.report(logvisor::Error, "unable to link shader program\n%s\n", log); + free(log); + return false; + } + + glUseProgram(m_program); + m_uniLoc = glGetUniformBlockIndex(m_program, "UniformBlock"); + + glGenBuffers(1, &m_uniformBufferGL); + glBindBuffer(GL_UNIFORM_BUFFER, m_uniformBufferGL); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformBuffer), nullptr, GL_DYNAMIC_DRAW); + + glGenBuffers(1, &m_aabbIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_aabbIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 19 * 4, &AABBIdxs, GL_STATIC_DRAW); + + glGenQueries(1, &m_query); + + return true; +} + +std::vector VISIRenderer::AABBToVerts(const zeus::CAABox& aabb, + const zeus::CColor& color) +{ + std::vector verts; + verts.resize(8); + + for (int i=0 ; i<8 ; ++i) + verts[i].color = color; + + verts[0].pos = aabb.min; + verts[1].pos = {aabb.max.x, aabb.min.y, aabb.min.z}; + verts[2].pos = {aabb.min.x, aabb.min.y, aabb.max.z}; + verts[3].pos = {aabb.max.x, aabb.min.y, aabb.max.z}; + verts[4].pos = {aabb.min.x, aabb.max.y, aabb.max.z}; + verts[5].pos = aabb.max; + verts[6].pos = {aabb.min.x, aabb.max.y, aabb.min.z}; + verts[7].pos = {aabb.max.x, aabb.max.y, aabb.min.z}; + + return verts; +} + +static zeus::CColor ColorForIndex(int i) +{ + return zeus::CColor((i & 0xff) / 255.f, + ((i >> 8) & 0xff) / 255.f, + ((i >> 16) & 0xff) / 255.f, + 1.f); +} + +bool VISIRenderer::SetupVertexBuffersAndFormats() +{ + for (Model& model : m_models) + { + glGenVertexArrays(1, &model.vao); + glGenBuffers(1, &model.vbo); + glGenBuffers(1, &model.ibo); + + glBindVertexArray(model.vao); + + glBindBuffer(GL_ARRAY_BUFFER, model.vbo); + glBufferData(GL_ARRAY_BUFFER, model.verts.size() * sizeof(Model::Vert), model.verts.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), (void*)16); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, model.idxs.size() * 4, model.idxs.data(), GL_STATIC_DRAW); + } + + int idx = m_models.size(); + for (Entity& ent : m_entities) + { + glGenVertexArrays(1, &ent.vao); + glGenBuffers(1, &ent.vbo); + + glBindVertexArray(ent.vao); + + auto verts = AABBToVerts(ent.aabb, ColorForIndex(idx++)); + glBindBuffer(GL_ARRAY_BUFFER, ent.vbo); + glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(Model::Vert), verts.data(), GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), (void*)16); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_aabbIBO); + } + + for (Light& light : m_lights) + { + glGenVertexArrays(1, &light.vao); + glGenBuffers(1, &light.vbo); + + glBindVertexArray(light.vao); + + Model::Vert vert; + vert.pos = light.point; + vert.color = ColorForIndex(idx++); + glBindBuffer(GL_ARRAY_BUFFER, light.vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(Model::Vert), &vert, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), 0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Model::Vert), (void*)16); + } + + return true; +} + +static zeus::CMatrix4f g_Proj; + +static void CalculateProjMatrix() +{ + float znear = 0.2f; + float zfar = 1000.f; + float tfov = std::tan(zeus::degToRad(90.f * 0.5f)); + float top = znear * tfov; + float bottom = -top; + float right = znear * tfov; + float left = -right; + + float rml = right - left; + float rpl = right + left; + float tmb = top - bottom; + float tpb = top + bottom; + float fpn = zfar + znear; + float fmn = zfar - znear; + + g_Proj = zeus::CMatrix4f(2.f * znear / rml, 0.f, rpl / rml, 0.f, + 0.f, 2.f * znear / tmb, tpb / tmb, 0.f, + 0.f, 0.f, -fpn / fmn, -2.f * zfar * znear / fmn, + 0.f, 0.f, -1.f, 0.f); +} + +static const zeus::CMatrix4f LookMATs[] = +{ + { // Forward + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, + { // Backward + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, + { // Up + 1.f, 0.f, 0.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, -1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, + { // Down + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, + { // Left + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, + { // Right + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + -1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }, +}; + +void VISIRenderer::RenderPVSOpaque(RGBA8* bufOut, const zeus::CVector3f& pos, bool& needTransparent) +{ + glViewport(0, 0, 768, 512); + glEnable(GL_CULL_FACE); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + glClearColor(1.f, 1.f, 1.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + for (int j=0 ; j<6 ; ++j) + { + GLint x = (j % 3) * 256; + GLint y = (j / 3) * 256; + glViewport(x, y, 256, 256); + + zeus::CMatrix4f mv = LookMATs[j] * zeus::CTransform::Translate(-pos).toMatrix4f(); + m_uniformBuffer.m_xf = g_Proj * mv; + glBindBuffer(GL_UNIFORM_BUFFER, m_uniformBufferGL); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformBuffer), &m_uniformBuffer, GL_DYNAMIC_DRAW); + + glUniformBlockBinding(m_program, m_uniLoc, 0); + glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_uniformBufferGL, 0, sizeof(UniformBuffer)); + + zeus::CFrustum frustum; + frustum.updatePlanes(mv, g_Proj); + + for (const Model& model : m_models) + { + if (!frustum.aabbFrustumTest(model.aabb)) + continue; + glBindVertexArray(model.vao); + for (const Model::Surface& surf : model.surfaces) + { + // Non-transparents first + if (!surf.transparent) + glDrawElements(model.topology, surf.count, GL_UNSIGNED_INT, + reinterpret_cast(surf.first * 4)); + else + needTransparent = true; + } + } + } + + //m_swapFunc(); + glFinish(); + glReadPixels(0, 0, 768, 512, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)bufOut); +} + +void VISIRenderer::RenderPVSTransparent(const std::function& passFunc, const zeus::CVector3f& pos) +{ + glDepthMask(GL_FALSE); + + for (int j=0 ; j<6 ; ++j) + { + GLint x = (j % 3) * 256; + GLint y = (j / 3) * 256; + glViewport(x, y, 256, 256); + + zeus::CMatrix4f mv = LookMATs[j] * zeus::CTransform::Translate(-pos).toMatrix4f(); + m_uniformBuffer.m_xf = g_Proj * mv; + glBindBuffer(GL_UNIFORM_BUFFER, m_uniformBufferGL); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformBuffer), &m_uniformBuffer, GL_DYNAMIC_DRAW); + + glUniformBlockBinding(m_program, m_uniLoc, 0); + glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_uniformBufferGL, 0, sizeof(UniformBuffer)); + + zeus::CFrustum frustum; + frustum.updatePlanes(mv, g_Proj); + + int idx = 0; + for (const Model& model : m_models) + { + if (!frustum.aabbFrustumTest(model.aabb)) + { + ++idx; + continue; + } + glBindVertexArray(model.vao); + for (const Model::Surface& surf : model.surfaces) + { + // transparents + if (surf.transparent) + { + glBeginQuery(GL_ANY_SAMPLES_PASSED, m_query); + glDrawElements(model.topology, surf.count, GL_UNSIGNED_INT, + reinterpret_cast(surf.first * 4)); + glEndQuery(GL_ANY_SAMPLES_PASSED); + GLint res; + glGetQueryObjectiv(m_query, GL_QUERY_RESULT, &res); + if (res) + passFunc(idx); + } + } + ++idx; + } + } +} + +void VISIRenderer::RenderPVSEntitiesAndLights(const std::function& passFunc, + const std::function& lightPassFunc, + const zeus::CVector3f& pos) +{ + glDepthMask(GL_FALSE); + + for (int j=0 ; j<6 ; ++j) + { + GLint x = (j % 3) * 256; + GLint y = (j / 3) * 256; + glViewport(x, y, 256, 256); + + zeus::CMatrix4f mv = LookMATs[j] * zeus::CTransform::Translate(-pos).toMatrix4f(); + m_uniformBuffer.m_xf = g_Proj * mv; + glBindBuffer(GL_UNIFORM_BUFFER, m_uniformBufferGL); + glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformBuffer), &m_uniformBuffer, GL_DYNAMIC_DRAW); + + glUniformBlockBinding(m_program, m_uniLoc, 0); + glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_uniformBufferGL, 0, sizeof(UniformBuffer)); + + zeus::CFrustum frustum; + frustum.updatePlanes(mv, g_Proj); + + int idx = m_models.size(); + for (const Entity& ent : m_entities) + { + if (!frustum.aabbFrustumTest(ent.aabb)) + { + ++idx; + continue; + } + glBindVertexArray(ent.vao); + glBeginQuery(GL_ANY_SAMPLES_PASSED, m_query); + glDrawElements(GL_TRIANGLE_STRIP, 19, GL_UNSIGNED_INT, 0); + glEndQuery(GL_ANY_SAMPLES_PASSED); + GLint res; + glGetQueryObjectiv(m_query, GL_QUERY_RESULT, &res); + if (res) + passFunc(idx); + ++idx; + } + + int lightIdx = 0; + for (const Light& light : m_lights) + { + if (!frustum.pointFrustumTest(light.point)) + { + ++idx; + ++lightIdx; + continue; + } + glBindVertexArray(light.vao); + glBeginQuery(GL_ANY_SAMPLES_PASSED, m_query); + glDrawArrays(GL_POINTS, 0, 1); + glEndQuery(GL_ANY_SAMPLES_PASSED); + GLint res; + glGetQueryObjectiv(m_query, GL_QUERY_RESULT, &res); + EPVSVisSetState state = m_totalAABB.pointInside(light.point) ? + EPVSVisSetState::EndOfTree : EPVSVisSetState::OutOfBounds; + if (res) + { + passFunc(idx); + if (state == EPVSVisSetState::EndOfTree) + state = EPVSVisSetState::NodeFound; + } + lightPassFunc(lightIdx, state); + ++idx; + ++lightIdx; + } + } +} + +void VISIRenderer::Run(FPercent updatePercent) +{ + m_updatePercent = updatePercent; + CalculateProjMatrix(); + + if (glewInit() != GLEW_OK) + { + Log.report(logvisor::Error, "unable to init glew"); + m_return = 1; + return; + } + + if (!GLEW_ARB_occlusion_query2) + { + Log.report(logvisor::Error, "GL_ARB_occlusion_query2 extension not present"); + m_return = 1; + return; + } + + if (!SetupShaders()) + { + m_return = 1; + return; + } + + if (m_argc < 3) + { + Log.report(logvisor::Error, "Missing input/output file args"); + m_return = 1; + return; + } + + { + athena::io::FileReader r(m_argv[1]); + if (r.hasError()) + return; + + uint32_t modelCount = r.readUint32Big(); + m_models.resize(modelCount); + for (uint32_t i=0 ; i dataOut = builder.build(m_totalAABB, m_models.size(), + m_entities, m_lights, m_updatePercent); + if (dataOut.empty()) + { + m_return = 1; + return; + } + + athena::io::FileWriter w(m_argv[2]); + w.writeUBytes(dataOut.data(), dataOut.size()); +} + +void VISIRenderer::Terminate() +{ + m_terminate = true; +} diff --git a/visigen/VISIRenderer.hpp b/visigen/VISIRenderer.hpp new file mode 100644 index 000000000..9d60b16fa --- /dev/null +++ b/visigen/VISIRenderer.hpp @@ -0,0 +1,109 @@ +#ifndef VISIRENDERER_HPP +#define VISIRENDERER_HPP + +#include "boo/graphicsdev/glew.h" +#include "hecl/hecl.hpp" +#include "zeus/CColor.hpp" +#include "zeus/CMatrix4f.hpp" +#include "zeus/CAABox.hpp" + +typedef void(*FPercent)(float); + +enum class EPVSVisSetState +{ + EndOfTree, + NodeFound, + OutOfBounds +}; + +class VISIRenderer +{ + friend struct VISIBuilder; + + int m_argc; + const hecl::SystemChar** m_argv; + int m_return = 0; + + zeus::CAABox m_totalAABB; + + struct UniformBuffer + { + zeus::CMatrix4f m_xf; + } m_uniformBuffer; + + struct Model + { + GLenum topology; + zeus::CAABox aabb; + + struct Vert + { + zeus::CVector3f pos; + zeus::CColor color; + }; + std::vector verts; + + std::vector idxs; + GLuint vbo, ibo, vao; + + struct Surface + { + uint32_t first; + uint32_t count; + bool transparent; + }; + std::vector surfaces; + }; + + struct Entity + { + uint32_t entityId; + zeus::CAABox aabb; + GLuint vbo, vao; + }; + + struct Light + { + zeus::CVector3f point; + GLuint vbo, vao; + }; + + GLuint m_vtxShader, m_fragShader, m_program, m_uniLoc; + GLuint m_uniformBufferGL; + GLuint m_aabbIBO; + bool SetupShaders(); + + std::vector m_models; + std::vector m_entities; + std::vector m_lights; + bool SetupVertexBuffersAndFormats(); + + GLuint m_query; + + FPercent m_updatePercent; + + static std::vector AABBToVerts(const zeus::CAABox& aabb, + const zeus::CColor& color); + +public: + bool m_terminate = false; + struct RGBA8 + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + + VISIRenderer(int argc, const hecl::SystemChar** argv) : m_argc(argc), m_argv(argv) {} + void Run(FPercent updatePercent); + void Terminate(); + void RenderPVSOpaque(RGBA8* bufOut, const zeus::CVector3f& pos, bool& needTransparent); + void RenderPVSTransparent(const std::function& passFunc, const zeus::CVector3f& pos); + void RenderPVSEntitiesAndLights(const std::function& passFunc, + const std::function& lightPassFunc, + const zeus::CVector3f& pos); + int ReturnVal() const { return m_return; } +}; + +#endif // VISIRENDERER_HPP