From 8c3a7da61657d277a5b260304dcfdaec692ffb51 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Thu, 23 Feb 2017 22:27:07 -1000 Subject: [PATCH] Updates to support VISI generation --- hecl/blender/hecl/__init__.py | 38 +++++++++- hecl/blender/hecl/hmdl/__init__.py | 7 ++ hecl/blender/hecl/srea/__init__.py | 75 +++++++++++++++++++ hecl/blender/hecl_blendershell.py | 31 ++++++++ hecl/driver/main.cpp | 11 ++- hecl/extern/boo | 2 +- .../hecl/Blender/BlenderConnection.hpp | 42 ++++++++++- hecl/include/hecl/hecl.hpp | 4 + hecl/lib/Blender/BlenderConnection.cpp | 62 +++++++++++++-- hecl/lib/hecl.cpp | 33 ++++++++ 10 files changed, 295 insertions(+), 10 deletions(-) diff --git a/hecl/blender/hecl/__init__.py b/hecl/blender/hecl/__init__.py index f04c24b0a..64d9af098 100644 --- a/hecl/blender/hecl/__init__.py +++ b/hecl/blender/hecl/__init__.py @@ -11,8 +11,9 @@ bl_info = { # Package import from . import hmdl, sact, srea, swld, mapa, mapu, frme, Nodegrid, Patching Nodegrid = Nodegrid.Nodegrid -import bpy, os, sys +import bpy, os, sys, struct from bpy.app.handlers import persistent +from mathutils import Vector # Appendable list allowing external addons to register additional resource types @@ -69,6 +70,41 @@ def add_export_type(type_tuple): def command(cmdline, writepipeline, writepipebuf): pass +def mesh_aabb(writepipebuf): + scene = bpy.context.scene + total_min = Vector((99999.0, 99999.0, 99999.0)) + total_max = Vector((-99999.0, -99999.0, -99999.0)) + + if bpy.context.scene.hecl_type == 'ACTOR': + sact_data = bpy.context.scene.hecl_sact_data + for subtype in sact_data.subtypes: + if subtype.linked_mesh in bpy.data.objects: + mesh = bpy.data.objects[subtype.linked_mesh] + minPt = mesh.bound_box[0] + maxPt = mesh.bound_box[6] + for comp in range(3): + if minPt[comp] < total_min[comp]: + total_min[comp] = minPt[comp] + for comp in range(3): + if maxPt[comp] > total_max[comp]: + total_max[comp] = maxPt[comp] + + elif bpy.context.scene.hecl_type == 'MESH': + meshName = bpy.context.scene.hecl_mesh_obj + if meshName in bpy.data.objects: + mesh = bpy.data.objects[meshName] + minPt = mesh.bound_box[0] + maxPt = mesh.bound_box[6] + for comp in range(3): + if minPt[comp] < total_min[comp]: + total_min[comp] = minPt[comp] + for comp in range(3): + if maxPt[comp] > total_max[comp]: + total_max[comp] = maxPt[comp] + + writepipebuf(struct.pack('fff', total_min[0], total_min[1], total_min[2])) + writepipebuf(struct.pack('fff', total_max[0], total_max[1], total_max[2])) + # Load scene callback from bpy.app.handlers import persistent @persistent diff --git a/hecl/blender/hecl/hmdl/__init__.py b/hecl/blender/hecl/hmdl/__init__.py index 4368441f7..8707018b1 100644 --- a/hecl/blender/hecl/hmdl/__init__.py +++ b/hecl/blender/hecl/hmdl/__init__.py @@ -25,6 +25,13 @@ def write_out_material(writebuf, mat, mesh_obj): writebuf(prop[0].encode()) writebuf(struct.pack('i', prop[1])) + transparent = False + if mat.game_settings.alpha_blend == 'ALPHA' or mat.game_settings.alpha_blend == 'ALPHA_SORT': + transparent = True + elif mat.game_settings.alpha_blend == 'ADD': + transparent = True + writebuf(struct.pack('b', int(transparent))) + # Takes a Blender 'Mesh' object (not the datablock) # and performs a one-shot conversion process to HMDL def cook(writebuf, mesh_obj, output_mode, max_skin_banks, max_octant_length=None): diff --git a/hecl/blender/hecl/srea/__init__.py b/hecl/blender/hecl/srea/__init__.py index 0a8184913..b61542863 100644 --- a/hecl/blender/hecl/srea/__init__.py +++ b/hecl/blender/hecl/srea/__init__.py @@ -1,5 +1,7 @@ import bpy from bpy.app.handlers import persistent +from mathutils import Quaternion, Color +import math from .. import Nodegrid # Preview update func (for lighting preview) @@ -404,6 +406,79 @@ class SREARenderLightmaps(bpy.types.Operator): return {'FINISHED'} +def shadeless_material(idx): + name = 'SHADELESS_MAT_%d' % idx + if name in bpy.data.materials: + return bpy.data.materials[name] + mat = bpy.data.materials.new(name) + mat.use_shadeless = True + r = idx % 256 + g = (idx % 65536) // 256 + b = idx // 65536 + mat.diffuse_color = Color((r / 255.0, g / 255.0, b / 255.0)) + return mat + +look_forward = Quaternion((1.0, 0.0, 0.0), math.radians(90.0)) +look_backward = Quaternion((0.0, 0.0, 1.0), math.radians(180.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0)) +look_up = Quaternion((1.0, 0.0, 0.0), math.radians(180.0)) +look_down = Quaternion((1.0, 0.0, 0.0), math.radians(0.0)) +look_left = Quaternion((0.0, 0.0, 1.0), math.radians(90.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0)) +look_right = Quaternion((0.0, 0.0, 1.0), math.radians(-90.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0)) +look_list = (look_forward, look_backward, look_up, look_down, look_left, look_right) + +# Render PVS for location +def render_pvs(pathOut, location): + bpy.context.scene.render.resolution_x = 256 + bpy.context.scene.render.resolution_y = 256 + bpy.context.scene.render.resolution_percentage = 100 + bpy.context.scene.render.use_antialiasing = False + bpy.context.scene.render.use_textures = False + bpy.context.scene.render.use_shadows = False + bpy.context.scene.render.use_sss = False + bpy.context.scene.render.use_envmaps = False + bpy.context.scene.render.use_raytrace = False + bpy.context.scene.render.engine = 'BLENDER_RENDER' + bpy.context.scene.display_settings.display_device = 'None' + bpy.context.scene.render.image_settings.file_format = 'PNG' + bpy.context.scene.world.horizon_color = Color((1.0, 1.0, 1.0)) + bpy.context.scene.world.zenith_color = Color((1.0, 1.0, 1.0)) + + cam = bpy.data.cameras.new('CUBIC_CAM') + cam_obj = bpy.data.objects.new('CUBIC_CAM', cam) + bpy.context.scene.objects.link(cam_obj) + bpy.context.scene.camera = cam_obj + cam.lens_unit = 'FOV' + cam.angle = math.radians(90.0) + + mat_idx = 0 + for obj in bpy.data.objects: + if obj.type == 'MESH': + if obj.name == 'CMESH': + continue + mat = shadeless_material(mat_idx) + for slot in obj.material_slots: + slot.material = mat + mat_idx += 1 + + cam_obj.location = location + cam_obj.rotation_mode = 'QUATERNION' + + for i in range(6): + cam_obj.rotation_quaternion = look_list[i] + bpy.context.scene.render.filepath = '%s%d' % (pathOut, i) + bpy.ops.render.render(write_still=True) + + bpy.context.scene.camera = None + bpy.context.scene.objects.unlink(cam_obj) + bpy.data.objects.remove(cam_obj) + bpy.data.cameras.remove(cam) + +# Render PVS for light +def render_pvs_light(pathOut, lightName): + if lightName not in bpy.data.objects: + raise RuntimeError('Unable to find light %s' % lightName) + render_pvs(pathOut, bpy.data.objects[lightName].location) + # Cook def cook(writebuffunc, platform, endianchar): print('COOKING SREA') diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index 9cfd115e9..287a79ee3 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -188,6 +188,20 @@ def dataout_loop(): if meshobj.type == 'MESH' and not meshobj.library: writepipestr(meshobj.name.encode()) + elif cmdargs[0] == 'LIGHTLIST': + lightCount = 0 + for obj in bpy.context.scene.objects: + if obj.type == 'LAMP' and not obj.library: + lightCount += 1 + writepipebuf(struct.pack('I', lightCount)) + for obj in bpy.context.scene.objects: + if obj.type == 'LAMP' and not obj.library: + writepipestr(obj.name.encode()) + + elif cmdargs[0] == 'MESHAABB': + writepipestr(b'OK') + hecl.mesh_aabb(writepipebuf) + elif cmdargs[0] == 'MESHCOMPILE': maxSkinBanks = int(cmdargs[2]) @@ -281,6 +295,7 @@ def dataout_loop(): 0.0, 0.0, 0.0, 1.0)) writepipebuf(struct.pack('fff', ambient_color[0], ambient_color[1], ambient_color[2])) writepipebuf(struct.pack('IIfffffb', 0, 0, ambient_energy, 0.0, 1.0, 0.0, 0.0, False)) + writepipestr(b'AMBIENT') for obj in bpy.context.scene.objects: if obj.type == 'LAMP': @@ -325,6 +340,8 @@ def dataout_loop(): writepipebuf(struct.pack('IIfffffb', layer, type, obj.data.energy, spotCutoff, constant, linear, quadratic, castShadow)) + writepipestr(obj.name.encode()) + elif cmdargs[0] == 'GETTEXTURES': writepipestr(b'OK') @@ -386,6 +403,20 @@ def dataout_loop(): for c in r: writepipebuf(struct.pack('f', c)) + elif cmdargs[0] == 'RENDERPVS': + pathOut = cmdargs[1] + locX = float(cmdargs[2]) + locY = float(cmdargs[3]) + locZ = float(cmdargs[4]) + hecl.srea.render_pvs(pathOut, (locX, locY, locZ)) + writepipestr(b'OK') + + elif cmdargs[0] == 'RENDERPVSLIGHT': + pathOut = cmdargs[1] + lightName = cmdargs[2] + hecl.srea.render_pvs_light(pathOut, lightName) + writepipestr(b'OK') + loaded_blend = None # Main exception handling diff --git a/hecl/driver/main.cpp b/hecl/driver/main.cpp index ac36ba81a..8d03f0450 100644 --- a/hecl/driver/main.cpp +++ b/hecl/driver/main.cpp @@ -85,6 +85,9 @@ static void AthenaExc(athena::error::Level level, const char* file, va_end(ap); } +static hecl::SystemChar cwdbuf[1024]; +hecl::SystemString ExeDir; + #if _WIN32 int wmain(int argc, const wchar_t** argv) #else @@ -138,7 +141,6 @@ int main(int argc, const char** argv) /* Assemble common tool pass info */ ToolPassInfo info; info.pname = argv[0]; - hecl::SystemChar cwdbuf[1024]; if (hecl::Getcwd(cwdbuf, 1024)) { info.cwd = cwdbuf; @@ -148,6 +150,13 @@ int main(int argc, const char** argv) #else info.cwd += _S('/'); #endif + + if (argv[0][0] != _S('/') && argv[0][0] != _S('\\')) + ExeDir = hecl::SystemString(cwdbuf) + _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); } /* Concatenate args */ diff --git a/hecl/extern/boo b/hecl/extern/boo index 245a39fd9..0cc794f49 160000 --- a/hecl/extern/boo +++ b/hecl/extern/boo @@ -1 +1 @@ -Subproject commit 245a39fd92da80af40dcaf5e7567ff0b4cbf9a9a +Subproject commit 0cc794f49d8884c30ba43b195aecdfed14062a53 diff --git a/hecl/include/hecl/Blender/BlenderConnection.hpp b/hecl/include/hecl/Blender/BlenderConnection.hpp index 9dc46b80d..b6300e203 100644 --- a/hecl/include/hecl/Blender/BlenderConnection.hpp +++ b/hecl/include/hecl/Blender/BlenderConnection.hpp @@ -354,6 +354,40 @@ public: return retval; } + std::vector getLightList() + { + m_parent->_writeStr("LIGHTLIST"); + uint32_t count; + m_parent->_readBuf(&count, 4); + std::vector retval; + retval.reserve(count); + for (uint32_t i=0 ; i_readStr(name, 128); + retval.push_back(name); + } + return retval; + } + + std::pair getMeshAABB() + { + if (m_parent->m_loadedType != BlendType::Mesh && + m_parent->m_loadedType != BlendType::Actor) + BlenderLog.report(logvisor::Fatal, _S("%s is not a MESH or ACTOR blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + m_parent->_writeStr("MESHAABB"); + char readBuf[256]; + m_parent->_readStr(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(logvisor::Fatal, "unable get AABB: %s", readBuf); + + Vector3f min(*m_parent); + Vector3f max(*m_parent); + return std::make_pair(min.val, max.val); + } + /* Vector types with integrated stream reading constructor */ struct Vector2f { @@ -418,6 +452,7 @@ public: std::string source; std::vector texs; std::unordered_map iprops; + bool transparent; Material(BlenderConnection& conn); bool operator==(const Material& other) const @@ -653,7 +688,7 @@ public: /** Intermediate lamp representation */ struct Light - { + { /* Object transform in scene */ Matrix4f sceneXf; Vector3f color; @@ -675,6 +710,8 @@ public: float quadratic; bool shadow; + std::string name; + Light(BlenderConnection& conn); }; @@ -814,6 +851,9 @@ public: inline const atVec3f& operator[](size_t idx) const {return m[idx];} }; std::unordered_map getBoneMatrices(const std::string& name); + + bool renderPvs(const std::string& path, const atVec3f& location); + bool renderPvsLight(const std::string& path, const std::string& lightName); }; DataStream beginData() { diff --git a/hecl/include/hecl/hecl.hpp b/hecl/include/hecl/hecl.hpp index 56b80fd9f..a99ad4058 100644 --- a/hecl/include/hecl/hecl.hpp +++ b/hecl/include/hecl/hecl.hpp @@ -264,6 +264,10 @@ static inline bool IsAbsolute(const SystemString& path) return false; } +const SystemChar* GetTmpDir(); + +int RunProcess(const SystemChar* path, const SystemChar* const args[]); + enum class FileLockType { None = 0, diff --git a/hecl/lib/Blender/BlenderConnection.cpp b/hecl/lib/Blender/BlenderConnection.cpp index 64f911602..01d97ebe0 100644 --- a/hecl/lib/Blender/BlenderConnection.cpp +++ b/hecl/lib/Blender/BlenderConnection.cpp @@ -237,16 +237,11 @@ BlenderConnection::BlenderConnection(int verbosityLevel) BlenderLog.report(logvisor::Info, "Establishing BlenderConnection..."); /* Put hecl_blendershell.py in temp dir */ + const SystemChar* TMPDIR = GetTmpDir(); #ifdef _WIN32 - wchar_t* TMPDIR = _wgetenv(L"TEMP"); - if (!TMPDIR) - TMPDIR = (wchar_t*)L"\\Temp"; m_startupBlend = hecl::WideToUTF8(TMPDIR); #else signal(SIGPIPE, SIG_IGN); - char* TMPDIR = getenv("TMPDIR"); - if (!TMPDIR) - TMPDIR = (char*)"/tmp"; m_startupBlend = TMPDIR; #endif @@ -905,6 +900,8 @@ BlenderConnection::DataStream::Mesh::Material::Material conn._readBuf(&val, 4); iprops[readStr] = val; } + + conn._readBuf(&transparent, 1); } BlenderConnection::DataStream::Mesh::Surface::Surface @@ -1088,6 +1085,14 @@ BlenderConnection::DataStream::Light::Light(BlenderConnection& conn) : sceneXf(conn), color(conn) { conn._readBuf(&layer, 29); + + uint32_t nameLen; + conn._readBuf(&nameLen, 4); + if (nameLen) + { + name.assign(nameLen, '\0'); + conn._readBuf(&name[0], nameLen); + } } BlenderConnection::DataStream::Actor::Actor(BlenderConnection& conn) @@ -1662,6 +1667,51 @@ BlenderConnection::DataStream::getBoneMatrices(const std::string& name) } +bool BlenderConnection::DataStream::renderPvs(const std::string& path, const atVec3f& location) +{ + if (path.empty()) + return false; + + if (m_parent->m_loadedType != BlendType::Area) + BlenderLog.report(logvisor::Fatal, _S("%s is not an AREA blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + char req[256]; + snprintf(req, 256, "RENDERPVS %s %f %f %f", path.c_str(), + location.vec[0], location.vec[1], location.vec[2]); + m_parent->_writeStr(req); + + char readBuf[256]; + m_parent->_readStr(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(logvisor::Fatal, "unable to render PVS for: %s; %s", + m_parent->m_loadedBlend.getAbsolutePath().c_str(), readBuf); + + return true; +} + +bool BlenderConnection::DataStream::renderPvsLight(const std::string& path, const std::string& lightName) +{ + if (path.empty()) + return false; + + if (m_parent->m_loadedType != BlendType::Area) + BlenderLog.report(logvisor::Fatal, _S("%s is not an AREA blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + char req[256]; + snprintf(req, 256, "RENDERPVSLIGHT %s %s", path.c_str(), lightName.c_str()); + m_parent->_writeStr(req); + + char readBuf[256]; + m_parent->_readStr(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(logvisor::Fatal, "unable to render PVS light %s for: %s; %s", lightName.c_str(), + m_parent->m_loadedBlend.getAbsolutePath().c_str(), readBuf); + + return true; +} + void BlenderConnection::quitBlender() { char lineBuf[256]; diff --git a/hecl/lib/hecl.cpp b/hecl/lib/hecl.cpp index 95e903818..234097f81 100644 --- a/hecl/lib/hecl.cpp +++ b/hecl/lib/hecl.cpp @@ -744,4 +744,37 @@ int RecursiveMakeDir(const SystemChar* dir) { } #endif +const SystemChar* GetTmpDir() +{ +#ifdef _WIN32 + wchar_t* TMPDIR = _wgetenv(L"TEMP"); + if (!TMPDIR) + TMPDIR = (wchar_t*)L"\\Temp"; +#else + char* TMPDIR = getenv("TMPDIR"); + if (!TMPDIR) + TMPDIR = (char*)"/tmp"; +#endif + return TMPDIR; +} + +int RunProcess(const SystemChar* path, const SystemChar* const args[]) +{ +#ifdef _WIN32 +#else + pid_t pid = fork(); + if (!pid) + { + execvp(path, (char * const *)args); + exit(1); + } + int ret; + if (waitpid(pid, &ret, 0) < 0) + return -1; + if (WIFEXITED(ret)) + return WEXITSTATUS(ret); + return -1; +#endif +} + }