diff --git a/hecl/blender/hecl/__init__.py b/hecl/blender/hecl/__init__.py index 64d9af098..fe4cb7ecf 100644 --- a/hecl/blender/hecl/__init__.py +++ b/hecl/blender/hecl/__init__.py @@ -146,6 +146,8 @@ def register(): sact.register() srea.register() frme.register() + mapa.register() + mapu.register() bpy.utils.register_class(hecl_scene_panel) bpy.types.Scene.hecl_auto_select = bpy.props.BoolProperty(name='HECL Auto Select', default=True) bpy.app.handlers.load_post.append(scene_loaded) diff --git a/hecl/blender/hecl/hmdl/HMDLMesh.py b/hecl/blender/hecl/hmdl/HMDLMesh.py index 95c3fe2c4..3c008ef89 100644 --- a/hecl/blender/hecl/hmdl/HMDLMesh.py +++ b/hecl/blender/hecl/hmdl/HMDLMesh.py @@ -95,6 +95,11 @@ class VertPool: for ent in entries: writebuf(struct.pack('If', ent[0], ent[1] / total_len)) + def write_out_map(self, writebuf): + writebuf(struct.pack('I', len(self.pos))) + for p in sorted(self.pos.items(), key=operator.itemgetter(1)): + writebuf(struct.pack('fff', p[0][0], p[0][1], p[0][2])) + def get_pos_idx(self, vert): pf = vert.co.copy().freeze() return self.pos[pf] @@ -160,6 +165,15 @@ class VertPool: sp = struct.pack('I', self.get_skin_idx(loop.vert)) writebuf(sp) + def loop_out_map(self, writebuf, loop): + writebuf(struct.pack('B', 1)) + writebuf(struct.pack('I', self.get_pos_idx(loop.vert))) + + def vert_out_map(self, writebuf, vert): + writebuf(struct.pack('B', 1)) + writebuf(struct.pack('I', self.get_pos_idx(vert))) + + def sort_faces_by_skin_group(dlay, faces): faces_out = [] done_sg = set() diff --git a/hecl/blender/hecl/mapa.py b/hecl/blender/hecl/mapa.py index 6f866f4a3..a71655e54 100644 --- a/hecl/blender/hecl/mapa.py +++ b/hecl/blender/hecl/mapa.py @@ -1,2 +1,167 @@ +import bpy, struct +from . import hmdl +from mathutils import Vector +VertPool = hmdl.HMDLMesh.VertPool + +def cook(writebuf, mesh_obj): + if mesh_obj.type != 'MESH': + raise RuntimeError("%s is not a mesh" % mesh_obj.name) + + # Copy mesh (and apply mesh modifiers with triangulation) + copy_name = mesh_obj.name + "_hmdltri" + copy_mesh = bpy.data.meshes.new(copy_name) + copy_obj = bpy.data.objects.new(copy_name, copy_mesh) + copy_obj.data = mesh_obj.to_mesh(bpy.context.scene, True, 'RENDER') + copy_mesh = copy_obj.data + copy_obj.scale = mesh_obj.scale + bpy.context.scene.objects.link(copy_obj) + bpy.ops.object.select_all(action='DESELECT') + bpy.context.scene.objects.active = copy_obj + copy_obj.select = True + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris() + bpy.ops.mesh.select_all(action='DESELECT') + bpy.context.scene.update() + bpy.ops.object.mode_set(mode='OBJECT') + copy_mesh.calc_normals_split() + rna_loops = copy_mesh.loops + + # Create master BMesh and VertPool + bm_master = bmesh.new() + bm_master.from_mesh(copy_obj.data) + vert_pool = VertPool(bm_master, rna_loops) + + # Output vert pool + vert_pool.write_out_map(writebuf) + + # Create map surfaces and borders + island_faces = list(bm_master.faces) + prev_loop_emit = None + out_count = 0 + loop_ranges = [] + loop_iter = 0 + while len(island_faces): + sel_lists_local = [] + restore_out_count = out_count + for start_face in island_faces: + for l in start_face.loops: + out_count = restore_out_count + island_local = list(island_faces) + if out_count & 1: + prev_loop = l.link_loop_prev + loop = prev_loop.link_loop_prev + sel_list = [l, prev_loop, loop] + prev_loop = loop + else: + prev_loop = l.link_loop_next + loop = prev_loop.link_loop_next + sel_list = [l, prev_loop, loop] + out_count += 3 + island_local.remove(start_face) + while True: + if not prev_loop.edge.is_contiguous or prev_loop.edge.seam: + break + loop, prev_loop = strip_next_loop(prev_loop, out_count) + face = loop.face + if face not in island_local: + break + sel_list.append(loop) + island_local.remove(face) + out_count += 1 + sel_lists_local.append((sel_list, island_local, out_count)) + max_count = 0 + max_sl = None + max_island_faces = None + for sl in sel_lists_local: + if len(sl[0]) > max_count: + max_count = len(sl[0]) + max_sl = sl[0] + max_island_faces = sl[1] + out_count = sl[2] + island_faces = max_island_faces + + loop_set = set() + edge_set = set() + loop_count = len(max_sl) + if prev_loop_emit: + vert_pool.loop_out_map(writebuf, prev_loop_emit) + vert_pool.loop_out_map(writebuf, max_sl[0]) + loop_count += 2 + loop_set.add(prev_loop_emit) + loop_set.add(max_sl[0]) + for loop in max_sl: + vert_pool.loop_out_map(writebuf, loop) + prev_loop_emit = loop + loop_set.add(loop) + for edge in loop.face.edges: + if edge.seam: + edge_set.add(edge) + + trace_edge = edge_set.pop() + edge_iter = loop_iter + loop_count + edge_count = 0 + if trace_edge: + vert_pool.vert_out_map(writebuf, trace_edge.verts[0]) + vert_pool.vert_out_map(writebuf, trace_edge.verts[1]) + edge_count += 2 + next_vert = trace_edge.verts[1] + found_edge = True + while found_edge: + found_edge = False + for edge in next_vert.link_edges: + if edge in edge_set: + edge_set.remove(edge) + next_vert = edge.other_vert(next_vert) + vert_pool.vert_out_map(writebuf, next_vert) + edge_count += 1 + found_edge = True + break + + pos_avg = Vector() + norm_avg = Vector() + if len(loop_set): + for loop in loop_set: + pos_avg += loop.co + norm_avg += loop.normal + pos_avg /= len(loop_set) + norm_avg /= len(loop_set) + norm_avg.normalize() + + loop_ranges.append((loop_iter, loop_count, edge_iter, edge_count, pos_avg, norm_avg)) + loop_iter += loop_count + edge_count + + # No more surfaces + writebuf(struct.pack('B', 0)) + + # Write out loop ranges and averages + writebuf(struct.pack('I', len(loop_ranges))) + for loop_range in loop_ranges: + writebuf(struct.pack('fff', loop_range[4][0], loop_range[4][1], loop_range[4][2])) + writebuf(struct.pack('fff', loop_range[5][0], loop_range[5][1], loop_range[5][2])) + writebuf(struct.pack('IIII', loop_range[0], loop_range[1], loop_range[2], loop_range[3])) + + # Write out mappable objects + poi_count = 0 + for obj in bpy.context.scene.objects: + if obj.retro_mappable_type != -1: + poi_count += 1 + + writebuf(struct.pack('I', poi_count)) + for obj in bpy.context.scene.objects: + if obj.retro_mappable_type != -1: + writebuf(struct.pack('III', + obj.retro_mappable_type, obj.retro_mappable_unk, obj.retro_mappable_sclyid)) + writebuf(struct.pack('ffffffffffffffff', + obj.matrix_world[0][0], obj.matrix_world[0][1], obj.matrix_world[0][2], obj.matrix_world[0][3], + obj.matrix_world[1][0], obj.matrix_world[1][1], obj.matrix_world[1][2], obj.matrix_world[1][3], + obj.matrix_world[2][0], obj.matrix_world[2][1], obj.matrix_world[2][2], obj.matrix_world[2][3], + obj.matrix_world[3][0], obj.matrix_world[3][1], obj.matrix_world[3][2], obj.matrix_world[3][3])) + def draw(layout, context): pass + +def register(): + bpy.types.Object.retro_mappable_type = bpy.props.IntProperty(name='Retro: MAPA object type', default=-1) + bpy.types.Object.retro_mappable_unk = bpy.props.IntProperty(name='Retro: MAPA object unk') + bpy.types.Object.retro_mappable_sclyid = bpy.props.StringProperty(name='Retro: MAPA object SCLY ID') diff --git a/hecl/blender/hecl/mapu.py b/hecl/blender/hecl/mapu.py index 6f866f4a3..bd31468fb 100644 --- a/hecl/blender/hecl/mapu.py +++ b/hecl/blender/hecl/mapu.py @@ -1,2 +1,49 @@ +import bpy + +def cook(writebuf): + found_lib = False + for obj in bpy.context.scene.objects: + if obj.library: + path = os.path.normpath(bpy.path.abspath(obj.library.filepath)) + writebuf(struct.pack('I', len(path))) + writebuf(path.encode()) + found_lib = True + break + if not found_lib: + raise RuntimeError('No hexagon segments present') + + for obj in bpy.context.scene.objects: + if not obj.parent and obj.type == 'EMPTY': + writebuf(struct.pack('I', len(obj.name))) + writebuf(obj.name.encode()) + writebuf(struct.pack('ffffffffffffffff', + obj.matrix_local[0][0], obj.matrix_local[0][1], obj.matrix_local[0][2], obj.matrix_local[0][3], + obj.matrix_local[1][0], obj.matrix_local[1][1], obj.matrix_local[1][2], obj.matrix_local[1][3], + obj.matrix_local[2][0], obj.matrix_local[2][1], obj.matrix_local[2][2], obj.matrix_local[2][3], + obj.matrix_local[3][0], obj.matrix_local[3][1], obj.matrix_local[3][2], obj.matrix_local[3][3])) + writebuf(struct.pack('I', len(obj.children))) + for child in obj.children: + writebuf(struct.pack('ffffffffffffffff', + child.matrix_local[0][0], child.matrix_local[0][1], child.matrix_local[0][2], child.matrix_local[0][3], + child.matrix_local[1][0], child.matrix_local[1][1], child.matrix_local[1][2], child.matrix_local[1][3], + child.matrix_local[2][0], child.matrix_local[2][1], child.matrix_local[2][2], child.matrix_local[2][3], + child.matrix_local[3][0], child.matrix_local[3][1], child.matrix_local[3][2], child.matrix_local[3][3])) + writebuf(struct.pack('ffff', obj.retro_mapworld_color[0], obj.retro_mapworld_color[1], + obj.retro_mapworld_color[2], obj.retro_mapworld_color[3])) + writebuf(struct.pack('I', len(obj.retro_mapworld_path))) + writebuf(obj.retro_mapworld_path.encode()) + def draw(layout, context): - pass + obj = context.active_object + if not obj: + return + while obj.parent: + obj = obj.parent + layout.prop(obj, 'retro_mapworld_color', text='Color') + layout.prop(obj, 'retro_mapworld_path', text='Path') + +# Registration +def register(): + bpy.types.Object.retro_mapworld_color = bpy.props.FloatVectorProperty(name='Retro: MapWorld Color',\ + description='Sets map world color', subtype='COLOR', size=4, min=0.0, max=1.0) + bpy.types.Object.retro_mapworld_path = bpy.props.StringProperty(name='Retro: MapWorld Path', description='Sets path to World root') diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index 287a79ee3..ee0d2c7bb 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -417,6 +417,21 @@ def dataout_loop(): hecl.srea.render_pvs_light(pathOut, lightName) writepipestr(b'OK') + elif cmdargs[0] == 'MAPAREACOMPILE': + if 'MAP' not in bpy.data.objects: + writepipestr(('"MAP" object not in .blend').encode()) + continue + map_obj = bpy.data.objects['MAP'] + if map_obj.type != 'MESH': + writepipestr(('object "MAP" not a MESH').encode()) + continue + writepipestr(b'OK') + hecl.mapa.cook(writepipebuf, map_obj) + + elif cmdargs[0] == 'MAPUNIVERSECOMPILE': + writepipestr(b'OK') + hecl.mapu.cook(writepipebuf) + loaded_blend = None # Main exception handling diff --git a/hecl/extern/boo b/hecl/extern/boo index 5f903c09e..9a7cadce3 160000 --- a/hecl/extern/boo +++ b/hecl/extern/boo @@ -1 +1 @@ -Subproject commit 5f903c09eead505d8af39b8b5c3c1f2550477f97 +Subproject commit 9a7cadce3a7de43cb9b3823439e97071395015b7 diff --git a/hecl/include/hecl/Blender/BlenderConnection.hpp b/hecl/include/hecl/Blender/BlenderConnection.hpp index 5dfa43a50..c862b14e0 100644 --- a/hecl/include/hecl/Blender/BlenderConnection.hpp +++ b/hecl/include/hecl/Blender/BlenderConnection.hpp @@ -715,6 +715,51 @@ public: Light(BlenderConnection& conn); }; + /** Intermediate MapArea representation */ + struct MapArea + { + std::vector verts; + std::vector indices; + struct Surface + { + Vector3f normal; + Vector3f centerOfMass; + Index start; + Index count; + Index borderStart; + Index borderCount; + Surface(BlenderConnection& conn); + }; + std::vector surfaces; + struct POI + { + uint32_t type; + uint32_t unk; + uint32_t objid; + Matrix4f xf; + POI(BlenderConnection& conn); + }; + std::vector pois; + MapArea(BlenderConnection& conn); + }; + + /** Intermediate MapUniverse representation */ + struct MapUniverse + { + hecl::ProjectPath hexagonPath; + struct World + { + std::string name; + Matrix4f xf; + std::vector hexagons; + Vector4f color; + hecl::ProjectPath worldPath; + World(BlenderConnection& conn); + }; + std::vector worlds; + MapUniverse(BlenderConnection& conn); + }; + static const char* MeshOutputModeString(HMDLTopology topology) { static const char* STRS[] = {"TRIANGLES", "TRISTRIPS"}; @@ -854,6 +899,9 @@ public: bool renderPvs(const std::string& path, const atVec3f& location); bool renderPvsLight(const std::string& path, const std::string& lightName); + + MapArea compileMapArea(); + MapUniverse compileMapUniverse(); }; DataStream beginData() { diff --git a/hecl/lib/Blender/BlenderConnection.cpp b/hecl/lib/Blender/BlenderConnection.cpp index e2e93238e..9cd61e90d 100644 --- a/hecl/lib/Blender/BlenderConnection.cpp +++ b/hecl/lib/Blender/BlenderConnection.cpp @@ -1094,6 +1094,104 @@ BlenderConnection::DataStream::Light::Light(BlenderConnection& conn) } } +BlenderConnection::DataStream::MapArea::Surface::Surface(BlenderConnection& conn) +{ + centerOfMass.read(conn); + normal.read(conn); + conn._readBuf(&start, 16); +} + +BlenderConnection::DataStream::MapArea::POI::POI(BlenderConnection& conn) +{ + conn._readBuf(&type, 12); + xf.read(conn); +} + +BlenderConnection::DataStream::MapArea::MapArea(BlenderConnection& conn) +{ + uint32_t vertCount; + conn._readBuf(&vertCount, 4); + verts.reserve(vertCount); + for (int i=0 ; i_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); + m_parent->m_loadedBlend.getAbsolutePathUTF8().c_str(), readBuf); return true; } @@ -1695,11 +1793,45 @@ bool BlenderConnection::DataStream::renderPvsLight(const std::string& path, cons 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); + m_parent->m_loadedBlend.getAbsolutePathUTF8().c_str(), readBuf); return true; } +BlenderConnection::DataStream::MapArea BlenderConnection::DataStream::compileMapArea() +{ + if (m_parent->m_loadedType != BlendType::MapArea) + BlenderLog.report(logvisor::Fatal, _S("%s is not a MAPAREA blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + m_parent->_writeStr("MAPAREACOMPILE"); + + char readBuf[256]; + m_parent->_readStr(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(logvisor::Fatal, "unable to compile map area: %s; %s", + m_parent->m_loadedBlend.getAbsolutePathUTF8().c_str(), readBuf); + + return {*m_parent}; +} + +BlenderConnection::DataStream::MapUniverse BlenderConnection::DataStream::compileMapUniverse() +{ + if (m_parent->m_loadedType != BlendType::MapUniverse) + BlenderLog.report(logvisor::Fatal, _S("%s is not a MAPUNIVERSE blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + m_parent->_writeStr("MAPUNIVERSECOMPILE"); + + char readBuf[256]; + m_parent->_readStr(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(logvisor::Fatal, "unable to compile map universe: %s; %s", + m_parent->m_loadedBlend.getAbsolutePathUTF8().c_str(), readBuf); + + return {*m_parent}; +} + void BlenderConnection::quitBlender() { char lineBuf[256];