Пример #1
0
        public void TestMaterialFbx(string filename)
        {
            var testFile = Path.Combine(PathHelper.FilesPath, filename);

            var documentNode = FbxIO.Read(testFile, ErrorLevel.Strict);
            var scaleFactor  = documentNode.GetScaleFactor();

            var materialIds = documentNode.GetMaterialIds();

            Assert.True(materialIds.Length > 0);

            foreach (var materialId in materialIds)
            {
                var materialName = documentNode.GetMaterialName(materialId);
                var diffuseColor = documentNode.GetMaterialDiffuseColor(materialId);
            }

            var geometryIds = documentNode.GetGeometryIds();

            Assert.True(geometryIds.Length > 0);

            foreach (var geometryId in geometryIds)
            {
                var vertexIndices = documentNode.GetVertexIndices(geometryId);
                var positions     = documentNode.GetPositions(geometryId, vertexIndices);
                var tangents      = documentNode.GetTangents(geometryId, vertexIndices);
                var binormals     = documentNode.GetBinormals(geometryId, vertexIndices);
                var texCoords     = documentNode.GetTexCoords(geometryId, vertexIndices);
                var materials     = documentNode.GetMaterials(geometryId, vertexIndices);

                var hasTexCoords = documentNode.GetGeometryHasTexCoords(geometryId);
                var hasTangents  = documentNode.GetGeometryHasTangents(geometryId);
                var hasBinormals = documentNode.GetGeometryHasBinormals(geometryId);
            }
        }
Пример #2
0
        public static FbxAsset Import(string path)
        {
            if (!File.Exists(path))
            {
                throw new FileNotFoundException("The file could not be found", path);
            }

            var document     = FbxIO.ReadBinary("E:\\projects\\NEngineResources\\Glock.fbx");
            var verticesNode = document.GetRelative("Objects/Geometry/Vertices");

            var fbxAsset = new FbxAsset();

            fbxAsset.Mesh = new Mesh();

            if (verticesNode.Properties[0] is double[] vertices)
            {
                fbxAsset.Mesh.Vertices = new Vector3[vertices.Length / 3];
                for (int i = 0, j = 0, len = vertices.Length; i < len; i += 3, j++)
                {
                    var xPos = vertices[i];
                    var yPos = vertices[i + 1];
                    var zPos = vertices[i + 2];
                    fbxAsset.Mesh.Vertices[j] = new Vector3(xPos, yPos, zPos);
                }
            }

            var polygonVertexIndexNode = document.GetRelative("Objects/Geometry/PolygonVertexIndex");

            if (polygonVertexIndexNode.Properties[0] is int[] polygonVertexIndex && polygonVertexIndex.Length >= 2)
            {
                bool quadMode = polygonVertexIndex[2] >= 0;

                if (quadMode)
                {
                    fbxAsset.Mesh.Triangles = new Triangle[(polygonVertexIndex.Length / 4) * 2];
                    for (int i = 0, j = 0, len = polygonVertexIndex.Length; i < len; i += 4, j += 2)
                    {
                        var v1 = polygonVertexIndex[i];
                        var v2 = polygonVertexIndex[i + 1];
                        var v3 = polygonVertexIndex[i + 2];
                        var v4 = -polygonVertexIndex[i + 3] - 1;
                        fbxAsset.Mesh.Triangles[j]     = new Triangle(v1, v2, v4);
                        fbxAsset.Mesh.Triangles[j + 1] = new Triangle(v4, v2, v3);
                    }
                }
                else
                {
                    fbxAsset.Mesh.Triangles = new Triangle[polygonVertexIndex.Length / 3];
                    for (int i = 0, j = 0, len = polygonVertexIndex.Length; i < len; i += 3, j++)
                    {
                        var v1 = polygonVertexIndex[i];
                        var v2 = polygonVertexIndex[i + 1];
                        var v3 = -polygonVertexIndex[i + 2] - 1;
                        fbxAsset.Mesh.Triangles[j] = new Triangle(v1, v2, v3);
                    }
                }
            }

            return(fbxAsset);
        }
