diff --git a/PCK-Studio/Controls/PckAssetBrowserEditor.cs b/PCK-Studio/Controls/PckAssetBrowserEditor.cs index a754f9c7..f0a7567a 100644 --- a/PCK-Studio/Controls/PckAssetBrowserEditor.cs +++ b/PCK-Studio/Controls/PckAssetBrowserEditor.cs @@ -78,6 +78,7 @@ namespace PckStudio.Controls private readonly ViewPanel _default; private readonly ViewPanel _models; + private readonly ViewPanel _skins; private int _timesSaved = 0; @@ -102,6 +103,7 @@ namespace PckStudio.Controls img = found ? asset.GetTexture() : default; return found; })); + _skins = new SkinsPanel(_currentEndianness); } treeViewMain.TreeViewNodeSorter = new PckNodeSorter(); @@ -918,6 +920,8 @@ namespace PckStudio.Controls { case PckAssetType.ModelsFile: return _models; + case PckAssetType.SkinDataFile: + return _skins; default: return _default; } diff --git a/PCK-Studio/Controls/SkinsPanel.Designer.cs b/PCK-Studio/Controls/SkinsPanel.Designer.cs new file mode 100644 index 00000000..13b43eb7 --- /dev/null +++ b/PCK-Studio/Controls/SkinsPanel.Designer.cs @@ -0,0 +1,119 @@ +namespace PckStudio.Controls +{ + partial class SkinsPanel + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.skinRenderer1 = new PckStudio.Rendering.SkinRenderer(); + this.treeView1 = new System.Windows.Forms.TreeView(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18))))); + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Controls.Add(this.skinRenderer1, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.treeView1, 0, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.ForeColor = System.Drawing.SystemColors.Control; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(660, 517); + this.tableLayoutPanel1.TabIndex = 0; + // + // skinRenderer1 + // + this.skinRenderer1.AllowCameraMovement = false; + this.skinRenderer1.Animate = true; + this.skinRenderer1.ArmorTexture = null; + this.skinRenderer1.BackColor = System.Drawing.Color.Black; + this.skinRenderer1.CapeTexture = null; + this.skinRenderer1.CenterOnSelect = false; + this.skinRenderer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.skinRenderer1.GuideLineColor = System.Drawing.Color.Empty; + this.skinRenderer1.HighlightlingColor = System.Drawing.Color.Aqua; + this.skinRenderer1.Location = new System.Drawing.Point(333, 3); + this.skinRenderer1.MouseSensetivity = 0.01F; + this.skinRenderer1.Name = "skinRenderer1"; + this.skinRenderer1.RefreshRate = 60; + this.skinRenderer1.RenderGroundPlane = true; + this.skinRenderer1.RenderSkyBox = true; + this.skinRenderer1.SelectedIndex = -1; + this.skinRenderer1.SelectedIndices = new int[0]; + this.skinRenderer1.ShowArmor = false; + this.skinRenderer1.ShowBoundingBox = false; + this.skinRenderer1.ShowGuideLines = false; + this.skinRenderer1.Size = new System.Drawing.Size(324, 252); + this.skinRenderer1.TabIndex = 0; + this.skinRenderer1.Texture = null; + this.skinRenderer1.VSync = true; + // + // treeView1 + // + this.treeView1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18))))); + this.treeView1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.treeView1.ForeColor = System.Drawing.SystemColors.Window; + this.treeView1.HideSelection = false; + this.treeView1.Location = new System.Drawing.Point(3, 3); + this.treeView1.Name = "treeView1"; + this.treeView1.PathSeparator = "."; + this.tableLayoutPanel1.SetRowSpan(this.treeView1, 2); + this.treeView1.ShowLines = false; + this.treeView1.ShowPlusMinus = false; + this.treeView1.ShowRootLines = false; + this.treeView1.Size = new System.Drawing.Size(324, 511); + this.treeView1.TabIndex = 1; + this.treeView1.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect); + // + // SkinsPanel + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18))))); + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "SkinsPanel"; + this.Size = new System.Drawing.Size(660, 517); + this.tableLayoutPanel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private Rendering.SkinRenderer skinRenderer1; + private System.Windows.Forms.TreeView treeView1; + } +} diff --git a/PCK-Studio/Controls/SkinsPanel.cs b/PCK-Studio/Controls/SkinsPanel.cs new file mode 100644 index 00000000..62ccf236 --- /dev/null +++ b/PCK-Studio/Controls/SkinsPanel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using OMI.Formats.Pck; +using OMI.Workers.Pck; +using PckStudio.Core.Extensions; +using PckStudio.Core.Skin; + +namespace PckStudio.Controls +{ + public partial class SkinsPanel : ViewPanel + { + readonly PckFileReader _reader; + readonly ImageList _imageList; + public SkinsPanel(OMI.ByteOrder byteOrder) + { + InitializeComponent(); + _reader = new PckFileReader(byteOrder); + _imageList = new ImageList() + { + ImageSize = new Size(32, 32), + ColorDepth = ColorDepth.Depth32Bit, + }; + treeView1.ImageList = _imageList; + } + + public override void LoadAsset(PckAsset asset, Action onChange) + { + Reset(); + treeView1.Nodes.AddRange( + asset.GetData(_reader).GetAssetsByType(PckAssetType.SkinFile) + .Select(PckAssetExtensions.GetSkin) + .enumerate() + .Select(a => + { + _imageList.Images.Add(a.value.GetPreviewImage(_imageList.ImageSize)); + return new TreeNode(a.value.MetaData.Name) { ImageIndex = a.index, SelectedImageIndex = a.index, Tag = a.value }; + }) + .ToArray() + ); + } + + public override void Reset() + { + _imageList.Images.Clear(); + treeView1.Nodes.Clear(); + } + + private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) + { + if (e?.Node.Tag is Skin skin) + { + skinRenderer1.LoadSkin(skin); + } + } + } +} diff --git a/PCK-Studio/Controls/SkinsPanel.resx b/PCK-Studio/Controls/SkinsPanel.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/PCK-Studio/Controls/SkinsPanel.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj index 6011bab2..8c62dd0b 100644 --- a/PCK-Studio/PckStudio.csproj +++ b/PCK-Studio/PckStudio.csproj @@ -161,6 +161,12 @@ ModelsPanel.cs + + UserControl + + + SkinsPanel.cs + UserControl @@ -423,6 +429,9 @@ PckAssetBrowserEditor.cs + + SkinsPanel.cs + FilterPrompt.cs diff --git a/PCK-Studio/Rendering/SceneViewport.cs b/PCK-Studio/Rendering/SceneViewport.cs index ae7cca25..33ce1f45 100644 --- a/PCK-Studio/Rendering/SceneViewport.cs +++ b/PCK-Studio/Rendering/SceneViewport.cs @@ -66,7 +66,7 @@ namespace PckStudio.Rendering set { base.BackColor = value; - if (!DesignMode && Context.IsCurrent) + if (!DesignMode) Renderer.SetClearColor(value); } } @@ -149,6 +149,8 @@ namespace PckStudio.Rendering Camera = new PerspectiveCamera(fov, camareaPosition); _shaderLibrary = new ShaderLibrary(); + if (!DesignMode) + GetContext(); } protected virtual void Initialize() { } @@ -171,9 +173,9 @@ namespace PckStudio.Rendering if (DesignMode) return; if (!Visible) - _timer.Stop(); + _timer?.Stop(); if (Visible) - _timer.Start(); + _timer?.Start(); } protected override void OnGotFocus(EventArgs e) diff --git a/PCK-Studio/Rendering/SkinRenderer.cs b/PCK-Studio/Rendering/SkinRenderer.cs index 48e778c6..cfa4a6ad 100644 --- a/PCK-Studio/Rendering/SkinRenderer.cs +++ b/PCK-Studio/Rendering/SkinRenderer.cs @@ -16,24 +16,23 @@ * 3. This notice may not be removed or altered from any source distribution. **/ using System; -using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Drawing; using System.Diagnostics; -using OpenTK; -using PckStudio.Internal; -using PckStudio.Core.Extensions; -using OpenTK.Graphics.OpenGL; using System.Windows.Forms; using System.ComponentModel; -using System.Drawing; -using PckStudio.Properties; +using System.Drawing.Imaging; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Drawing.Imaging; -using System.IO; +using OpenTK; +using OpenTK.Graphics.OpenGL; +using PckStudio.Properties; using PckStudio.Rendering.Extension; using PckStudio.Rendering.Texture; using PckStudio.Rendering.Shader; -using System.Linq; +using PckStudio.Core.Extensions; using PckStudio.Core.Skin; using PckStudio.Core; @@ -183,7 +182,8 @@ namespace PckStudio.Rendering set { _anim = value; - OnANIMUpdate(); + if (value is not null && !DesignMode) + OnANIMUpdate(); } } @@ -290,6 +290,15 @@ namespace PckStudio.Rendering }; public SkinRenderer() : base(fov: 60f) + { + InitializeComponent(); + + ANIM ??= new SkinANIM(SkinAnimMask.RESOLUTION_64x64); + ModelData = new ObservableCollection(); + ModelData.CollectionChanged += ModelData_CollectionChanged; + } + + protected override void Initialize() { InitializeSkinData(); InitializeCapeData(); @@ -312,15 +321,6 @@ namespace PckStudio.Rendering CalculateSkinBounds(); InitializeArmorData(); InitializeCamera(); - InitializeComponent(); - - ANIM ??= new SkinANIM(SkinAnimMask.RESOLUTION_64x64); - ModelData = new ObservableCollection(); - ModelData.CollectionChanged += ModelData_CollectionChanged; - } - - protected override void Initialize() - { InitializeShaders(); Renderer.SetClearColor(BackColor); GLErrorCheck(); @@ -755,6 +755,8 @@ namespace PckStudio.Rendering private void OnANIMUpdate() { + if (ANIM is null) + return; head.SetVisible(0, !ANIM.GetFlag(SkinAnimFlag.HEAD_DISABLED)); head.SetVisible(1, !ANIM.GetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED)); diff --git a/PckStudio.Core/Extensions/SkinBOXExtensions.cs b/PckStudio.Core/Extensions/SkinBOXExtensions.cs index 2c95b2ba..7ed6229e 100644 --- a/PckStudio.Core/Extensions/SkinBOXExtensions.cs +++ b/PckStudio.Core/Extensions/SkinBOXExtensions.cs @@ -77,5 +77,46 @@ namespace PckStudio.Core.Extensions int index = Array.IndexOf(SkinBOX.OverlayTypes, type); return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : ""; } + + public enum SkinBoxFace + { + Front, + Back, + Top, + Bottom, + Left, + Right + } + + public static Rectangle GetFaceArea(this SkinBOX skinBox, SkinBoxFace face) => new Rectangle(skinBox.GetPoint(face), skinBox.GetSize(face)); + + public static Point GetPoint(this SkinBOX skinBox, SkinBoxFace face) + { + return Point.Truncate((face) switch + { + SkinBoxFace.Front => new PointF(skinBox.UV.X + skinBox.Size.Z , skinBox.UV.Y + skinBox.Size.Z), + SkinBoxFace.Back => new PointF(skinBox.UV.X + skinBox.Size.Z * 2 + skinBox.Size.X, skinBox.UV.Y + skinBox.Size.Z), + SkinBoxFace.Top => new PointF(skinBox.UV.X + skinBox.Size.X , skinBox.UV.Y), + SkinBoxFace.Bottom => new PointF(skinBox.UV.X + skinBox.Size.X * 2 , skinBox.UV.Y), + SkinBoxFace.Left => new PointF(skinBox.UV.X + skinBox.Size.Z + skinBox.Size.X , skinBox.UV.Y + skinBox.Size.Z), + SkinBoxFace.Right => new PointF(skinBox.UV.X + skinBox.Size.Z , skinBox.UV.Y + skinBox.Size.Z), + _ => PointF.Empty, + }); + } + + public static Size GetSize(this SkinBOX skinBox, SkinBoxFace face) + { + return Size.Truncate((face) switch + { + SkinBoxFace.Front => new SizeF(skinBox.Size.X, skinBox.Size.Y), + SkinBoxFace.Back => new SizeF(skinBox.Size.X, skinBox.Size.Y), + SkinBoxFace.Top => new SizeF(skinBox.Size.X, skinBox.Size.Z), + SkinBoxFace.Bottom => new SizeF(skinBox.Size.X, skinBox.Size.Z), + SkinBoxFace.Left => new SizeF(skinBox.Size.Z, skinBox.Size.Y), + SkinBoxFace.Right => new SizeF(skinBox.Size.Z, skinBox.Size.Y), + _ => SizeF.Empty, + }); + } + } } diff --git a/PckStudio.Core/Extensions/SkinExtensions.cs b/PckStudio.Core/Extensions/SkinExtensions.cs index 83887d85..6a5950fe 100644 --- a/PckStudio.Core/Extensions/SkinExtensions.cs +++ b/PckStudio.Core/Extensions/SkinExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Linq; @@ -68,5 +69,30 @@ namespace PckStudio.Core.Extensions capeFile.SetTexture(capeTexture); return capeFile; } + + public static Image GetPreviewImage(this Skin.Skin skin, Size size) => skin.GetPreviewImage(size.Width, size.Height); + public static Image GetPreviewImage(this Skin.Skin skin, int width = 16, int height = 16) + { + Image result = new Bitmap(width, height); + using Graphics g = Graphics.FromImage(result); + g.ApplyConfig(GraphicsConfig.PixelPerfect()); + g.Clear(Color.Transparent); + + if (!skin.Anim.GetFlag(SkinAnimFlag.HEAD_DISABLED)) + { + g.DrawImage(skin.Texture.GetArea(new Rectangle(8, 8, 8, 8)), 0, 0, width, height); + } + else if (!skin.Anim.GetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED)) + { + g.DrawImage(skin.Texture.GetArea(new Rectangle(40, 8, 8, 8)), 0, 0, width, height); + } + else + { + Rectangle area = skin.Model.AdditionalBoxes.Where(sb => sb.Type == "HEAD" || sb.Type == "HEADWEAR").OrderBy(sb=> sb.Pos.Z - sb.Scale).FirstOrDefault()?.GetFaceArea(SkinBOXExtensions.SkinBoxFace.Front) ?? Rectangle.Empty; + Image img = skin.Texture.GetArea(area); + g.DrawImage(img, 0, 0, width, height); + } + return result; + } } }