private void OnYakChildElementSelected(YakChildTreeViewItem childEntry)
        {
            SelectedNodeImage = TexDecoder.Decode(childEntry.YakFile.FileData, childEntry.Value.TextureOffset + childEntry.Value.VifOffset,
                childEntry.Value.VifLength - childEntry.Value.TextureOffset);
            var log = new StringLogger();
            _modelViewModel.Texture = SelectedNodeImage;
            _modelViewModel.AnimData = null;
            Model model = new Model();
            model.meshList = VifDecoder.Decode(
                log,
                childEntry.YakFile.FileData,
                childEntry.Value.VifOffset,
                childEntry.Value.TextureOffset,
                SelectedNodeImage.PixelWidth,
                SelectedNodeImage.PixelHeight);
            _modelViewModel.VifModel = model;

            LogText += log.ToString();

            _window.tabControl.SelectedIndex = 1; // Model View
            _window.ResetCamera();
            _window.SetViewportText(1, childEntry.Text + " of " + ((YakTreeViewItem)childEntry.Parent).Text, "");
        }
        private void OnWorldEntrySelected(WorldFileTreeViewModel worldFileModel)
        {
            var engineVersion = App.Settings.Get<EngineVersion>("Core.EngineVersion", EngineVersion.DarkAlliance);
            var lmpFile = worldFileModel.LmpFileProperty;
            var entry = lmpFile.Directory[worldFileModel.Text];
            WorldFileDecoder decoder = new WorldFileDecoder();
            var log = new StringLogger();
            _world.worldData = decoder.Decode(engineVersion, _worldTreeViewModel.World().WorldTex, log, lmpFile.FileData, entry.StartOffset, entry.Length);
            worldFileModel.ReloadChildren();
            _levelViewModel.WorldNode = worldFileModel;
            _levelViewModel.WorldData = _world.worldData;
            LogText = log.ToString();
            LogText += _world.worldData.ToString();

            _window.tabControl.SelectedIndex = 3; // Level View
            _window.ResetCamera();
            _window.SetViewportText(3, worldFileModel.Text, ""); // Set Level View Text
        }
        private void OnLmpEntrySelected(LmpEntryTreeViewModel lmpEntry)
        {
            var lmpFile = lmpEntry.LmpFileProperty;
            var entry = lmpFile.Directory[lmpEntry.Text];

            var ext = (Path.GetExtension(lmpEntry.Text) ?? "").ToLower();

            switch (ext)
            {
                case ".tex":
                    {
                        SelectedNodeImage = TexDecoder.Decode(lmpFile.FileData, entry.StartOffset, entry.Length);

                        _window.tabControl.SelectedIndex = 0; // Texture View
                    }
                    break;
                case ".vif":
                    {
                        string texFilename = Path.GetFileNameWithoutExtension(lmpEntry.Text) + ".tex";
                        var texEntry = lmpFile.Directory[texFilename];
                        SelectedNodeImage = TexDecoder.Decode(lmpFile.FileData, texEntry.StartOffset, texEntry.Length);
                        var log = new StringLogger();
                        _modelViewModel.Texture = SelectedNodeImage;
                        _modelViewModel.AnimData = null;
                        Model model = new Model();
                        model.meshList = VifDecoder.Decode(log, lmpFile.FileData, entry.StartOffset, entry.Length,
                                                           SelectedNodeImage.PixelWidth, SelectedNodeImage.PixelHeight);
                        _modelViewModel.VifModel = model;

                        /*// Load animation data
                        var animData = LoadFirstAnim(lmpFile);
                        // Make sure the animation will work with the model
                        if (animData.Count > 0 && animData[0].NumBones == model.CountBones())
                            _modelViewModel.AnimData = animData.Count == 0 ? null : animData.First();*/

                        LogText += log.ToString();

                        _window.tabControl.SelectedIndex = 1; // Model View
                        _window.ResetCamera();
                        _window.SetViewportText(1, lmpEntry.Text, "");
                    }
                    break;
                case ".anm":
                    {
                        var engineVersion = App.Settings.Get("Core.EngineVersion", EngineVersion.DarkAlliance);
                        var animData = AnmDecoder.Decode(engineVersion, lmpFile.FileData, entry.StartOffset, entry.Length);
                        _skeletonViewModel.AnimData = animData;
                        LogText = animData.ToString();

                        if (_modelViewModel.VifModel != null)
                        {
                            int boneCount = _modelViewModel.VifModel.CountBones();
                            if (boneCount != 0 && boneCount == animData.NumBones)
                            {
                                _modelViewModel.AnimData = animData;

                                // Switch tab to animation tab only if the current tab isnt the model view tab
                                if (_window.tabControl.SelectedIndex != 1) // Model View
                                {
                                    _window.tabControl.SelectedIndex = 2; // Skeleton View
                                    _window.ResetCamera();
                                }
                            }
                            else
                            {
                                // Bone count doesn't match, switch to skeleton view
                                _window.tabControl.SelectedIndex = 2; // Skeleton View
                                _window.ResetCamera();
                            }
                        }
                        else
                        {
                            _window.tabControl.SelectedIndex = 2; // Skeleton View
                            _window.ResetCamera();
                        }
                    }

                    _window.SetViewportText(2, lmpEntry.Text, ""); // Set Skeleton View Text

                    break;
                case ".ob":
                    var objects = ObDecoder.Decode(lmpFile.FileData, entry.StartOffset, entry.Length);

                    var sb = new StringBuilder();

                    foreach (var obj in objects)
                    {
                        sb.AppendFormat("Name: {0}\n", obj.Name);
                        sb.AppendFormat("I6: {0}\n", obj.I6.ToString("X4"));
                        sb.AppendFormat("Floats: {0},{1},{2}\n", obj.Floats[0], obj.Floats[1], obj.Floats[2]);
                        if (obj.Properties != null)
                        {
                            foreach (var prop in obj.Properties)
                            {
                                sb.AppendFormat("Property: {0}\n", prop);
                            }
                        }
                        sb.Append("\n");
                    }

                    LogText = sb.ToString();
                    _window.tabControl.SelectedIndex = 4; // Log View

                    break;
            }
        }
        public WorldData Decode(EngineVersion engineVersion, WorldTexFile texFile, ILogger log, byte[] data, int startOffset, int length)
        {
            WorldData worldData = new WorldData();

            var reader = new DataReader(data, startOffset, length);

            int numElements = reader.ReadInt32();       // 0

            reader.Skip(12); // Skipping 3 ints

            int numCols = reader.ReadInt32();           // x10
            int numRows = reader.ReadInt32();           // x14

            reader.Skip(12); // Skipping 3 ints         // x18 x1c x20
            int elementArrayStart = reader.ReadInt32(); // x24

            reader.Skip(8); // Skipping 2 ints
            int off38Cols = reader.ReadInt32();
            int off38Rows = reader.ReadInt32();
            int off38 = reader.ReadInt32();

            reader.Skip(28);
            int texll = reader.ReadInt32();
            int texur = reader.ReadInt32();
            int texX0 = texll % 100;
            int texY0 = texll / 100;
            int texX1 = texur % 100;
            int texY1 = texur / 100;

            reader.Skip(4);
            int worldTexOffsetsOffset = reader.ReadInt32();
            worldData.textureChunkOffsets = readTextureChunkOffsets(engineVersion, data, startOffset + worldTexOffsetsOffset, texX0, texY0, texX1+1, texY1);
            worldData.worldElements = new List<WorldElement>(numElements);

            for (int elementIdx = 0; elementIdx < numElements; ++elementIdx)
            {
                var element = new WorldElement();

                if (EngineVersion.ReturnToArms == engineVersion)
                {
                    reader.SetOffset(elementArrayStart + elementIdx * 0x3C);
                }
                else // Default to Dark Allience version
                {
                    reader.SetOffset(elementArrayStart + elementIdx * 0x38);
                }

                int vifDataOffset = reader.ReadInt32();

                if (EngineVersion.DarkAlliance == engineVersion)
                {
                    int tex2 = reader.ReadInt32();
                    if (tex2 != 0) {
                        log.LogLine("Tex2=" + tex2);
                    }
                }

                int vifLen = reader.ReadInt32();
                log.LogLine("-----------");
                log.LogLine("vifdata: " + vifDataOffset + ", " + vifLen);

                float x1 = reader.ReadFloat();
                float y1 = reader.ReadFloat();
                float z1 = reader.ReadFloat();
                float x2 = reader.ReadFloat();
                float y2 = reader.ReadFloat();
                float z2 = reader.ReadFloat();

                element.boundingBox = new Rect3D(x1, y1, z1, x2 - x1, y2 - y1, z2 - z1);

                log.LogLine("Bounding Box: " + element.boundingBox.ToString());

                int textureNum = reader.ReadInt32() / 0x40;
                log.LogLine("Texture Num: " + textureNum);

                int texCellxy = reader.ReadInt16();
                int y = texCellxy / 100;
                int x = texCellxy % 100;

                if (EngineVersion.ReturnToArms == engineVersion)
                {
                    x += texX0;
                    y += texY0;
                }

                if (textureNum != 0)
                {
                    if (EngineVersion.ReturnToArms == engineVersion)
                    {
                        element.Texture = texFile.GetBitmapRTA(x, y, textureNum);
                    }
                    else
                    {
                        element.Texture = texFile.GetBitmap(worldData.textureChunkOffsets[y, x], textureNum);
                    }
                }

                if (element.Texture != null)
                {
                    log.LogLine("Found in texture chunk: " + x + ", " + y);
                }

                var vifLogger = new StringLogger();
                int texWidth = 100;
                int texHeight = 100;
                if (element.Texture != null)
                {
                    texWidth = element.Texture.PixelWidth;
                    texHeight = element.Texture.PixelHeight;
                }

                byte nregs = data[startOffset + vifDataOffset + 0x10];
                int vifStartOffset = (nregs + 2) * 0x10;
                element.VifDataOffset = startOffset + vifDataOffset + vifStartOffset;
                element.VifDataLength = vifLen*0x10 - vifStartOffset;
                element.model = decodeModel(engineVersion, vifLogger, data, startOffset + vifDataOffset + vifStartOffset, vifLen * 0x10 - vifStartOffset, texWidth, texHeight);

                if (EngineVersion.ReturnToArms == engineVersion)
                {
                    int unk = reader.ReadInt16();
                    log.LogLine("Unknown: " + unk);
                }

                int posx = reader.ReadInt16();
                int posy = reader.ReadInt16();
                int posz = reader.ReadInt16();

                log.LogLine("Position : " + posx + ", " + posy + ", " + posz);

                element.pos = new Vector3D(posx / 16.0, posy / 16.0, posz / 16.0);

                if (EngineVersion.ReturnToArms == engineVersion)
                {
                    // Just a guess, maybe wrong.
                    element.pos = new Vector3D(posx / 16.0, posz / 16.0, posy / 16.0);
                }

                // I don't think RTA uses this flags scheme. From the data it looks like there are
                // 2 shorts (or possibly floats) following.

                int flags = reader.ReadInt32();

                if ((flags & 0x01) == 0)
                {
                    log.LogLine("Flags   : " + HexUtil.formatHexUShort(flags & 0xFFFF));
                    element.cosAlpha = (flags >> 16) / 32767.0;
                    element.sinAlpha = reader.ReadInt16() / 32767.0;
                    log.LogLine("cos alpha : " + element.cosAlpha);
                    log.LogLine("sin alpha : " + element.sinAlpha);
                    log.LogLine("alpha(cos, sin): " + Math.Acos(element.cosAlpha) * 180.0 / Math.PI + ", " + Math.Asin(element.sinAlpha) * 180.0 / Math.PI);

                    element.usesRotFlags = false;
                }
                else
                {
                    reader.ReadInt16();     // not necessary but makes the code more obvious.
                    log.LogLine("Flags   : " + HexUtil.formatHex(flags));
                    element.xyzRotFlags = (flags >> 16) & 7;
                    element.usesRotFlags = true;
                    log.LogLine("Rot Flags   : " + element.xyzRotFlags);
                }

                element.negYaxis = (flags & 0x40) == 0x40;

                if (EngineVersion.ReturnToArms == engineVersion)
                {
                    flags = 0;
                    element.usesRotFlags = true;
                    log.LogLine("Forcing flags to 0 until we know the format better");
                }

                worldData.worldElements.Add(element);
            }

            return worldData;
        }
        private Visual3D LoadModelFromOtherLmp(string lmpName, string file, string textureFile)
        {
            var par = _manager.LevelViewModel.WorldNode.Parent.Parent;

            if (par is GobTreeViewModel)
            {
                var gob = (GobTreeViewModel) par;
                foreach (LmpTreeViewModel child in gob.Children)
                {
                    if (string.Compare(child.Text, lmpName, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        child.ForceLoadChildren();
                        var entry = child.LmpFileProperty.FindFile(file);
                        var texEntry = child.LmpFileProperty.FindFile(textureFile);

                        if (entry == null || texEntry == null)
                            return CreateBox(5, Color.FromRgb(255,0,0));

                        var tex = TexDecoder.Decode(child.LmpFileProperty.FileData, texEntry.StartOffset, texEntry.Length);

                        var logger = new StringLogger();
                        var vifModel = VifDecoder.Decode(
                            logger,
                            child.LmpFileProperty.FileData,
                            entry.StartOffset,
                            entry.Length,
                            tex.PixelWidth,
                            tex.PixelHeight);

                        var model = new ModelVisual3D();
                        model.Content = VifDecoder.CreateModel3D(vifModel, tex, null, -1);
                        model.Transform = new ScaleTransform3D(1.0 / 4, 1.0 / 4, 1.0 / 4);
                        return model;
                    }
                }
            }

            return CreateBox(5, Color.FromRgb(255, 0, 0));
        }
        void SaveParsedDataClicked(object sender, RoutedEventArgs e)
        {
            if (_menu.DataContext == null)
                return;

            if (_menu.DataContext is LmpEntryTreeViewModel)
            {
                var lmpEntry = (LmpEntryTreeViewModel)_menu.DataContext;
                var lmpFile = lmpEntry.LmpFileProperty;

                var entry = lmpFile.Directory[lmpEntry.Text];
                var texEntry = lmpFile.Directory[Path.GetFileNameWithoutExtension(lmpEntry.Text)+".tex"];

                var tex = TexDecoder.Decode(lmpFile.FileData, texEntry.StartOffset, texEntry.Length);

                if ((Path.GetExtension(lmpEntry.Text) ?? "").ToLower() != ".vif")
                {
                    MessageBox.Show("Not a .vif file!", "Error");
                    return;
                }

                var dialog = new SaveFileDialog();
                dialog.FileName = lmpEntry.Text+".txt";

                bool? result = dialog.ShowDialog();
                if (result.GetValueOrDefault(false))
                {
                    var exporter = new VifExporter();

                    var logger = new StringLogger();
                    var chunks = VifDecoder.DecodeChunks(
                        logger,
                        lmpFile.FileData,
                        entry.StartOffset,
                        entry.Length,
                        tex.PixelWidth,
                        tex.PixelHeight);

                    exporter.WriteChunks(dialog.FileName, chunks);
                }
            }
            else if (_menu.DataContext is WorldElementTreeViewModel)
            {
                var worldElement = (WorldElementTreeViewModel)_menu.DataContext;
                var lmpEntry = (LmpTreeViewModel)worldElement.Parent;
                var lmpFile = lmpEntry.LmpFileProperty;

                var dialog = new SaveFileDialog();
                dialog.FileName = worldElement.Text + ".txt";

                bool? result = dialog.ShowDialog();
                if (result.GetValueOrDefault(false))
                {
                    var exporter = new VifExporter();

                    var logger = new StringLogger();
                    var chunks = VifDecoder.ReadVerts(
                        logger,
                        lmpFile.FileData,
                        worldElement.WorldElement.VifDataOffset,
                        worldElement.WorldElement.VifDataOffset + worldElement.WorldElement.VifDataLength);

                    exporter.WriteChunks(dialog.FileName, chunks);
                }
            }
        }