mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-06-16 03:11:54 +00:00
627 lines
27 KiB
C#
627 lines
27 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using System.Drawing.Imaging;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Collections.Generic;
|
|
using MetroFramework.Forms;
|
|
using PckStudio.Internal;
|
|
using PckStudio.Extensions;
|
|
using PckStudio.IO.PSM;
|
|
using PckStudio.Internal.FileFormats;
|
|
using System.Linq;
|
|
using PckStudio.Forms.Additional_Popups;
|
|
using PckStudio.External.Format;
|
|
using Newtonsoft.Json;
|
|
using System.Numerics;
|
|
using PckStudio.Rendering;
|
|
using System.Diagnostics;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace PckStudio.Forms.Editor
|
|
{
|
|
public partial class CustomSkinEditor : MetroForm
|
|
{
|
|
public Image PreviewImage => _previewImage;
|
|
|
|
public Skin ResultSkin => _skin;
|
|
|
|
private Image _previewImage;
|
|
private Skin _skin;
|
|
private Random rng;
|
|
private bool _inflateOverlayParts;
|
|
private bool _allowInflate;
|
|
|
|
private static readonly FileDialogFilter[] fileFilters =
|
|
[
|
|
new ("Pck skin model(*.psm)", "*.psm"),
|
|
new ("Block bench model(*.bbmodel)", "*.bbmodel"),
|
|
new ("Bedrock Model(*.geo.json)", "*.geo.json"),
|
|
new ("Bedrock Legacy Model(*.json)", "*.json"),
|
|
];
|
|
|
|
private string AvailableModelFileFilters => string.Join("|", fileFilters);
|
|
|
|
private BindingSource skinPartListBindingSource;
|
|
private BindingSource skinOffsetListBindingSource;
|
|
|
|
private static GraphicsConfig _graphicsConfig = new GraphicsConfig()
|
|
{
|
|
InterpolationMode = InterpolationMode.NearestNeighbor,
|
|
PixelOffsetMode = PixelOffsetMode.HighQuality,
|
|
};
|
|
|
|
private CustomSkinEditor()
|
|
{
|
|
InitializeComponent();
|
|
rng = new Random();
|
|
skinPartListBindingSource = new BindingSource(renderer3D1.ModelData, null);
|
|
skinPartListBox.DataSource = skinPartListBindingSource;
|
|
skinPartListBox.DisplayMember = "Type";
|
|
}
|
|
|
|
public CustomSkinEditor(Skin skin, bool inflateOverlayParts = false, bool allowInflate = false) : this()
|
|
{
|
|
_skin = skin;
|
|
_allowInflate = allowInflate;
|
|
_inflateOverlayParts = inflateOverlayParts;
|
|
}
|
|
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
base.OnLoad(e);
|
|
renderer3D1.Initialize(_inflateOverlayParts);
|
|
renderer3D1.OutlineColor = Color.DarkSlateBlue;
|
|
LoadModelData(_skin);
|
|
}
|
|
|
|
private void LoadModelData(Skin skin)
|
|
{
|
|
skinNameLabel.Text = skin.Name;
|
|
var boxProperties = skin.AdditionalBoxes;
|
|
var offsetProperties = skin.PartOffsets;
|
|
|
|
renderer3D1.ANIM = skin.ANIM;
|
|
|
|
if (skin.HasCape)
|
|
renderer3D1.CapeTexture = skin.CapeTexture;
|
|
|
|
renderer3D1.ModelData.Clear();
|
|
foreach (SkinBOX box in boxProperties)
|
|
{
|
|
renderer3D1.ModelData.Add(box);
|
|
}
|
|
renderer3D1.ResetOffsets();
|
|
foreach (SkinPartOffset offset in offsetProperties)
|
|
{
|
|
renderer3D1.SetPartOffset(offset);
|
|
}
|
|
if (skin.Texture is not null)
|
|
{
|
|
renderer3D1.Texture = skin.Texture;
|
|
}
|
|
|
|
skinOffsetListBindingSource = new BindingSource(renderer3D1.GetOffsets().ToArray(), null);
|
|
offsetListBox.DataSource = skinOffsetListBindingSource;
|
|
offsetListBox.DisplayMember = "Type";
|
|
offsetListBox.ValueMember = "Value";
|
|
|
|
skinPartListBindingSource.ResetBindings(false);
|
|
skinOffsetListBindingSource.ResetBindings(false);
|
|
}
|
|
|
|
private void GenerateUVTextureMap(SkinBOX skinBox)
|
|
{
|
|
using (Graphics graphics = Graphics.FromImage(_skin.Texture))
|
|
{
|
|
graphics.ApplyConfig(_graphicsConfig);
|
|
int argb = rng.Next(unchecked((int)0xFF000000), -1);
|
|
var color = Color.FromArgb(argb);
|
|
Brush brush = new SolidBrush(color);
|
|
graphics.FillPath(brush, skinBox.GetUVGraphicsPath());
|
|
}
|
|
renderer3D1.Texture = _skin.Texture;
|
|
}
|
|
|
|
private void createToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var boxEditor = new BoxEditor(SkinBOX.Empty, _allowInflate);
|
|
if (boxEditor.ShowDialog() == DialogResult.OK)
|
|
{
|
|
var newBox = boxEditor.Result;
|
|
renderer3D1.ModelData.Add(newBox);
|
|
skinPartListBindingSource.ResetBindings(false);
|
|
if (generateTextureCheckBox.Checked)
|
|
GenerateUVTextureMap(newBox);
|
|
}
|
|
}
|
|
|
|
private void exportTextureButton_Click(object sender, EventArgs e)
|
|
{
|
|
using SaveFileDialog saveFileDialog = new SaveFileDialog();
|
|
saveFileDialog.Filter = "PNG Image Files | *.png";
|
|
if (saveFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
_skin.Texture.Save(saveFileDialog.FileName, ImageFormat.Png);
|
|
}
|
|
}
|
|
|
|
private void importTextureButton_Click(object sender, EventArgs e)
|
|
{
|
|
OpenFileDialog openFileDialog = new OpenFileDialog();
|
|
openFileDialog.Filter = "PNG Image Files | *.png";
|
|
openFileDialog.Title = "Select Skin Texture";
|
|
|
|
if (openFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
generateTextureCheckBox.Checked = false;
|
|
renderer3D1.Texture = Image.FromFile(openFileDialog.FileName).ReleaseFromFile();
|
|
}
|
|
}
|
|
|
|
private void buttonDone_Click(object sender, EventArgs e)
|
|
{
|
|
_skin.AdditionalBoxes.Clear();
|
|
_skin.AdditionalBoxes.AddRange(renderer3D1.ModelData);
|
|
_skin.PartOffsets.Clear();
|
|
_skin.PartOffsets.AddRange(renderer3D1.GetOffsets());
|
|
// just in case they're not the same instance
|
|
_skin.ANIM = renderer3D1.ANIM;
|
|
DialogResult = DialogResult.OK;
|
|
}
|
|
|
|
private void exportSkinButton_Click(object sender, EventArgs e)
|
|
{
|
|
SaveFileDialog saveFileDialog = new SaveFileDialog();
|
|
saveFileDialog.Title = "Save Model File";
|
|
saveFileDialog.Filter = AvailableModelFileFilters;
|
|
if (saveFileDialog.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
string fileExtension = Path.GetExtension(saveFileDialog.FileName);
|
|
switch (fileExtension)
|
|
{
|
|
case ".psm":
|
|
var writer = new PSMFileWriter(PSMFile.FromSkin(_skin));
|
|
writer.WriteToFile(saveFileDialog.FileName);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void importSkinButton_Click(object sender, EventArgs e)
|
|
{
|
|
OpenFileDialog openFileDialog = new OpenFileDialog();
|
|
openFileDialog.Title = "Select Model File";
|
|
openFileDialog.Filter = AvailableModelFileFilters;
|
|
if (MessageBox.Show("Import custom model project file? Your current work will be lost!", "", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) == DialogResult.Yes && openFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string fileExtension = Path.GetExtension(openFileDialog.FileName);
|
|
switch (fileExtension)
|
|
{
|
|
case ".psm":
|
|
var reader = new PSMFileReader();
|
|
PSMFile csmbFile = reader.FromFile(openFileDialog.FileName);
|
|
_skin.ANIM = csmbFile.SkinANIM;
|
|
_skin.AdditionalBoxes.Clear();
|
|
_skin.PartOffsets.Clear();
|
|
_skin.AdditionalBoxes.AddRange(csmbFile.Parts);
|
|
_skin.PartOffsets.AddRange(csmbFile.Offsets);
|
|
LoadModelData(_skin);
|
|
break;
|
|
case ".json":
|
|
ImportBedrockJson(openFileDialog.FileName);
|
|
break;
|
|
case ".bbmodel":
|
|
BlockBenchModel blockBenchModel = JsonConvert.DeserializeObject<BlockBenchModel>(File.ReadAllText(openFileDialog.FileName));
|
|
_skin.AdditionalBoxes.Clear();
|
|
_skin.PartOffsets.Clear();
|
|
|
|
if (blockBenchModel.Textures.IndexInRange(0))
|
|
_skin.Texture = blockBenchModel.Textures[0].GetTexture();
|
|
|
|
// TODO: clean this up -miku
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RESOLUTION_64x64, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.SLIM_MODEL, false);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.HEAD_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.BODY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.BODY_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_ARM_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_ARM_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_ARM_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_LEG_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_LEG_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_LEG_OVERLAY_DISABLED, true);
|
|
|
|
foreach (JToken token in blockBenchModel.Outliner)
|
|
{
|
|
if (token.Type == JTokenType.String && Guid.TryParse((string)token, out Guid tokenGuid))
|
|
{
|
|
Element element = blockBenchModel.Elements.First(e => e.Uuid.Equals(tokenGuid));
|
|
if (!SkinBOX.IsValidType(element.Name) || element.Type != "cube")
|
|
continue;
|
|
LoadElement(element.Name, element);
|
|
continue;
|
|
}
|
|
if (token.Type == JTokenType.Object)
|
|
{
|
|
Outline outline = token.ToObject<Outline>();
|
|
string type = outline.Name;
|
|
if (!SkinBOX.IsValidType(type))
|
|
continue;
|
|
ReadOutliner(token, type, blockBenchModel.Elements);
|
|
}
|
|
}
|
|
|
|
LoadModelData(_skin);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ImportBedrockJson(string fileName)
|
|
{
|
|
_skin.AdditionalBoxes.Clear();
|
|
_skin.PartOffsets.Clear();
|
|
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RESOLUTION_64x64, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.SLIM_MODEL, false);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.HEAD_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.BODY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.BODY_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_ARM_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_ARM_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_ARM_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_ARM_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_LEG_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.RIGHT_LEG_OVERLAY_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_LEG_DISABLED, true);
|
|
_skin.ANIM.SetFlag(SkinAnimFlag.LEFT_LEG_OVERLAY_DISABLED, true);
|
|
|
|
Geometry selectedGeometry = null;
|
|
// Bedrock Entity (Model)
|
|
if (fileName.EndsWith(".geo.json"))
|
|
{
|
|
BedrockModel bedrockModel = JsonConvert.DeserializeObject<BedrockModel>(File.ReadAllText(fileName));
|
|
ItemSelectionPopUp itemSelectionPopUp = new ItemSelectionPopUp(bedrockModel.Models.Select(m => m.Description.Identifier).ToArray());
|
|
if (itemSelectionPopUp.ShowDialog() == DialogResult.OK && bedrockModel.Models.IndexInRange(itemSelectionPopUp.SelectedIndex))
|
|
{
|
|
selectedGeometry = bedrockModel.Models[itemSelectionPopUp.SelectedIndex];
|
|
}
|
|
itemSelectionPopUp.Dispose();
|
|
}
|
|
|
|
// Bedrock Legacy Model
|
|
if (fileName.EndsWith(".json"))
|
|
{
|
|
BedrockLegacyModel bedrockModel = JsonConvert.DeserializeObject<BedrockLegacyModel>(File.ReadAllText(fileName));
|
|
ItemSelectionPopUp itemSelectionPopUp = new ItemSelectionPopUp(bedrockModel.Select(m => m.Key).ToArray());
|
|
if (itemSelectionPopUp.ShowDialog() == DialogResult.OK)
|
|
{
|
|
selectedGeometry = bedrockModel[itemSelectionPopUp.SelectedItem];
|
|
}
|
|
itemSelectionPopUp.Dispose();
|
|
}
|
|
|
|
if (selectedGeometry is not null)
|
|
{
|
|
LoadGeometry(selectedGeometry);
|
|
LoadModelData(_skin);
|
|
}
|
|
}
|
|
|
|
private void ReadOutliner(JToken token, string type, IReadOnlyCollection<Element> elements)
|
|
{
|
|
if (TryReadElement(token, type, elements))
|
|
return;
|
|
|
|
if (token.Type == JTokenType.Object)
|
|
{
|
|
Outline outline = token.ToObject<Outline>();
|
|
foreach (JToken childToken in outline.Children)
|
|
{
|
|
ReadOutliner(childToken, type, elements);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool TryReadElement(JToken token, string type, IReadOnlyCollection<Element> elements)
|
|
{
|
|
if (token.Type == JTokenType.String && Guid.TryParse((string)token, out Guid tokenGuid))
|
|
{
|
|
Element element = elements.First(e => e.Uuid.Equals(tokenGuid));
|
|
LoadElement(type, element);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool LoadElement(string boxType, Element element)
|
|
{
|
|
if (!element.UseBoxUv || !element.IsVisibile)
|
|
return false;
|
|
|
|
//Debug.WriteLine($"{type} {element.Name}({element.Uuid})");
|
|
BoundingBox boundingBox = new BoundingBox(element.From.ToOpenTKVector(), element.To.ToOpenTKVector());
|
|
Vector3 pos = boundingBox.Start.ToNumericsVector();
|
|
Vector3 size = boundingBox.Volume.ToNumericsVector();
|
|
Vector2 uv = element.UvOffset;
|
|
pos = TranslatePosition(boxType, pos, size, new Vector3(1, 1, 0));
|
|
//Debug.WriteLine(pos);
|
|
|
|
// IMPROVMENT: detect default body parts and toggle anim flag instead of adding box data -miku
|
|
|
|
var box = new SkinBOX(boxType, pos, size, uv);
|
|
if (box.IsBasePart() && ((boxType == "HEAD" && element.Inflate == 0.5f) || (element.Inflate == 0.25f)))
|
|
box.Type = box.GetOverlayType();
|
|
|
|
_skin.AdditionalBoxes.Add(box);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Translates coordinate unit system into our coordinate system
|
|
/// </summary>
|
|
/// <param name="boxType">See <see cref="SkinBOX.BaseTypes"/> and <see cref="SkinBOX.OverlayTypes"/>.</param>
|
|
/// <param name="origin">Position/Origin of the Object(Cube).</param>
|
|
/// <param name="size">The Size of the Object(Cube).</param>
|
|
/// <param name="translationUnit">Describes what axises need translation.</param>
|
|
/// <returns>The translated position</returns>
|
|
private Vector3 TranslatePosition(string boxType, Vector3 origin, Vector3 size, Vector3 translationUnit)
|
|
{
|
|
// The translation unit describes what axises need to be swapped
|
|
// Example:
|
|
// translation unit = (1, 0, 0) => This translation unit will ONLY swap the X axis
|
|
translationUnit = Vector3.Clamp(translationUnit, Vector3.Zero, Vector3.One);
|
|
// To better understand see:
|
|
// https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AOQgFsBTMASwGMBnAbj1QGYT0iBhIgG88RMb3SjxI3OLlEAbgEMwRBlAAOEYEQC8RKLQDuRAGq0mwAPZguACkwwijogQCUHWfLHLVtAB4aFsC0cHoGxmbBNvYAtC7xTpgeUt6+RGC0LOEAKmBKUCwAYjbU/FY2cOpKISx26lrAKV7epACcdpkszd5i7Z1ZevoBQZahPeIAvqlEM9wkmABsUZYxRHkFxaXlldW1duartmqa2m4zMr2KKhmD+ofWtmT8ADZK1Br1p8BODzFkAC16FZftEngB5QwTbxdIgAKn06E8V1hsXuYK4ZEhtGRvVQAHYiLEurixNNcJMgA
|
|
Vector3 transformUnit = -((translationUnit * 2) - Vector3.One);
|
|
|
|
Vector3 pos = origin;
|
|
// The next line essentialy does uses the fomular below just on all axis.
|
|
// x = -(pos.x + size.x)
|
|
pos *= transformUnit;
|
|
pos -= size * translationUnit;
|
|
// Skin Renderer (and Game) specific offset value.
|
|
pos.Y += 24f;
|
|
|
|
Vector3 translation = renderer3D1.GetTranslation(boxType).ToNumericsVector();
|
|
Vector3 pivot = renderer3D1.GetPivot(boxType).ToNumericsVector();
|
|
|
|
// This will cancel out the part specific translation and pivot.
|
|
pos += translation * -Vector3.UnitX - pivot * Vector3.UnitY;
|
|
|
|
return pos;
|
|
}
|
|
|
|
private void cloneToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (skinPartListBox.SelectedItem is SkinBOX box)
|
|
{
|
|
renderer3D1.ModelData.Add((SkinBOX)box.Clone());
|
|
skinPartListBindingSource.ResetBindings(false);
|
|
}
|
|
}
|
|
|
|
private void LoadGeometry(Geometry geometry)
|
|
{
|
|
foreach (Bone bone in geometry.Bones)
|
|
{
|
|
string boxType = bone.Name;
|
|
if (!SkinBOX.IsValidType(boxType))
|
|
{
|
|
switch (bone.Name)
|
|
{
|
|
case "head":
|
|
case "helmet":
|
|
boxType = "HEAD";
|
|
break;
|
|
case "body":
|
|
boxType = "BODY";
|
|
break;
|
|
case "rightArm":
|
|
boxType = "ARM0";
|
|
break;
|
|
case "leftArm":
|
|
boxType = "ARM1";
|
|
break;
|
|
case "rightLeg":
|
|
boxType = "LEG0";
|
|
break;
|
|
case "leftLeg":
|
|
boxType = "LEG1";
|
|
break;
|
|
case "hat":
|
|
boxType = "HEADWEAR";
|
|
break;
|
|
case "jacket":
|
|
boxType = "JACKET";
|
|
break;
|
|
case "rightSleeve":
|
|
boxType = "SLEEVE0";
|
|
break;
|
|
case "leftSleeve":
|
|
boxType = "SLEEVE1";
|
|
break;
|
|
case "rightPants":
|
|
boxType = "PANTS0";
|
|
break;
|
|
case "leftPants":
|
|
boxType = "PANTS1";
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
foreach (External.Format.Cube cube in bone.Cubes)
|
|
{
|
|
Vector3 pos = TranslatePosition(boxType, cube.Origin, cube.Size, Vector3.UnitY);
|
|
var skinBox = new SkinBOX(boxType, pos, cube.Size, cube.Uv);
|
|
if (bone.Name == "helmet")
|
|
{
|
|
skinBox.HideWithArmor = true;
|
|
}
|
|
_skin.AdditionalBoxes.Add(skinBox);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (skinPartListBox.SelectedItem is SkinBOX box)
|
|
{
|
|
renderer3D1.ModelData.Remove(box);
|
|
skinPartListBindingSource.ResetBindings(false);
|
|
}
|
|
}
|
|
|
|
private void CustomSkinEditor_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
renderer3D1.Dispose();
|
|
}
|
|
|
|
private void outlineColorButton_Click(object sender, EventArgs e)
|
|
{
|
|
ColorDialog colorDialog = new ColorDialog();
|
|
colorDialog.SolidColorOnly = true;
|
|
if (colorDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
renderer3D1.OutlineColor = colorDialog.Color;
|
|
skinPartListBox_SelectedIndexChanged(sender, e);
|
|
}
|
|
}
|
|
|
|
private void renderer3D1_TextureChanging(object sender, Rendering.TextureChangingEventArgs e)
|
|
{
|
|
var img = e.NewTexture;
|
|
// Skins can only be a 1:1 ratio (base 64x64) or a 2:1 ratio (base 64x32)
|
|
if (img.Width != img.Height && img.Height != img.Width / 2)
|
|
{
|
|
e.Cancel = true;
|
|
MessageBox.Show("The selected image does not suit a skin texture.", "Invalid image dimensions.", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
uvPictureBox.Image = _skin.Texture = img;
|
|
}
|
|
|
|
private void skinPartListBox_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (skinPartListBox.SelectedItem is SkinBOX box)
|
|
{
|
|
var boxEditor = new BoxEditor(box, _allowInflate);
|
|
if (boxEditor.ShowDialog() == DialogResult.OK)
|
|
{
|
|
renderer3D1.ModelData[skinPartListBox.SelectedIndex] = boxEditor.Result;
|
|
skinPartListBindingSource.ResetItem(skinPartListBox.SelectedIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void skinPartListBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
int scale = 4;
|
|
if (skinPartListBox.SelectedItem is SkinBOX box)
|
|
{
|
|
renderer3D1.SelectedIndex = skinPartListBox.SelectedIndex;
|
|
Size scaleSize = new Size(_skin.Texture.Width * scale, _skin.Texture.Height * scale);
|
|
uvPictureBox.Image = new Bitmap(scaleSize.Width, scaleSize.Height);
|
|
using (Graphics g = Graphics.FromImage(uvPictureBox.Image))
|
|
{
|
|
float lineWidth = ((_skin.Texture.Width / renderer3D1.TextureSize.Width) + (_skin.Texture.Height / renderer3D1.TextureSize.Height)) / 2f;
|
|
GraphicsPath graphicsPath = box.GetUVGraphicsPath(new System.Numerics.Vector2(scaleSize.Width * renderer3D1.TillingFactor.X, scaleSize.Height * renderer3D1.TillingFactor.Y));
|
|
g.ApplyConfig(_graphicsConfig);
|
|
g.DrawImage(_skin.Texture, new Rectangle(Point.Empty, scaleSize), new Rectangle(Point.Empty, _skin.Texture.Size), GraphicsUnit.Pixel);
|
|
g.DrawPath(new Pen(renderer3D1.OutlineColor, lineWidth), graphicsPath);
|
|
}
|
|
uvPictureBox.Invalidate();
|
|
}
|
|
}
|
|
|
|
private void clampToViewCheckbox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
renderer3D1.ClampModel = clampToViewCheckbox.Checked;
|
|
}
|
|
|
|
private void captureScreenshotButton_Click(object sender, EventArgs e)
|
|
{
|
|
using SaveFileDialog saveFileDialog = new SaveFileDialog()
|
|
{
|
|
Filter = "PNG|*.png"
|
|
};
|
|
if (saveFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
renderer3D1.GetThumbnail().Save(saveFileDialog.FileName, ImageFormat.Png);
|
|
}
|
|
}
|
|
|
|
private void checkGuide_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
outlineColorButton.Visible = renderer3D1.ShowGuideLines = checkGuide.Checked;
|
|
}
|
|
|
|
private void showArmorCheckbox_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
renderer3D1.ShowArmor = showArmorCheckbox.Checked;
|
|
}
|
|
|
|
private void skinPartListBox_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.KeyCode == Keys.Delete)
|
|
deleteToolStripMenuItem_Click(sender, e);
|
|
}
|
|
|
|
private void ReloadOffsetList()
|
|
{
|
|
skinOffsetListBindingSource = new BindingSource(renderer3D1.GetOffsets().ToArray(), null);
|
|
offsetListBox.DataSource = skinOffsetListBindingSource;
|
|
skinOffsetListBindingSource.ResetBindings(false);
|
|
}
|
|
|
|
private void addOffsetToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var offsets = renderer3D1.GetOffsets().Select(offset => offset.Type).ToList();
|
|
string[] available = SkinPartOffset.ValidModelOffsetTypes.Where(s => !offsets.Contains(s)).ToArray();
|
|
using ItemSelectionPopUp typeSelection = new ItemSelectionPopUp(available);
|
|
using NumericPrompt valuePrompt = new NumericPrompt(0f, -100_000f, 100_000f);
|
|
valuePrompt.DecimalPlaces = 1;
|
|
valuePrompt.ValueStep = (decimal)0.1f;
|
|
if (typeSelection.ShowDialog() == DialogResult.OK && valuePrompt.ShowDialog() == DialogResult.OK)
|
|
{
|
|
renderer3D1.SetPartOffset(typeSelection.SelectedItem, (float)valuePrompt.SelectedValue);
|
|
ReloadOffsetList();
|
|
}
|
|
}
|
|
|
|
private void removeOffsetToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (offsetListBox.SelectedItem is not SkinPartOffset offset)
|
|
return;
|
|
renderer3D1.SetPartOffset(offset.Type, 0f);
|
|
ReloadOffsetList();
|
|
}
|
|
|
|
private void offsetListBox_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (offsetListBox.SelectedItem is not SkinPartOffset offset)
|
|
return;
|
|
|
|
using NumericPrompt valuePrompt = new NumericPrompt(offset.Value, -100_000f, 100_000f);
|
|
valuePrompt.ToolTipText = "Set new Value for " + offset.Type;
|
|
valuePrompt.DecimalPlaces = 1;
|
|
valuePrompt.ValueStep = (decimal)0.1f;
|
|
if (valuePrompt.ShowDialog() == DialogResult.OK)
|
|
{
|
|
renderer3D1.SetPartOffset(offset.Type, (float)valuePrompt.SelectedValue);
|
|
ReloadOffsetList();
|
|
}
|
|
}
|
|
}
|
|
} |