October 22, 2025Oct 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
October 22, 2025Oct 22 Supporter 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 ...
October 23, 2025Oct 23 Author 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).
October 23, 2025Oct 23 Author 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.
October 23, 2025Oct 23 Supporter 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, 2025Oct 23 by shak-otay
October 23, 2025Oct 23 Author 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.
October 23, 2025Oct 23 Supporter 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?
October 25, 2025Oct 25 Author Solution 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')
Create an account or sign in to comment