Serge25 Posted October 22 Posted October 22 I have been struggling with the 'decals.dat' decals from the game 'The Swarm' (MorphX) for a long time. I wrote a parser template for '010 Editor', it gives the values, but the decals are rotated incorrectly (either the 3x2 matrix is incomplete in the data or something else...). Maybe someone knows the solution? decals.zip Swarm-Decals (BT).zip
Engineers shak-otay Posted October 22 Engineers Posted October 22 1 hour ago, Serge25 said: Maybe someone knows the solution? Hard to tell from what you provide, I see points only: Maybe focus on the akm_concrete_ objects (16, afaics), visualize them (including the rotations you've found) and compare the angles to the ones to be expected (maybe the concrete blocks build a wall in game?). [63] 26 12.5 -13.6 [68] 26 13 -14.1 [69] 26 12.6 -14.2 ...
Serge25 Posted October 23 Author Posted October 23 Yes, these points are the positions for the projector (instead of it, I temporarily use a regular cube). Decal meshes are created later, but that's not the point. The positions of the projector cubes (they are also not all there for some reason:() and the scale is correct, but the rotation is wrong... I do this 'project' in Unity (it's more convenient).
Serge25 Posted October 23 Author Posted October 23 Here in this example you can see (here are two identical fragments of level one with correct decals and it was captured through 'Ninja Ripper'), the other I recreate from the level data and decals.
Engineers shak-otay Posted October 23 Engineers Posted October 23 (edited) 1 hour ago, Serge25 said: (here are two identical fragments of level Thanks, but I don't see the two fragments in that picture. (Did you miss to upload the NJ screenshot?) Edited October 23 by shak-otay
Serge25 Posted October 23 Author Posted October 23 They are superimposed on each other - the screenshot shows their overlay. White is a piece of the level with NinjaRipper (there are baked decals in it, one of which I highlighted in orange in the screenshot), textured in a checkerboard texture, this is obtained in Unity from the chunk-mdl level of the game itself. In this old game, I want to get the interesting ambient level.
Engineers shak-otay Posted October 23 Engineers Posted October 23 When you check Decals[57] = struct { Name = Char[32] ('akm_concrete_02\x00\x00\x00\x00\x00') Position = struct { x = Float(26.0) y = Float(13.125176429748535) z = Float(-11.330219268798828) } Rot = struct { m1 = Float(1.0) m2 = Float(0.0) m3 = Float(0.0) m4 = Float(0.0) m5 = Float(1.0) m6 = Float(0.0) } same rot for Decals[63], [68], [69]..., [75],[79],[80]-[84] Do they really all have the same rotations in game?
Solution Serge25 Posted October 25 Author Solution Posted October 25 The issue is closed. The solution is to restore the first three rotation data (the first position in the 3x3 matrix). Here's the script for Blender v3.3: #Script for Blender v3.3 import bpy import os import mathutils import math from mathutils import Matrix, Vector, Euler from bpy_extras.io_utils import ImportHelper from bpy.props import StringProperty class TargemDecal: def __init__(self): self.name = "" self.position = Vector((0, 0, 0)) self.rotation_matrix = [0.0] * 6 self.scale = Vector((1, 1, 1)) self.flags = 0 def read_null_terminated_string(data, offset, max_length=32): chars = [] for i in range(max_length): char = data[offset + i] if char == 0: break chars.append(chr(char)) return ''.join(chars), offset + max_length def read_float(data, offset): import struct value = struct.unpack('<f', data[offset:offset+4])[0] return value, offset + 4 def read_uint(data, offset): import struct value = struct.unpack('<I', data[offset:offset+4])[0] return value, offset + 4 def parse_dat_file(filepath): decals = [] try: with open(filepath, 'rb') as file: data = file.read() offset = 0 magic_number, offset = read_uint(data, offset) decals_count, offset = read_uint(data, offset) print(f"Magic number: {magic_number}") print(f"Number of decals: {decals_count}") for i in range(decals_count): decal = TargemDecal() decal.name, offset = read_null_terminated_string(data, offset, 32) x, offset = read_float(data, offset) y, offset = read_float(data, offset) z, offset = read_float(data, offset) decal.position = Vector((x, y, z)) for j in range(6): decal.rotation_matrix[j], offset = read_float(data, offset) scale_x, offset = read_float(data, offset) scale_y, offset = read_float(data, offset) scale_z, offset = read_float(data, offset) decal.scale = Vector((scale_x, scale_y, scale_z)) decal.flags, offset = read_uint(data, offset) decals.append(decal) print(f"Decal {i}: {decal.name}, Pos: {decal.position}, Scale: {decal.scale}") except Exception as e: print(f"Error parsing file: {e}") return [] return decals def rotation_matrix_to_euler(rotation_matrix): try: v1 = Vector((rotation_matrix[0], rotation_matrix[1], rotation_matrix[2])) v2 = Vector((rotation_matrix[3], rotation_matrix[4], rotation_matrix[5])) v0 = v1.cross(v2) if v0.length > 0: v0.normalize() if v1.length > 0: v1.normalize() if v2.length > 0: v2.normalize() rotation_matrix_3x3 = Matrix(( (v0.x, v1.x, v2.x), (v0.y, v1.y, v2.y), (v0.z, v1.z, v2.z) )) euler_rotation = rotation_matrix_3x3.to_euler('XYZ') return euler_rotation except Exception as e: print(f"Error converting rotation matrix: {e}") return Euler((0, 0, 0), 'XYZ') def convert_to_blender_coordinates(position, rotation_matrix, scale): conversion_matrix = Matrix(( (1, 0, 0), (0, 0, 1), (0, 1, 0) )) pos_vec = Vector((position.x, position.y, position.z)) blender_position = conversion_matrix @ pos_vec scale_vec = Vector((scale.x, scale.y, scale.z)) blender_scale = conversion_matrix @ scale_vec euler_rotation = rotation_matrix_to_euler(rotation_matrix) rotation_mat = euler_rotation.to_matrix() blender_rotation_mat = conversion_matrix @ rotation_mat @ conversion_matrix.transposed() blender_rotation = blender_rotation_mat.to_euler('XYZ') return blender_position, blender_rotation, blender_scale def create_green_material(): material_name = "GreenDecalMaterial" if material_name in bpy.data.materials: mat = bpy.data.materials[material_name] else: mat = bpy.data.materials.new(name=material_name) mat.use_nodes = True nodes = mat.node_tree.nodes for node in nodes: nodes.remove(node) output_node = nodes.new(type='ShaderNodeOutputMaterial') principled_node = nodes.new(type='ShaderNodeBsdfPrincipled') principled_node.inputs[0].default_value = (0.0, 0.6, 0.0, 1.0) # Base Color principled_node.inputs[9].default_value = 0.8 # Roughness mat.node_tree.links.new(principled_node.outputs['BSDF'], output_node.inputs['Surface']) mat.shadow_method = 'NONE' return mat def create_decals_from_file(filepath): decals = parse_dat_file(filepath) if not decals: print("No decals found or error parsing file") return green_material = create_green_material() collection_name = os.path.basename(filepath) if collection_name in bpy.data.collections: decal_collection = bpy.data.collections[collection_name] else: decal_collection = bpy.data.collections.new(collection_name) bpy.context.scene.collection.children.link(decal_collection) for i, decal in enumerate(decals): blender_position, blender_rotation, blender_scale = convert_to_blender_coordinates( decal.position, decal.rotation_matrix, decal.scale ) bpy.ops.mesh.primitive_cube_add(size=1.0, location=blender_position) cube = bpy.context.active_object cube.name = f"Decal_{i}_{decal.name}" cube.rotation_euler = blender_rotation cube.scale = blender_scale if cube.data.materials: cube.data.materials[0] = green_material else: cube.data.materials.append(green_material) if cube.name in bpy.context.scene.collection.objects: bpy.context.scene.collection.objects.unlink(cube) decal_collection.objects.link(cube) print(f"Created cube for decal {i}: {cube.name}") print(f"Successfully created {len(decals)} decal cubes") class ImportTargemDecals(bpy.types.Operator, ImportHelper): bl_idname = "import_scene.targem_decals" bl_label = "Import Targem Decals" bl_options = {'PRESET', 'UNDO'} filename_ext = ".dat" filter_glob: StringProperty( default="*.dat", options={'HIDDEN'}, maxlen=255, ) def execute(self, context): filepath = self.filepath if not os.path.exists(filepath): self.report({'ERROR'}, f"File not found: {filepath}") return {'CANCELLED'} try: create_decals_from_file(filepath) self.report({'INFO'}, f"Successfully imported decals from {filepath}") except Exception as e: self.report({'ERROR'}, f"Error importing decals: {e}") return {'CANCELLED'} return {'FINISHED'} def register(): bpy.utils.register_class(ImportTargemDecals) def unregister(): bpy.utils.unregister_class(ImportTargemDecals) if __name__ == "__main__": register() bpy.ops.import_scene.targem_decals('INVOKE_DEFAULT')
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now