Search code examples
c#.netrelative-pathfbxassimp

Strange relative paths containing "*0\0" or "*1\0" cutting off the first 3 characters of path while working with Blender, Assimp.NET and FBX files


I am trying to make a program to merge fbx models. Each model have multiple nodes. Some of the nodes have a specific name meaning that those are functioning as connection points for other models.

I already solved pretty much everything regarding the merge, except for the problem of textures. Mesh and material indexes has been updated, but when it come to textures, which are referenced by materials as file paths, I encountered a strange problem. To solve the problem of merging models with different full paths, textures cause a problem, since they are referenced with relative paths to the model file.

When loaded with the basic code below:

// Initialize the Assimp context
AssimpContext importer = new AssimpContext();

// Load the model with desired post-processing flags
Scene model = importer.ImportFile(filePath, PostProcessPreset.TargetRealTimeMaximumQuality | 
                                            PostProcessSteps.Triangulate |
                                            PostProcessSteps.FlipUVs |
                                            PostProcessSteps.EmbedTextures |
                                            PostProcessSteps.GlobalScale |
                                            PostProcessSteps.ValidateDataStructure);

The paths stored by the material looks very strange. For example a relative path of the file "Normal.jpg" in the same folder as the fbx file itself gets stored as "*1\0mal.jpg", another one in a .fbm subfolder named "GPV-2.fbm\BaseColor.jpg" as "*0\0-2 BG.fbm\BaseColor.jpg". To me it looks like the first 3 character gets overwritten at some point during import.

I use the Assimp.NET 5.0.0-beta1 package.

Has someone seen anything like this before? What is the reason the paths look like this and how can I solve it?

Since Assimp.NET has no problem loading the textures I guess the paths are good during the import. Perhaps I could somehow get the fullpath out of assimp?


Path.Combine Does not work. I tried to manually cut the pieces, but the missing characters still cause a problem, and I would try file search if nothing else would work.

I tried to look into Assimp code, but couldn't find exactly how it imports.

I also tried to discuss it with ChatGPT, but couldn't solve it either, neither could it give me pointers where to look.


Solution

  • In the meantime I found the solution.

    If the textures are referenced, so the GLB file is actually referencing some kind of image, for example a JPEG next to it, then the Material in the model will have the reference as a filepath. In case of embedded textures however, they get converted into a compressed binary form and added to the "Textures" property of the Scene. Materials then use "*index" as filepath to know that it is not an external reference, but an embedded one. The "\0" might be coming from a C++/C# compatibility error because of the differences in string handling.

    In relation to the actual task I wanted to do (Merging models), The solution was rather easy now. I just added the embedded textures to the root models textures and then adjusted the indexes in the subModel's materials before adding them to the root.

    private static void MergeModelData(string path, Scene model, Scene subModel)
    {
        UpdateMeshIndices(model, subModel.RootNode);
        foreach (Mesh mesh in subModel.Meshes)
        {
            mesh.MaterialIndex += model.MaterialCount;
        }
    
        int idx = model.Textures.Count;
    
        model.Textures.AddRange(subModel.Textures);
    
        foreach (Material mat in subModel.Materials)
        {
            AppendMaterial(model, mat, mat.TextureAmbient, path, idx);
            AppendMaterial(model, mat, mat.TextureAmbientOcclusion, path, idx);
            AppendMaterial(model, mat, mat.TextureDiffuse, path, idx);
            AppendMaterial(model, mat, mat.TextureDisplacement, path, idx);
            AppendMaterial(model, mat, mat.TextureEmissive, path, idx);
            AppendMaterial(model, mat, mat.TextureHeight, path, idx);
            AppendMaterial(model, mat, mat.TextureLightMap, path, idx);
            AppendMaterial(model, mat, mat.TextureNormal, path, idx);
            AppendMaterial(model, mat, mat.TextureOpacity, path, idx);
            AppendMaterial(model, mat, mat.TextureReflection, path, idx);
            AppendMaterial(model, mat, mat.TextureSpecular, path, idx);
        }
    
        model.Meshes.AddRange(subModel.Meshes);
    }
    
    private static void UpdateMeshIndices(Scene model, Node currentNode)
    {
        if (currentNode.HasMeshes)
        {
            for (int i = 0; i < currentNode.MeshIndices.Count; i++)
            {
                currentNode.MeshIndices[i] += model.MeshCount;
            }
        }
    
        foreach (Node child in currentNode.Children)
        {
            UpdateMeshIndices(model, child);
        }
    }
    
    private static void AppendMaterial(Scene model, Material mat, TextureSlot slot, string path, int idx)
    {
        if (slot.FilePath == null)
        {
            return;
        }
    
        string texPath;
    
        SwitchTextureInMaterial(mat, slot, $"*{idx++}\0");
    
        model.Materials.Add(mat);
    }
    
    private static void SwitchTextureInMaterial(Material mat, TextureSlot slot, string texPath)
    {
        TextureSlot ts = new TextureSlot();
        ts.FilePath = texPath;
        ts.TextureType = slot.TextureType;
        ts.TextureIndex = slot.TextureIndex;
        ts.Mapping = slot.Mapping;
        ts.UVIndex = slot.UVIndex;
        ts.BlendFactor = slot.BlendFactor;
        ts.Operation = slot.Operation;
        ts.WrapModeU = slot.WrapModeU;
        ts.WrapModeV = slot.WrapModeV;
        ts.Flags = slot.Flags;
    
        mat.RemoveMaterialTexture(slot);
        mat.AddMaterialTexture(in ts);
    }
    

    I hope I helped the next guy who would try the same. =)