Пример #3
0
        private void TestCommon(byte[] data, bool isBinary)
        {
            // Binary
            using (var streamIn = new MemoryStream(data))
                using (var streamOut = new MemoryStream())
                    using (var streamTmp = new MemoryStream())
                    {
                        if (isBinary)
                        {
                            var reader = new FbxBinaryReader(streamIn);
                            var doc    = reader.Read();
                            FbxIO.WriteAscii(doc, streamOut);

                            // read output again and ensure for correct output data
                            streamOut.Position = 0;
                            reader             = new FbxBinaryReader(streamOut);
                            FbxIO.WriteBinary(doc, streamTmp);
                        }
                        else
                        {
                            var reader = new FbxAsciiReader(streamIn);
                            var doc    = reader.Read();
                            FbxIO.WriteAscii(doc, streamOut);

                            // read output again and ensure for correct output data
                            streamOut.Position = 0;
                            reader             = new FbxAsciiReader(streamOut);
                            FbxIO.WriteAscii(doc, streamTmp);
                        }
                    }
        }
Пример #4
0
        public static void CompareBinaryFiles(string filename)
        {
            var testFile     = Path.Combine(PathHelper.FilesPath, filename);
            var originalData = File.ReadAllBytes(testFile);
            var isBinary     = FbxIO.IsBinaryFbx(testFile);

            Assert.True(isBinary);
            var documentNode = FbxIO.Read(testFile);

            using (var newStream = new MemoryStream())
            {
                FbxIO.WriteBinary(documentNode, newStream);
                var newData = newStream.ToArray();

                Assert.True(newData.Length <= originalData.Length, $"Unexpected size comparisson");

                var identical = true;
                for (var i = 0; i < newData.Length; i++)
                {
                    if (originalData[i] != newData[i])
                    {
                        identical = false;
                        break;
                    }
                }

                Assert.True(identical, $"Files data did not match as expected");
            }
        }
Пример #5
0
 public static void StoreCurveNode(FbxAnimUtilities.CurveNodeIntfce pData, FbxIO mFileObject)
 {
     FbxWrapperNativePINVOKE.FbxAnimUtilities_StoreCurveNode(FbxAnimUtilities.CurveNodeIntfce.getCPtr(pData), FbxIO.getCPtr(mFileObject));
     if (FbxWrapperNativePINVOKE.SWIGPendingException.Pending)
     {
         throw FbxWrapperNativePINVOKE.SWIGPendingException.Retrieve();
     }
 }
Пример #6
0
        static void ParseFbxTextures(string path, string downloadRoot, string destinationRoot)
        {
            //var isBinary = FbxIO.IsBinaryFbx(path);
            var documentNode = FbxIO.Read(path);

            Console.WriteLine("Parsing fbx - {0}", path);

            ParseNodes(documentNode.Nodes, downloadRoot, destinationRoot);
        }
Пример #7
0
 public static FbxAnimUtilities.CurveNodeIntfce CreateCurveNode(FbxIO pFileObject, FbxAnimUtilities.CurveNodeIntfce pParent)
 {
     FbxAnimUtilities.CurveNodeIntfce ret = new FbxAnimUtilities.CurveNodeIntfce(FbxWrapperNativePINVOKE.FbxAnimUtilities_CreateCurveNode__SWIG_3(FbxIO.getCPtr(pFileObject), FbxAnimUtilities.CurveNodeIntfce.getCPtr(pParent)), true);
     if (FbxWrapperNativePINVOKE.SWIGPendingException.Pending)
     {
         throw FbxWrapperNativePINVOKE.SWIGPendingException.Retrieve();
     }
     return(ret);
 }
Пример #8
0
 public static FbxAnimUtilities.CurveNodeIntfce CreateCurveNode(FbxIO pFileObject, FbxAnimUtilities.CurveNodeIntfce pParent, bool pOnlyDefaults)
 {
     FbxAnimUtilities.CurveNodeIntfce ret = new FbxAnimUtilities.CurveNodeIntfce(fbx_wrapperPINVOKE.FbxAnimUtilities_CreateCurveNode__SWIG_2(FbxIO.getCPtr(pFileObject), FbxAnimUtilities.CurveNodeIntfce.getCPtr(pParent), pOnlyDefaults), true);
     if (fbx_wrapperPINVOKE.SWIGPendingException.Pending)
     {
         throw fbx_wrapperPINVOKE.SWIGPendingException.Retrieve();
     }
     return(ret);
 }
