static GlMaterialConstants()
 {
     NULL_WHITE_TEXTURE ??=
     new GlTexture(FinImage.Create1x1WithColor(Color.White));
     NULL_GRAY_TEXTURE ??=
     new GlTexture(FinImage.Create1x1WithColor(Color.Gray));
     NULL_BLACK_TEXTURE ??=
     new GlTexture(FinImage.Create1x1WithColor(Color.Black));
 }
예제 #2
0
        public unsafe IModel LoadModel(GloModelFileBundle gloModelFileBundle)
        {
            var gloFile            = gloModelFileBundle.GloFile;
            var textureDirectories = gloModelFileBundle.TextureDirectories;
            var fps = 20;

            var glo = gloFile.Impl.ReadNew <Glo>(Endianness.LittleEndian);

            var textureFilesByName = new Dictionary <string, IFileHierarchyFile>();

            foreach (var textureDirectory in textureDirectories)
            {
                foreach (var textureFile in textureDirectory.Files)
                {
                    textureFilesByName[textureFile.Name.ToLower()] = textureFile;
                }
            }

            /*new MeshCsvWriter().WriteToFile(
             *  glo, new FinFile(Path.Join(outputDirectory.FullName, "mesh.csv")));
             * new FaceCsvWriter().WriteToFile(
             *  glo, new FinFile(Path.Join(outputDirectory.FullName, "face.csv")));
             * new VertexCsvWriter().WriteToFile(
             *  glo, new FinFile(Path.Join(outputDirectory.FullName, "vertex.csv")));*/

            var finModel = new ModelImpl();
            var finSkin  = finModel.Skin;

            var finRootBone = finModel.Skeleton.Root;

            var finTextureMap = new LazyDictionary <string, ITexture?>(
                textureFilename => {
                if (!textureFilesByName.TryGetValue(textureFilename.ToLower(),
                                                    out var textureFile))
                {
                    return(null);
                }

                using var rawTextureImage = FinImage.FromFile(textureFile.Impl);
                var textureImageWithAlpha =
                    GloModelLoader.AddTransparencyToGloImage_(rawTextureImage);

                var finTexture = finModel.MaterialManager.CreateTexture(
                    textureImageWithAlpha);
                finTexture.Name = textureFilename;

                if (this.mirrorTextures_.Contains(textureFilename))
                {
                    finTexture.WrapModeU = WrapMode.MIRROR_REPEAT;
                    finTexture.WrapModeV = WrapMode.MIRROR_REPEAT;
                }
                else
                {
                    finTexture.WrapModeU = WrapMode.REPEAT;
                    finTexture.WrapModeV = WrapMode.REPEAT;
                }

                return(finTexture);
            });
            var withCullingMap =
                new LazyDictionary <string, IMaterial>(textureFilename => {
                var finTexture = finTextureMap[textureFilename];
                if (finTexture == null)
                {
                    return(finModel.MaterialManager.AddStandardMaterial());
                }
                return(finModel.MaterialManager.AddTextureMaterial(finTexture));
            });
            var withoutCullingMap = new LazyDictionary <string, IMaterial>(
                textureFilename => {
                var finTexture        = finTextureMap[textureFilename];
                IMaterial finMaterial = finTexture == null
                                        ? finModel.MaterialManager
                                        .AddStandardMaterial()
                                        : finModel.MaterialManager
                                        .AddTextureMaterial(
                    finTexture);
                finMaterial.CullingMode = CullingMode.SHOW_BOTH;
                return(finMaterial);
            });

            var firstMeshMap = new Dictionary <string, GloMesh>();

            // TODO: Consider separating these out as separate models
            foreach (var gloObject in glo.Objects)
            {
                var finObjectRootBone = finRootBone.AddRoot(0, 0, 0);
                var meshQueue         = new Queue <(GloMesh, IBone)>();
                foreach (var topLevelGloMesh in gloObject.Meshes)
                {
                    meshQueue.Enqueue((topLevelGloMesh, finObjectRootBone));
                }

                List <(IAnimation, int, int)> finAndGloAnimations = new();
                foreach (var animSeg in gloObject.AnimSegs)
                {
                    var startFrame = (int)animSeg.StartFrame;
                    var endFrame   = (int)animSeg.EndFrame;

                    var finAnimation = finModel.AnimationManager.AddAnimation();
                    finAnimation.Name       = animSeg.Name;
                    finAnimation.FrameCount =
                        (int)(animSeg.EndFrame - animSeg.StartFrame + 1);

                    finAnimation.FrameRate = fps * animSeg.Speed;

                    finAndGloAnimations.Add((finAnimation, startFrame, endFrame));
                }

                while (meshQueue.Count > 0)
                {
                    var(gloMesh, parentFinBone) = meshQueue.Dequeue();

                    var name = gloMesh.Name;

                    GloMesh idealMesh;
                    if (!firstMeshMap.TryGetValue(name, out idealMesh))
                    {
                        firstMeshMap[name] = idealMesh = gloMesh;
                    }

                    var position = gloMesh.MoveKeys[0].Xyz;

                    var rotation   = gloMesh.RotateKeys[0];
                    var quaternion =
                        new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W);
                    var xyzRadians = QuaternionUtil.ToEulerRadians(quaternion);

                    var scale = gloMesh.ScaleKeys[0].Scale;

                    var finBone = parentFinBone
                                  .AddChild(position.X, position.Y, position.Z)
                                  .SetLocalRotationRadians(
                        xyzRadians.X, xyzRadians.Y, xyzRadians.Z)
                                  .SetLocalScale(scale.X, scale.Y, scale.Z);
                    finBone.Name = name + "_bone";

                    var child = gloMesh.Pointers.Child;
                    if (child != null)
                    {
                        meshQueue.Enqueue((child, finBone));
                    }

                    var next = gloMesh.Pointers.Next;
                    if (next != null)
                    {
                        meshQueue.Enqueue((next, parentFinBone));
                    }

                    foreach (var(finAnimation, startFrame, endFrame) in
                             finAndGloAnimations)
                    {
                        var finBoneTracks = finAnimation.AddBoneTracks(finBone);

                        long prevTime = -1;
                        foreach (var moveKey in gloMesh.MoveKeys)
                        {
                            Asserts.True(moveKey.Time > prevTime);
                            prevTime = moveKey.Time;

                            if (!(moveKey.Time >= startFrame && moveKey.Time <= endFrame))
                            {
                                continue;
                            }

                            var time = (int)(moveKey.Time - startFrame);
                            Asserts.True(time >= 0 && time < finAnimation.FrameCount);

                            var moveValue = moveKey.Xyz;
                            finBoneTracks.Positions.Set(time, 0, moveValue.X);
                            finBoneTracks.Positions.Set(time, 1, moveValue.Y);
                            finBoneTracks.Positions.Set(time, 2, moveValue.Z);
                        }

                        prevTime = -1;
                        foreach (var rotateKey in gloMesh.RotateKeys)
                        {
                            Asserts.True(rotateKey.Time > prevTime);
                            prevTime = rotateKey.Time;

                            if (!(rotateKey.Time >= startFrame &&
                                  rotateKey.Time <= endFrame))
                            {
                                continue;
                            }

                            var time = (int)(rotateKey.Time - startFrame);
                            Asserts.True(time >= 0 && time < finAnimation.FrameCount);

                            var quaternionKey =
                                new Quaternion(rotateKey.X, rotateKey.Y, rotateKey.Z,
                                               rotateKey.W);
                            var xyzRadiansKey = QuaternionUtil.ToEulerRadians(quaternionKey);

                            finBoneTracks.Rotations.Set(time, 0,
                                                        xyzRadiansKey.X);
                            finBoneTracks.Rotations.Set(time, 1,
                                                        xyzRadiansKey.Y);
                            finBoneTracks.Rotations.Set(time, 2,
                                                        xyzRadiansKey.Z);
                        }

                        prevTime = -1;
                        foreach (var scaleKey in gloMesh.ScaleKeys)
                        {
                            Asserts.True(scaleKey.Time > prevTime);
                            prevTime = scaleKey.Time;

                            if (!(scaleKey.Time >= startFrame && scaleKey.Time <= endFrame))
                            {
                                continue;
                            }

                            var time = (int)(scaleKey.Time - startFrame);
                            Asserts.True(time >= 0 && time < finAnimation.FrameCount);

                            var scaleValue = scaleKey.Scale;
                            finBoneTracks.Scales.Set(time, 0, scaleValue.X);
                            finBoneTracks.Scales.Set(time, 1, scaleValue.Y);
                            finBoneTracks.Scales.Set(time, 2, scaleValue.Z);
                        }
                    }

                    // Anything with these names are debug objects and can be ignored.
                    if (this.hiddenNames_.Contains(name))
                    {
                        continue;
                    }

                    var finMesh = finSkin.AddMesh();
                    finMesh.Name = name;

                    var gloVertices = idealMesh.Vertices;

                    string    previousTextureName = null;
                    IMaterial?previousMaterial    = null;

                    foreach (var gloFace in idealMesh.Faces)
                    {
                        // TODO: What can we do if texture filename is empty?
                        var textureFilename = gloFace.TextureFilename;

                        var gloFaceColor = gloFace.Color;
                        var finFaceColor = ColorImpl.FromRgbaBytes(
                            gloFaceColor.R, gloFaceColor.G, gloFaceColor.B, gloFaceColor.A);

                        var enableBackfaceCulling = (gloFace.Flags & 1 << 2) == 0;

                        IMaterial?finMaterial;
                        if (textureFilename == previousTextureName)
                        {
                            finMaterial = previousMaterial;
                        }
                        else
                        {
                            previousTextureName = textureFilename;
                            finMaterial         = enableBackfaceCulling
                                ? withCullingMap[textureFilename]
                                : withoutCullingMap[textureFilename];
                            previousMaterial = finMaterial;
                        }

                        // Face flag:
                        // 0: potentially some kind of repeat mode??

                        var color = (gloFace.Flags & 1 << 6) != 0
                            ? ColorImpl.FromRgbaBytes(255, 0, 0, 255)
                            : ColorImpl.FromRgbaBytes(0, 255, 0, 255);

                        var finFaceVertices = new IVertex[3];
                        for (var v = 0; v < 3; ++v)
                        {
                            var gloVertexRef = gloFace.VertexRefs[v];
                            var gloVertex    = gloVertices[gloVertexRef.Index];

                            var finVertex = finSkin
                                            .AddVertex(gloVertex.X, gloVertex.Y, gloVertex.Z)
                                            .SetUv(gloVertexRef.U, gloVertexRef.V);
                            //.SetColor(color);
                            finVertex.SetBoneWeights(finSkin.GetOrCreateBoneWeights(
                                                         PreprojectMode.BONE, finBone));
                            finFaceVertices[v] = finVertex;
                        }

                        // TODO: Merge triangles together
                        var finTriangles = new (IVertex, IVertex, IVertex)[1];
예제 #3
0
        public BmdFixedFunctionMaterial(
            IMaterialManager materialManager,
            int materialEntryIndex,
            BMD bmd,
            IList <BmdTexture> tex1Textures)
        {
            // TODO: materialEntry.Flag determines draw order

            var materialEntry = bmd.MAT3.MaterialEntries[materialEntryIndex];
            var materialName  = bmd.MAT3.MaterialNameTable[materialEntryIndex];

            var populatedMaterial = bmd.MAT3.PopulatedMaterials[materialEntryIndex];

            var textures =
                populatedMaterial.TextureIndices
                .Select(i => i != -1 ? tex1Textures[i] : null)
                .ToArray();

            var material = materialManager.AddFixedFunctionMaterial();

            material.Name        = materialName;
            material.CullingMode =
                bmd.MAT3.CullModes[materialEntry.CullModeIndex] switch {
                BMD.CullMode.None => CullingMode.SHOW_BOTH,
                BMD.CullMode.Front => CullingMode.SHOW_BACK_ONLY,
                BMD.CullMode.Back => CullingMode.SHOW_FRONT_ONLY,
                BMD.CullMode.All => CullingMode.SHOW_NEITHER,
                _ => throw new ArgumentOutOfRangeException(),
            };

            material.SetBlending(
                ConvertBmdBlendModeToFin(populatedMaterial.BlendMode.BlendMode),
                ConvertBmdBlendFactorToFin(populatedMaterial.BlendMode.SrcFactor),
                ConvertBmdBlendFactorToFin(populatedMaterial.BlendMode.DstFactor),
                ConvertBmdLogicOpToFin(populatedMaterial.BlendMode.LogicOp));

            material.SetAlphaCompare(
                ConvertBmdAlphaOpToFin(populatedMaterial.AlphaCompare.MergeFunc),
                ConvertBmdAlphaCompareTypeToFin(populatedMaterial.AlphaCompare.Func0),
                populatedMaterial.AlphaCompare.Reference0 / 255f,
                ConvertBmdAlphaCompareTypeToFin(populatedMaterial.AlphaCompare.Func1),
                populatedMaterial.AlphaCompare.Reference1 / 255f);

            this.Material = material;

            var colorConstants = new List <Color>();

            // TODO: Need to use material entry indices

            var equations = material.Equations;

            var colorZero = equations.CreateColorConstant(0);

            var scZero      = equations.CreateScalarConstant(0);
            var scOne       = equations.CreateScalarConstant(1);
            var scTwo       = equations.CreateScalarConstant(2);
            var scFour      = equations.CreateScalarConstant(4);
            var scHalf      = equations.CreateScalarConstant(.5);
            var scMinusHalf = equations.CreateScalarConstant(-.5);

            var colorFixedFunctionOps  = new ColorFixedFunctionOps(equations);
            var scalarFixedFunctionOps = new ScalarFixedFunctionOps(equations);

            var valueManager = new ValueManager(equations);

            // TODO: Where are color constants set inside the materials?
            // TODO: Need to support registers
            // TODO: Need to support multiple vertex colors
            // TODO: Colors should just be RGB in the fixed function library
            // TODO: Seems like only texture 1 is used, is this accurate?

            // TODO: This might need to be TevKonstColorIndexes
            valueManager.SetColorRegisters(
                materialEntry.TevColorIndexes.Take(4)
                .Select(
                    tevColorIndex => bmd.MAT3.ColorS10[tevColorIndex])
                .ToArray());

            var konstColors =
                materialEntry.TevKonstColorIndexes
                .Take(4)
                .Select(konstIndex => bmd.MAT3.Color3[konstIndex])
                .ToArray();

            valueManager.SetKonstColors(konstColors);

            for (var i = 0; i < materialEntry.TevStageInfoIndexes.Length; ++i)
            {
                var tevStageIndex = materialEntry.TevStageInfoIndexes[i];
                if (tevStageIndex == -1)
                {
                    continue;
                }

                var tevStage = bmd.MAT3.TevStages[tevStageIndex];

                var tevOrderIndex = materialEntry.TevOrderInfoIndexes[i];
                var tevOrder      = bmd.MAT3.TevOrders[tevOrderIndex];

                // Updates which texture is referred to by TEXC
                var textureIndex = tevOrder.TexMap;
                if (textureIndex == -1)
                {
                    valueManager.UpdateTextureColor(null);
                }
                else
                {
                    var bmdTexture = textures[textureIndex];

                    // TODO: Share texture definitions between materials?
                    var texture = materialManager.CreateTexture(bmdTexture.Image);

                    texture.Name      = bmdTexture.Name;
                    texture.WrapModeU = bmdTexture.WrapModeS;
                    texture.WrapModeV = bmdTexture.WrapModeT;
                    texture.ColorType = bmdTexture.ColorType;

                    var texCoordGen =
                        bmd.MAT3.TexCoordGens[
                            materialEntry.TexGenInfo[tevOrder.TexCoordId]];

                    var texGenSrc = texCoordGen.TexGenSrc;
                    switch (texGenSrc)
                    {
                    case >= GxTexGenSrc.Tex0 and <= GxTexGenSrc.Tex7: {
                        var texCoordIndex = texGenSrc - GxTexGenSrc.Tex0;
                        texture.UvIndex = texCoordIndex;
                        break;
                    }

                    case GxTexGenSrc.Normal: {
                        texture.UvType = UvType.LINEAR;
                        break;
                    }

                    default: {
                        //Asserts.Fail($"Unsupported texGenSrc type: {texGenSrc}");
                        texture.UvIndex = 0;
                        break;
                    }
                    }

                    valueManager.UpdateTextureColor(textureIndex);
                    material.SetTextureSource(textureIndex, texture);
                }

                // Updates which color is referred to by RASC
                var colorChannel = tevOrder.ColorChannelId;
                valueManager.UpdateRascColor(colorChannel);

                // Updates which values are referred to by konst
                valueManager.UpdateKonst(materialEntry.KonstColorSel[tevOrderIndex],
                                         materialEntry.KonstAlphaSel[tevOrderIndex]);

                // Set up color logic
                {
                    var colorA = valueManager.GetColor(tevStage.color_a);
                    var colorB = valueManager.GetColor(tevStage.color_b);
                    var colorC = valueManager.GetColor(tevStage.color_c);
                    var colorD = valueManager.GetColor(tevStage.color_d);

                    IColorValue?colorValue = null;

                    // TODO: Switch this to an enum
                    var colorOp = tevStage.color_op;
                    switch (colorOp)
                    {
                    // ADD: out = a*(1 - c) + b*c + d
                    case TevOp.GX_TEV_ADD:
                    case TevOp.GX_TEV_SUB: {
                        var bias = tevStage.color_bias switch {
                            TevBias.GX_TB_ZERO => null,
                            TevBias.GX_TB_ADDHALF => scHalf,
                            TevBias.GX_TB_SUBHALF => scMinusHalf,
                            _ => throw new ArgumentOutOfRangeException(
                                      "Unsupported color bias!")
                        };

                        var scale = tevStage.color_scale switch {
                            TevScale.GX_CS_SCALE_1 => scOne,
                            TevScale.GX_CS_SCALE_2 => scTwo,
                            TevScale.GX_CS_SCALE_4 => scFour,
                            TevScale.GX_CS_DIVIDE_2 => scHalf,
                            _ => throw new ArgumentOutOfRangeException(
                                      "Unsupported color scale!")
                        };

                        colorValue =
                            colorFixedFunctionOps.AddOrSubtractOp(
                                colorOp == TevOp.GX_TEV_ADD,
                                colorA,
                                colorB,
                                colorC,
                                colorD,
                                bias,
                                scale
                                );

                        colorValue ??= colorZero;
                        colorValue.Clamp = tevStage.color_clamp;

                        break;
                    }

                    default: {
                        if (BmdFixedFunctionMaterial.STRICT)
                        {
                            throw new NotImplementedException();
                        }
                        else
                        {
                            colorValue = colorC;
                        }
                        break;
                    }
                    }

                    valueManager.UpdateColorRegister(tevStage.color_regid, colorValue);

                    var colorAText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(colorA);
                    var colorBText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(colorB);
                    var colorCText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(colorC);
                    var colorDText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(colorD);

                    var colorValueText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(colorValue);

                    ;
                }

                // Set up alpha logic
                {
                    var alphaA = valueManager.GetAlpha(tevStage.alpha_a);
                    var alphaB = valueManager.GetAlpha(tevStage.alpha_b);
                    var alphaC = valueManager.GetAlpha(tevStage.alpha_c);
                    var alphaD = valueManager.GetAlpha(tevStage.alpha_d);

                    IScalarValue?alphaValue = null;

                    // TODO: Switch this to an enum
                    var alphaOp = tevStage.alpha_op;
                    switch (alphaOp)
                    {
                    // ADD: out = a*(1 - c) + b*c + d
                    case TevOp.GX_TEV_ADD:
                    case TevOp.GX_TEV_SUB: {
                        var bias = tevStage.alpha_bias switch {
                            TevBias.GX_TB_ZERO => null,
                            TevBias.GX_TB_ADDHALF => scHalf,
                            TevBias.GX_TB_SUBHALF => scMinusHalf,
                            _ => throw new ArgumentOutOfRangeException(
                                      "Unsupported alpha bias!")
                        };

                        var scale = tevStage.alpha_scale switch {
                            TevScale.GX_CS_SCALE_1 => scOne,
                            TevScale.GX_CS_SCALE_2 => scTwo,
                            TevScale.GX_CS_SCALE_4 => scFour,
                            TevScale.GX_CS_DIVIDE_2 => scHalf,
                            _ => throw new ArgumentOutOfRangeException(
                                      "Unsupported alpha scale!")
                        };

                        alphaValue =
                            scalarFixedFunctionOps.AddOrSubtractOp(
                                alphaOp == TevOp.GX_TEV_ADD,
                                alphaA,
                                alphaB,
                                alphaC,
                                alphaD,
                                bias,
                                scale
                                );

                        alphaValue ??= scZero;
                        //alphaValue.Clamp = tevStage.alpha_clamp;

                        break;
                    }

                    default: {
                        if (BmdFixedFunctionMaterial.STRICT)
                        {
                            throw new NotImplementedException();
                        }
                        else
                        {
                            alphaValue = scZero;
                        }
                        break;
                    }
                    }

                    valueManager.UpdateAlphaRegister(tevStage.alpha_regid, alphaValue);

                    var alphaAText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(alphaA);
                    var alphaBText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(alphaB);
                    var alphaCText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(alphaC);
                    var alphaDText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(alphaD);

                    var alphaValueText =
                        new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                        .Print(alphaValue);

                    ;
                }
            }

            equations.CreateColorOutput(
                FixedFunctionSource.OUTPUT_COLOR,
                valueManager.GetColor(GxCc.GX_CC_CPREV));

            equations.CreateScalarOutput(
                FixedFunctionSource.OUTPUT_ALPHA,
                valueManager.GetAlpha(GxCa.GX_CA_APREV));

            // TODO: Set up compiled texture
            // TODO: If only a const color, create a texture for that


            var sb = new StringBuilder();

            {
                using var os = new StringWriter(sb);

                // TODO: Print textures, colors

                new FixedFunctionEquationsPrettyPrinter <FixedFunctionSource>()
                .Print(os, equations);
            }

            var output = sb.ToString();

            var colorTextureCount =
                material.Textures.Count(
                    texture => texture.ColorType == ColorType.COLOR);

            // TODO: This is a bad assumption!
            if (colorTextureCount == 0 && colorConstants.Count > 0)
            {
                var colorConstant = colorConstants.Last();

                var intensityTexture = material.Textures
                                       .FirstOrDefault(
                    texture => texture.ColorType ==
                    ColorType.INTENSITY);
                if (intensityTexture != null)
                {
                    return;
                }

                var colorImage   = FinImage.Create1x1WithColor(colorConstant);
                var colorTexture = materialManager.CreateTexture(colorImage);
                material.CompiledTexture = colorTexture;
            }
        }
예제 #4
0
 public void ExportToStream(Stream stream, LocalImageFormat imageFormat)
 => this.impl_.Save(
     stream, FinImage.ConvertFinImageFormatToSystem(imageFormat));
예제 #5
0
        public IModel LoadModel(ModlModelFileBundle modelFileBundle)
        {
            var flipSign = ModlFlags.FLIP_HORIZONTALLY ? -1 : 1;

            var modlFile = modelFileBundle.ModlFile;

            using var er = new EndianBinaryReader(modlFile.Impl.OpenRead(),
                                                  Endianness.LittleEndian);
            var bwModel = modelFileBundle.ModlType switch {
                ModlType.BW1 => (IModl)er.ReadNew <Bw1Modl>(),
                ModlType.BW2 => er.ReadNew <Bw2Modl>(),
            };

            var model   = new ModelImpl();
            var finMesh = model.Skin.AddMesh();

            var finBones             = new IBone[bwModel.Nodes.Count];
            var finBonesByModlNode   = new Dictionary <IBwNode, IBone>();
            var finBonesByIdentifier = new Dictionary <string, IBone>();

            {
                var nodeQueue =
                    new FinTuple2Queue <IBone, ushort>((model.Skeleton.Root, 0));

                while (nodeQueue.TryDequeue(out var parentFinBone,
                                            out var modlNodeId))
                {
                    var modlNode = bwModel.Nodes[modlNodeId];

                    var transform    = modlNode.Transform;
                    var bonePosition = transform.Position;

                    var modlRotation = transform.Rotation;
                    var rotation     = new Quaternion(
                        flipSign * modlRotation.X,
                        modlRotation.Y,
                        modlRotation.Z,
                        flipSign * modlRotation.W);
                    var eulerRadians = QuaternionUtil.ToEulerRadians(rotation);

                    var finBone =
                        parentFinBone
                        .AddChild(flipSign * bonePosition.X, bonePosition.Y,
                                  bonePosition.Z)
                        .SetLocalRotationRadians(
                            eulerRadians.X, eulerRadians.Y, eulerRadians.Z);

                    var identifier = modlNode.GetIdentifier();
                    finBone.Name                     = identifier;
                    finBones[modlNodeId]             = finBone;
                    finBonesByModlNode[modlNode]     = finBone;
                    finBonesByIdentifier[identifier] = finBone;

                    if (bwModel.CnctParentToChildren.TryGetList(
                            modlNodeId, out var modlChildIds))
                    {
                        nodeQueue.Enqueue(
                            modlChildIds !.Select(modlChildId => (finBone, modlChildId)));
                    }
                }

                foreach (var animFile in modelFileBundle.AnimFiles ??
                         Array.Empty <IFileHierarchyFile>())
                {
                    var anim = modelFileBundle.ModlType switch {
                        ModlType.BW1 => (IAnim)animFile.Impl.ReadNew <Bw1Anim>(
                            Endianness.BigEndian),
                        ModlType.BW2 => animFile.Impl.ReadNew <Bw2Anim>(
                            Endianness.BigEndian)
                    };

                    var maxFrameCount = -1;
                    foreach (var animBone in anim.AnimBones)
                    {
                        maxFrameCount = (int)Math.Max(maxFrameCount,
                                                      Math.Max(
                                                          animBone
                                                          .PositionKeyframeCount,
                                                          animBone
                                                          .RotationKeyframeCount));
                    }

                    var finAnimation = model.AnimationManager.AddAnimation();
                    finAnimation.Name       = animFile.NameWithoutExtension;
                    finAnimation.FrameRate  = 30;
                    finAnimation.FrameCount = maxFrameCount;

                    for (var b = 0; b < anim.AnimBones.Count; ++b)
                    {
                        var animBone       = anim.AnimBones[b];
                        var animBoneFrames = anim.AnimBoneFrames[b];

                        var animNodeIdentifier = animBone.GetIdentifier();
                        if (!finBonesByIdentifier.TryGetValue(
                                animNodeIdentifier, out var finBone))
                        {
                            // TODO: Gross hack for the vet models, what's the real fix???
                            if (animNodeIdentifier == Bw1Node.GetIdentifier(33))
                            {
                                finBone = finBonesByIdentifier[Bw1Node.GetIdentifier(34)];
                            }
                            else if (finBonesByIdentifier.TryGetValue(
                                         animNodeIdentifier + 'X', out var xBone))
                            {
                                finBone = xBone;
                            }
                            else if (finBonesByIdentifier.TryGetValue(
                                         "BONE_" + animNodeIdentifier,
                                         out var prefixBone))
                            {
                                finBone = prefixBone;
                            }
                            else if (animNodeIdentifier == "WF_GRUNT_BACKPAC")
                            {
                                // TODO: Is this right?????
                                finBone = finBonesByIdentifier["BONE_BCK_MISC"];
                            }
                            else
                            {
                                ;
                            }
                        }

                        var finBoneTracks = finAnimation.AddBoneTracks(finBone !);

                        var fbtPositions = finBoneTracks.Positions;
                        for (var f = 0; f < animBone.PositionKeyframeCount; ++f)
                        {
                            var(fPX, fPY, fPZ) = animBoneFrames.PositionFrames[f];

                            fbtPositions.Set(f, 0, flipSign * fPX);
                            fbtPositions.Set(f, 1, fPY);
                            fbtPositions.Set(f, 2, fPZ);
                        }

                        var fbtRotations = finBoneTracks.Rotations;
                        for (var f = 0; f < animBone.RotationKeyframeCount; ++f)
                        {
                            var(fRX, fRY, fRZ, frW) = animBoneFrames.RotationFrames[f];

                            var animationQuaternion =
                                new Quaternion(flipSign * fRX, fRY, fRZ, flipSign * frW);
                            var eulerRadians =
                                QuaternionUtil.ToEulerRadians(animationQuaternion);

                            fbtRotations.Set(f, 0, eulerRadians.X);
                            fbtRotations.Set(f, 1, eulerRadians.Y);
                            fbtRotations.Set(f, 2, eulerRadians.Z);
                        }
                    }
                }

                var textureDictionary = new LazyDictionary <string, ITexture>(
                    textureName => {
                    var textureFile =
                        modlFile.Parent.Files.Single(
                            file => file.Name.ToLower() == $"{textureName}.png");

                    var image = FinImage.FromFile(textureFile.Impl);

                    var finTexture =
                        model.MaterialManager.CreateTexture(image);
                    finTexture.Name = textureName;

                    // TODO: Need to handle wrapping
                    finTexture.WrapModeU = WrapMode.REPEAT;
                    finTexture.WrapModeV = WrapMode.REPEAT;

                    return(finTexture);
                });

                foreach (var modlNode in bwModel.Nodes)
                {
                    var finMaterials =
                        modlNode.Materials.Select(modlMaterial => {
                        var textureName = modlMaterial.Texture1.ToLower();
                        if (textureName == "")
                        {
                            return(null);
                        }

                        var finTexture = textureDictionary[textureName];

                        var finMaterial =
                            model.MaterialManager
                            .AddTextureMaterial(finTexture);

                        return(finMaterial);
                    })
                        .ToArray();

                    foreach (var modlMesh in modlNode.Meshes)
                    {
                        var finMaterial = finMaterials[modlMesh.MaterialIndex];

                        foreach (var triangleStrip in modlMesh.TriangleStrips)
                        {
                            var vertices =
                                new IVertex[triangleStrip.VertexAttributeIndicesList.Count];
                            for (var i = 0; i < vertices.Length; i++)
                            {
                                var vertexAttributeIndices =
                                    triangleStrip.VertexAttributeIndicesList[i];

                                var position =
                                    modlNode.Positions[vertexAttributeIndices.PositionIndex];
                                var vertex = vertices[i] = model.Skin.AddVertex(
                                    flipSign * position.X * modlNode.Scale,
                                    position.Y * modlNode.Scale,
                                    position.Z * modlNode.Scale);

                                if (vertexAttributeIndices.NormalIndex != null)
                                {
                                    var normal =
                                        modlNode.Normals[
                                            vertexAttributeIndices.NormalIndex.Value];
                                    vertex.SetLocalNormal(flipSign * normal.X, normal.Y,
                                                          normal.Z);
                                }

                                if (vertexAttributeIndices.NodeIndex != null)
                                {
                                    var finBone =
                                        finBones[vertexAttributeIndices.NodeIndex.Value];
                                    vertex.SetBoneWeights(
                                        model.Skin
                                        .GetOrCreateBoneWeights(
                                            PreprojectMode.NONE,
                                            new BoneWeight(finBone, null, 1)));
                                }
                                else
                                {
                                    var finBone = finBonesByModlNode[modlNode];
                                    vertex.SetBoneWeights(
                                        model.Skin.GetOrCreateBoneWeights(
                                            PreprojectMode.BONE, finBone));
                                }

                                var texCoordIndex0 = vertexAttributeIndices.TexCoordIndices[0];
                                var texCoordIndex1 = vertexAttributeIndices.TexCoordIndices[1];
                                if (texCoordIndex1 != null)
                                {
                                    int texCoordIndex;
                                    if (texCoordIndex0 != null)
                                    {
                                        texCoordIndex =
                                            (texCoordIndex0.Value << 8) | texCoordIndex1.Value;
                                    }
                                    else
                                    {
                                        texCoordIndex = texCoordIndex1.Value;
                                    }

                                    var uv = modlNode.UvMaps[0][texCoordIndex];
                                    vertex.SetUv(uv.U, uv.V);
                                }
                            }

                            var triangleStripPrimitive = finMesh.AddTriangleStrip(vertices);
                            if (finMaterial != null)
                            {
                                triangleStripPrimitive.SetMaterial(finMaterial);
                            }
                        }
                    }
                }
            }

            return(model);
        }
    }