Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.

decals.dat from 'The Swarm'.

Featured Replies

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

Solved by Serge25

  • Supporter
1 hour ago, Serge25 said:

Maybe someone knows the solution?

 

Hard to tell from what you provide, I see points only:

blender_y8RakkpCVW.thumb.png.129678aba12a9bf4bb40597577bf817b.png

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

...

  • 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).

  • 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.

Sample01.jpg

  • 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 by shak-otay

  • 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.

  • 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?

  • 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')

DecalsOK.jpg

Create an account or sign in to comment

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.