Пример #9
0
        static void Main(string[] args)
        {
            //var document = FbxIO.ReadBinary(args[0]);
            //FbxIO.WriteAscii(document, Path.GetDirectoryName(args[0]) + "/test_ascii.fbx");
            var reader = new FbxAsciiReader(new FileStream(Path.GetDirectoryName(args[0]) + "/test_ascii.fbx", FileMode.Open));
            var doc    = reader.Read();

            FbxIO.WriteAscii(doc, Path.GetDirectoryName(args[0]) + "/test_ascii_2.fbx");
        }
Пример #10
0
 public Task <IContentContainer> ReadAsync(Stream stream)
 {
     return(Task.Run(() =>
     {
         var contentContainer = new ContentContainer();
         var fbxDocument = FbxIO.Read(stream, ErrorLevel.Permissive);
         return (IContentContainer)contentContainer;
     }));
 }
Пример #11
0
        public void Load(string path)
        {
            var rootNode = new View.FbxNodeItem(System.IO.Path.GetFileName(path));
            var content  = FbxIO.ReadBinary(path);

            foreach (var item in content.Nodes.Where(n => n != null))
            {
                SeekNode(rootNode, item);
            }
            nodeModel.Nodes.Add(rootNode);
        }
Пример #12
0
        public static void CompareAsciiFiles(string filename)
        {
            var path     = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            var testFile = Path.Combine(path, "Files", filename);
            var isBinary = FbxIO.IsBinaryFbx(testFile);

            Assert.False(isBinary);
            var documentNode = FbxIO.Read(testFile);

            using (var tempStream = new MemoryStream())
            {
                FbxIO.WriteAscii(documentNode, tempStream);
                tempStream.Position = 0;

                var originalBuffer = string.Empty;
                using (StreamReader originalStream = new StreamReader(testFile))
                {
                    while (originalStream.EndOfStream)
                    {
                        originalBuffer += FilterLine(originalStream.ReadLine());
                    }
                }

                var newBuffer = string.Empty;
                using (StreamReader newStream = new StreamReader(tempStream))
                {
                    while (newStream.EndOfStream)
                    {
                        newBuffer += FilterLine(newStream.ReadLine());
                    }
                }

                Assert.True(originalBuffer.Length == newBuffer.Length, $"Unexpected size comparisson");

                var identical = true;
                for (var i = 0; i < newBuffer.Length; i++)
                {
                    if (originalBuffer[i] != newBuffer[i])
                    {
                        identical = false;
                        break;
                    }
                }

                Assert.True(identical, $"Files data did not match as expected");
            }
        }
Пример #13
0
        static void Test1()
        {
            string dir   = @"C:\Users\dell\AppData\Local\Colossal Order\Cities_Skylines\Addons\Import\ARDumps\";
            string file1 = "RoadMediumNode._ascii.fbx";

            var    doc   = FbxIO.ReadAscii(dir + file1);
            string fileA = "testA_" + file1;

            FbxIO.WriteAscii(doc, dir + fileA);

            doc = FbxIO.ReadAscii(dir + fileA);
            string fileB = "testB_" + file1;

            FbxIO.WriteBinary(doc, dir + fileB); // i can't open this

            doc = FbxIO.ReadBinary(dir + fileB);
            FbxIO.WriteAscii(doc, dir + "testC_" + file1); // i can open this
        }
Пример #14
0
        public YdrFile ConvertToYdr(string name, byte[] fbxdata)
        {
            var fdoc = FbxIO.Read(fbxdata);

            if (fdoc == null)
            {
                return(null);
            }

            var dwbl = TryConvertDrawable(fdoc, name);


            YdrFile ydr = new YdrFile();

            ydr.Drawable = dwbl;
            ydr.Name     = name;

            return(ydr);
        }
Пример #15
0
        static void Test4()
        {
            Test3();
            string dir   = @"C:\Users\dell\AppData\Local\Colossal Order\Cities_Skylines\Addons\Import\ARDumps\";
            string file1 = "RoadMediumNode._ascii.fbx";       // can open this
            string file2 = "TEST3_RoadMediumNode.binary.fbx"; // can open this
            string file3 = "TEST3_RoadMediumNode.ascii.fbx";
            string fileB = "TEST3B_RoadMediumNode.binary.fbx";

            Console.WriteLine("reading binary ...");
            var doc1 = FbxIO.ReadBinary(dir + file2);

            FbxIO.WriteAscii(doc1, dir + file3);
            var doc2 = FbxIO.ReadAscii(dir + file3);

            doc1.Diff(doc2);

            FbxIO.WriteBinary(doc2, dir + fileB);
        }
Пример #16
0
 public static FbxAnimUtilities.CurveNodeIntfce CreateCurveNode(FbxIO pFileObject)
 {
     FbxAnimUtilities.CurveNodeIntfce ret = new FbxAnimUtilities.CurveNodeIntfce(FbxWrapperNativePINVOKE.FbxAnimUtilities_CreateCurveNode__SWIG_1(FbxIO.getCPtr(pFileObject)), true);
     return(ret);
 }
Пример #17
0
        public void TestFbx(string filename, bool expectedIsBinary, double expectedScaleFacor, bool expectedHasTexCoord, bool expectedHasTangent, bool expectedHasBinormal)
        {
            var testFile = Path.Combine(PathHelper.FilesPath, filename);

            Assert.True(expectedIsBinary == FbxIO.IsBinaryFbx(testFile), $"IsBinaryFbx expected {expectedIsBinary}");

            var documentNode = FbxIO.Read(testFile, ErrorLevel.Strict);
            var scaleFactor  = documentNode.GetScaleFactor();

            Assert.True(expectedScaleFacor == scaleFactor, $"ScaleFactor expected {expectedScaleFacor}");

            var materialIds = documentNode.GetMaterialIds();

            var geometryIds = documentNode.GetGeometryIds();

            Assert.True(geometryIds.Length > 0);

            var fbxIndexer = new FbxIndexer();

            foreach (var geometryId in geometryIds)
            {
                var vertexIndices = documentNode.GetVertexIndices(geometryId);
                var positions     = documentNode.GetPositions(geometryId, vertexIndices);
                var normals       = documentNode.GetNormals(geometryId, vertexIndices);
                var tangents      = documentNode.GetTangents(geometryId, vertexIndices);
                var binormals     = documentNode.GetBinormals(geometryId, vertexIndices);
                var texCoords     = documentNode.GetTexCoords(geometryId, vertexIndices);
                var materials     = documentNode.GetMaterials(geometryId, vertexIndices);

                var hasNormals = documentNode.GetGeometryHasNormals(geometryId);

                var hasTexCoords = documentNode.GetGeometryHasTexCoords(geometryId);
                Assert.True(expectedHasTexCoord == hasTexCoords, $"HasTexCoord expected {expectedHasTexCoord}");

                var hasTangents = documentNode.GetGeometryHasTangents(geometryId);
                Assert.True(expectedHasTangent == hasTangents, $"HasTangent expected {expectedHasTangent}");

                var hasBinormals = documentNode.GetGeometryHasBinormals(geometryId);
                Assert.True(expectedHasBinormal == hasBinormals, $"HasBinormal expected {expectedHasBinormal}");

                var hasMaterials = documentNode.GetGeometryHasMaterials(geometryId);

                for (var i = 0; i < positions.Length; i++)
                {
                    var vertex = new FbxVertex
                    {
                        Position = positions[i],
                        Normal   = hasNormals ? normals[i] : new Vector3(),
                        Tangent  = hasTangents ? tangents[i] : new Vector3(),
                        Binormal = hasBinormals ? binormals[i] : new Vector3(),
                        TexCoord = hasTexCoords ? texCoords[i] : new Vector2()
                    };
                    var materialId = hasMaterials ? materials[i] : 0;
                    fbxIndexer.AddVertex(vertex, materialId);
                }
            }

            foreach (var materialId in materialIds)
            {
                var materialName = documentNode.GetMaterialName(materialId);
                var diffuseColor = documentNode.GetMaterialDiffuseColor(materialId);
                fbxIndexer.Index(materialId, out var indexedVertices, out var indexedIndices);
            }
        }
Пример #18
0
        public bool Export(FbxDocument pDocument, FbxIO pFbxObject)
        {
            bool ret = FbxWrapperNativePINVOKE.FbxExporter_Export__SWIG_2(swigCPtr, FbxDocument.getCPtr(pDocument), FbxIO.getCPtr(pFbxObject));

            return(ret);
        }
Пример #19
0
        public virtual bool ReadExtendedHeaderInformation(FbxIO arg0)
        {
            bool ret = fbx_wrapperPINVOKE.FbxIOFileHeaderInfo_ReadExtendedHeaderInformation(swigCPtr, FbxIO.getCPtr(arg0));

            return(ret);
        }
Пример #20
0
 public static void ExportBinaryFbx(this Mesh mesh, string path) =>
 FbxIO.WriteBinary(mesh.ToFBXDocument(), path);
Пример #21
0
        public bool GetExportOptions(FbxIO pFbxObject)
        {
            bool ret = FbxWrapperNativePINVOKE.FbxExporter_GetExportOptions__SWIG_1(swigCPtr, FbxIO.getCPtr(pFbxObject));

            return(ret);
        }
Пример #22
0
        public override bool Store(FbxIO pFileObject)
        {
            bool ret = fbx_wrapperPINVOKE.FbxAnimCurve_Store__SWIG_1(swigCPtr, FbxIO.getCPtr(pFileObject));

            return(ret);
        }
Пример #23
0
        public override bool Retrieve(FbxIO pFileObject)
        {
            bool ret = fbx_wrapperPINVOKE.FbxAnimCurve_Retrieve(swigCPtr, FbxIO.getCPtr(pFileObject));

            return(ret);
        }
Пример #24
0
        public virtual bool Retrieve(FbxIO pFileObject)
        {
            bool ret = FbxWrapperNativePINVOKE.FbxAnimCurveBase_Retrieve(swigCPtr, FbxIO.getCPtr(pFileObject));

            return(ret);
        }
Пример #25
0
        public override bool Store(FbxIO pFileObject, bool pLegacyVersion)
        {
            bool ret = fbx_wrapperPINVOKE.FbxAnimCurve_Store__SWIG_0(swigCPtr, FbxIO.getCPtr(pFileObject), pLegacyVersion);

            return(ret);
        }
Пример #26
0
        public virtual bool Store(FbxIO pFileObject)
        {
            bool ret = FbxWrapperNativePINVOKE.FbxAnimCurveBase_Store__SWIG_1(swigCPtr, FbxIO.getCPtr(pFileObject));

            return(ret);
        }
Пример #27
0
        public virtual bool Store(FbxIO pFileObject, bool pLegacyVersion)
        {
            bool ret = FbxWrapperNativePINVOKE.FbxAnimCurveBase_Store__SWIG_0(swigCPtr, FbxIO.getCPtr(pFileObject), pLegacyVersion);

            return(ret);
        }
Пример #28
0
        public bool GetImportOptions(FbxIO pFbxObject)
        {
            bool ret = fbx_wrapperPINVOKE.FbxImporter_GetImportOptions__SWIG_2(swigCPtr, FbxIO.getCPtr(pFbxObject));

            return(ret);
        }
Пример #29
0
        public static void LoadFbx(string path, ref Vector3[] positions, ref Vector3[] normals, ref Vector2[] texCoords,
                                   ref int[] vertexIndices)
        {
            var isBinary     = FbxIO.IsBinaryFbx(path);
            var documentNode = FbxIO.Read(path);

            // Scale factor usually 1 or 2.54
            var scaleFactor = documentNode.GetScaleFactor();
            var geometryIds = documentNode.GetGeometryIds();

            var fbxIndexer = new FbxIndexer();

            foreach (var geometryId in geometryIds)
            {
                vertexIndices = documentNode.GetVertexIndices(geometryId);

                long[] normalLayerIndices   = documentNode.GetLayerIndices(geometryId, FbxLayerElementType.Normal);
                long[] tangentLayerIndices  = documentNode.GetLayerIndices(geometryId, FbxLayerElementType.Tangent);
                long[] binormalLayerIndices = documentNode.GetLayerIndices(geometryId, FbxLayerElementType.Binormal);
                long[] texCoordLayerIndices = documentNode.GetLayerIndices(geometryId, FbxLayerElementType.TexCoord);
                long[] materialLayerIndices = documentNode.GetLayerIndices(geometryId, FbxLayerElementType.Material);

                positions = documentNode.GetPositions(geometryId, vertexIndices);
                normals   = documentNode.GetNormals(geometryId, vertexIndices, normalLayerIndices[0]);
                texCoords = documentNode.GetTexCoords(geometryId, vertexIndices, texCoordLayerIndices[0]);
                int[] materials = documentNode.GetMaterials(geometryId, vertexIndices, materialLayerIndices[0]);

                bool hasNormals   = documentNode.GetGeometryHasNormals(geometryId);
                bool hasTexCoords = documentNode.GetGeometryHasTexCoords(geometryId);
                bool hasTangents  = documentNode.GetGeometryHasTangents(geometryId);
                bool hasBinormals = documentNode.GetGeometryHasBinormals(geometryId);
                bool hasMaterials = documentNode.GetGeometryHasMaterials(geometryId);

                Vector3[] tangents  = Array.Empty <Vector3>();
                Vector3[] binormals = Array.Empty <Vector3>();

                if (hasTangents)
                {
                    tangents = documentNode.GetTangents(geometryId, vertexIndices, tangentLayerIndices[0]);
                }

                if (hasBinormals)
                {
                    binormals = documentNode.GetBinormals(geometryId, vertexIndices, binormalLayerIndices[0]);
                }

                for (var i = 0; i < positions.Length; i++)
                {
                    var vertex = new FbxVertex
                    {
                        Position = positions[i],
                        Normal   = hasNormals ? normals[i] : new Vector3(),
                        Tangent  = hasTangents ? tangents[i] : new Vector3(),
                        Binormal = hasBinormals ? binormals[i] : new Vector3(),
                        TexCoord = hasTexCoords ? texCoords[i] : new Vector2()
                    };
                    var materialId = hasMaterials ? materials[i] : 0;
                    fbxIndexer.AddVertex(vertex, materialId, 0);
                }
            }
        }
Пример #30
0
        private static IKn5 LodFbxToKn5(IKn5 preparedKn5, string fbxFilename, CarLodGeneratorStageParams stage)
        {
            var fbx        = FbxIO.Read(fbxFilename);
            var geometries = fbx.GetGeometryIds()
                             .Select(id => new { id, name = fbx.GetNode("Model", fbx.GetConnection(id)).GetName("Model") })
                             .GroupBy(v => v.name).ToDictionary(x => x.Key, x => x.Select(v => v.id).ToList());

            MergeNode(preparedKn5.RootNode);
            foreach (var geometry in geometries.Where(g => g.Value.Count > 0))
            {
                var parent = preparedKn5.FirstByName(geometry.Key);
                if (parent == null)
                {
                    AcToolsLogging.Write($"Error: parent {geometry.Key} is missing");
                    continue;
                }

                var mesh = Kn5MeshUtils.Create($"{geometry.Key}__mesh_", parent.Children[0].MaterialId);
                if (parent.Children.Count != 1)
                {
                    throw new Exception("Unexpected arrangement");
                }
                MergeMeshWith(parent, mesh, geometry.Value.Select(x => Tuple.Create(fbx.GetGeometry(x), 1d)));
                parent.Children.Add(mesh);
            }
            RemoveEmpty(preparedKn5.RootNode);
            return(preparedKn5);

            void MergeMeshWith(Kn5Node parent, Kn5Node mesh, IEnumerable <Tuple <FbxNode, double> > geometriesList)
            {
                var builder    = new Kn5MeshBuilder();
                var subCounter = 1;

                foreach (var geometry in geometriesList)
                {
                    var fbxIndices = geometry?.Item1?.GetRelative("PolygonVertexIndex")?.Value?.GetAsIntArray();
                    if (fbxIndices == null)
                    {
                        continue;
                    }

                    var fbxVertices = geometry.Item1.GetRelative("Vertices").Value.GetAsFloatArray();
                    var fbxNormals  = geometry.Item1.GetRelative("LayerElementNormal").GetRelative("Normals").Value.GetAsFloatArray();
                    var fbxUvs      = geometry.Item1.GetRelative("LayerElementUV").GetRelative("UV").Value.GetAsFloatArray();
                    var offset      = MoveAsideDistance(geometry.Item2, stage);
                    var scale       = (float)(1d / geometry.Item2);

                    for (var i = 0; i < fbxIndices.Length; ++i)
                    {
                        if (i % 3 == 0 && builder.IsCloseToLimit)
                        {
                            builder.SetTo(mesh);
                            mesh.RecalculateTangents();

                            builder.Clear();
                            var oldIndex = parent.Children.IndexOf(mesh);
                            mesh = Kn5MeshUtils.Create(mesh.Name + $"___$sub:{subCounter}", mesh.MaterialId);
                            ++subCounter;
                            if (oldIndex != -1 && oldIndex < parent.Children.Count - 1)
                            {
                                parent.Children.Insert(oldIndex + 1, mesh);
                            }
                            else
                            {
                                parent.Children.Add(mesh);
                            }
                        }

                        var index = fbxIndices[i] < 0 ? -fbxIndices[i] - 1 : fbxIndices[i];
                        builder.AddVertex(new Kn5Node.Vertex {
                            Position = new Vec3((fbxVertices[index * 3] - offset.X) * scale, (fbxVertices[index * 3 + 1] - offset.Y) * scale,
                                                (fbxVertices[index * 3 + 2] - offset.Z) * scale),
                            Normal = new Vec3(fbxNormals[i * 3], fbxNormals[i * 3 + 1], fbxNormals[i * 3 + 2]),
                            Tex    = new Vec2(fbxUvs[i * 2], 1f - fbxUvs[i * 2 + 1])
                        });
                    }
                }
                builder.SetTo(mesh);
                mesh.RecalculateTangents();
            }

            void MergeMesh(Kn5Node parent, Kn5Node node, IEnumerable <Kn5Node> merges)
            {
                if (Regex.IsMatch(node.Name, @"___\$extra:\d+$"))
                {
                    node.Vertices = new Kn5Node.Vertex[0];
                    return;
                }

                MergeMeshWith(parent, node, Enumerable.Range(-1, 100).Select(i => GetGeometry(node, i)).TakeWhile(i => i != null)
                              .Concat(merges.Select(n => GetGeometry(n))));
            }

            Tuple <FbxNode, double> GetGeometry(Kn5Node node, int extraBit = -1)
            {
                var name = extraBit < 0 ? node.Name : $"{node.Name}___$extra:{extraBit}";

                if (geometries.TryGetValue(name, out var list))
                {
                    if (list.Count == 0)
                    {
                        return(null);
                    }
                    var geometryId = list[0];
                    list.RemoveAt(0);
                    return(Tuple.Create(fbx.GetGeometry(geometryId), node.Tag is double priority ? priority : 1d));
                }
                return(null);
            }

            void MergeNode(Kn5Node node)
            {
                foreach (var child in node.Children.ToList())
                {
                    if (child.NodeClass == Kn5NodeClass.Base)
                    {
                        MergeNode(child);
                    }
                    else if (child.NodeClass == Kn5NodeClass.Mesh && child.Vertices.Length > 0)
                    {
                        var mergeKey = MergeKey(child);
                        var merge    = node.Children.ApartFrom(child).Where(c => c.NodeClass == Kn5NodeClass.Mesh && MergeKey(c) == mergeKey).ToList();
                        MergeMesh(node, child, merge);
                        merge.ForEach(m => m.Vertices = new Kn5Node.Vertex[0]);
                    }
                }
            }

            int MergeKey(Kn5Node node)
            {
                return((int)(node.MaterialId * 397) | (node.IsTransparent ? 1 << 31 : 0) | (node.CastShadows ? 1 << 30 : 0));
            }

            bool RemoveEmpty(Kn5Node node)
            {
                if (node.NodeClass == Kn5NodeClass.Mesh)
                {
                    if (Regex.IsMatch(node.Name, @"___\$extra:\d+$"))
                    {
                        return(false);
                    }
                    return(node.Vertices.Length > 0);
                }
                if (node.NodeClass == Kn5NodeClass.SkinnedMesh)
                {
                    return(false);
                }
                var found = node.Name.IndexOf("___$unique:", StringComparison.Ordinal);

                if (found != -1)
                {
                    node.Name = node.Name.Substring(0, found);
                }
                for (var i = 0; i < node.Children.Count; ++i)
                {
                    if (!RemoveEmpty(node.Children[i]))
                    {
                        node.Children.RemoveAt(i);
                        --i;
                    }
                }
                return(node.Children.Count > 0);
            }
        }