diff --git a/MinecraftUSkinEditor.sln b/MinecraftUSkinEditor.sln index d49771b9..6dc2d4e9 100644 --- a/MinecraftUSkinEditor.sln +++ b/MinecraftUSkinEditor.sln @@ -12,21 +12,49 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|x64.Build.0 = Debug|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Debug|x86.Build.0 = Debug|Any CPU {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|Any CPU.Build.0 = Release|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|x64.ActiveCfg = Release|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|x64.Build.0 = Release|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|x86.ActiveCfg = Release|Any CPU + {0ACAAEDE-93F5-4B5D-B8D7-A0C43359C0D6}.Release|x86.Build.0 = Release|Any CPU {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|x64.ActiveCfg = Debug|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|x64.Build.0 = Debug|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|x86.ActiveCfg = Debug|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Debug|x86.Build.0 = Debug|Any CPU {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|Any CPU.ActiveCfg = Release|Any CPU {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|Any CPU.Build.0 = Release|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|x64.ActiveCfg = Release|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|x64.Build.0 = Release|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|x86.ActiveCfg = Release|Any CPU + {F77A61F1-0C6F-45DC-A5B5-A7BF38D64322}.Release|x86.Build.0 = Release|Any CPU {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|x64.ActiveCfg = Debug|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|x64.Build.0 = Debug|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|x86.ActiveCfg = Debug|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Debug|x86.Build.0 = Debug|Any CPU {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|Any CPU.ActiveCfg = Release|Any CPU {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|Any CPU.Build.0 = Release|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|x64.ActiveCfg = Release|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|x64.Build.0 = Release|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|x86.ActiveCfg = Release|Any CPU + {3530A9F2-AE0F-44B4-84F9-8FBACB456070}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MinecraftUSkinEditor/App.config b/MinecraftUSkinEditor/App.config index cc5964e3..44d8a515 100644 --- a/MinecraftUSkinEditor/App.config +++ b/MinecraftUSkinEditor/App.config @@ -1,41 +1,41 @@ - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/MinecraftUSkinEditor/Classes/PCK.cs b/MinecraftUSkinEditor/Classes/PCK.cs index 355c6840..234130b8 100644 --- a/MinecraftUSkinEditor/Classes/PCK.cs +++ b/MinecraftUSkinEditor/Classes/PCK.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using System.IO; using System.Windows.Forms; @@ -11,6 +9,40 @@ namespace MinecraftUSkinEditor public class PCK { + public PCK(string filename) + { + try + { + IsLittleEndian = !(Read(File.ReadAllBytes(filename))); + if(IsLittleEndian) + ReadVita(File.ReadAllBytes(filename)); + } + catch + { + } + } + + private static byte[] endianReverseUnicode(byte[] str) + { + List NewBte = new List(); + NewBte.AddRange(str); + NewBte.Reverse(); + return NewBte.ToArray(); + + + /* Old Endian Reverse Code + byte[] newStr = new byte[str.Length]; + for (int i = 0; i < str.Length; i += 2) + { + newStr[i] = str[i + 1]; + newStr[i + 1] = str[i]; + } + return newStr; + */ + } + + #region Variables + public class MineFile { public int filesize; @@ -26,35 +58,9 @@ namespace MinecraftUSkinEditor public Dictionary typeCodes = new Dictionary(); public List mineFiles = new List(); - public PCK() - { + #endregion - } - - public PCK(string filename) - { - try - { - Read(File.ReadAllBytes(filename)); - IsLittleEndian = false; - } - catch - { - ReadVita(File.ReadAllBytes(filename)); - IsLittleEndian = true; - } - } - - private static byte[] endianReverseUnicode(byte[] str) - { - byte[] newStr = new byte[str.Length]; - for (int i = 0; i < str.Length; i += 2) - { - newStr[i] = str[i + 1]; - newStr[i + 1] = str[i]; - } - return newStr; - } + #region NormalPCK public static string readMineString(FileData f) { @@ -63,6 +69,153 @@ namespace MinecraftUSkinEditor return Encoding.Unicode.GetString(endianReverseUnicode(f.readBytes(length))); } + + public bool Read(byte[] data) + { + try + { + pckType = 0; + types = new Dictionary(); + typeCodes = new Dictionary(); + mineFiles = new List(); + + FileData fileData = new FileData(data); + fileData.Endian = Endianness.Big; + fileData.readInt(); + int entryTypeCount = fileData.readInt(); + //int a = 0; + for (int i = 0; i < entryTypeCount; i++) + { + int unk = fileData.readInt(); + string text = ""; + try + { + text = readMineString(fileData); + //File.WriteAllText(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\exp\\PCKDump" + a + ".bin", text); + //a++; + } + catch + { + text = "Hello!"; + } + types.Add(unk, text); + typeCodes.Add(text, unk); + fileData.skip(4); + } + + int itemCount = fileData.readInt(); + + Console.WriteLine(itemCount); + // no metadata + if (entryTypeCount == 0) + { + Console.WriteLine("PckType0"); + } + // type 1 or 2 + else if (itemCount < 3) + { + pckType = itemCount; + if (pckType == 1) + { + Console.WriteLine("PckType1"); + itemCount = fileData.readInt(); + } + if (pckType == 2) + Console.WriteLine("PckType2"); + } + // regular pck + else + { + Console.WriteLine("NormalPCK"); + } + + + for (int j = 0; j < itemCount; j++) + { + MineFile mineFile = new MineFile(); + mineFile.filesize = fileData.readInt(); + mineFile.type = fileData.readInt(); + int length = fileData.readInt() * 2; + mineFile.name = Encoding.Unicode.GetString(endianReverseUnicode(fileData.readBytes(length))); + fileData.skip(4); + mineFiles.Add(mineFile); + } + + foreach (MineFile mineFile2 in mineFiles) + { + int num4 = fileData.readInt(); + for (int k = 0; k < num4; k++) + { + object[] array = new object[2]; + int key = fileData.readInt(); + array[0] = types[key]; + array[1] = readMineString(fileData); + fileData.skip(4); + mineFile2.entries.Add(array); + } + mineFile2.data = fileData.readBytes(mineFile2.filesize); + } + return true; + } + catch + { + return false; + } + } + + private static void writeMinecraftString(FileOutput f, string str) + { + byte[] bytes = Encoding.Unicode.GetBytes(str); + f.writeInt(bytes.Length / 2); + f.writeBytes(PCK.endianReverseUnicode(bytes)); + f.writeInt(0); + } + + public byte[] Rebuild() + { + FileOutput fileOutput = new FileOutput(); + fileOutput.Endian = Endianness.Big; + fileOutput.writeInt(3); + fileOutput.writeInt(this.types.Count); + foreach (int num in this.types.Keys) + { + fileOutput.writeInt(num); + PCK.writeMinecraftString(fileOutput, this.types[num]); + } + fileOutput.writeInt(this.mineFiles.Count); + foreach (PCK.MineFile mineFile in this.mineFiles) + { + fileOutput.writeInt(mineFile.data.Length); + fileOutput.writeInt(mineFile.type); + PCK.writeMinecraftString(fileOutput, mineFile.name); + } + foreach (PCK.MineFile mineFile2 in this.mineFiles) + { + string str = ""; + try + { + fileOutput.writeInt(mineFile2.entries.Count); + foreach (object[] array in mineFile2.entries) + { + str = array[0].ToString(); + fileOutput.writeInt(this.typeCodes[(string)array[0]]); + PCK.writeMinecraftString(fileOutput, (string)array[1]); + } + fileOutput.writeBytes(mineFile2.data); + } + catch (Exception) + { + MessageBox.Show(str + " is not in the main metadatabase"); + break; + } + } + return fileOutput.getBytes(); + } + + #endregion + + #region VitaPCK + public static string readMineStringVita(FileData f) { int length = f.readIntVita() * 2; @@ -70,90 +223,6 @@ namespace MinecraftUSkinEditor return Encoding.Unicode.GetString((f.readBytes(length))); } - public void Read(byte[] data) - { - pckType = 0; - types = new Dictionary(); - typeCodes = new Dictionary(); - mineFiles = new List(); - - FileData fileData = new FileData(data); - fileData.Endian = Endianness.Big; - fileData.readInt(); - int entryTypeCount = fileData.readInt(); - //int a = 0; - for (int i = 0; i < entryTypeCount; i++) - { - int unk = fileData.readInt(); - string text = ""; - try - { - text = readMineString(fileData); - //File.WriteAllText(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\exp\\PCKDump" + a + ".bin", text); - //a++; - } - catch - { - text = "Hello!"; - } - types.Add(unk, text); - typeCodes.Add(text, unk); - fileData.skip(4); - } - - int itemCount = fileData.readInt(); - - Console.WriteLine(itemCount); - // no metadata - if (entryTypeCount == 0) - { - Console.WriteLine("PckType0"); - } - // type 1 or 2 - else if (itemCount < 3) - { - pckType = itemCount; - if (pckType == 1) - { - Console.WriteLine("PckType1"); - itemCount = fileData.readInt(); - } - if (pckType == 2) - Console.WriteLine("PckType2"); - } - // regular pck - else - { - Console.WriteLine("NormalPCK"); - } - - - for (int j = 0; j < itemCount; j++) - { - MineFile mineFile = new MineFile(); - mineFile.filesize = fileData.readInt(); - mineFile.type = fileData.readInt(); - int length = fileData.readInt() * 2; - mineFile.name = Encoding.Unicode.GetString(endianReverseUnicode(fileData.readBytes(length))); - fileData.skip(4); - mineFiles.Add(mineFile); - } - - foreach (MineFile mineFile2 in mineFiles) - { - int num4 = fileData.readInt(); - for (int k = 0; k < num4; k++) - { - object[] array = new object[2]; - int key = fileData.readInt(); - array[0] = types[key]; - array[1] = readMineString(fileData); - fileData.skip(4); - mineFile2.entries.Add(array); - } - mineFile2.data = fileData.readBytes(mineFile2.filesize); - } - } public void ReadVita(byte[] data) { @@ -240,14 +309,6 @@ namespace MinecraftUSkinEditor } } - private static void writeMinecraftString(FileOutput f, string str) - { - byte[] bytes = Encoding.Unicode.GetBytes(str); - f.writeInt(bytes.Length / 2); - f.writeBytes(PCK.endianReverseUnicode(bytes)); - f.writeInt(0); - } - private static void writeMinecraftStringVita(FileOutput f, string str) { Console.WriteLine("WriteVita -- " + str); @@ -257,47 +318,6 @@ namespace MinecraftUSkinEditor f.writeIntVita(0); } - public byte[] Rebuild() - { - FileOutput fileOutput = new FileOutput(); - fileOutput.Endian = Endianness.Big; - fileOutput.writeInt(3); - fileOutput.writeInt(this.types.Count); - foreach (int num in this.types.Keys) - { - fileOutput.writeInt(num); - PCK.writeMinecraftString(fileOutput, this.types[num]); - } - fileOutput.writeInt(this.mineFiles.Count); - foreach (PCK.MineFile mineFile in this.mineFiles) - { - fileOutput.writeInt(mineFile.data.Length); - fileOutput.writeInt(mineFile.type); - PCK.writeMinecraftString(fileOutput, mineFile.name); - } - foreach (PCK.MineFile mineFile2 in this.mineFiles) - { - string str = ""; - try - { - fileOutput.writeInt(mineFile2.entries.Count); - foreach (object[] array in mineFile2.entries) - { - str = array[0].ToString(); - fileOutput.writeInt(this.typeCodes[(string)array[0]]); - PCK.writeMinecraftString(fileOutput, (string)array[1]); - } - fileOutput.writeBytes(mineFile2.data); - } - catch (Exception) - { - MessageBox.Show(str + " is not in the main metadatabase"); - break; - } - } - return fileOutput.getBytes(); - } - public byte[] RebuildVita() { FileOutput fileOutput = new FileOutput(); @@ -338,5 +358,7 @@ namespace MinecraftUSkinEditor } return fileOutput.getBytes(); } + + #endregion } } \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/CSM.cs b/MinecraftUSkinEditor/Classes/StoneVOX/CSM.cs new file mode 100644 index 00000000..0b88577a --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/CSM.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace stonevox +{ + public class CSM + { + public List boxes = new List(); + public CSM(string path) + { + Open(path); + } + void Open(string path) + { + string RawCSM = path; + string[] lines = RawCSM.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + int i = 0; + while (i < lines.Length / 11) + { + string PartName = lines[(i*11)+0]; + string ParentName = lines[(i*11)+1]; + string PartName2 = lines[(i*11)+2]; + int x = (int)Math.Round(float.Parse(lines[(i*11)+3]), 1, MidpointRounding.ToEven); + int y = (int)Math.Round(float.Parse(lines[(i*11)+4]), 1, MidpointRounding.ToEven); + int z = (int)Math.Round(float.Parse(lines[(i*11)+5]), 1, MidpointRounding.ToEven); + int Sizex = (int)Math.Round(float.Parse(lines[(i*11)+6]), 1, MidpointRounding.ToEven); + int Sizey = (int)Math.Round(float.Parse(lines[(i*11)+7]), 1, MidpointRounding.ToEven); + int Sizez = (int)Math.Round(float.Parse(lines[(i*11)+8]), 1, MidpointRounding.ToEven); + int uvx = (int)Math.Round(float.Parse(lines[(i*11)+9]), 1, MidpointRounding.ToEven); + int uvy = (int)Math.Round(float.Parse(lines[(i*11)+10]), 1, MidpointRounding.ToEven); + + Box box = new Box(); + box.PartName = PartName; + box.ParentName = ParentName; + box.x = x; + box.y = y; + box.z = z; + box.SizeX = Sizex; + box.SizeY = Sizey; + box.SizeZ = Sizez; + box.UvX = uvx; + box.UvY = uvy; + boxes.Add(box); + + i++; + } + } + + } + public class Box + { + public string PartName; + public string ParentName; + public int x; + public int y; + public int z; + public int SizeX; + public int SizeY; + public int SizeZ; + public int UvX; + public int UvY; + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Alias.cs b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Alias.cs new file mode 100644 index 00000000..e5e3ccb2 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Alias.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace stonevox.Mod +{ + public class Alias + { + public string name; + public FileReference file; + } + + public class FunctionAlias + { + public enum endpoint + { + client, + server + } + + public string name; + public FileReference controller; + public endpoint enpoint; + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/Mod/FileReference.cs b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/FileReference.cs new file mode 100644 index 00000000..d56818d4 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/FileReference.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace stonevox.Mod +{ + public class FileReference + { + public string file { get; } + + public FileReference(string file) + { + this.file = file; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Manifest.cs b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Manifest.cs new file mode 100644 index 00000000..831d6191 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/Manifest.cs @@ -0,0 +1,88 @@ +using Ionic.Zip; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace stonevox.Mod +{ + public class Manifest + { + public class info + { + public string name { get; set; } + public string version { get; set; } + } + + public class game + { + public FileReference[] script; + } + + public FileReference client_init_script; + public FileReference server_init_script; + + public List aliases; + // don't need the other stuff now, will add + + Manifest() + { + aliases = new List(); + } + + Manifest(string modDirectory) + : this() + { + if (Directory.Exists(modDirectory)) + { + if (File.Exists(Path.Combine(modDirectory , "manifest.json"))) + { + + } + else + throw new Exception("No Manifest File found."); + } + else + throw new Exception("Expecting a folder path."); + } + + Manifest(ZipFile file) + : this() + { + } + + + void LoadFromFile(string manifestPath) + { + + } + + void LoadFromZip(ZipEntry manifestEntry) + { + + } + + public static Manifest FromDirectory(string modDirectory) + { + return new Manifest(modDirectory); + } + + public static Manifest FromSMOD(string smodPath) + { + if (File.Exists(smodPath)) + { + if (ZipFile.IsZipFile(smodPath)) + { + ZipFile zip = ZipFile.Read(smodPath); + return new Manifest(zip); + } + else + throw new Exception("Not a valid .smod"); + } + else + throw new Exception("Expecting *.smod at path, but found nothing"); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/Mod/StoneHearthMod.cs b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/StoneHearthMod.cs new file mode 100644 index 00000000..c5b6a80f --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/Mod/StoneHearthMod.cs @@ -0,0 +1,16 @@ +using Ionic.Zip; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace stonevox.Mod +{ + public class StoneHearthMod + { + public string path { get; } + + private ZipFile zip; + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/OpenTK.dll b/MinecraftUSkinEditor/Classes/StoneVOX/OpenTK.dll new file mode 100644 index 00000000..46388490 Binary files /dev/null and b/MinecraftUSkinEditor/Classes/StoneVOX/OpenTK.dll differ diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/Program.cs b/MinecraftUSkinEditor/Classes/StoneVOX/Program.cs new file mode 100644 index 00000000..83f334eb --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/Program.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Windows.Forms; + +namespace stonevox +{ + class Program + { + public static Thread serverthread; + public static Thread clientthread; + + [STAThread()] + static void Main(string[] Arg) + { + Console.Title = "CSM Viewer 3D"; + + string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(""); + Console.ForegroundColor = ConsoleColor.White; + + File.WriteAllText(Environment.CurrentDirectory + "\\test.csm", Arg[0]); + + startClient(); + } + + public static void startClient() + { + Client.beginstonevox(); + } + + public static void startServer() + { + Server.defaultConfigure(); + Server.start(); + } + + [DllImport("kernel32.dll", ExactSpelling = true)] + public static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + static string lol = "i_made_onion_games_in_the_past"; + + public static string Encrypt(string ip) + { + string e = EncryptOrDecyzpt(ip, lol); + string re = ""; + + foreach (var c in e) + re += ((int)c).ToString() + '-'; + + re = re.Remove(re.Length - 1); + + return re; + } + + public static string Decrypt(string ip) + { + string d = ""; + + string[] chars = ip.Split('-'); + + foreach (var c in chars) + d += (char)(Convert.ToInt32(c)); + + return EncryptOrDecyzpt(d, lol); + } + + public static string EncryptOrDecyzpt(string text, string Key) + { + var result = new StringBuilder(); + + for (int c = 0; c < text.Length; c++) + result.Append((char)((uint)text[c] ^ (uint)Key[c % Key.Length])); + + return result.ToString(); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Builder.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Builder.cs new file mode 100644 index 00000000..f645b293 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Builder.cs @@ -0,0 +1,871 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.IO; + +namespace QuickFont +{ + + /// + /// Class for building a Quick Font, given a Font + /// and a configuration object. + /// + class Builder + { + + private string charSet; + private QFontBuilderConfiguration config; + private Font font; + + public Builder(Font font, QFontBuilderConfiguration config) + { + this.charSet = config.charSet; + this.config = config; + this.font = font; + + } + + private static Dictionary CreateCharGlyphMapping(QFontGlyph[] glyphs) + { + var dict = new Dictionary(); + for (int i = 0; i < glyphs.Length; i++) + dict.Add(glyphs[i].character, glyphs[i]); + + return dict; + } + + //these do not affect the actual width of glyphs (we measure widths pixel-perfectly ourselves), but is used to detect whether a font is monospaced + private List GetGlyphSizes(Font font) + { + Bitmap bmp = new Bitmap(512, 512, PixelFormat.Format24bppRgb); + Graphics graph = Graphics.FromImage(bmp); + List sizes = new List(); + + for (int i = 0; i < charSet.Length; i++) + { + var charSize = graph.MeasureString("" + charSet[i], font); + sizes.Add(new SizeF(charSize.Width, charSize.Height)); + } + + graph.Dispose(); + bmp.Dispose(); + + return sizes; + } + + private SizeF GetMaxGlyphSize(List sizes) + { + SizeF maxSize = new SizeF(0f, 0f); + for (int i = 0; i < charSet.Length; i++) + { + if (sizes[i].Width > maxSize.Width) + maxSize.Width = sizes[i].Width; + + if (sizes[i].Height > maxSize.Height) + maxSize.Height = sizes[i].Height; + } + + return maxSize; + } + + private SizeF GetMinGlyphSize(List sizes) + { + SizeF minSize = new SizeF(float.MaxValue, float.MaxValue); + for (int i = 0; i < charSet.Length; i++) + { + if (sizes[i].Width < minSize.Width) + minSize.Width = sizes[i].Width; + + if (sizes[i].Height < minSize.Height) + minSize.Height = sizes[i].Height; + } + + return minSize; + } + + /// + /// Returns true if all glyph widths are within 5% of each other + /// + /// + /// + private bool IsMonospaced(List sizes) + { + var min = GetMinGlyphSize(sizes); + var max = GetMaxGlyphSize(sizes); + + if (max.Width - min.Width < max.Width * 0.05f) + return true; + + return false; + } + + /* + private SizeF GetMaxGlyphSize(Font font) + { + Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb); + Graphics graph = Graphics.FromImage(bmp); + + SizeF maxSize = new SizeF(0f, 0f); + for (int i = 0; i < charSet.Length; i++) + { + var charSize = graph.MeasureString("" + charSet[i], font); + + if (charSize.Width > maxSize.Width) + maxSize.Width = charSize.Width; + + if (charSize.Height > maxSize.Height) + maxSize.Height = charSize.Height; + } + + graph.Dispose(); + bmp.Dispose(); + + return maxSize; + }*/ + + //The initial bitmap is simply a long thin strip of all glyphs in a row + private Bitmap CreateInitialBitmap(Font font, SizeF maxSize, int initialMargin, out QFontGlyph[] glyphs, TextGenerationRenderHint renderHint) + { + glyphs = new QFontGlyph[charSet.Length]; + + int spacing = (int)Math.Ceiling(maxSize.Width) + 2 * initialMargin; + Bitmap bmp = new Bitmap(spacing * charSet.Length, (int)Math.Ceiling(maxSize.Height) + 2 * initialMargin, PixelFormat.Format24bppRgb); + Graphics graph = Graphics.FromImage(bmp); + + switch(renderHint){ + case TextGenerationRenderHint.SizeDependent: + graph.TextRenderingHint = font.Size <= 12.0f ? TextRenderingHint.ClearTypeGridFit : TextRenderingHint.AntiAlias; + break; + case TextGenerationRenderHint.AntiAlias: + graph.TextRenderingHint = TextRenderingHint.AntiAlias; + break; + case TextGenerationRenderHint.AntiAliasGridFit: + graph.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; + break; + case TextGenerationRenderHint.ClearTypeGridFit: + graph.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + break; + case TextGenerationRenderHint.SystemDefault: + graph.TextRenderingHint = TextRenderingHint.SystemDefault; + break; + } + + int xOffset = initialMargin; + for (int i = 0; i < charSet.Length; i++) + { + graph.DrawString("" + charSet[i], font, Brushes.White, xOffset, initialMargin); + var charSize = graph.MeasureString("" + charSet[i], font); + glyphs[i] = new QFontGlyph(0, new Rectangle(xOffset - initialMargin, 0, (int)charSize.Width + initialMargin * 2, (int)charSize.Height + initialMargin * 2), 0, charSet[i]); + xOffset += (int)charSize.Width + initialMargin * 2; + } + + graph.Flush(); + graph.Dispose(); + + return bmp; + } + + private delegate bool EmptyDel(BitmapData data, int x, int y); + + private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance) + { + int startX, endX; + int startY, endY; + + var rect = glyph.rect; + + EmptyDel emptyPix; + + if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) + emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; + else + emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyPixel(data, x, y); }; + + + unsafe + { + + for (startX = rect.X; startX < bitmapData.Width; startX++) + for (int j = rect.Y; j < rect.Y + rect.Height; j++) + if (!emptyPix(bitmapData, startX, j)) + goto Done1; + Done1: + + for (endX = rect.X + rect.Width; endX >= 0; endX--) + for (int j = rect.Y; j < rect.Y + rect.Height; j++) + if (!emptyPix(bitmapData, endX, j)) + goto Done2; + Done2: + + for (startY = rect.Y; startY < bitmapData.Height; startY++) + for (int i = startX; i < endX; i++) + if (!emptyPix(bitmapData, i, startY)) + goto Done3; + + Done3: + + for (endY = rect.Y + rect.Height; endY >= 0; endY--) + for (int i = startX; i < endX; i++) + if (!emptyPix(bitmapData, i, endY)) + goto Done4; + Done4:; + + + } + + if (endY < startY) + startY = endY = rect.Y; + + if (endX < startX) + startX = endX = rect.X; + + glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); + + if (setYOffset) + glyph.yOffset = glyph.rect.Y; + + } + + private static void RetargetGlyphRectangleOutwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance) + { + int startX,endX; + int startY,endY; + + var rect = glyph.rect; + + EmptyDel emptyPix; + + if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb) + emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); }; + else + emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyPixel(data, x, y); }; + + + unsafe + { + + for (startX = rect.X; startX >= 0; startX--) + { + bool foundPix = false; + for (int j = rect.Y; j <= rect.Y + rect.Height; j++) + { + if (!emptyPix(bitmapData, startX, j)) + { + foundPix = true; + break; + } + } + + if (!foundPix) + { + startX++; + break; + } + } + + + for (endX = rect.X + rect.Width; endX < bitmapData.Width; endX++) + { + bool foundPix = false; + for (int j = rect.Y; j <= rect.Y + rect.Height; j++) + { + if (!emptyPix(bitmapData, endX, j)) + { + foundPix = true; + break; + } + } + + if (!foundPix) + { + endX--; + break; + } + } + + + + for (startY = rect.Y; startY >= 0; startY--) + { + bool foundPix = false; + for (int i = startX; i <= endX; i++) + { + if (!emptyPix(bitmapData, i, startY)) + { + foundPix = true; + break; + } + } + + if (!foundPix) + { + startY++; + break; + } + } + + + + for (endY = rect.Y + rect.Height; endY < bitmapData.Height; endY++) + { + bool foundPix = false; + for (int i = startX; i <= endX; i++) + { + if (!emptyPix(bitmapData, i, endY)) + { + foundPix = true; + break; + } + } + + if (!foundPix) + { + endY--; + break; + } + } + } + + glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1); + + if (setYOffset) + glyph.yOffset = glyph.rect.Y; + + } + + private static List GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) + { + var pages = new List(); + destGlyphs = new QFontGlyph[sourceGlyphs.Length]; + + QBitmap currentPage = null; + + + int maxY = 0; + foreach (var glph in sourceGlyphs) + maxY = Math.Max(glph.rect.Height, maxY); + + + int finalPageIndex = 0; + int finalPageRequiredWidth = 0; + int finalPageRequiredHeight = 0; + + + for (int k = 0; k < 2; k++) + { + bool pre = k == 0; //first iteration is simply to determine the required size of the final page, so that we can crop it in advance + + + int xPos = 0; + int yPos = 0; + int maxYInRow = 0; + int totalTries = 0; + + for (int i = 0; i < sourceGlyphs.Length; i++) + { + + + if(!pre && currentPage == null){ + + if (finalPageIndex == pages.Count) + { + int width = Math.Min(destSheetWidth, usePowerOfTwo ? PowerOfTwo(finalPageRequiredWidth) : finalPageRequiredWidth); + int height = Math.Min(destSheetHeight, usePowerOfTwo ? PowerOfTwo(finalPageRequiredHeight) : finalPageRequiredHeight); + + currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb)); + currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent + } + else + { + currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb)); + currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent + } + pages.Add(currentPage); + + } + + + + totalTries++; + + if (totalTries > 10 * sourceGlyphs.Length) + throw new Exception("Failed to fit font into texture pages"); + + + var rect = sourceGlyphs[i].rect; + + if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight) + { + if (!pre) + { + //add to page + if(sourceBitmaps[sourceGlyphs[i].page].PixelFormat == PixelFormat.Format32bppArgb) + QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); + else + QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin); + + destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].yOffset, sourceGlyphs[i].character); + } + else + { + finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin); + finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin); + } + + + xPos += rect.Width + 2 * destMargin; + maxYInRow = Math.Max(maxYInRow, rect.Height); + + continue; + } + + + if (xPos + rect.Width + 2 * destMargin > destSheetWidth) + { + i--; + + yPos += maxYInRow + 2 * destMargin; + xPos = 0; + + if (yPos + maxY + 2 * destMargin > destSheetHeight) + { + yPos = 0; + + if (!pre) + { + currentPage = null; + } + else + { + finalPageRequiredWidth = 0; + finalPageRequiredHeight = 0; + finalPageIndex++; + } + } + continue; + } + + } + + } + + + return pages; + + + } + + + + public QFontData BuildFontData() + { + return BuildFontData(null); + } + + public QFontData BuildFontData(string saveName) + { + if (config.ForcePowerOfTwo && config.SuperSampleLevels != PowerOfTwo(config.SuperSampleLevels)) + { + throw new ArgumentOutOfRangeException("SuperSampleLevels must be a power of two when using ForcePowerOfTwo."); + } + + if (config.SuperSampleLevels <= 0 || config.SuperSampleLevels > 8) + { + throw new ArgumentOutOfRangeException("SuperSampleLevels = [" + config.SuperSampleLevels + "] is an unsupported value. Please use values in the range [1,8]"); + } + + int margin = 2; //margin in initial bitmap (don't bother to make configurable - likely to cause confusion + int pageWidth = config.PageWidth * config.SuperSampleLevels; //texture page width + int pageHeight = config.PageHeight * config.SuperSampleLevels; //texture page height + bool usePowerOfTwo = config.ForcePowerOfTwo; + int glyphMargin = config.GlyphMargin * config.SuperSampleLevels; + + QFontGlyph[] initialGlyphs; + var sizes = GetGlyphSizes(font); + var maxSize = GetMaxGlyphSize(sizes); + var initialBmp = CreateInitialBitmap(font, maxSize, margin, out initialGlyphs,config.TextGenerationRenderHint); + var initialBitmapData = initialBmp.LockBits(new Rectangle(0, 0, initialBmp.Width, initialBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + + int minYOffset = int.MaxValue; + foreach (var glyph in initialGlyphs){ + RetargetGlyphRectangleInwards(initialBitmapData, glyph, true, config.KerningConfig.alphaEmptyPixelTolerance); + minYOffset = Math.Min(minYOffset,glyph.yOffset); + } + minYOffset--; //give one pixel of breathing room? + + foreach (var glyph in initialGlyphs) + glyph.yOffset -= minYOffset; + + + QFontGlyph[] glyphs; + var bitmapPages = GenerateBitmapSheetsAndRepack( initialGlyphs,new BitmapData[1] { initialBitmapData},pageWidth, pageHeight, out glyphs, glyphMargin, usePowerOfTwo); + + initialBmp.UnlockBits(initialBitmapData); + initialBmp.Dispose(); + + if (config.SuperSampleLevels != 1) + { + ScaleSheetsAndGlyphs(bitmapPages, glyphs, 1.0f / config.SuperSampleLevels); + RetargetAllGlyphs(bitmapPages, glyphs,config.KerningConfig.alphaEmptyPixelTolerance); + } + + + //create list of texture pages + var pages = new List(); + foreach (var page in bitmapPages) + pages.Add(new TexturePage(page.bitmapData)); + + var fontData = new QFontData(); + fontData.CharSetMapping = CreateCharGlyphMapping(glyphs); + fontData.Pages = pages.ToArray(); + fontData.CalculateMeanWidth(); + fontData.CalculateMaxHeight(); + fontData.KerningPairs = KerningCalculator.CalculateKerning(charSet.ToCharArray(), glyphs, bitmapPages,config.KerningConfig); + fontData.naturallyMonospaced = IsMonospaced(sizes); + + if (saveName != null) + { + if (bitmapPages.Count == 1) + bitmapPages[0].bitmap.Save(saveName + ".png", System.Drawing.Imaging.ImageFormat.Png); + else + { + for (int i = 0; i < bitmapPages.Count; i++) + bitmapPages[i].bitmap.Save(saveName + "_sheet_" + i + ".png", System.Drawing.Imaging.ImageFormat.Png); + } + } + + + if (config.ShadowConfig != null) + fontData.dropShadow = BuildDropShadow(bitmapPages, glyphs, config.ShadowConfig, charSet.ToCharArray(),config.KerningConfig.alphaEmptyPixelTolerance); + + + + foreach (var page in bitmapPages) + page.Free(); + + + //validate glyphs + var intercept = FirstIntercept(fontData.CharSetMapping); + if (intercept != null) + throw new Exception("Failed to create glyph set. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This is could be due to an error in the font, or a bug in Graphics.MeasureString()."); + + + + + return fontData; + + } + + + + private static QFont BuildDropShadow(List sourceFontSheets, QFontGlyph[] sourceFontGlyphs, QFontShadowConfiguration shadowConfig, char[] charSet, byte alphaTolerance) + { + + QFontGlyph[] newGlyphs; + + var sourceBitmapData = new List(); + foreach(var sourceSheet in sourceFontSheets) + sourceBitmapData.Add(sourceSheet.bitmapData); + + + //GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo) + + var bitmapSheets = GenerateBitmapSheetsAndRepack(sourceFontGlyphs, sourceBitmapData.ToArray(), shadowConfig.PageWidth, shadowConfig.PageHeight, out newGlyphs, shadowConfig.GlyphMargin + shadowConfig.blurRadius*3, shadowConfig.ForcePowerOfTwo); + + //scale up in case we wanted bigger/smaller shadows + if (shadowConfig.Scale != 1.0f) + ScaleSheetsAndGlyphs(bitmapSheets, newGlyphs, shadowConfig.Scale); //no point in retargeting yet, since we will do it after blur + + + //blacken and blur + foreach (var bitmapSheet in bitmapSheets) + { + bitmapSheet.Colour32(0, 0, 0); + bitmapSheet.BlurAlpha(shadowConfig.blurRadius, shadowConfig.blurPasses); + + } + + + + + //retarget after blur and scale + RetargetAllGlyphs(bitmapSheets, newGlyphs, alphaTolerance); + + //create list of texture pages + var newTextureSheets = new List(); + foreach (var page in bitmapSheets) + newTextureSheets.Add(new TexturePage(page.bitmapData)); + + + var fontData = new QFontData(); + fontData.CharSetMapping = new Dictionary(); + for(int i = 0; i < charSet.Length; i++) + fontData.CharSetMapping.Add(charSet[i],newGlyphs[i]); + + fontData.Pages = newTextureSheets.ToArray(); + fontData.CalculateMeanWidth(); + fontData.CalculateMaxHeight(); + + + foreach (var sheet in bitmapSheets) + sheet.Free(); + + + return new QFont(fontData); + } + + + + private static void ScaleSheetsAndGlyphs(List pages, QFontGlyph[] glyphs, float scale) + { + foreach (var page in pages) + page.DownScale32((int)(page.bitmap.Width * scale), (int)(page.bitmap.Height * scale)); + + + foreach (var glyph in glyphs) + { + glyph.rect = new Rectangle((int)(glyph.rect.X * scale), (int)(glyph.rect.Y * scale), (int)(glyph.rect.Width * scale), (int)(glyph.rect.Height * scale)); + glyph.yOffset = (int)(glyph.yOffset * scale); + + } + } + + private static void RetargetAllGlyphs(List pages, QFontGlyph[] glyphs, byte alphaTolerance) + { + foreach (var glyph in glyphs) + RetargetGlyphRectangleOutwards(pages[glyph.page].bitmapData, glyph, false, alphaTolerance); + } + + + + + + public static void SaveQFontDataToFile(QFontData data, string filePath) + { + var lines = data.Serialize(); + StreamWriter writer = new StreamWriter(filePath + ".qfont"); + foreach (var line in lines) + writer.WriteLine(line); + + writer.Close(); + + } + + + + public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps){ + destBitmaps = new QBitmap[sourceGlyphs.Length]; + destGlyphs = new QFontGlyph[sourceGlyphs.Length]; + for(int i = 0; i < sourceGlyphs.Length; i++){ + var sg = sourceGlyphs[i]; + destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character); + destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb)); + QBitmap.Blit(sourceBitmaps[sg.page].bitmapData,destBitmaps[i].bitmapData,sg.rect,0,0); + } + } + + + public static QFontData LoadQFontDataFromFile(string filePath, float downSampleFactor, QFontLoaderConfiguration loaderConfig) + { + var lines = new List(); + StreamReader reader = new StreamReader(filePath); + string line; + while((line = reader.ReadLine()) != null) + lines.Add(line); + reader.Close(); + + var data = new QFontData(); + int pageCount = 0; + char[] charSet; + data.Deserialize(lines, out pageCount, out charSet); + + + string namePrefix = filePath.Replace(".qfont","").Replace(" ", ""); + + var bitmapPages = new List(); + + + + if (pageCount == 1) + { + bitmapPages.Add(new QBitmap(namePrefix + ".png")); + } + else + { + for (int i = 0; i < pageCount; i++) + bitmapPages.Add(new QBitmap(namePrefix + "_sheet_" + i)); + } + + + foreach (var glyph in data.CharSetMapping.Values) + RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, loaderConfig.KerningConfig.alphaEmptyPixelTolerance); + + var intercept = FirstIntercept(data.CharSetMapping); + if (intercept != null) + { + throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. If you are texturing your font without locking pixel opacity, then consider using a larger glyph margin. This can be done by setting QFontBuilderConfiguration myQfontBuilderConfig.GlyphMargin, and passing it into CreateTextureFontFiles."); + } + + + if (downSampleFactor > 1.0f) + { + foreach (var page in bitmapPages) + page.DownScale32((int)(page.bitmap.Width * downSampleFactor), (int)(page.bitmap.Height * downSampleFactor)); + + foreach (var glyph in data.CharSetMapping.Values) + { + + glyph.rect = new Rectangle((int)(glyph.rect.X * downSampleFactor), + (int)(glyph.rect.Y * downSampleFactor), + (int)(glyph.rect.Width * downSampleFactor), + (int)(glyph.rect.Height * downSampleFactor)); + glyph.yOffset = (int)(glyph.yOffset * downSampleFactor); + } + } + else if (downSampleFactor < 1.0f ) + { + // If we were simply to shrink the entire texture, then at some point we will make glyphs overlap, breaking the font. + // For this reason it is necessary to copy every glyph to a separate bitmap, and then shrink each bitmap individually. + QFontGlyph[] shrunkGlyphs; + QBitmap[] shrunkBitmapsPerGlyph; + CreateBitmapPerGlyph(Helper.ToArray(data.CharSetMapping.Values), bitmapPages.ToArray(), out shrunkGlyphs, out shrunkBitmapsPerGlyph); + + //shrink each bitmap + for (int i = 0; i < shrunkGlyphs.Length; i++) + { + var bmp = shrunkBitmapsPerGlyph[i]; + bmp.DownScale32(Math.Max((int)(bmp.bitmap.Width * downSampleFactor),1), Math.Max((int)(bmp.bitmap.Height * downSampleFactor),1)); + shrunkGlyphs[i].rect = new Rectangle(0, 0, bmp.bitmap.Width, bmp.bitmap.Height); + shrunkGlyphs[i].yOffset = (int)(shrunkGlyphs[i].yOffset * downSampleFactor); + } + + var shrunkBitmapData = new BitmapData[shrunkBitmapsPerGlyph.Length]; + for(int i = 0; i < shrunkBitmapsPerGlyph.Length; i ++ ){ + shrunkBitmapData[i] = shrunkBitmapsPerGlyph[i].bitmapData; + } + + //use roughly the same number of pages as before.. + int newWidth = (int)(bitmapPages[0].bitmap.Width * (0.1f + downSampleFactor)); + int newHeight = (int)(bitmapPages[0].bitmap.Height * (0.1f + downSampleFactor)); + + //free old bitmap pages since we are about to chuck them away + for (int i = 0; i < pageCount; i++) + bitmapPages[i].Free(); + + QFontGlyph[] shrunkRepackedGlyphs; + bitmapPages = GenerateBitmapSheetsAndRepack(shrunkGlyphs, shrunkBitmapData, newWidth, newHeight, out shrunkRepackedGlyphs, 4, false); + data.CharSetMapping = CreateCharGlyphMapping(shrunkRepackedGlyphs); + + foreach (var bmp in shrunkBitmapsPerGlyph) + bmp.Free(); + + pageCount = bitmapPages.Count; + } + + + data.Pages = new TexturePage[pageCount]; + for(int i = 0; i < pageCount; i ++ ) + data.Pages[i] = new TexturePage(bitmapPages[i].bitmapData); + + + if (downSampleFactor != 1.0f) + { + foreach (var glyph in data.CharSetMapping.Values) + RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, loaderConfig.KerningConfig.alphaEmptyPixelTolerance); + + + intercept = FirstIntercept(data.CharSetMapping); + if (intercept != null) + { + throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This occurred only after resizing your texture font, implying that there is a bug in QFont. "); + } + } + + + + var glyphList = new List(); + + foreach (var c in charSet) + glyphList.Add(data.CharSetMapping[c]); + + if (loaderConfig.ShadowConfig != null) + data.dropShadow = BuildDropShadow(bitmapPages, glyphList.ToArray(), loaderConfig.ShadowConfig, Helper.ToArray(charSet),loaderConfig.KerningConfig.alphaEmptyPixelTolerance); + + + data.KerningPairs = KerningCalculator.CalculateKerning(Helper.ToArray(charSet), glyphList.ToArray(), bitmapPages, loaderConfig.KerningConfig); + + + + data.CalculateMeanWidth(); + data.CalculateMaxHeight(); + + + for (int i = 0; i < pageCount; i++) + bitmapPages[i].Free(); + + + return data; + } + + + + private static char[] FirstIntercept(Dictionary charSet) + { + char[] keys = Helper.ToArray(charSet.Keys); + + for (int i = 0; i < keys.Length; i++) + { + for (int j = i + 1; j < keys.Length; j++) + { + if (charSet[keys[i]].page == charSet[keys[j]].page && RectangleIntersect(charSet[keys[i]].rect, charSet[keys[j]].rect)) + { + return new char[2] { keys[i], keys[j] }; + } + + } + } + return null; + } + + + private static bool RectangleIntersect(Rectangle r1, Rectangle r2) + { + return (r1.X < r2.X + r2.Width && r1.X + r1.Width > r2.X && + r1.Y < r2.Y + r2.Height && r1.Y + r1.Height > r2.Y); + + } + + + + + /// + /// Returns the power of 2 that is closest to x, but not smaller than x. + /// + private static int PowerOfTwo(int x) + { + int shifts = 0; + uint val = (uint)x; + + if (x < 0) + return 0; + + while (val > 0) + { + val = val >> 1; + shifts++; + } + + val = (uint)1 << (shifts - 1); + if (val < x) + { + val = val << 1; + } + + return (int)val; + } + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ChangeLog.txt b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ChangeLog.txt new file mode 100644 index 00000000..7d5f7ad7 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ChangeLog.txt @@ -0,0 +1,30 @@ + +05/11/2011 - Added TransformToCurrentOrthogProjection option +06/11/2011 - Fixed bug whereby it was not possible to set the opacity of the drop shadow +24/11/2011 - Fixed bug in RetargetGlyphRectangleInwards causing an invalid pointer dereference +17/12/2011 - Added UseDefaultBlendFunction option +18/12/2011 - Added PushOptions(), and PopOptions() +22/12/2011 - Added LockToPixelRatio render option +27/12/2011 - Simplified ProjectionStack +27/12/2011 - Added RefreshViewport +30/12/2011 - Fixed bug where fonts with empty glyphs caused QFont to crash +30/12/2011 - Added TextGenerationRenderHint option to QFontBuilderConfiguration (see below for new default configuration) +30/12/2011 - by default, ttf fonts smaller than 12.0 are now generated with TextRenderingHint.ClearTypeGridFit, giving much sharper results +30/12/2011 - Underscore, quote and double quote characters will now kern properly +30/12/2011 - Added kerning character configuration to manually configure kerning rules for particular characters +30/12/2011 - QFontShadowConfiguration is now a member of QFontBuilderConfiguration, simplifying the QFont constructors +30/12/2011 - QFont.FromQFontFile now takes a QFontLoaderConfiguration. This contains a QFontShadowConfiguration. +30/12/2011 - Added alphaEmptyPixelTolerance config value +31/12/2011 - Fixed limitation on downscaling texture fonts (previously glyphs could overlap, throwing an exception) + +----- 1.0.1 released + +07/01/2011 - Removed RefreshViewport +07/01/2011 - Added InvalidateViewport(), ForceViewportRefresh(), PushSoftwareViewport(Viewport viewport), PopSoftwareViewport() +14/04/2012 - When TransformToViewport is enabled for a font, but QFont.Begin/End are not called, the font will now be rendered at the correct size (not tested for all alignments!) +18/07/2012 - Measuring text no longer sets the color and binds the texture, nor does it set a blend function +14/08/2012 - Reload() method added for reloading a qfont object. +14/08/2012 - QFont now correctly implements IDisposable + + +----- 1.0.2 released \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontBuilderConfiguration.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontBuilderConfiguration.cs new file mode 100644 index 00000000..d18964c6 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontBuilderConfiguration.cs @@ -0,0 +1,83 @@ +namespace QuickFont +{ + public enum TextGenerationRenderHint + { + /// + /// Use AntiAliasGridFit when rendering the ttf character set to create the QFont texture + /// + AntiAliasGridFit, + /// + /// Use AntiAlias when rendering the ttf character set to create the QFont texture + /// + AntiAlias, + /// + /// Use ClearTypeGridFit if the font is smaller than 12, otherwise use AntiAlias + /// + SizeDependent, + /// + /// Use ClearTypeGridFit when rendering the ttf character set to create the QFont texture + /// + ClearTypeGridFit, + /// + /// Use SystemDefault when rendering the ttf character set to create the QFont texture + /// + SystemDefault + } + + /// + /// What settings to use when building the font + /// + public class QFontBuilderConfiguration : QFontConfiguration + { + + /// + /// Whether to use super sampling when building font texture pages + /// + /// + /// + public int SuperSampleLevels = 1; + + /// + /// The standard width of texture pages (the page will + /// automatically be cropped if there is extra space) + /// + public int PageWidth = 512; + + /// + /// The standard height of texture pages (the page will + /// automatically be cropped if there is extra space) + /// + public int PageHeight = 512; + + /// + /// Whether to force texture pages to use a power of two. + /// + public bool ForcePowerOfTwo = true; + + /// + /// The margin (on all sides) around glyphs when rendered to + /// their texture page + /// + public int GlyphMargin = 2; + + /// + /// Set of characters to support + /// + public string charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890.:,;'\"(!?)+-*/=_{}[]@~#\\<>|^%$£&"; + + /// + /// Which render hint to use when rendering the ttf character set to create the QFont texture + /// + public TextGenerationRenderHint TextGenerationRenderHint = TextGenerationRenderHint.SizeDependent; + + + public QFontBuilderConfiguration() { } + public QFontBuilderConfiguration(bool addDropShadow) : this(addDropShadow, false) { } + public QFontBuilderConfiguration(bool addDropShadow, bool TransformToOrthogProjection) + { + if (addDropShadow) + this.ShadowConfig = new QFontShadowConfiguration(); + this.TransformToCurrentOrthogProjection = TransformToOrthogProjection; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontConfiguration.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontConfiguration.cs new file mode 100644 index 00000000..2b61e362 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontConfiguration.cs @@ -0,0 +1,14 @@ +namespace QuickFont +{ + public class QFontConfiguration + { + public QFontShadowConfiguration ShadowConfig = null; + public QFontKerningConfiguration KerningConfig = new QFontKerningConfiguration(); + + /// + /// Render the font pixel-prefectly at a size in units of the current orthogonal projection, independent of the viewport pixel size. + /// + public bool TransformToCurrentOrthogProjection = false; + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontKerningConfiguration.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontKerningConfiguration.cs new file mode 100644 index 00000000..b0091262 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontKerningConfiguration.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; + +namespace QuickFont +{ + public enum CharacterKerningRule + { + /// + /// Ordinary kerning + /// + Normal, + /// + /// All kerning pairs involving this character will kern by 0. This will + /// override both Normal and NotMoreThanHalf for any pair. + /// + Zero, + /// + /// Any kerning pairs involving this character will not kern + /// by more than half the minimum width of the two characters + /// involved. This will override Normal for any pair. + /// + NotMoreThanHalf + } + + public class QFontKerningConfiguration + { + /// + /// Kerning rules for particular characters + /// + private Dictionary CharacterKerningRules = new Dictionary(); + + /// + /// When measuring the bounds of glyphs, and performing kerning calculations, + /// this is the minimum alpha level that is necessray for a pixel to be considered + /// non-empty. This should be set to a value on the range [0,255] + /// + public byte alphaEmptyPixelTolerance = 0; + + + /// + /// Sets all characters in the given string to the specified kerning rule. + /// + /// + /// + public void BatchSetCharacterKerningRule(String chars, CharacterKerningRule rule) + { + foreach (var c in chars) + { + CharacterKerningRules[c] = rule; + } + } + + /// + /// Sets the specified character kerning rule. + /// + /// + /// + public void SetCharacterKerningRule(char c, CharacterKerningRule rule) + { + CharacterKerningRules[c] = rule; + } + + public CharacterKerningRule GetCharacterKerningRule(char c) + { + if (CharacterKerningRules.ContainsKey(c)) + { + return CharacterKerningRules[c]; + } + + return CharacterKerningRule.Normal; + } + + /// + /// Given a pair of characters, this will return the overriding + /// CharacterKerningRule. + /// + /// + /// + public CharacterKerningRule GetOverridingCharacterKerningRuleForPair(String str) + { + + if (str.Length < 2) + { + return CharacterKerningRule.Normal; + } + + char c1 = str[0]; + char c2 = str[1]; + + if (GetCharacterKerningRule(c1) == CharacterKerningRule.Zero || GetCharacterKerningRule(c2) == CharacterKerningRule.Zero) + { + return CharacterKerningRule.Zero; + } + else if (GetCharacterKerningRule(c1) == CharacterKerningRule.NotMoreThanHalf || GetCharacterKerningRule(c2) == CharacterKerningRule.NotMoreThanHalf) + { + return CharacterKerningRule.NotMoreThanHalf; + } + + return CharacterKerningRule.Normal; + } + + public QFontKerningConfiguration() + { + BatchSetCharacterKerningRule("_^", CharacterKerningRule.Zero); + SetCharacterKerningRule('\"', CharacterKerningRule.NotMoreThanHalf); + SetCharacterKerningRule('\'', CharacterKerningRule.NotMoreThanHalf); + } + } + + + +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontLoaderConfiguration.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontLoaderConfiguration.cs new file mode 100644 index 00000000..971433a7 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontLoaderConfiguration.cs @@ -0,0 +1,20 @@ +namespace QuickFont +{ + /// + /// The configuraiton used when loading a font from a qfont file. + /// + public class QFontLoaderConfiguration : QFontConfiguration + { + + public QFontLoaderConfiguration() { } + public QFontLoaderConfiguration(bool addDropShadow) : this(addDropShadow, false) { } + public QFontLoaderConfiguration(bool addDropShadow, bool TransformToOrthogProjection) + { + if (addDropShadow) + this.ShadowConfig = new QFontShadowConfiguration(); + + this.TransformToCurrentOrthogProjection = TransformToOrthogProjection; + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontShadowConfiguration.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontShadowConfiguration.cs new file mode 100644 index 00000000..1673ad5b --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Configuration/QFontShadowConfiguration.cs @@ -0,0 +1,50 @@ +namespace QuickFont +{ + /// + /// The configuration used when building a font drop shadow. + /// + public class QFontShadowConfiguration + { + /// + /// Scale in relation to the actual font glyphs + /// + public float Scale = 1.0f; + + /// + /// The blur radius. Caution: high values will greatly impact the + /// time it takes to build a font shadow + /// + public int blurRadius = 3; + + /// + /// Number of blur passes. Caution: high values will greatly impact the + /// time it takes to build a font shadow + /// + public int blurPasses = 2; + + /// + /// The standard width of texture pages (the page will + /// automatically be cropped if there is extra space) + /// + public int PageWidth = 512; + + /// + /// The standard height of texture pages (the page will + /// automatically be cropped if there is extra space) + /// + public int PageHeight = 512; + + /// + /// Whether to force texture pages to use a power of two. + /// + public bool ForcePowerOfTwo = true; + + /// + /// The margin (on all sides) around glyphs when rendered to + /// their texture page. Note this is in addition to 3xblurRadius margin + /// which is automatically added. + /// + public int GlyphMargin = 2; + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/FontLoadDescription.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/FontLoadDescription.cs new file mode 100644 index 00000000..12f7dc7d --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/FontLoadDescription.cs @@ -0,0 +1,46 @@ +using System; +using System.Drawing; + +namespace QuickFont +{ + + enum FontLoadMethod { FontObject, FontFile, QFontFile }; + + /// + /// Describes how a font was loaded so that it can be reloaded + /// + class FontLoadDescription + { + public FontLoadMethod Method { get; private set; } + public String Path { get; private set; } + public float Size { get; private set;} + public FontStyle Style {get; private set; } + public QFontBuilderConfiguration BuilderConfig {get; private set;} + public float DownSampleFactor {get;private set;} + public QFontLoaderConfiguration LoaderConfig {get; private set;} + + public FontLoadDescription(String Path, float DownSampleFactor, QFontLoaderConfiguration LoaderConfig){ + Method = FontLoadMethod.QFontFile; + + this.Path = Path; + this.DownSampleFactor = DownSampleFactor; + this.LoaderConfig = LoaderConfig; + } + + public FontLoadDescription(String Path, float Size, FontStyle Style, QFontBuilderConfiguration BuilderConfig){ + Method = FontLoadMethod.FontFile; + + this.Path = Path; + this.Size = Size; + this.Style = Style; + this.BuilderConfig = BuilderConfig; + } + + public FontLoadDescription(Font font, QFontBuilderConfiguration config){ + Method = FontLoadMethod.FontObject; + //we don't reload fonts loaded direct from a font object... + } + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Helper.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Helper.cs new file mode 100644 index 00000000..c7c97b76 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/Helper.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace QuickFont +{ + class Helper + { + public static T[] ToArray(ICollection collection) + { + T[] output = new T[collection.Count]; + collection.CopyTo(output, 0); + return output; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JBitmap.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JBitmap.cs new file mode 100644 index 00000000..91ff965c --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JBitmap.cs @@ -0,0 +1,971 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; + +namespace QuickFont +{ + + /// + /// A class for basic bitmap manipulation: clearing bitmaps, blitting from one bitmap to another etc. + /// + /// Generally methods in this class are quite slow and are only intended for pre-rendering. + /// + /// TODO : replace loops with block copies + /// + public partial class JBitmap + { + public enum JBitmapPixelFormat {Format8bpp=1, Format24bppBGR=3, Format32bppBGRA=4 } + + + /// + /// Gets the corresponding C# pixel format + /// + /// + /// + private static System.Drawing.Imaging.PixelFormat GetPixFormat(JBitmapPixelFormat format) { + switch (format){ + + case JBitmapPixelFormat.Format8bpp: + return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; + + case JBitmapPixelFormat.Format24bppBGR: + return System.Drawing.Imaging.PixelFormat.Format24bppRgb; + + case JBitmapPixelFormat.Format32bppBGRA: + return System.Drawing.Imaging.PixelFormat.Format32bppArgb; + + default: + return System.Drawing.Imaging.PixelFormat.Format8bppIndexed; + } + + + + } + + + public Bitmap bitmap; + public BitmapData bitmapData; + public JBitmapPixelFormat format; + + + public int h + { + get { return bitmapData.Height; } + } + + public int w + { + get { return bitmapData.Width; } + } + + + + public JBitmap(int w, int h, JBitmapPixelFormat _format) + { + + format = _format; + bitmap = new Bitmap(w, h, GetPixFormat(format)); + bitmapData = bitmap.LockBits(new Rectangle(0, 0, w, h),ImageLockMode.ReadWrite, GetPixFormat(format)); + } + + + + public JBitmap(String fileName, JBitmapPixelFormat _format) + { + format = _format; + bitmap = new Bitmap(fileName); + bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, GetPixFormat(format)); + } + + + + + public JBitmap CreateCopy() + { + JBitmap newBitmap = new JBitmap(w, h, format); + Blit(newBitmap, w, h); + + return newBitmap; + + } + + + + + public void Clear(byte val) + { + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + + for (int i = 0; i < bitmapData.Height; i++) + { + for (int j = 0; j < bitmapData.Width; j++) + { + (*sourcePtr) = val; + sourcePtr++; + } + sourcePtr += bitmapData.Stride - bitmapData.Width * 1; //move to the end of the line (past unused space) + } + } + } + + public void Clear24(byte r, byte g, byte b) + { + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + + for (int i = 0; i < bitmapData.Height; i++) + { + for (int j = 0; j < bitmapData.Width; j++) + { + *(sourcePtr) = b; + *(sourcePtr + 1) = g; + *(sourcePtr + 2) = r; + + sourcePtr += 3; + } + sourcePtr += bitmapData.Stride - bitmapData.Width * 3; //move to the end of the line (past unused space) + } + } + } + + + + + + + /// + /// Blits this bitmap onto the target - clips regions that are out of bounds. + /// + /// Only supported for 8bbp + /// + /// + /// + /// + public void BlitOLD(JBitmap target, int px, int py){ + + //currently only supported for 8 bpp source / target + if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && + target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) + { + + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px,0); + targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); + + targetStartY = Math.Max(py,0); + targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if(copyW < 0 ){ + return; + } + + if(copyH < 0){ + return; + } + + int sourceStartX = targetStartX - px; + int sourceStartY = targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX; + byte* sourceOffset = sourceY + sourceStartX; + for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) + { + *(targetOffset) = *(sourceOffset); + + } + + } + } + + + } + + + + } + + + + + public void Blit(JBitmap target, int srcPx, int srcPy, int srcW, int srcH, int px, int py){ + + if (target.format != format) + { + throw new Exception("Attempted to blit between bitmaps not of the same format"); + } + + int bpp = (int)format; + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + srcW, target.bitmapData.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + srcH, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = srcPx + targetStartX - px; + int sourceStartY = srcPy + targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX * bpp; + byte* sourceOffset = sourceY + sourceStartX * bpp; + for (int x = 0; x < copyW * bpp; x++, targetOffset++, sourceOffset++) + { + *(targetOffset) = *(sourceOffset); + + } + + } + } + } + + + + + public void Blit(JBitmap target, int px, int py) + { + + + + + if (target.format != format) + { + throw new Exception("Attempted to blit between bitmaps not of the same format"); + } + + int bpp = (int)format; + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = targetStartX - px; + int sourceStartY = targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX*bpp; + byte* sourceOffset = sourceY + sourceStartX*bpp; + for (int x = 0; x < copyW*bpp; x++, targetOffset++, sourceOffset++) + { + *(targetOffset) = *(sourceOffset); + + } + + } + } + + + + } + + + + + + /// + /// Additively blits onto the target. + /// + /// Only supported for 8bbp. + /// + /// + /// + /// + public void BlitAdditive(JBitmap target, int px, int py, float strength) + { + + //currently only supported for 8 bpp source / target + if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && + target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) + { + + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = targetStartX - px; + int sourceStartY = targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + int sum; + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX; + byte* sourceOffset = sourceY + sourceStartX; + for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) + { + sum = (int)*(targetOffset) + (int) (*(sourceOffset) * strength); + + if (sum > 255) + { + *(targetOffset) = 255; + } else { + *(targetOffset) = (byte)sum; + } + + } + + } + } + + + } + + + + + + + } + + + + /// + /// Additively blits onto the target. + /// + /// Supports just 24bpp (source and target) + /// + /// + /// + /// + public void BlitAdditive24(JBitmap target, int px, int py, float strength) + { + + //currently only supported for 8 bpp source / target + if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb && + target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb) + { + + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = targetStartX - px; + int sourceStartY = targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + int sum; + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX*3; + byte* sourceOffset = sourceY + sourceStartX*3; + for (int x = 0; x < copyW*3; x++, targetOffset++, sourceOffset++) + { + sum = (int)*(targetOffset) + (int)(*(sourceOffset) * strength); + + if (sum > 255) + { + *(targetOffset) = 255; + } + else + { + *(targetOffset) = (byte)sum; + } + + } + + } + } + + + } + + } + + + public void BlitMax(JBitmap target, int px, int py, float strength) + { + + //currently only supported for 8 bpp source / target + if (bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed && + target.bitmapData.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) + { + + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + bitmapData.Width, target.bitmapData.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + bitmapData.Height, target.bitmapData.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = targetStartX - px; + int sourceStartY = targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + byte* targetPtr = (byte*)(target.bitmapData.Scan0); + int max; + + byte* targetY = targetPtr + targetStartY * target.bitmapData.Stride; + byte* sourceY = sourcePtr + sourceStartY * bitmapData.Stride; + for (int y = 0; y < copyH; y++, targetY += target.bitmapData.Stride, sourceY += bitmapData.Stride) + { + + byte* targetOffset = targetY + targetStartX; + byte* sourceOffset = sourceY + sourceStartX; + for (int x = 0; x < copyW; x++, targetOffset++, sourceOffset++) + { + max = Math.Max((int)*(targetOffset), (int) (*(sourceOffset) * strength)); + + if (*(sourceOffset) * strength > *(targetOffset)) + { + max = (int) (*(sourceOffset) * strength ); + + if (max > 255) + { + *(targetOffset) = 255; + } + else + { + *(targetOffset) = (byte)max; + } + } + + + + + } + + } + } + + + } + + + + } + + + + /// + /// Just unlocks the bits, so that the bitmap can no longer be blitted to / from. + /// + public void Free(){ + bitmap.UnlockBits(bitmapData); + bitmap.Dispose(); + } + + /* + * Resizes this bitmap to a smaller size using area-weighted averaging. + * + */ + public void DownScale(int newWidth, int newHeight) + { + JBitmap newBitmap = new JBitmap(newWidth, newHeight, format); + + float xscale = (float)bitmapData.Width / newWidth; + float yscale = (float)bitmapData.Height / newHeight; + + + byte r = 0, g = 0, b = 0, a = 0; + float summedR = 0f; + float summedG = 0f; + float summedB = 0f; + float summedA = 0f; + + int left, right, top, bottom; //the area of old pixels covered by the new bitmap + + + float targetStartX, targetEndX; + float targetStartY, targetEndY; + + float leftF, rightF, topF, bottomF; //edges of new pixel in old pixel coords + float weight; + float weightScale = xscale * yscale; + float weightTotalTest = 0f; + + for (int m = 0; m < newHeight; m++) + { + for (int n = 0; n < newWidth; n++) + { + + leftF = n * xscale; + rightF = (n + 1) * xscale; + + topF = m * yscale; + bottomF = (m + 1) * yscale; + + left = (int)leftF; + right = (int)rightF; + + top = (int)topF; + bottom = (int)bottomF; + + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= bitmapData.Width) right = bitmapData.Width - 1; + if (bottom >= bitmapData.Height) bottom = bitmapData.Height - 1; + + summedR = 0f; + summedG = 0f; + summedB = 0f; + summedA = 0f; + weightTotalTest = 0f; + + for (int j = top; j <= bottom; j++) + { + for (int i = left; i <= right; i++) + { + targetStartX = Math.Max(leftF, i); + targetEndX = Math.Min(rightF, i + 1); + + targetStartY = Math.Max(topF, j); + targetEndY = Math.Min(bottomF, j + 1); + + weight = (targetEndX - targetStartX) * (targetEndY - targetStartY); + + GetPixelFast(i, j, ref r, ref g, ref b, ref a); + + summedR += weight * r; + summedG += weight * g; + summedB += weight * b; + summedA += weight * a; + + weightTotalTest += weight; + + } + } + + summedR /= weightScale; + summedG /= weightScale; + summedB /= weightScale; + summedA /= weightScale; + + if (summedR < 0) summedR = 0f; + if (summedG < 0) summedG = 0f; + if (summedB < 0) summedB = 0f; + if (summedA < 0) summedA = 0f; + + if (summedR >= 256) summedR = 255; + if (summedG >= 256) summedG = 255; + if (summedB >= 256) summedB = 255; + if (summedA >= 256) summedA = 255; + + newBitmap.PutPixelFast(n, m, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); + } + + } + + + + this.Free(); + + this.bitmap = newBitmap.bitmap; + this.bitmapData = newBitmap.bitmapData; + this.format = newBitmap.format; + + + } + + + + public byte GetPixel(int px, int py) + { + + py = bitmapData.Height - py; + + if (px >= 0 && py >= 0 && px < bitmapData.Width && py < bitmapData.Height) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0); + addr += bitmapData.Stride * py + px; + return *addr; + + } + } + else + { + return 0; + } + } + + + public void GetPixel24(int px, int py, out byte r, out byte g, out byte b) + { + py = bitmapData.Height - py; + + if (px >= 0 && py >= 0 && px < bitmapData.Width && py < bitmapData.Height) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0); + addr += bitmapData.Stride * py + px * 3; + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + } + } + else + { + r = 0; + g = 0; + b = 0; + } + + } + + + public void GetPixelFast(int px, int py, ref byte r, ref byte g, ref byte b, ref byte a) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + r = *addr; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + a = *(addr + 3); + } + } + } + + + public void GetPixelFast(int px, int py, ref byte r, ref byte g, ref byte b) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + r = *addr; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + } + } + + } + + + /* + * For 24-bit - just returns blue channel + * For 32-bit - returns alpha channel + */ + public void GetPixelFast(int px, int py, ref byte col) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + col = *addr; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + col = *addr; + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + col = *(addr + 3); + } + } + + } + + + + public void PutPixelFast(int px, int py, byte r, byte g, byte b, byte a) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + *addr = r; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + *(addr + 3) = a; + } + } + } + + + public void PutPixelFast(int px, int py, byte r, byte g, byte b) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + *addr = r; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + } + } + + } + + + /* + * For 24-bit - just sets blue channel + * For 32-bit - sets alpha channel + */ + public void PutPixelFast(int px, int py, byte col) + { + unsafe + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * (int)format; + + if (format == JBitmapPixelFormat.Format8bpp) + { + *addr = col; + } + else if (format == JBitmapPixelFormat.Format24bppBGR) + { + *addr = col; + } + else if (format == JBitmapPixelFormat.Format32bppBGRA) + { + *(addr + 3) = col; + } + } + + } + + + + + } + + + public class JBitmapManager + { + List bitmapList; + + public JBitmapManager() + { + bitmapList = new List(); + } + + public JBitmap Add(JBitmap bmp) + { + bitmapList.Add(bmp); + return bmp; + } + + + public void Remove(JBitmap bmpToRemove) + { + bitmapList.Remove(bmpToRemove); + bmpToRemove.Free(); + } + + public void FreeAll() + { + foreach (JBitmap bmp in bitmapList) + { + bmp.Free(); + } + } + + } + + + + + + + + +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JMath.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JMath.cs new file mode 100644 index 00000000..9e058bf4 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JMath.cs @@ -0,0 +1,136 @@ +using System; + +namespace QuickFont +{ + public class JMath + { + + public const float PI = (float)Math.PI; + + public static float Cos(float ang) + { + return (float)Math.Cos(ang); + } + public static float Sin(float ang) + { + return (float)Math.Sin(ang); + } + + + //fast version of gluproject which only works only for scale / translate transformations in gluOrthog - use FastProject2 for even better speed + public static void FastProject(float px, float py, double[] modelView, double[] projection, int[] view, out float scrx, out float scry){ + float w = (float) (2 / projection[0]); + float h = (float) (-2 / projection[5]); + + scrx = (float)((px * modelView[0] + modelView[12]) * view[2] / w + view[0]); + scry = (float)((1 - (py * modelView[5] + modelView[13]) / h) * view[3] + view[1]); + } + + //even faster simplified version of gluproject + public static void FastProject2(float px, float py, out float scrx, out float scry) + { + scrx = px * fastProjectConsts[0] + fastProjectConsts[1]; + scry = py * fastProjectConsts[2] + fastProjectConsts[3]; + } + + + + private static float[] fastProjectConsts = new float[4]; + + public static void FastProjectSetConsts(double[] modelView, double[] projection, int[] view){ + float w = (float) (2 / projection[0]); + float h = (float) (-2 / projection[5]); + + + fastProjectConsts[0] = (float)(modelView[0] * view[2] / w); + fastProjectConsts[1] = (float)(modelView[12] * view[2] / w + view[0]); + + fastProjectConsts[2] = (float)(-modelView[5] * view[3] / h); + fastProjectConsts[3] = (float)( (1 - modelView[13] /h) * view[3] + view[1]); + + } + + + + public static float distSquared(float x1, float y1, float x2, float y2) + { + return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + } + + + /// + /// Returns the power of 2 that is closest to x, but not smaller than x. + /// + /// + /// + public static int pot(int x){ + int shifts = 0; + uint val = (uint)x; + + if (x < 0) + { + return 0; + } + + while (val > 0) + { + val = val >> 1; + shifts++; + } + + val = (uint)1 << (shifts - 1); + if (val < x) + { + val = val << 1; + } + return (int)val; + } + + + + + public static double StandardDeviation(double[] data) + { + + if (data.Length == 0) + return 0; + + double avg = Average(data); + double totalVariance = 0; + + foreach (var v in data) + totalVariance += (v - avg) * (v - avg); + + return Math.Sqrt(totalVariance / data.Length); + + + } + + + + private static double Average(double[] data) + { + + if (data.Length == 0) + return 0d; + + double total = 0; + + foreach (var v in data) + total += v; + + return total / data.Length; + + + } + + + + + + + } + + + +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTexture.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTexture.cs new file mode 100644 index 00000000..8706aa40 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTexture.cs @@ -0,0 +1,463 @@ +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System; +using System.Drawing; +using System.Drawing.Imaging; + +namespace QuickFont +{ + + public class JTexture + { + + private bool isSubTexture; + + private float _h; + public float h + { + get { return _h; } + } + + private float _w; + public float w + { + get { return _w; } + } + + private int _GLTexID; + public int GLTexID + { + get { return _GLTexID; } + } + + private bool _hasAlpha; + public bool hasAlpha + { + get { return _hasAlpha; } + } + + private bool _useAlpha; + public bool useAlpha + { + get { return _useAlpha; } + //can only use alpha if the texture has alpha (but can turn alpha off + //when drawing even if the texture has alpha) + set { _useAlpha = value && _hasAlpha; } + } + + + + private Vector2 bottomLeft; + private Vector2 topRight; + + public JTexture() + { + _h = _w = 0; + _GLTexID = -1; + _useAlpha = false; + _hasAlpha = false; + } + + + public void SetDrawSize(float width, float height) + { + _w = width; + _h = height; + } + + + #region Drawing + + public void Draw(Vector2 pos) + { + Draw(pos,1f); + + } + public void Draw(Vector2 pos, float scale) + { + Draw(pos, new Vector2(scale,scale)); + } + + + public void Draw(Vector2 pos, Vector2 scale) + { + GL.PushMatrix(); + GL.Translate(pos.X, pos.Y, 0f); + GL.Scale(scale.X, scale.Y, 1f); + Draw(); + GL.PopMatrix(); + + } + + + + + + /// + /// Draw using JTexture blending settings (ordinary ones). + /// + public void Draw() + { + + if (_GLTexID == -1) + { + return; //no texture loaded yet + } + + + if (_useAlpha) + { + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + } + else + { + GL.Disable(EnableCap.Blend); + } + + DrawBasic(); + + } + + + /// + /// Draw using JTexture blending settings (ordinary ones). + /// + public void DrawCentred() + { + if (_GLTexID == -1) + { + return; //no texture loaded yet + } + + + if (_useAlpha) + { + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + } + else + { + GL.Disable(EnableCap.Blend); + } + + DrawBasicCentred(); + + } + + + + + + + + + public void DrawBasicCentred(Vector2 pos, Vector2 scale,float ang) + { + GL.PushMatrix(); + + GL.Translate(pos.X, pos.Y, 0f); + GL.Scale(scale.X, scale.Y, 1f); + GL.Rotate(ang, 0f, 0f, 1f); + GL.Translate(-w * 0.5f, -h * 0.5f, 0f); + + DrawBasic(); + GL.PopMatrix(); + } + + + + public void DrawBasicCentred() + { + GL.PushMatrix(); + GL.Translate(-w * 0.5f, -h * 0.5f, 0f); + DrawBasic(); + GL.PopMatrix(); + } + + + public void DrawBasic(Vector2 pos) + { + DrawBasic(pos, 1f); + } + + public void DrawBasic(Vector2 pos, float scale) + { + DrawBasic(pos, new Vector2(scale, scale)); + } + + public void DrawBasic(Vector2 pos, Vector2 scale) + { + GL.PushMatrix(); + GL.Translate(pos.X, pos.Y, 0f); + GL.Scale(scale.X, scale.Y, 1f); + DrawBasic(); + GL.PopMatrix(); + } + + /// + /// Draws the texture without changing any blending settings. Use this + /// when you wish to set the blending settings yourself. + /// + public void DrawBasic() + { + GL.Enable(EnableCap.Texture2D); + GL.BindTexture(TextureTarget.Texture2D, _GLTexID); + + GL.Begin(PrimitiveType.Quads); + GL.TexCoord2(bottomLeft); GL.Vertex2(0f, _h); + GL.TexCoord2(bottomLeft.X, topRight.Y); GL.Vertex2(0, 0); + GL.TexCoord2(topRight); GL.Vertex2(_w, 0f); + GL.TexCoord2(topRight.X, bottomLeft.Y); GL.Vertex2(_w, _h); + GL.End(); + } + + + + + + #endregion + + + + public void Free() + { + + if (_GLTexID != -1) + { + //only free root textures + if (!isSubTexture) + GL.DeleteTexture(_GLTexID); + } + + } + + + + + /// + /// Construct a JTexture when the GLTexture already exists. texW and texH are the actual + /// dimensions of the given opengl texture (usually pots). Width and height are always + /// less than or equal to texW and texH respectively, and are the part of the texture to + /// be used starting in the bottom left corner. + /// + /// + /// + /// + /// + public JTexture(int GLTex, float width, float height, bool alpha) + { + isSubTexture = false; + _GLTexID = GLTex; + + _w = width; + _h = height; + _hasAlpha = _useAlpha = alpha; + + bottomLeft = new Vector2(0f, 0f); + topRight = new Vector2(1f, 1f); + + } + + + + /// + /// Constructs a JTexture from a parent texture + /// + /// + /// + /// + /// + /// + public JTexture(JTexture parent, float topLeftX, float topLeftY, float width, float height, bool alpha) + { + if (topLeftX < 0 || topLeftY < 0 || topLeftX + width > parent.w || topLeftY + height > parent.h) + { + throw new ArgumentException("Attempted to define a sub JTexture outside the bounds of the parent"); + } + + + isSubTexture = true; + _GLTexID = parent.GLTexID; + _hasAlpha = parent.hasAlpha; + useAlpha = alpha; + + _w = width; + _h = height; + + float parentTextureW = parent.topRight.X - parent.bottomLeft.X; + float parentTextureH = parent.topRight.Y - parent.bottomLeft.Y; + + bottomLeft.X = parent.bottomLeft.X + (float)topLeftX * parentTextureW / parent.w; + topRight.X = parent.bottomLeft.X + (float)(topLeftX + width) * parentTextureW / parent.w; + + bottomLeft.Y = parent.bottomLeft.Y + (1 - (float)(topLeftY + height) / parent.h) * parentTextureH; + topRight.Y = parent.bottomLeft.Y + (1 - (float)topLeftY / parent.h) * parentTextureH; + + } + + + + + + public JTexture(String fileName, bool alpha) + : this(fileName, alpha, false) + { + } + + + + + public JTexture(String fileName, bool alpha, bool padToPowerOfTwo) + { + Bitmap bitmapSource = new Bitmap(fileName); + + + var format = alpha ? System.Drawing.Imaging.PixelFormat.Format32bppArgb : System.Drawing.Imaging.PixelFormat.Format24bppRgb; + BitmapData dataSource = bitmapSource.LockBits(new Rectangle(0, 0, bitmapSource.Width, bitmapSource.Height),ImageLockMode.ReadOnly, format); + + JTextureConstructorHelper(dataSource, alpha, padToPowerOfTwo); + + + bitmapSource.UnlockBits(dataSource); + } + + + public JTexture(JBitmap jbitmap, bool alpha) + { + JTextureConstructorHelper(jbitmap.bitmapData, alpha, false); + } + + + public JTexture(JBitmap jbitmap, bool alpha, bool padToPowerOfTwo) + { + JTextureConstructorHelper(jbitmap.bitmapData, alpha, padToPowerOfTwo); + } + + + + private void JTextureConstructorHelper(BitmapData dataSource, bool alpha, bool padToPowerOfTwo) + { + + int texture; + int bpp; + + Bitmap bitmapTarget; + + _w = dataSource.Width; + _h = dataSource.Height; + _hasAlpha = _useAlpha = alpha; + + + isSubTexture = false; + + GL.Enable(EnableCap.Texture2D); + + GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); + + GL.GenTextures(1, out texture); + GL.BindTexture(TextureTarget.Texture2D, texture); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp); + + System.Drawing.Imaging.PixelFormat format1; + PixelInternalFormat format2; + OpenTK.Graphics.OpenGL.PixelFormat format3; + + if (alpha) + { + format1 = System.Drawing.Imaging.PixelFormat.Format32bppArgb; + format2 = PixelInternalFormat.Rgba; + format3 = OpenTK.Graphics.OpenGL.PixelFormat.Bgra; + bpp = 4; + } + else + { + format1 = System.Drawing.Imaging.PixelFormat.Format24bppRgb; + format2 = PixelInternalFormat.Three; + format3 = OpenTK.Graphics.OpenGL.PixelFormat.Bgr; + bpp = 3; + } + + int targetW, targetH; + + if (!padToPowerOfTwo) + { + targetW = (int)_w; + targetH = (int)_h; + } + else + { + targetW = JMath.pot((int)_w); + targetH = JMath.pot((int)_h); + + + } + + bitmapTarget = new Bitmap(targetW, targetH, format1); + bottomLeft = new Vector2(0f, 0f); + topRight = new Vector2((float)_w / targetW, (float)_h / targetH); + + + + + BitmapData dataTarget = bitmapTarget.LockBits(new Rectangle(0, 0, bitmapTarget.Width, bitmapTarget.Height), + ImageLockMode.ReadWrite, format1); + + + //copy source data into the target data, flipping it vertically in the process + + unsafe + { + byte* sourcePtr = (byte*)(dataSource.Scan0); + byte* targetPtr = (byte*)(dataTarget.Scan0); + + targetPtr += dataTarget.Stride * (dataSource.Height - 1); //target moves to start of last line + + for (int i = 0; i < dataSource.Height; i++) + { + for (int j = 0; j < dataSource.Width; j++) + { + // write the logic implementation here + + for (int k = 0; k < bpp; k++) + { + (*targetPtr) = (*sourcePtr); + + sourcePtr++; + targetPtr++; + } + } + sourcePtr += dataSource.Stride - dataSource.Width * bpp; //move to the end of the line (past unused space) + targetPtr += dataTarget.Stride - dataSource.Width * bpp; //move to the end of the line (past unused space) + + targetPtr -= dataTarget.Stride * 2; //move up a line + } + + } + + + + GL.TexImage2D(TextureTarget.Texture2D, 0, format2, dataTarget.Width, dataTarget.Height, 0, + format3, PixelType.UnsignedByte, dataTarget.Scan0); + + + + bitmapTarget.UnlockBits(dataTarget); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + + _GLTexID = texture; + + } + + + + + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTextureManager.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTextureManager.cs new file mode 100644 index 00000000..0dc89fac --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/JTextureManager.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace QuickFont +{ + + /// + /// A very simple class for managing a collection of textures. + /// + public class JTextureManager + { + List textureList; + + public JTextureManager() + { + textureList = new List(); + } + + public JTexture Add(JTexture texture) + { + textureList.Add(texture); + return texture; + } + + public void Remove(JTexture textureToRemove) + { + textureList.Remove(textureToRemove); + textureToRemove.Free(); + } + + public void FreeAll() + { + foreach (JTexture tex in textureList) + { + tex.Free(); + } + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/KerningCalculator.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/KerningCalculator.cs new file mode 100644 index 00000000..5f2202e3 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/KerningCalculator.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; + +namespace QuickFont +{ + + class KerningCalculator + { + + private struct XLimits + { + public int Min; + public int Max; + } + + private static int Kerning(QFontGlyph g1, QFontGlyph g2, XLimits[] lim1, XLimits[] lim2, QFontKerningConfiguration config) + { + int yOffset1 = g1.yOffset; + int yOffset2 = g2.yOffset; + + int startY = Math.Max(yOffset1, yOffset2); + int endY = Math.Min(g1.rect.Height + yOffset1, g2.rect.Height + yOffset2); + + int w1 = g1.rect.Width; + + int worstCase = w1; + + //TODO - offset startY, endY by yOffset1 so that lim1[j-yOffset1] can be written as lim1[j], will need another var for yOffset2 + + for (int j = startY; j < endY; j++) + worstCase = Math.Min(worstCase, w1 - lim1[j-yOffset1].Max + lim2[j-yOffset2].Min); + + + worstCase = Math.Min(worstCase, g1.rect.Width); + worstCase = Math.Min(worstCase, g2.rect.Width); + + + //modify by character kerning rules + CharacterKerningRule kerningRule = config.GetOverridingCharacterKerningRuleForPair(""+g1.character + g2.character); + if (kerningRule == CharacterKerningRule.Zero) + { + return 0; + } + else if (kerningRule == CharacterKerningRule.NotMoreThanHalf) + { + return (int)Math.Min(Math.Min(g1.rect.Width,g2.rect.Width)*0.5f, worstCase); + } + + + return worstCase; + } + + public static Dictionary CalculateKerning(char[] charSet, QFontGlyph[] glyphs, List bitmapPages, QFontKerningConfiguration config) + { + var kerningPairs = new Dictionary(); + + + + //we start by computing the index of the first and last non-empty pixel in each row of each glyph + XLimits[][] limits = new XLimits[charSet.Length][]; + int maxHeight = 0; + for (int n = 0; n < charSet.Length; n++) + { + var rect = glyphs[n].rect; + var page = bitmapPages[glyphs[n].page]; + + limits[n] = new XLimits[rect.Height]; + + maxHeight = Math.Max(rect.Height, maxHeight); + + int yStart = rect.Y; + int yEnd = rect.Y + rect.Height; + int xStart = rect.X; + int xEnd = rect.X + rect.Width; + + for (int j = yStart; j < yEnd; j++) + { + int last = xStart; + + bool yetToFindFirst = true; + for (int i = xStart; i < xEnd; i++) + { + if (!QBitmap.EmptyAlphaPixel(page.bitmapData, i, j,config.alphaEmptyPixelTolerance)) + { + + if (yetToFindFirst) + { + limits[n][j - yStart].Min = i - xStart; + yetToFindFirst = false; + } + last = i; + } + } + + limits[n][j - yStart].Max = last - xStart; + + if (yetToFindFirst) + limits[n][j - yStart].Min = xEnd - 1; + } + } + + + //we now bring up each row to the max (or min) of it's two adjacent rows, this is to stop glyphs sliding together too closely + var tmp = new XLimits[maxHeight]; + + for (int n = 0; n < charSet.Length; n++) + { + //clear tmp + for (int j = 0; j < limits[n].Length; j++) + tmp[j] = limits[n][j]; + + for (int j = 0; j < limits[n].Length; j++) + { + if(j != 0){ + tmp[j].Min = Math.Min(limits[n][j - 1].Min, tmp[j].Min); + tmp[j].Max = Math.Max(limits[n][j - 1].Max, tmp[j].Max); + } + + if (j != limits[n].Length - 1) + { + tmp[j].Min = Math.Min(limits[n][j + 1].Min, tmp[j].Min); + tmp[j].Max = Math.Max(limits[n][j + 1].Max, tmp[j].Max); + } + + } + + for (int j = 0; j < limits[n].Length; j++) + limits[n][j] = tmp[j]; + + } + + for (int i = 0; i < charSet.Length; i++) + for (int j = 0; j < charSet.Length; j++) + kerningPairs.Add("" + charSet[i] + charSet[j], 1-Kerning(glyphs[i], glyphs[j], limits[i], limits[j],config)); + + return kerningPairs; + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ProjectionStack.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ProjectionStack.cs new file mode 100644 index 00000000..effa6de3 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/ProjectionStack.cs @@ -0,0 +1,156 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using System.Collections.Generic; + +namespace QuickFont +{ + + public struct Viewport + { + public int X, Y, Width, Height; + public Viewport(int X, int Y, int Width, int Height) { this.X = X; this.Y = Y; this.Width = Width; this.Height = Height; } + } + + public struct TransformViewport + { + public float X, Y, Width, Height; + public TransformViewport(float X, float Y, float Width, float Height) { this.X = X; this.Y = Y; this.Width = Width; this.Height = Height; } + } + + class ProjectionStack + { + public static bool Begun { get; private set; } + + private static Stack cachedViewportStack = new Stack(); + static ProjectionStack() + { + Begun = false; + cachedViewportStack.Push(null); + } + + + //The currently set viewport + public static Viewport? CurrentViewport { + get { + lock (cachedViewportStack) + { + var currentViewport = cachedViewportStack.Peek(); + if (currentViewport == null) + { + UpdateCurrentViewportFromHardware(); + currentViewport = cachedViewportStack.Peek(); + } + + return currentViewport; + } + } + } + + public static void UpdateCurrentViewportFromHardware() + { + lock (cachedViewportStack) + { + GraphicsContext.Assert(); + Viewport viewport = new Viewport(); + GL.GetInteger(GetPName.Viewport, out viewport.X); + cachedViewportStack.Pop(); + cachedViewportStack.Push(viewport); + } + } + + public static void PushSoftwareViewport(Viewport viewport) + { + lock (cachedViewportStack) + { + cachedViewportStack.Push(viewport); + } + } + + public static void PopSoftwareViewport() + { + lock (cachedViewportStack) + { + cachedViewportStack.Pop(); + } + } + + + public static void InvalidateViewport() + { + lock (cachedViewportStack) + { + cachedViewportStack.Pop(); + cachedViewportStack.Push(null); + } + } + + + public static void GetCurrentOrthogProjection(out bool isOrthog, out float left, out float right, out float bottom, out float top) + { + Matrix4 matrix = new Matrix4(); + GL.GetFloat(GetPName.ProjectionMatrix, out matrix.Row0.X); + + if (matrix.M11 == 0 || matrix.M22 == 0) + { + isOrthog = false; + left = right = bottom = top = 0; + return; + } + + left = -(1f + matrix.M41) / (matrix.M11); + right = (1f - matrix.M41) / (matrix.M11); + + bottom = -(1 + matrix.M42) / (matrix.M22); + top = (1 - matrix.M42) / (matrix.M22); + + isOrthog = + matrix.M12 == 0 && matrix.M13 == 0 && matrix.M14 == 0 && + matrix.M21 == 0 && matrix.M23 == 0 && matrix.M24 == 0 && + matrix.M31 == 0 && matrix.M32 == 0 && matrix.M34 == 0 && + matrix.M44 == 1f; + } + + + public static void Begin() + { + + GraphicsContext.Assert(); + + Viewport currentVp = (Viewport)CurrentViewport; + + GL.MatrixMode(MatrixMode.Projection); + GL.PushMatrix(); //push projection matrix + GL.LoadIdentity(); + GL.Ortho(currentVp.X, currentVp.Width, currentVp.Height, currentVp.Y, -1.0, 1.0); + + GL.MatrixMode(MatrixMode.Modelview); + GL.PushMatrix(); //push modelview matrix + GL.LoadIdentity(); + + Begun = true; + + } + + public static void End() + { + GraphicsContext.Assert(); + + GL.MatrixMode(MatrixMode.Modelview); + GL.PopMatrix(); //pop modelview + + GL.MatrixMode(MatrixMode.Projection); + GL.PopMatrix(); //pop projection + + GL.MatrixMode(MatrixMode.Modelview); + + Begun = false; + } + + + + + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QBitmap.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QBitmap.cs new file mode 100644 index 00000000..2b0173ee --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QBitmap.cs @@ -0,0 +1,552 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; + +namespace QuickFont +{ + public class QBitmap + { + public Bitmap bitmap; + public BitmapData bitmapData; + + + public QBitmap(string filePath) + { + LockBits(new Bitmap(filePath)); + } + + public QBitmap(Bitmap bitmap) + { + LockBits(bitmap); + } + + + private void LockBits(Bitmap bitmap) + { + this.bitmap = bitmap; + bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat); + } + + + + public void Clear32(byte r, byte g, byte b, byte a) + { + unsafe + { + byte* sourcePtr = (byte*)(bitmapData.Scan0); + + for (int i = 0; i < bitmapData.Height; i++) + { + for (int j = 0; j < bitmapData.Width; j++) + { + *(sourcePtr) = b; + *(sourcePtr + 1) = g; + *(sourcePtr + 2) = r; + *(sourcePtr + 3) = a; + + sourcePtr += 4; + } + sourcePtr += bitmapData.Stride - bitmapData.Width * 4; //move to the end of the line (past unused space) + } + } + } + + + + + /// + /// Returns try if the given pixel is empty (i.e. black) + /// + public static unsafe bool EmptyPixel(BitmapData bitmapData, int px, int py) + { + + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 3; + return (*addr == 0 && *(addr + 1) == 0 && *(addr + 2) == 0); + + } + + /// + /// Returns try if the given pixel is empty (i.e. alpha is zero) + /// + public static unsafe bool EmptyAlphaPixel(BitmapData bitmapData, int px, int py, byte alphaEmptyPixelTolerance) + { + + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 4; + return (*(addr + 3) <= alphaEmptyPixelTolerance); + + } + + /// + /// Blits a block of a bitmap data from source to destination, using the luminance of the source to determine the + /// alpha of the target. Source must be 24-bit, target must be 32-bit. + /// + public static void BlitMask(BitmapData source, BitmapData target, int srcPx, int srcPy, int srcW, int srcH, int px, int py) + { + + int sourceBpp = 3; + int targetBpp = 4; + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(px, 0); + targetEndX = Math.Min(px + srcW, target.Width); + + targetStartY = Math.Max(py, 0); + targetEndY = Math.Min(py + srcH, target.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = srcPx + targetStartX - px; + int sourceStartY = srcPy + targetStartY - py; + + + unsafe + { + byte* sourcePtr = (byte*)(source.Scan0); + byte* targetPtr = (byte*)(target.Scan0); + + + byte* targetY = targetPtr + targetStartY * target.Stride; + byte* sourceY = sourcePtr + sourceStartY * source.Stride; + for (int y = 0; y < copyH; y++, targetY += target.Stride, sourceY += source.Stride) + { + + byte* targetOffset = targetY + targetStartX * targetBpp; + byte* sourceOffset = sourceY + sourceStartX * sourceBpp; + for (int x = 0; x < copyW; x++, targetOffset += targetBpp, sourceOffset += sourceBpp) + { + int lume = *(sourceOffset) + *(sourceOffset + 1) + *(sourceOffset + 2); + + lume /= 3; + + if (lume > 255) + lume = 255; + + *(targetOffset + 3) = (byte)lume; + + } + + } + } + } + + /// + /// Blits from source to target. Both source and target must be 32-bit + /// + public static void Blit(BitmapData source, BitmapData target, Rectangle sourceRect, int px, int py) + { + Blit(source, target, sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height, px, py); + } + + /// + /// Blits from source to target. Both source and target must be 32-bit + /// + public static void Blit(BitmapData source, BitmapData target, int srcPx, int srcPy, int srcW, int srcH, int destX, int destY) + { + + int bpp = 4; + + int targetStartX, targetEndX; + int targetStartY, targetEndY; + int copyW, copyH; + + targetStartX = Math.Max(destX, 0); + targetEndX = Math.Min(destX + srcW, target.Width); + + targetStartY = Math.Max(destY, 0); + targetEndY = Math.Min(destY + srcH, target.Height); + + copyW = targetEndX - targetStartX; + copyH = targetEndY - targetStartY; + + if (copyW < 0) + { + return; + } + + if (copyH < 0) + { + return; + } + + int sourceStartX = srcPx + targetStartX - destX; + int sourceStartY = srcPy + targetStartY - destY; + + + unsafe + { + byte* sourcePtr = (byte*)(source.Scan0); + byte* targetPtr = (byte*)(target.Scan0); + + + byte* targetY = targetPtr + targetStartY * target.Stride; + byte* sourceY = sourcePtr + sourceStartY * source.Stride; + for (int y = 0; y < copyH; y++, targetY += target.Stride, sourceY += source.Stride) + { + + byte* targetOffset = targetY + targetStartX * bpp; + byte* sourceOffset = sourceY + sourceStartX * bpp; + for (int x = 0; x < copyW*bpp; x++, targetOffset ++, sourceOffset ++) + *(targetOffset) = *(sourceOffset); + + } + } + } + + + public unsafe void PutPixel32(int px, int py, byte r, byte g, byte b, byte a) + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 4; + + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + *(addr + 3) = a; + } + + public unsafe void GetPixel32(int px, int py, ref byte r, ref byte g, ref byte b, ref byte a) + { + byte* addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 4; + + b = *addr; + g = *(addr + 1); + r = *(addr + 2); + a = *(addr + 3); + } + + + public unsafe void PutAlpha32(int px, int py, byte a) + { + *((byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 4 + 3) = a; + } + + public unsafe void GetAlpha32(int px, int py, ref byte a) + { + a = *((byte*)(bitmapData.Scan0) + bitmapData.Stride * py + px * 4 + 3); + } + + public void DownScale32(int newWidth, int newHeight) + { + + + QBitmap newBitmap = new QBitmap(new Bitmap(newWidth, newHeight, bitmap.PixelFormat)); + + if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) + throw new Exception("DownsScale32 only works on 32 bit images"); + + float xscale = (float)bitmapData.Width / newWidth; + float yscale = (float)bitmapData.Height / newHeight; + + byte r = 0, g = 0, b = 0, a = 0; + float summedR = 0f; + float summedG = 0f; + float summedB = 0f; + float summedA = 0f; + + int left, right, top, bottom; //the area of old pixels covered by the new bitmap + + + float targetStartX, targetEndX; + float targetStartY, targetEndY; + + float leftF, rightF, topF, bottomF; //edges of new pixel in old pixel coords + float weight; + float weightScale = xscale * yscale; + float totalColourWeight = 0f; + + for (int m = 0; m < newHeight; m++) + { + for (int n = 0; n < newWidth; n++) + { + + leftF = n * xscale; + rightF = (n + 1) * xscale; + + topF = m * yscale; + bottomF = (m + 1) * yscale; + + left = (int)leftF; + right = (int)rightF; + + top = (int)topF; + bottom = (int)bottomF; + + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= bitmapData.Width) right = bitmapData.Width - 1; + if (bottom >= bitmapData.Height) bottom = bitmapData.Height - 1; + + summedR = 0f; + summedG = 0f; + summedB = 0f; + summedA = 0f; + totalColourWeight = 0f; + + for (int j = top; j <= bottom; j++) + { + for (int i = left; i <= right; i++) + { + targetStartX = Math.Max(leftF, i); + targetEndX = Math.Min(rightF, i + 1); + + targetStartY = Math.Max(topF, j); + targetEndY = Math.Min(bottomF, j + 1); + + weight = (targetEndX - targetStartX) * (targetEndY - targetStartY); + + GetPixel32(i, j, ref r, ref g, ref b, ref a); + + summedA += weight * a; + + if (a != 0) + { + summedR += weight * r; + summedG += weight * g; + summedB += weight * b; + totalColourWeight += weight; + } + + } + } + + summedR /= totalColourWeight; + summedG /= totalColourWeight; + summedB /= totalColourWeight; + summedA /= weightScale; + + if (summedR < 0) summedR = 0f; + if (summedG < 0) summedG = 0f; + if (summedB < 0) summedB = 0f; + if (summedA < 0) summedA = 0f; + + if (summedR >= 256) summedR = 255; + if (summedG >= 256) summedG = 255; + if (summedB >= 256) summedB = 255; + if (summedA >= 256) summedA = 255; + + newBitmap.PutPixel32(n, m, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); + } + + } + + + this.Free(); + + this.bitmap = newBitmap.bitmap; + this.bitmapData = newBitmap.bitmapData; + } + + + + + + /// + /// Sets colour without touching alpha values + /// + /// + /// + /// + public void Colour32(byte r, byte g, byte b) + { + unsafe + { + byte* addr; + for (int i = 0; i < bitmapData.Width; i++) + { + for (int j = 0; j < bitmapData.Height; j++) + { + addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * j + i * 4; + *addr = b; + *(addr + 1) = g; + *(addr + 2) = r; + } + } + } + } + + + /* + + public void Blur(int radius, int passes) + { + QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); + + byte r=0,g=0,b=0,a=0; + int summedR, summedG, summedB, summedA; + int weight = 0; + int xpos, ypos, x, y, kx, ky; + + + for (int pass = 0; pass < passes; pass++) + { + + //horizontal pass + for (y = 0; y < bitmap.Height; y++) + { + for (x = 0; x < bitmap.Width; x++) + { + summedR = summedG = summedB = summedA = weight = 0; + for (kx = -radius; kx <= radius; kx++) + { + xpos = x + kx; + if (xpos >= 0 && xpos < bitmap.Width) + { + GetPixel32(xpos, y, ref r, ref g, ref b, ref a); + + + summedR += r; + summedG += g; + summedB += b; + summedA += a; + weight++; + } + + } + + summedR /= weight; + summedG /= weight; + summedB /= weight; + summedA /= weight; + + tmp.PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); + } + } + + + + + //vertical pass + for (x = 0; x < bitmap.Width; ++x) + { + for (y = 0; y < bitmap.Height; ++y) + { + summedR = summedG = summedB = summedA = weight = 0; + for (ky = -radius; ky <= radius; ky++) + { + ypos = y + ky; + if (ypos >= 0 && ypos < bitmap.Height) + { + tmp.GetPixel32(x, ypos, ref r, ref g, ref b, ref a); + + summedR += r; + summedG += g; + summedB += b; + summedA += a; + weight++; + } + } + + summedR /= weight; + summedG /= weight; + summedB /= weight; + summedA /= weight; + + PutPixel32(x, y, (byte)summedR, (byte)summedG, (byte)summedB, (byte)summedA); + + } + } + + } + + tmp.Free(); + + }*/ + + + + + public void BlurAlpha(int radius, int passes) + { + QBitmap tmp = new QBitmap(new Bitmap(this.bitmap.Width, this.bitmap.Height, bitmap.PixelFormat)); + + byte a = 0; + int summedA; + int weight = 0; + int xpos, ypos, x, y, kx, ky; + int width = bitmap.Width; + int height = bitmap.Height; + + for (int pass = 0; pass < passes; pass++) + { + + //horizontal pass + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + summedA = weight = 0; + for (kx = -radius; kx <= radius; kx++) + { + xpos = x + kx; + if (xpos >= 0 && xpos < width) + { + GetAlpha32(xpos, y, ref a); + summedA += a; + weight++; + } + } + + summedA /= weight; + tmp.PutAlpha32(x, y, (byte)summedA); + } + } + + + + + //vertical pass + for (x = 0; x = 0 && ypos < height) + { + tmp.GetAlpha32(x, ypos,ref a); + summedA += a; + weight++; + } + } + + summedA /= weight; + + PutAlpha32(x, y, (byte)summedA); + + } + } + + } + + tmp.Free(); + + } + + + + + + + public void Free() + { + bitmap.UnlockBits(bitmapData); + bitmap.Dispose(); + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFont.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFont.cs new file mode 100644 index 00000000..68bfb79b --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFont.cs @@ -0,0 +1,1321 @@ +using OpenTK; +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Text; + +namespace QuickFont +{ + + public class QFont : IDisposable + { + + //private QFontRenderOptions options = new QFontRenderOptions(); + private Stack optionsStack = new Stack(); + internal QFontData fontData; + + private FontLoadDescription fontLoadDescription; + + + public QFontRenderOptions Options + { + get { + + if (optionsStack.Count == 0) + { + optionsStack.Push(new QFontRenderOptions()); + } + + return optionsStack.Peek() ; + } + private set { //not sure if we should even allow this... + optionsStack.Pop(); + optionsStack.Push(value); + } + } + + + #region Constructors and font builders + + /// + /// Reloads the font using the original loader/builder options. This may be useful if the underlying + /// files change, or, more commonly, if the "TransformToCurrentOrthogProjection" option was used when + /// creating the font, and the orthog projection has since changed (e.g. resizing the window). This + /// will do nothing for fonts created directly from a Font object. + /// + public void Reload() + { + switch (fontLoadDescription.Method) + { + case FontLoadMethod.QFontFile: + { + fontData.Dispose(); //dispose old data + LoadQFontFromQFontFile(fontLoadDescription); + break; + } + + case FontLoadMethod.FontFile: + { + fontData.Dispose(); //dispose old data + LoadQFontFromFontFile(fontLoadDescription); + break; + } + } + } + + + + + + + + private QFont() { } + internal QFont(QFontData fontData) { this.fontData = fontData; } + public QFont(Font font) : this(font, null) { } + public QFont(Font font, QFontBuilderConfiguration config) + { + if (config == null) + config = new QFontBuilderConfiguration(); + + fontData = BuildFont(font, config, null); + + if (config.ShadowConfig != null) + Options.DropShadowActive = true; + } + + + private void LoadQFontFromFontFile(FontLoadDescription loadDescription) + { + var config = loadDescription.BuilderConfig; + var fileName = loadDescription.Path; + var size = loadDescription.Size; + var style = loadDescription.Style; + + if (config == null) + config = new QFontBuilderConfiguration(); + + TransformViewport? transToVp = null; + + float dpiX, dpiY; + float fontScale = 1.0f; + using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero)) + { + dpiX = graphics.DpiX; + dpiY = graphics.DpiY; + } + + float distancefrom120 = 120 - dpiX; + float percent = (120 - distancefrom120) / 120; + + if (dpiX != 120f) + if (percent > 1) + fontScale = 1 - Math.Abs(percent - 1); + else + fontScale = 1f + (1 - percent); + + fontScale = Math.Abs(fontScale); + + if (config.TransformToCurrentOrthogProjection) + transToVp = OrthogonalTransform(out fontScale); + + //dont move this into a separate method - it needs to stay in scope! + PrivateFontCollection pfc = new PrivateFontCollection(); + pfc.AddFontFile(fileName); + var fontFamily = pfc.Families[0]; + + if (!fontFamily.IsStyleAvailable(style)) + throw new ArgumentException("Font file: " + fileName + " does not support style: " + style); + + + var font = new Font(fontFamily, size * fontScale * config.SuperSampleLevels, style); + //var font = ObtainFont(fileName, size * fontScale * config.SuperSampleLevels, style) + fontData = BuildFont(font, config, null); + fontData.scaleDueToTransformToViewport = fontScale; + font.Dispose(); + + if (config.ShadowConfig != null) + Options.DropShadowActive = true; + if (transToVp != null) + Options.TransformToViewport = transToVp; + } + + + private void LoadQFontFromQFontFile(FontLoadDescription loadDescription) + { + var loaderConfig = loadDescription.LoaderConfig; + var filePath = loadDescription.Path; + var downSampleFactor = loadDescription.DownSampleFactor; + + + if (loaderConfig == null) + loaderConfig = new QFontLoaderConfiguration(); + + TransformViewport? transToVp = null; + float fontScale = 1f; + if (loaderConfig.TransformToCurrentOrthogProjection) + transToVp = OrthogonalTransform(out fontScale); + + fontData = Builder.LoadQFontDataFromFile(filePath, downSampleFactor * fontScale, loaderConfig); + fontData.scaleDueToTransformToViewport = fontScale; + + if (loaderConfig.ShadowConfig != null) + Options.DropShadowActive = true; + if (transToVp != null) + Options.TransformToViewport = transToVp; + } + + + + public QFont(string fileName, float size) : this(fileName, size, FontStyle.Regular, null) { } + public QFont(string fileName, float size, FontStyle style) : this(fileName, size, style, null) { } + public QFont(string fileName, float size, QFontBuilderConfiguration config) : this(fileName, size, FontStyle.Regular, config) { } + public QFont(string fileName, float size, FontStyle style, QFontBuilderConfiguration config) + { + fontLoadDescription = new FontLoadDescription(fileName, size, style, config); + LoadQFontFromFontFile(fontLoadDescription); + } + + + public static void CreateTextureFontFiles(Font font, string newFontName) { CreateTextureFontFiles(font, null); } + public static void CreateTextureFontFiles(Font font, string newFontName, QFontBuilderConfiguration config) + { + var fontData = BuildFont(font, config, newFontName); + Builder.SaveQFontDataToFile(fontData, newFontName); + } + + + + public static void CreateTextureFontFiles(string fileName, float size, string newFontName) { CreateTextureFontFiles(fileName, size, FontStyle.Regular, null, newFontName); } + public static void CreateTextureFontFiles(string fileName, float size, FontStyle style, string newFontName) { CreateTextureFontFiles(fileName, size, style, null, newFontName); } + public static void CreateTextureFontFiles(string fileName, float size, QFontBuilderConfiguration config, string newFontName) { CreateTextureFontFiles(fileName, size, FontStyle.Regular, config, newFontName); } + public static void CreateTextureFontFiles(string fileName, float size, FontStyle style, QFontBuilderConfiguration config, string newFontName) + { + + QFontData fontData = null; + if (config == null) + config = new QFontBuilderConfiguration(); + + + //dont move this into a separate method - it needs to stay in scope! + PrivateFontCollection pfc = new PrivateFontCollection(); + pfc.AddFontFile(fileName); + var fontFamily = pfc.Families[0]; + + if (!fontFamily.IsStyleAvailable(style)) + throw new ArgumentException("Font file: " + fileName + " does not support style: " + style); + + var font = new Font(fontFamily, size * config.SuperSampleLevels, style); + //var font = ObtainFont(fileName, size * config.SuperSampleLevels, style); + try + { + fontData = BuildFont(font, config, newFontName); + } + finally + { + if (font != null) + font.Dispose(); + } + + Builder.SaveQFontDataToFile(fontData, newFontName); + + } + + public static QFont FromQFontFile(string filePath) { return FromQFontFile(filePath, 1.0f, null); } + public static QFont FromQFontFile(string filePath, QFontLoaderConfiguration loaderConfig) { return FromQFontFile(filePath, 1.0f, loaderConfig); } + public static QFont FromQFontFile(string filePath, float downSampleFactor) { return FromQFontFile(filePath, downSampleFactor,null); } + public static QFont FromQFontFile(string filePath, float downSampleFactor, QFontLoaderConfiguration loaderConfig) + { + QFont qfont = new QFont(); + qfont.fontLoadDescription = new FontLoadDescription(filePath,downSampleFactor,loaderConfig); + qfont.LoadQFontFromQFontFile(qfont.fontLoadDescription); + return qfont; + } + + private static QFontData BuildFont(Font font, QFontBuilderConfiguration config, string saveName){ + + Builder builder = new Builder(font, config); + return builder.BuildFontData(saveName); + } + + + + #endregion + + + + + /// + /// When TransformToOrthogProjection is enabled, we need to get the current orthogonal transformation, + /// the font scale, and ensure that the projection is actually orthogonal + /// + /// + /// + private static TransformViewport OrthogonalTransform(out float fontScale) + { + bool isOrthog; + float left,right,bottom,top; + ProjectionStack.GetCurrentOrthogProjection(out isOrthog,out left,out right,out bottom,out top); + + if (!isOrthog) + throw new ArgumentOutOfRangeException("Current projection matrix was not Orthogonal. Please ensure that you have set an orthogonal projection before attempting to create a font with the TransformToOrthogProjection flag set to true."); + + var viewportTransform = new TransformViewport(left, top, right - left, bottom - top); + fontScale = Math.Abs((float)ProjectionStack.CurrentViewport.Value.Height / viewportTransform.Height); + return viewportTransform; + } + + + + + + /// + /// Pushes the specified QFont options onto the options stack + /// + /// + public void PushOptions(QFontRenderOptions newOptions) + { + optionsStack.Push(newOptions); + } + + /// + /// Creates a clone of the current font options and pushes + /// it onto the stack + /// + public void PushOptions() + { + PushOptions(Options.CreateClone()); + } + + public void PopOptions() + { + if (optionsStack.Count > 1) + { + optionsStack.Pop(); + } + else + { + throw new Exception("Attempted to pop from options stack when there is only one Options object on the stack."); + } + } + + + + public float LineSpacing + { + get { return (float)Math.Ceiling(fontData.maxGlyphHeight * Options.LineSpacing); } + } + + public bool IsMonospacingActive + { + get { return fontData.IsMonospacingActive(Options); } + } + + + public float MonoSpaceWidth + { + get { return fontData.GetMonoSpaceWidth(Options); } + } + + + + private void RenderDropShadow(float x, float y, char c, QFontGlyph nonShadowGlyph) + { + //note can cast drop shadow offset to int, but then you can't move the shadow smoothly... + if (fontData.dropShadow != null && Options.DropShadowActive) + { + //make sure fontdata font's options are synced with the actual options + if (fontData.dropShadow.Options != Options) + fontData.dropShadow.Options = Options; + + fontData.dropShadow.RenderGlyph( + x + (fontData.meanGlyphWidth * Options.DropShadowOffset.X + nonShadowGlyph.rect.Width * 0.5f), + y + (fontData.meanGlyphWidth * Options.DropShadowOffset.Y + nonShadowGlyph.rect.Height * 0.5f + nonShadowGlyph.yOffset), c, true); + } + } + + public void RenderGlyph(float x, float y, char c, bool isDropShadow) + { + + + var glyph = fontData.CharSetMapping[c]; + + //note: it's not immediately obvious, but this combined with the paramteters to + //RenderGlyph for the shadow mean that we render the shadow centrally (despite it being a different size) + //under the glyph + if (isDropShadow) + { + x -= (int)(glyph.rect.Width * 0.5f); + y -= (int)(glyph.rect.Height * 0.5f + glyph.yOffset); + } + + + RenderDropShadow(x, y, c, glyph); + + + if (isDropShadow) + { + GL.Color4(1.0f, 1.0f, 1.0f, Options.DropShadowOpacity); + } + else + { + GL.Color4(Options.Colour); + } + + + TexturePage sheet = fontData.Pages[glyph.page]; + GL.BindTexture(TextureTarget.Texture2D, sheet.GLTexID); + + + float tx1 = (float)(glyph.rect.X) / sheet.Width; + float ty1 = (float)(glyph.rect.Y) / sheet.Height; + float tx2 = (float)(glyph.rect.X + glyph.rect.Width) / sheet.Width; + float ty2 = (float)(glyph.rect.Y + glyph.rect.Height) / sheet.Height; + + GL.Begin(PrimitiveType.Quads); + GL.TexCoord2(tx1, ty1); GL.Vertex2(x, y + glyph.yOffset); + GL.TexCoord2(tx1, ty2); GL.Vertex2(x, y + glyph.yOffset + glyph.rect.Height); + GL.TexCoord2(tx2, ty2); GL.Vertex2(x + glyph.rect.Width, y + glyph.yOffset + glyph.rect.Height); + GL.TexCoord2(tx2, ty1); GL.Vertex2(x + glyph.rect.Width, y + glyph.yOffset); + GL.End(); + + + } + + + + + + private float MeasureNextlineLength(string text) + { + + float xOffset = 0; + + for(int i=0; i < text.Length;i++) + { + char c = text[i]; + + if (c == '\r' || c == '\n') + { + break; + } + + + if (IsMonospacingActive) + { + xOffset += MonoSpaceWidth; + } + else + { + //space + if (c == ' ') + { + xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * Options.WordSpacing); + } + //normal character + else if (fontData.CharSetMapping.ContainsKey(c)) + { + QFontGlyph glyph = fontData.CharSetMapping[c]; + xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); + } + } + } + return xOffset; + } + + + private Vector2 TransformPositionToViewport(Vector2 input) + { + + var v2 = Options.TransformToViewport; + if (v2 == null || !ProjectionStack.Begun) + { + return input; + } + var v1 = ProjectionStack.CurrentViewport; + + float X, Y; + + X = (input.X - v2.Value.X) * ((float)v1.Value.Width / v2.Value.Width); + Y = (input.Y - v2.Value.Y) * ((float)v1.Value.Height / v2.Value.Height); + + return new Vector2(X, Y); + } + + private float TransformWidthToViewport(float input) + { + + var v2 = Options.TransformToViewport; + if (v2 == null) + { + return input; + } + var v1 = ProjectionStack.CurrentViewport; + + return input * ((float)v1.Value.Width / v2.Value.Width); + } + + private SizeF TransformMeasureFromViewport(SizeF input) + { + + var v2 = Options.TransformToViewport; + if (v2 == null) + { + return input; + } + var v1 = ProjectionStack.CurrentViewport; + + float X, Y; + + X = input.Width * ((float)v2.Value.Width / v1.Value.Width); + Y = input.Height * ((float)v2.Value.Height / v1.Value.Height); + + return new SizeF(X, Y); + } + + private Vector2 LockToPixel(Vector2 input) + { + if (Options.LockToPixel) + { + float r = Options.LockToPixelRatio; + return new Vector2((1 - r) * input.X + r * ((int)Math.Round(input.X)), (1 - r) * input.Y + r * ((int)Math.Round(input.Y))); + } + return input; + } + + + + public void Print(ProcessedText processedText, Vector2 position) + { + position = TransformPositionToViewport(position); + position = LockToPixel(position); + + GL.PushMatrix(); + GL.Translate(position.X,position.Y,0f); + Print(processedText); + GL.PopMatrix(); + } + + public void Print(string text, float maxWidth, QFontAlignment alignment, Vector2 position) + { + position = TransformPositionToViewport(position); + position = LockToPixel(position); + + GL.PushMatrix(); + GL.Translate(position.X, position.Y, 0f); + Print(text, maxWidth, alignment); + GL.PopMatrix(); + } + + + public void Print(string text, Vector2 position) + { + position = TransformPositionToViewport(position); + position = LockToPixel(position); + + GL.PushMatrix(); + GL.Translate(position.X, position.Y, 0f); + Print(text); + GL.PopMatrix(); + } + + public void Print(string text, QFontAlignment alignment, Vector2 position) + { + position = TransformPositionToViewport(position); + position = LockToPixel(position); + + GL.PushMatrix(); + GL.Translate(position.X, position.Y, 0f); + Print(text, alignment); + GL.PopMatrix(); + } + + + public void Print(string text) + { + Print(text, QFontAlignment.Left); + } + + + + public void Print(string text, QFontAlignment alignment) + { + PrintOrMeasure(text, alignment, false); + } + + + public SizeF Measure(string text) + { + return Measure(text, QFontAlignment.Left); + } + + public SizeF Measure(string text, QFontAlignment alignment) + { + return TransformMeasureFromViewport(PrintOrMeasure(text, alignment, true)); + } + + /// + /// Measures the actual width and height of the block of text. + /// + /// + /// + /// + /// + public SizeF Measure(string text, float maxWidth, QFontAlignment alignment) + { + var processedText = ProcessText(text, maxWidth, alignment); + return Measure(processedText); + } + + /// + /// Measures the actual width and height of the block of text + /// + /// + /// + public SizeF Measure(ProcessedText processedText) + { + return TransformMeasureFromViewport(PrintOrMeasure(processedText, true)); + } + + + private SizeF PrintOrMeasure(string text, QFontAlignment alignment, bool measureOnly) + { + var popRequired = false; + if (!measureOnly && !ProjectionStack.Begun && Options.TransformToViewport != null) + { + GL.PushMatrix(); + popRequired = true; + GL.Scale(1 / fontData.scaleDueToTransformToViewport, 1 / fontData.scaleDueToTransformToViewport, 0); + } + + + float maxXpos = float.MinValue; + float minXPos = float.MaxValue; + + if (!measureOnly) + { + GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); + GL.Enable(EnableCap.Texture2D); + GL.Enable(EnableCap.Blend); + + if (Options.UseDefaultBlendFunction) + { + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.One); + } + + } + + float xOffset = 0f; + float yOffset = 0f; + + text = text.Replace("\r\n", "\r"); + + if (alignment == QFontAlignment.Right) + xOffset -= MeasureNextlineLength(text); + else if (alignment == QFontAlignment.Centre) + xOffset -= (int)(0.5f * MeasureNextlineLength(text)); + + for(int i = 0; i < text.Length; i++) + { + char c = text[i]; + + + //newline + if (c == '\r' || c == '\n') + { + yOffset += LineSpacing; + xOffset = 0f; + + if (alignment == QFontAlignment.Right) + xOffset -= MeasureNextlineLength(text.Substring(i + 1)); + else if (alignment == QFontAlignment.Centre) + xOffset -= (int)(0.5f * MeasureNextlineLength(text.Substring(i + 1))); + + } + else + { + + minXPos = Math.Min(xOffset, minXPos); + + //normal character + if (c != ' ' && fontData.CharSetMapping.ContainsKey(c)) + { + QFontGlyph glyph = fontData.CharSetMapping[c]; + if(!measureOnly) + RenderGlyph(xOffset, yOffset, c, false); + } + + + if (IsMonospacingActive) + xOffset += MonoSpaceWidth; + else + { + if (c == ' ') + xOffset += (float)Math.Ceiling(fontData.meanGlyphWidth * Options.WordSpacing); + //normal character + else if (fontData.CharSetMapping.ContainsKey(c)) + { + QFontGlyph glyph = fontData.CharSetMapping[c]; + xOffset += (float)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, text, null)); + } + } + + maxXpos = Math.Max(xOffset, maxXpos); + } + } + + float maxWidth = 0f; + + if (minXPos != float.MaxValue) + maxWidth = maxXpos - minXPos; + + + if (popRequired) + GL.PopMatrix(); + + GL.Disable(EnableCap.Texture2D); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + + return new SizeF(maxWidth, yOffset + LineSpacing); + } + + + + + + + + + private void RenderWord(float x, float y, TextNode node) + { + + if (node.Type != TextNodeType.Word) + return; + + int charGaps = node.Text.Length - 1; + bool isCrumbleWord = CrumbledWord(node); + if (isCrumbleWord) + charGaps++; + + int pixelsPerGap = 0; + int leftOverPixels = 0; + + if (charGaps != 0) + { + pixelsPerGap = (int)node.LengthTweak / charGaps; + leftOverPixels = (int)node.LengthTweak - pixelsPerGap * charGaps; + } + + for(int i = 0; i < node.Text.Length; i++){ + char c = node.Text[i]; + if(fontData.CharSetMapping.ContainsKey(c)){ + var glyph = fontData.CharSetMapping[c]; + + RenderGlyph(x,y,c, false); + + + if (IsMonospacingActive) + x += MonoSpaceWidth; + else + x += (int)Math.Ceiling(glyph.rect.Width + fontData.meanGlyphWidth * Options.CharacterSpacing + fontData.GetKerningPairCorrection(i, node.Text, node)); + + x += pixelsPerGap; + if (leftOverPixels > 0) + { + x += 1.0f; + leftOverPixels--; + } + else if (leftOverPixels < 0) + { + x -= 1.0f; + leftOverPixels++; + } + + + } + } + } + + + + + + + /// + /// Computes the length of the next line, and whether the line is valid for + /// justification. + /// + /// + /// + /// + /// + private float TextNodeLineLength(TextNode node, float maxLength) + { + + if (node == null) + return 0; + + bool atLeastOneNodeCosumedOnLine = false; + float length = 0; + for (; node != null; node = node.Next) + { + + if (node.Type == TextNodeType.LineBreak) + break; + + if (SkipTrailingSpace(node, length, maxLength) && atLeastOneNodeCosumedOnLine) + break; + + if (length + node.Length <= maxLength || !atLeastOneNodeCosumedOnLine) + { + atLeastOneNodeCosumedOnLine = true; + length += node.Length; + } + else + { + break; + } + + + } + return length; + } + + + private bool CrumbledWord(TextNode node) + { + return (node.Type == TextNodeType.Word && node.Next != null && node.Next.Type == TextNodeType.Word); + } + + + /// + /// Computes the length of the next line, and whether the line is valid for + /// justification. + /// + private void JustifyLine(TextNode node, float targetLength) + { + + bool justifiable = false; + + if (node == null) + return; + + var headNode = node; //keep track of the head node + + + //start by finding the length of the block of text that we know will actually fit: + + int charGaps = 0; + int spaceGaps = 0; + + bool atLeastOneNodeCosumedOnLine = false; + float length = 0; + var expandEndNode = node; //the node at the end of the smaller list (before adding additional word) + for (; node != null; node = node.Next) + { + + + + if (node.Type == TextNodeType.LineBreak) + break; + + if (SkipTrailingSpace(node, length, targetLength) && atLeastOneNodeCosumedOnLine) + { + justifiable = true; + break; + } + + if (length + node.Length < targetLength || !atLeastOneNodeCosumedOnLine) + { + + expandEndNode = node; + + if (node.Type == TextNodeType.Space) + spaceGaps++; + + if (node.Type == TextNodeType.Word) + { + charGaps += (node.Text.Length - 1); + + //word was part of a crumbled word, so there's an extra char cap between the two words + if (CrumbledWord(node)) + charGaps++; + + } + + atLeastOneNodeCosumedOnLine = true; + length += node.Length; + } + else + { + justifiable = true; + break; + } + + + + } + + + //now we check how much additional length is added by adding an additional word to the line + float extraLength = 0f; + int extraSpaceGaps = 0; + int extraCharGaps = 0; + bool contractPossible = false; + TextNode contractEndNode = null; + for (node = expandEndNode.Next; node != null; node = node.Next) + { + + + if (node.Type == TextNodeType.LineBreak) + break; + + if (node.Type == TextNodeType.Space) + { + extraLength += node.Length; + extraSpaceGaps++; + } + else if (node.Type == TextNodeType.Word) + { + contractEndNode = node; + contractPossible = true; + extraLength += node.Length; + extraCharGaps += (node.Text.Length - 1); + break; + } + } + + + + if (justifiable) + { + + //last part of this condition is to ensure that the full contraction is possible (it is all or nothing with contractions, since it looks really bad if we don't manage the full) + bool contract = contractPossible && (extraLength + length - targetLength) * Options.JustifyContractionPenalty < (targetLength - length) && + ((targetLength - (length + extraLength + 1)) / targetLength > -Options.JustifyCapContract); + + if((!contract && length < targetLength) || (contract && length + extraLength > targetLength)) //calculate padding pixels per word and char + { + + if (contract) + { + length += extraLength + 1; + charGaps += extraCharGaps; + spaceGaps += extraSpaceGaps; + } + + + + int totalPixels = (int)(targetLength - length); //the total number of pixels that need to be added to line to justify it + int spacePixels = 0; //number of pixels to spread out amongst spaces + int charPixels = 0; //number of pixels to spread out amongst char gaps + + + + + + if (contract) + { + + if (totalPixels / targetLength < -Options.JustifyCapContract) + totalPixels = (int)(-Options.JustifyCapContract * targetLength); + } + else + { + if (totalPixels / targetLength > Options.JustifyCapExpand) + totalPixels = (int)(Options.JustifyCapExpand * targetLength); + } + + + //work out how to spread pixles between character gaps and word spaces + if (charGaps == 0) + { + spacePixels = totalPixels; + } + else if (spaceGaps == 0) + { + charPixels = totalPixels; + } + else + { + + if(contract) + charPixels = (int)(totalPixels * Options.JustifyCharacterWeightForContract * charGaps / spaceGaps); + else + charPixels = (int)(totalPixels * Options.JustifyCharacterWeightForExpand * charGaps / spaceGaps); + + + if ((!contract && charPixels > totalPixels) || + (contract && charPixels < totalPixels) ) + charPixels = totalPixels; + + spacePixels = totalPixels - charPixels; + } + + + int pixelsPerChar = 0; //minimum number of pixels to add per char + int leftOverCharPixels = 0; //number of pixels remaining to only add for some chars + + if (charGaps != 0) + { + pixelsPerChar = charPixels / charGaps; + leftOverCharPixels = charPixels - pixelsPerChar * charGaps; + } + + + int pixelsPerSpace = 0; //minimum number of pixels to add per space + int leftOverSpacePixels = 0; //number of pixels remaining to only add for some spaces + + if (spaceGaps != 0) + { + pixelsPerSpace = spacePixels / spaceGaps; + leftOverSpacePixels = spacePixels - pixelsPerSpace * spaceGaps; + } + + //now actually iterate over all nodes and set tweaked length + for (node = headNode; node != null; node = node.Next) + { + + if (node.Type == TextNodeType.Space) + { + node.LengthTweak = pixelsPerSpace; + if (leftOverSpacePixels > 0) + { + node.LengthTweak += 1; + leftOverSpacePixels--; + } + else if (leftOverSpacePixels < 0) + { + node.LengthTweak -= 1; + leftOverSpacePixels++; + } + + + } + else if (node.Type == TextNodeType.Word) + { + int cGaps = (node.Text.Length - 1); + if (CrumbledWord(node)) + cGaps++; + + node.LengthTweak = cGaps * pixelsPerChar; + + + if (leftOverCharPixels >= cGaps) + { + node.LengthTweak += cGaps; + leftOverCharPixels -= cGaps; + } + else if (leftOverCharPixels <= -cGaps) + { + node.LengthTweak -= cGaps; + leftOverCharPixels += cGaps; + } + else + { + node.LengthTweak += leftOverCharPixels; + leftOverCharPixels = 0; + } + } + + if ((!contract && node == expandEndNode) || (contract && node == contractEndNode)) + break; + + } + + } + + } + + + } + + + /// + /// Checks whether to skip trailing space on line because the next word does not + /// fit. + /// + /// We only check one space - the assumption is that if there is more than one, + /// it is a deliberate attempt to insert spaces. + /// + /// + /// + /// + /// + private bool SkipTrailingSpace(TextNode node, float lengthSoFar, float boundWidth) + { + + if (node.Type == TextNodeType.Space && node.Next != null && node.Next.Type == TextNodeType.Word && node.ModifiedLength + node.Next.ModifiedLength + lengthSoFar > boundWidth) + { + return true; + } + + return false; + + } + + + + + + /// + /// Prints text inside the given bounds. + /// + /// + /// + /// + public void Print(string text, float maxWidth, QFontAlignment alignment) + { + var processedText = ProcessText(text, maxWidth, alignment); + Print(processedText); + } + + + + + + /// + /// Creates node list object associated with the text. + /// + /// + /// + /// + public ProcessedText ProcessText(string text, float maxWidth, QFontAlignment alignment) + { + //TODO: bring justify and alignment calculations in here + + maxWidth = TransformWidthToViewport(maxWidth); + + var nodeList = new TextNodeList(text); + nodeList.MeasureNodes(fontData, Options); + + //we "crumble" words that are two long so that that can be split up + var nodesToCrumble = new List(); + foreach (TextNode node in nodeList) + if (node.Length >= maxWidth && node.Type == TextNodeType.Word) + nodesToCrumble.Add(node); + + foreach (var node in nodesToCrumble) + nodeList.Crumble(node, 1); + + //need to measure crumbled words + nodeList.MeasureNodes(fontData, Options); + + + var processedText = new ProcessedText(); + processedText.textNodeList = nodeList; + processedText.maxWidth = maxWidth; + processedText.alignment = alignment; + + + return processedText; + } + + + + + /// + /// Prints text as previously processed with a boundary and alignment. + /// + /// + public void Print(ProcessedText processedText) + { + PrintOrMeasure(processedText, false); + } + + + + private SizeF PrintOrMeasure(ProcessedText processedText, bool measureOnly) + { + + var popRequired = false; + if (!measureOnly && !ProjectionStack.Begun && Options.TransformToViewport != null) + { + GL.PushMatrix(); + popRequired = true; + GL.Scale(1 / fontData.scaleDueToTransformToViewport, 1 / fontData.scaleDueToTransformToViewport, 0); + } + + float maxMeasuredWidth = 0f; + + if (!measureOnly) + { + GL.Color4(1.0f, 1.0f, 1.0f, 1.0f); + GL.Enable(EnableCap.Texture2D); + GL.Enable(EnableCap.Blend); + + + if (Options.UseDefaultBlendFunction) + { + + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + + } + } + + + float maxWidth = processedText.maxWidth; + var alignment = processedText.alignment; + + + //TODO - use these instead of translate when rendering by position (at some point) + float xPos = 0f; + float yPos = 0f; + + + float xOffset = xPos; + float yOffset = yPos; + + var nodeList = processedText.textNodeList; + for (TextNode node = nodeList.Head; node != null; node = node.Next) + node.LengthTweak = 0f; //reset tweaks + + + if (alignment == QFontAlignment.Right) + xOffset -= (float)Math.Ceiling(TextNodeLineLength(nodeList.Head, maxWidth) - maxWidth); + else if (alignment == QFontAlignment.Centre) + xOffset -= (float)Math.Ceiling(0.5f * TextNodeLineLength(nodeList.Head, maxWidth) ); + else if (alignment == QFontAlignment.Justify) + JustifyLine(nodeList.Head, maxWidth); + + + + + bool atLeastOneNodeCosumedOnLine = false; + float length = 0f; + for (TextNode node = nodeList.Head; node != null; node = node.Next) + { + bool newLine = false; + + if (node.Type == TextNodeType.LineBreak) + { + newLine = true; + } + else + { + + if (SkipTrailingSpace(node, length, maxWidth) && atLeastOneNodeCosumedOnLine) + { + newLine = true; + } + else if (length + node.ModifiedLength <= maxWidth || !atLeastOneNodeCosumedOnLine) + { + atLeastOneNodeCosumedOnLine = true; + if(!measureOnly) + RenderWord(xOffset + length, yOffset, node); + length += node.ModifiedLength; + + maxMeasuredWidth = Math.Max(length, maxMeasuredWidth); + + } + else + { + newLine = true; + if (node.Previous != null) + node = node.Previous; + } + + } + + if (newLine) + { + + yOffset += LineSpacing; + xOffset = xPos; + length = 0f; + atLeastOneNodeCosumedOnLine = false; + + if (node.Next != null) + { + if (alignment == QFontAlignment.Right) + xOffset -= (float)Math.Ceiling(TextNodeLineLength(node.Next, maxWidth) - maxWidth); + else if (alignment == QFontAlignment.Centre) + xOffset -= (float)Math.Ceiling(0.5f * TextNodeLineLength(node.Next, maxWidth) ); + else if (alignment == QFontAlignment.Justify) + JustifyLine(node.Next, maxWidth); + } + } + + } + + + if (popRequired) + GL.PopMatrix(); + + GL.Disable(EnableCap.Texture2D); + return new SizeF(maxMeasuredWidth, yOffset + LineSpacing - yPos); + + } + + + /* + public void Begin() + { + ProjectionStack.Begin(); + } + + public void End() + { + ProjectionStack.End(); + }*/ + + + public static void Begin() + { + ProjectionStack.Begin(); + } + + public static void End() + { + ProjectionStack.End(); + } + + /// + /// Invalidates the internally cached viewport, causing it to be + /// reread the next time it is required. This should be called + /// if the viewport and text is to be rendered to the new + /// viewport. + /// + public static void InvalidateViewport() + { + ProjectionStack.InvalidateViewport(); + } + + /// + /// Forces the current viewport used by QFont to be read + /// from "hardware" + /// + public static void ForceViewportRefresh() + { + ProjectionStack.UpdateCurrentViewportFromHardware(); + } + + /// + /// Use a new viewport. This is more efficient + /// than calling ForceViewportRefresh() or InvalidateViewport() + /// + /// + public static void PushSoftwareViewport(Viewport viewport) + { + ProjectionStack.PushSoftwareViewport(viewport); + } + + /// + /// Pops the last pushed viewport, returning + /// to the previous viewport in use + /// + public static void PopSoftwareViewport() + { + ProjectionStack.PopSoftwareViewport(); + } + + /// + /// Dispose of the QFont data. + /// + public virtual void Dispose() + { + fontData.Dispose(); + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontData.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontData.cs new file mode 100644 index 00000000..9bef73a2 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontData.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace QuickFont +{ + class QFontData : IDisposable + { + + /// + /// Mapping from a pair of characters to a pixel offset + /// + public Dictionary KerningPairs; + + /// + /// List of texture pages + /// + public TexturePage[] Pages; + + /// + /// Mapping from character to glyph index + /// + public Dictionary CharSetMapping; + + /// + /// The average glyph width + /// + public float meanGlyphWidth; + + /// + /// The maximum glyph height + /// + public int maxGlyphHeight; + + /// + /// Null if no dropShadow is available + /// + public QFont dropShadow; + + /// + /// Whether the original font (from ttf) was detected to be monospaced + /// + public bool naturallyMonospaced = false; + + /// + /// The font scaling due to the font being transformed to the + /// current viewport for consistent pixel-perfect size across + /// any resolution + /// + public float scaleDueToTransformToViewport = 1.0f; + + public bool IsMonospacingActive(QFontRenderOptions options) + { + return (options.Monospacing == QFontMonospacing.Natural && naturallyMonospaced) || options.Monospacing == QFontMonospacing.Yes; + } + + + + public float GetMonoSpaceWidth(QFontRenderOptions options) + { + return (float)Math.Ceiling(1 + (1 + options.CharacterSpacing) * meanGlyphWidth); + } + + + + + public List Serialize() + { + var data = new List(); + + + data.Add("" + Pages.Length); + data.Add("" + CharSetMapping.Count); + + foreach (var glyphChar in CharSetMapping) + { + var chr = glyphChar.Key; + var glyph = glyphChar.Value; + + data.Add("" + chr + " " + + glyph.page + " " + + glyph.rect.X + " " + + glyph.rect.Y + " " + + glyph.rect.Width + " " + + glyph.rect.Height + " " + + glyph.yOffset); + } + return data; + } + + public void Deserialize(List input, out int pageCount, out char[] charSet) + { + CharSetMapping = new Dictionary(); + var charSetList = new List(); + + try + { + pageCount = int.Parse(input[0]); + int glyphCount = int.Parse(input[1]); + + for (int i = 0; i < glyphCount; i++) + { + var vals = input[2 + i].Split(' '); + var glyph = new QFontGlyph(int.Parse(vals[1]), new Rectangle(int.Parse(vals[2]), int.Parse(vals[3]), int.Parse(vals[4]), int.Parse(vals[5])), int.Parse(vals[6]), vals[0][0]); + + CharSetMapping.Add(vals[0][0], glyph); + charSetList.Add(vals[0][0]); + } + + + } + catch (Exception e) + { + throw new Exception("Failed to parse qfont file. Invalid format.",e); + } + + charSet = charSetList.ToArray(); + + } + + public void CalculateMeanWidth() + { + meanGlyphWidth = 0f; + foreach (var glyph in CharSetMapping) + meanGlyphWidth += glyph.Value.rect.Width; + + meanGlyphWidth /= CharSetMapping.Count; + + } + + + public void CalculateMaxHeight() + { + maxGlyphHeight = 0; + foreach (var glyph in CharSetMapping) + maxGlyphHeight = Math.Max(glyph.Value.rect.Height, maxGlyphHeight); + + } + + + /// + /// Returns the kerning length correction for the character at the given index in the given string. + /// Also, if the text is part of a textNode list, the nextNode is given so that the following + /// node can be checked incase of two adjacent word nodes. + /// + /// + /// + /// + /// + public int GetKerningPairCorrection(int index, string text, TextNode textNode) + { + if (KerningPairs == null) + return 0; + + + var chars = new char[2]; + + if (index + 1 == text.Length) + { + if (textNode != null && textNode.Next != null && textNode.Next.Type == TextNodeType.Word) + chars[1] = textNode.Next.Text[0]; + else + return 0; + } + else + { + chars[1] = text[index + 1]; + } + + chars[0] = text[index]; + + String str = new String(chars); + + + if (KerningPairs.ContainsKey(str)) + return KerningPairs[str]; + + return 0; + + } + + + public virtual void Dispose() + { + foreach (var page in Pages) + page.Dispose(); + } + + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontGlyph.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontGlyph.cs new file mode 100644 index 00000000..f13dd019 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontGlyph.cs @@ -0,0 +1,36 @@ +using System.Drawing; + +namespace QuickFont +{ + public class QFontGlyph + { + + /// + /// Which texture page the glyph is on + /// + public int page; + + /// + /// The rectangle defining the glyphs position on the page + /// + public Rectangle rect; + + /// + /// How far the glyph would need to be vertically offset to be vertically in line with the tallest glyph in the set of all glyphs + /// + public int yOffset; + + /// + /// Which character this glyph represents + /// + public char character; + + public QFontGlyph(int page, Rectangle rect, int yOffset, char character) + { + this.page = page; + this.rect = rect; + this.yOffset = yOffset; + this.character = character; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontRenderOptions.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontRenderOptions.cs new file mode 100644 index 00000000..1a400ca6 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/QFontRenderOptions.cs @@ -0,0 +1,257 @@ +using OpenTK; +using OpenTK.Graphics; +using System.Drawing; + +namespace QuickFont +{ + public enum QFontAlignment { Left=0, Right, Centre, Justify } + public enum QFontMonospacing { Natural = 0, Yes, No } + + public class QFontRenderOptions + { + /// + /// The font colour + /// + public Color4 Colour = Color.FromArgb(255,255,255,255); + + /// + /// Spacing between characters in units of average glyph width + /// + public float CharacterSpacing = 0.05f; + + /// + /// Spacing between words in units of average glyph width + /// + public float WordSpacing = 0.9f; + + /// + /// Line spacing in units of max glyph width + /// + public float LineSpacing = 1.0f; + + + /// + /// Whether to draw a drop-shadow. Note: this requires + /// the QFont to have been loaded with a drop shadow to + /// take effect. + /// + public bool DropShadowActive = false; + + /// + /// Offset of the shadow from the font glyphs in units of average glyph width + /// + public Vector2 DropShadowOffset = new Vector2(0.16f, 0.16f); + + /// + /// Opacity of drop shadows + /// + public float DropShadowOpacity = 0.5f; + + + /// + /// Whether to render the font in monospaced mode. If set to "Natural", then + /// monospacing will be used if the font loaded font was detected to be monospaced. + /// + public QFontMonospacing Monospacing = QFontMonospacing.Natural; + + + /// + /// This is intended as a means of rendering text pixel-perfectly at a + /// fixed display size (size on screen) independent of the screen resolution. + /// + /// Ordinarily it is possible to render pixel-perfect text by calling + /// QFont.Begin() / QFont.End(); however, this means working in a coordinate + /// system corresponding to the current screen resolution. If the screen + /// resolution changes, then the display size of the font will change + /// accordingly which may not be desirable. Many games/applications prefer + /// to use a fixed orthog coordinate system that is independent of screen + /// resolution (e.g. 1000x1000) so that when the screen resolution changes, + /// everything is still the same size on screen, it simply has higher + /// definition - which is what this setting supports. + /// + /// One option is simply not to call QFont.Begin() / QFont.End(). This + /// works; however, it becomes impossible to assure that glyphs are + /// rendered pixel-perfectly. Instead they will be scaled in hardware. + /// In most cases this looks fine; however, if you are a perfectionist, + /// you may prefer to use this option to assure pixel perfection. + /// + /// Setting this option does two things: + /// + /// Rendering to a specified position is transformed + /// Measurements are transformed + /// + /// So for example, suppose the screen resolution is 1024x768, but you + /// wish to run orthog mode at 1000x1000. If you set: + /// + /// myFont.Options.TransformToViewport = new Viewport(0,0,1000,1000); + /// + /// Then, if you render at position 500,500: + /// + /// QFont.Begin(); + /// myFont.Options.LockToPixel = true; + /// myFont.Print("Hello",new Vector2(500,500)); + /// QFont.End(); + /// + /// This will be printed pixel-pefectly at pixel position 512, 384. + /// + /// Additionally the font will be measured in terms of your 500x500 + /// coordinate system. + /// + /// The only issue is that if you change the resolution, the size of your + /// font will change. You can get around this by loading a font size + /// that is proportional to the screen resolution. This makes sense: + /// if you want a font to be rendered pixel-perfectly at a higher + /// resolution, it will need to be a larger font. At present this + /// needs to be doen manually. E.g: + /// + /// float fontScale = (float)Height / 800; + /// compyFontSmall = new QFont("Data/comfy.ttf", 14 * fontScale); + /// + /// + /// + public TransformViewport? TransformToViewport = null; + + /// + /// Locks the position to a particular pixel, allowing the text to be rendered pixel-perfectly. + /// You need to turn this off if you wish to move text around the screen smoothly by fractions + /// of a pixel. + /// + public bool LockToPixel; + + /// + /// Only applies when LockToPixel is true: + /// This is used to transition smoothly between being locked to pixels and not + /// + public float LockToPixelRatio = 1.0f; + + /// + /// Whether to always set : + /// GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.DstAlpha); + /// before rendering text. + /// + /// + public bool UseDefaultBlendFunction = true; + + + + + #region Justify Options + + /// + /// When a line of text is justified, space may be inserted between + /// characters, and between words. + /// + /// This parameter determines how this choice is weighted: + /// + /// 0.0f => word spacing only + /// 1.0f => "fairly" distributed between both + /// > 1.0 => in favour of character spacing + /// + /// This applies to expansions only. + /// + /// + public float JustifyCharacterWeightForExpand + { + get { return justifyCharWeightForExpand; } + set { + + justifyCharWeightForExpand = value; + + if (justifyCharWeightForExpand < 0f) + justifyCharWeightForExpand = 0f; + else if (justifyCharWeightForExpand > 1.0f) + justifyCharWeightForExpand = 1.0f; + } + } + + private float justifyCharWeightForExpand = 0.5f; + + + /// + /// When a line of text is justified, space may be removed between + /// characters, and between words. + /// + /// This parameter determines how this choice is weighted: + /// + /// 0.0f => word spacing only + /// 1.0f => "fairly" distributed between both + /// > 1.0 => in favour of character spacing + /// + /// This applies to contractions only. + /// + /// + public float JustifyCharacterWeightForContract + { + get { return justifyCharWeightForContract; } + set + { + + justifyCharWeightForContract = value; + + if (justifyCharWeightForContract < 0f) + justifyCharWeightForContract = 0f; + else if (justifyCharWeightForContract > 1.0f) + justifyCharWeightForContract = 1.0f; + } + } + + private float justifyCharWeightForContract = 0.2f; + + + + /// + /// Total justification cap as a fraction of the boundary width. + /// + public float JustifyCapExpand = 0.5f; + + + /// + /// Total justification cap as a fraction of the boundary width. + /// + public float JustifyCapContract = 0.1f; + + /// + /// By what factor justification is penalized for being negative. + /// + /// (e.g. if this is set to 3, then a contraction will only happen + /// over an expansion if it is 3 of fewer times smaller than the + /// expansion). + /// + /// + /// + public float JustifyContractionPenalty = 2; + + + #endregion + + + + public QFontRenderOptions CreateClone() + { + var clone = new QFontRenderOptions(); + + clone.Colour = Colour; + clone.CharacterSpacing = CharacterSpacing; + clone.WordSpacing = WordSpacing; + clone.LineSpacing = LineSpacing; + clone.DropShadowActive = DropShadowActive; + clone.DropShadowOffset = DropShadowOffset; + clone.DropShadowOpacity = DropShadowOpacity; + clone.Monospacing = Monospacing; + clone.TransformToViewport = TransformToViewport; + clone.LockToPixel = LockToPixel; + clone.LockToPixelRatio = LockToPixelRatio; + clone.UseDefaultBlendFunction = UseDefaultBlendFunction; + clone.JustifyCharacterWeightForExpand = JustifyCharacterWeightForExpand; + clone.justifyCharWeightForExpand = justifyCharWeightForExpand; + clone.JustifyCharacterWeightForContract = JustifyCharacterWeightForContract; + clone.justifyCharWeightForContract = justifyCharWeightForContract; + clone.JustifyCapExpand = JustifyCapExpand; + clone.JustifyCapContract = JustifyCapContract; + clone.JustifyContractionPenalty = JustifyContractionPenalty; + + return clone; + } + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TextNodeList.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TextNodeList.cs new file mode 100644 index 00000000..6db7e2a1 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TextNodeList.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections; +using System.Text; + +namespace QuickFont +{ + enum TextNodeType { Word, LineBreak, Space } + + class TextNode + { + public TextNodeType Type; + public string Text; + public float Length; //pixel length (without tweaks) + public float LengthTweak; //length tweak for justification + + public float ModifiedLength + { + get { return Length + LengthTweak; } + } + + public TextNode(TextNodeType Type, string Text){ + this.Type = Type; + this.Text = Text; + } + + public TextNode Next; + public TextNode Previous; + + } + + + /// + /// Class to hide TextNodeList and related classes from + /// user whilst allowing a textNodeList to be passed around. + /// + public class ProcessedText + { + internal TextNodeList textNodeList; + internal float maxWidth; + internal QFontAlignment alignment; + } + + + /// + /// A doubly linked list of text nodes + /// + class TextNodeList : IEnumerable + { + public TextNode Head; + public TextNode Tail; + + + + /// + /// Builds a doubly linked list of text nodes from the given input string + /// + /// + public TextNodeList(string text) + { + + + #region parse text + + text = text.Replace("\r\n", "\r"); + + bool wordInProgress = false; + StringBuilder currentWord = new StringBuilder(); + + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '\r' || text[i] == '\n' || text[i] == ' ') + { + if (wordInProgress) + { + Add(new TextNode(TextNodeType.Word, currentWord.ToString())); + wordInProgress = false; + } + + + + if (text[i] == '\r' || text[i] == '\n') + Add(new TextNode(TextNodeType.LineBreak, null)); + else if (text[i] == ' ') + Add(new TextNode(TextNodeType.Space, null)); + + } + else + { + if (!wordInProgress) + { + wordInProgress = true; + currentWord = new StringBuilder(); + } + + currentWord.Append(text[i]); + } + + } + + if (wordInProgress) + Add(new TextNode(TextNodeType.Word, currentWord.ToString())); + + + #endregion + + + + } + + public void MeasureNodes(QFontData fontData, QFontRenderOptions options){ + + foreach(TextNode node in this){ + if(node.Length == 0f) + node.Length = MeasureTextNodeLength(node,fontData,options); + } + } + + + + private float MeasureTextNodeLength(TextNode node, QFontData fontData, QFontRenderOptions options) + { + + bool monospaced = fontData.IsMonospacingActive(options); + float monospaceWidth = fontData.GetMonoSpaceWidth(options); + + if (node.Type == TextNodeType.Space) + { + if (monospaced) + return monospaceWidth; + + return (float)Math.Ceiling(fontData.meanGlyphWidth * options.WordSpacing); + } + + + float length = 0f; + if (node.Type == TextNodeType.Word) + { + + for (int i = 0; i < node.Text.Length; i++) + { + char c = node.Text[i]; + if (fontData.CharSetMapping.ContainsKey(c)) + { + if (monospaced) + length += monospaceWidth; + else + length += (float)Math.Ceiling(fontData.CharSetMapping[c].rect.Width + fontData.meanGlyphWidth * options.CharacterSpacing + fontData.GetKerningPairCorrection(i, node.Text, node)); + } + } + } + return length; + } + + + + /// + /// Splits a word into sub-words of size less than or equal to baseCaseSize + /// + /// + /// + public void Crumble(TextNode node, int baseCaseSize){ + + //base case + if(node.Text.Length <= baseCaseSize ) + return; + + var left = SplitNode(node); + var right = left.Next; + + Crumble(left,baseCaseSize); + Crumble(right,baseCaseSize); + + } + + + /// + /// Splits a word node in two, adding both new nodes to the list in sequence. + /// + /// + /// The first new node + public TextNode SplitNode(TextNode node) + { + if (node.Type != TextNodeType.Word) + throw new Exception("Cannot slit text node of type: " + node.Type); + + int midPoint = node.Text.Length / 2; + + string newFirstHalf = node.Text.Substring(0, midPoint); + string newSecondHalf = node.Text.Substring(midPoint, node.Text.Length - midPoint); + + + TextNode newFirst = new TextNode(TextNodeType.Word, newFirstHalf); + TextNode newSecond = new TextNode(TextNodeType.Word, newSecondHalf); + newFirst.Next = newSecond; + newSecond.Previous = newFirst; + + //node is head + if (node.Previous == null) + Head = newFirst; + else + { + node.Previous.Next = newFirst; + newFirst.Previous = node.Previous; + } + + //node is tail + if (node.Next == null) + Tail = newSecond; + else + { + node.Next.Previous = newSecond; + newSecond.Next = node.Next; + } + + return newFirst; + } + + + + + + public void Add(TextNode node){ + + //new node is head (and tail) + if(Head == null){ + Head = node; + Tail = node; + } else { + Tail.Next = node; + node.Previous = Tail; + Tail = node; + } + + } + + + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + + + // for (var node = Head; node.Next != null; node = node.Next) + + foreach(TextNode node in this) + { + if (node.Type == TextNodeType.Space) + builder.Append(" "); + if (node.Type == TextNodeType.LineBreak) + builder.Append(System.Environment.NewLine); + if (node.Type == TextNodeType.Word) + builder.Append("" + node.Text + ""); + + } + + return builder.ToString(); + } + + + + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return new TextNodeListEnumerator(this); + } + + #endregion + + + + + private class TextNodeListEnumerator : IEnumerator + { + private TextNode currentNode = null; + private TextNodeList targetList; + + public TextNodeListEnumerator(TextNodeList targetList) + { + this.targetList = targetList; + } + + public object Current + { + get { return currentNode; } + } + + public virtual bool MoveNext() + { + if (currentNode == null) + currentNode = targetList.Head; + else + currentNode = currentNode.Next; + return currentNode != null; + } + + public void Reset() + { + currentNode = null; + } + + public void Dispose() + { + + } + + } + + + } + + + + + +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TexturePage.cs b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TexturePage.cs new file mode 100644 index 00000000..fe240f48 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/QFont/TexturePage.cs @@ -0,0 +1,95 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Drawing.Imaging; + + +namespace QuickFont +{ + internal class TexturePage : IDisposable + { + int gLTexID; + int width; + int height; + + public int GLTexID { get { return gLTexID; } } + public int Width { get { return width; } } + public int Height { get { return height; } } + + + public TexturePage(string filePath) + { + var bitmap = new QBitmap(filePath); + CreateTexture(bitmap.bitmapData); + bitmap.Free(); + } + + public TexturePage(BitmapData dataSource) + { + CreateTexture(dataSource); + } + + + private void CreateTexture(BitmapData dataSource) + { + + width = dataSource.Width; + height = dataSource.Height; + + GL.Enable(EnableCap.Texture2D); + GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); + + GL.GenTextures(1, out gLTexID); + GL.BindTexture(TextureTarget.Texture2D, gLTexID); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, + OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, dataSource.Scan0); + } + + + + #region IDisposable + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + + //Note: finalizer NOT included. .Net runs finalizer on a separate thread, + //which means that it does not know about the OpenGL context. + /* + ~TexturePage() + { + // Finalizer calls Dispose(false) + Dispose(false); + }*/ + + // The bulk of the clean-up code is implemented in Dispose(bool) + private bool deletedTexture = false; + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + //dispose managed resources here - if there were any! + } + + //free managed resources here (if there were any!) + if (!deletedTexture) + { + GL.DeleteTexture(gLTexID); + deletedTexture = true; + } + } + + #endregion + + + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushAdd.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushAdd.cs new file mode 100644 index 00000000..9e44e131 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushAdd.cs @@ -0,0 +1,125 @@ +using OpenTK; +using OpenTK.Input; +using System.Collections.Generic; +using System; + +namespace stonevox +{ + public class BrushAdd : IVoxelBrush + { + ToolState state = ToolState.Start; + VoxelLocation startPosition; + VoxelLocation endPosition; + VoxelVolume volume; + VoxelVolume lastvolume; + QbMatrix lastmatrix; + Dictionary modifiedVoxels = new Dictionary(); + public MouseCursor Cursor { get; set; } + + public VoxelBrushType BrushType { get { return VoxelBrushType.Add; } } + + public bool Active { get; set; } + + public string CursorPath + { + get + { + return "./data/images/target_cursor.png"; + } + } + + public BrushAdd() + { + } + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + return true; + } + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + for (int z = currentVolume.minz; z <= currentVolume.maxz; z++) + for (int y = currentVolume.miny; y <= currentVolume.maxy; y++) + for (int x = currentVolume.minx; x <= currentVolume.maxx; x++) + { + if (!volume.ContainsPoint(x,y,z) && !modifiedVoxels.ContainsKey(matrix.GetHash(x,y,z))) + modifiedVoxels.Add(matrix.GetHash(x, y, z), new VoxelUndoData(matrix.Add(x, y, z, color))); + } + } + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (!currentVolume.ContainsPoint(x, y, z)) + { + if (modifiedVoxels[matrix.GetHash(x, y, z)].changed) + { + matrix.Remove(x, y, z, false, false); + modifiedVoxels.Remove(matrix.GetHash(x, y, z)); + } + } + } + } + + void CleanForToolReset() + { + RemoveVolume(volume, lastmatrix, modifiedVoxels); + modifiedVoxels.Clear(); + lastvolume = VoxelVolume.NEGATIVE_ZERO; + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + VoxelUndoData _temp; + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (modifiedVoxels.TryGetValue(matrix.GetHash(x, y, z), out _temp) && _temp.changed) + { + matrix.Remove(x, y, z, false, false); + modifiedVoxels.Remove(matrix.GetHash(x, y, z)); + } + } + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + double hash; + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + hash = matrix.GetHash(x, y, z); + if (!modifiedVoxels.ContainsKey(hash)) + modifiedVoxels.Add(hash, new VoxelUndoData(matrix.Add(x, y, z, color))); + } + } + + enum ToolState + { + Disabled, + Start, + Base, + Limit + } + + public void Enable() + { + Active = true; + } + + public void Disable() + { + Active = false; + } + + public void Dispose() + { + } + + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushColorSelection.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushColorSelection.cs new file mode 100644 index 00000000..4d2fa644 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushColorSelection.cs @@ -0,0 +1,109 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using System; +using System.Collections.Generic; + +namespace stonevox +{ + public class BrushColorSelection : IVoxelBrush + { + public bool Active { get; set; } + + public VoxelBrushType BrushType + { + get + { + return VoxelBrushType.ColorSelect; + } + } + + public MouseCursor Cursor + { + get; set; + } + + public string CursorPath + { + get + { + return "./data/images/cursor_eyedrop.png"; + } + } + + public BrushColorSelection() + { + Singleton.INSTANCE.AddHandler(new InputHandler() + { + Keydownhandler = (e) => + { + if (Singleton.INSTANCE.OverWidget) return; + if (!Active && e.Shift) + { + Singleton.INSTANCE.SetCurrentBrush(BrushType); + } + }, + Keyuphandler = (e) => + { + if (Singleton.INSTANCE.OverWidget) return; + + if (Active && (e.Key == Key.ShiftLeft || e.Key == Key.ShiftRight)) + { + var clientbrush = Singleton.INSTANCE; + clientbrush.SetCurrentBrush(clientbrush.previousBrush.BrushType); + } + } + }); + } + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + if ((e != null && e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mousedown(MouseButton.Left))) + { + QbMatrix mat = Singleton.INSTANCE.ActiveModel.matrices[hit.matrixIndex]; + if (mat != null) + { + Voxel voxel; + if (mat.voxels.TryGetValue(mat.GetHash(hit.x, hit.y, hit.z), out voxel)) + { + } + } + return true; + } + return false; + } + + public void Disable() + { + Active = false; + Singleton.INSTANCE.Mode = RaycastMode.ActiveMatrix; + } + + public void Enable() + { + Active = true; + Singleton.INSTANCE.Mode = RaycastMode.MatrixColorSelection; + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushMatrixSelection.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushMatrixSelection.cs new file mode 100644 index 00000000..83fd0191 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushMatrixSelection.cs @@ -0,0 +1,127 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using System; +using System.Collections.Generic; + +namespace stonevox +{ + public class BrushMatrixSelection : IVoxelBrush + { + public bool Active { get; set; } + + public VoxelBrushType BrushType + { + get + { + return VoxelBrushType.MatrixSelect; + } + } + + public MouseCursor Cursor + { + get; set; + } + + public string CursorPath + { + get + { + return "./data/images/target_cursor.png"; + } + } + + private QbMatrix lastmatrix; + + public BrushMatrixSelection() + { + Singleton.INSTANCE.AddHandler(new InputHandler() + { + Keydownhandler = (e) => + { + if (Singleton.INSTANCE.OverWidget) return; + if (!Active && e.Key == Key.Space) + { + Singleton.INSTANCE.SetCurrentBrush(BrushType); + } + }, + Keyuphandler = (e) => + { + if (Singleton.INSTANCE.OverWidget) return; + + if (Active && (e.Key == Key.Space || e.Key == Key.Space)) + { + var clientbrush = Singleton.INSTANCE; + clientbrush.SetCurrentBrush(clientbrush.previousBrush.BrushType); + if (lastmatrix != null) + { + Singleton.INSTANCE.ActiveMatrix = lastmatrix; + Singleton.INSTANCE.TransitionToMatrix(); + lastmatrix.highlight = Color4.White; + lastmatrix = null; + } + } + } + }); + } + + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + if (matrix == null) + { + if (lastmatrix != null) + { + lastmatrix.highlight = Color4.White; + lastmatrix = null; + } + return true; + } + + if(matrix != lastmatrix) + { + if (lastmatrix != null) + lastmatrix.highlight = Color4.White; + matrix.highlight = new Colort(1.5f, 1.5f, 1.5f); + + lastmatrix = matrix; + } + return true; + } + + public void Disable() + { + Active = false; + Singleton.INSTANCE.Mode = RaycastMode.ActiveMatrix; + Singleton.INSTANCE.Visible = true; + } + + public void Enable() + { + Active = true; + Singleton.INSTANCE.Mode = RaycastMode.MatrixSelection; + Singleton.INSTANCE.Visible = false; + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + throw new NotImplementedException(); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRecolor.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRecolor.cs new file mode 100644 index 00000000..094e9581 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRecolor.cs @@ -0,0 +1,202 @@ +using OpenTK; +using OpenTK.Input; +using System.Collections.Generic; + +namespace stonevox +{ + public class BrushRecolor : IVoxelBrush + { + ToolState state = ToolState.Start; + VoxelLocation startPosition; + VoxelLocation endPosition; + VoxelVolume volume; + VoxelVolume lastvolume; + QbMatrix lastmatrix; + Dictionary modifiedVoxels = new Dictionary(); + public MouseCursor Cursor { get; set; } + + + public VoxelBrushType BrushType { get { return VoxelBrushType.Recolor; } } + + public bool Active { get; set; } + + public string CursorPath + { + get + { + return "./data/images/cursor_paint.png"; + } + } + + public BrushRecolor() + { + Singleton.INSTANCE.AddHandler(new InputHandler() + { + mouseuphandler = (e) => + { + if (!Active) return; + + if (e.Button == MouseButton.Right && Singleton.INSTANCE.Keydown(Key.AltLeft) && state == ToolState.Base) + { + state = ToolState.Disabled; + if (Singleton.INSTANCE.mouseup(MouseButton.Left)) + { + CleanForToolReset(); + state = ToolState.Start; + } + } + + if (state == ToolState.Disabled && e.Button == MouseButton.Left) + { + state = ToolState.Start; + CleanForToolReset(); + } + } + }); + } + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + lastmatrix = matrix; + switch (state) + { + case ToolState.Start: + if ((e != null && e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mousedown(MouseButton.Left))) + { + state = ToolState.Base; + Singleton.INSTANCE.testdirt = true; + startPosition = new VoxelLocation(hit, false); + endPosition = new VoxelLocation(hit, false); + volume = new VoxelVolume(startPosition, endPosition); + modifiedVoxels.Clear(); + EnumerateVolume(volume, lastvolume, matrix, ref color, modifiedVoxels); + lastvolume = volume; + return true; + } + break; + case ToolState.Base: + if ((e != null && e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mousedown(MouseButton.Left))) + { + endPosition = new VoxelLocation(hit, false); + volume = new VoxelVolume(startPosition, endPosition); + + EnumerateVolume(volume, lastvolume, matrix, ref color, modifiedVoxels); + CleanLastVolume(lastvolume, volume, matrix, modifiedVoxels); + lastvolume = volume; + + return true; + } + else if ((e != null && !e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mouseup(MouseButton.Left))) + { + state = ToolState.Start; + lastvolume = VoxelVolume.NEGATIVE_ZERO; + Singleton.INSTANCE.AddUndo(BrushType, matrix, volume, color, modifiedVoxels); + return true; + } + break; + case ToolState.Limit: + break; + } + return false; + } + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + double hash; + Voxel voxel = null; + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + hash = matrix.GetHash(x, y, z); + + if (!modifiedVoxels.ContainsKey(hash) && matrix.voxels.TryGetValue(hash, out voxel) && voxel.alphamask > 1) + { + modifiedVoxels.Add(hash, new VoxelUndoData(matrix.voxels[hash].colorindex, 0)); + matrix.Color(x, y, z, color); + } + } + } + + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + double hash; + VoxelUndoData voxel = new VoxelUndoData(); + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (!currentVolume.ContainsPoint(x, y, z)) + { + hash = matrix.GetHash(x, y, z); + if (modifiedVoxels.TryGetValue(hash, out voxel)) + { + matrix.Color(x, y, z, voxel.colorindex, false, true); + modifiedVoxels.Remove(hash); + } + } + } + } + + void CleanForToolReset() + { + RemoveVolume(volume, lastmatrix, modifiedVoxels); + modifiedVoxels.Clear(); + lastvolume = VoxelVolume.NEGATIVE_ZERO; + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + double hash; + VoxelUndoData voxel = new VoxelUndoData(); + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + hash = matrix.GetHash(x, y, z); + if (modifiedVoxels.TryGetValue(hash, out voxel)) + { + matrix.Color(x, y, z, voxel.colorindex, false, true); + modifiedVoxels.Remove(hash); + } + } + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + double hash; + Voxel voxel = null; + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + hash = matrix.GetHash(x, y, z); + + if (!modifiedVoxels.ContainsKey(hash) && matrix.voxels.TryGetValue(hash, out voxel) && voxel.alphamask > 1) + { + modifiedVoxels.Add(hash, new VoxelUndoData(matrix.voxels[hash].colorindex, 0)); + matrix.Color(x, y, z, color); + } + } + } + + enum ToolState + { + Disabled, + Start, + Base, + Limit + } + + public void Enable() + { + Active = true; + } + + public void Disable() + { + Active = false; + } + + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRemove.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRemove.cs new file mode 100644 index 00000000..f01cd2c6 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushRemove.cs @@ -0,0 +1,203 @@ +using OpenTK; +using OpenTK.Input; +using System.Collections.Generic; + +namespace stonevox +{ + public class BrushRemove : IVoxelBrush + { + ToolState state = ToolState.Start; + VoxelLocation startPosition; + VoxelLocation endPosition; + VoxelVolume volume; + VoxelVolume lastvolume; + QbMatrix lastmatrix; + Dictionary modifiedVoxels = new Dictionary(); + public MouseCursor Cursor { get; set; } + + public VoxelBrushType BrushType { get { return VoxelBrushType.Remove; } } + + public bool Active { get; set; } + + public string CursorPath + { + get + { + return "./data/images/cursor_remove.png"; + } + } + + public BrushRemove() + { + Singleton.INSTANCE.AddHandler(new InputHandler() + { + mouseuphandler = new MouseHandler((MouseButtonEventArgs e) => + { + if (!Active) return; + + if (e.Button == MouseButton.Right && Singleton.INSTANCE.Keydown(Key.AltLeft) && state == ToolState.Base) + { + state = ToolState.Disabled; + if (Singleton.INSTANCE.mouseup(MouseButton.Left)) + { + CleanForToolReset(); + state = ToolState.Start; + } + } + + if (state == ToolState.Disabled && e.Button == MouseButton.Left) + { + state = ToolState.Start; + CleanForToolReset(); + } + }) + }); + } + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + lastmatrix = matrix; + switch (state) + { + case ToolState.Start: + if ((e != null && e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mousedown(MouseButton.Left))) + { + state = ToolState.Base; + Singleton.INSTANCE.testdirt = true; + startPosition = new VoxelLocation(hit, false); + endPosition = new VoxelLocation(hit, false); + volume = new VoxelVolume(startPosition, endPosition); + modifiedVoxels.Clear(); + EnumerateVolume(volume, lastvolume, matrix, ref color, modifiedVoxels); + lastvolume = volume; + return true; + } + break; + case ToolState.Base: + if ((e != null && e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mousedown(MouseButton.Left))) + { + endPosition = new VoxelLocation(hit, false); + volume = new VoxelVolume(startPosition, endPosition); + + EnumerateVolume(volume, lastvolume, matrix, ref color, modifiedVoxels); + CleanLastVolume(lastvolume, volume, matrix, modifiedVoxels); + lastvolume = volume; + + return true; + } + else if ((e != null && !e.IsPressed && e.Button == MouseButton.Left) || (e == null && input.mouseup(MouseButton.Left))) + + { + state = ToolState.Start; + lastvolume = VoxelVolume.NEGATIVE_ZERO; + Singleton.INSTANCE.AddUndo(BrushType, matrix, volume, color, modifiedVoxels); + return true; + } + break; + case ToolState.Limit: + break; + } + return false; + } + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + VoxelUndoData removed = new VoxelUndoData(); + + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (!modifiedVoxels.ContainsKey(matrix.GetHash(x, y, z))) + { + if (matrix.GetColorIndex_Alphamask(x, y, z, out removed.colorindex, out removed.alphamask)) + { + if (matrix.Remove(x, y, z, true, false)) + modifiedVoxels.Add(matrix.GetHash(x, y, z), removed); + } + } + } + } + + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + VoxelUndoData removed = new VoxelUndoData(); + + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (!currentVolume.ContainsPoint(x, y, z)) + { + if (modifiedVoxels.TryGetValue(matrix.GetHash(x, y, z), out removed)) + { + if (removed.alphamask > 1) + matrix.Add(x, y, z, matrix.colors[removed.colorindex]); + modifiedVoxels.Remove(matrix.GetHash(x, y, z)); + } + } + } + } + + void CleanForToolReset() + { + RemoveVolume(volume, lastmatrix, modifiedVoxels); + modifiedVoxels.Clear(); + lastvolume = VoxelVolume.NEGATIVE_ZERO; + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + VoxelUndoData removed = new VoxelUndoData(); + + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (modifiedVoxels.TryGetValue(matrix.GetHash(x, y, z), out removed)) + { + if (removed.alphamask > 1) + matrix.Add(x, y, z, matrix.colors[removed.colorindex]); + modifiedVoxels.Remove(matrix.GetHash(x, y, z)); + } + } + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + VoxelUndoData removed = new VoxelUndoData(); + + for (int z = volume.minz; z <= volume.maxz; z++) + for (int y = volume.miny; y <= volume.maxy; y++) + for (int x = volume.minx; x <= volume.maxx; x++) + { + if (!modifiedVoxels.ContainsKey(matrix.GetHash(x, y, z))) + { + if (matrix.GetColorIndex_Alphamask(x, y, z, out removed.colorindex, out removed.alphamask)) + { + if (matrix.Remove(x, y, z, true, false)) + modifiedVoxels.Add(matrix.GetHash(x, y, z), removed); + } + } + } + } + + enum ToolState + { + Disabled, + Start, + Base, + Limit + } + + public void Enable() + { + Active = true; + } + + public void Disable() + { + Active = false; + } + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushVoxelSelection.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushVoxelSelection.cs new file mode 100644 index 00000000..c27b41ed --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/BrushVoxelSelection.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenTK; +using OpenTK.Input; + +namespace stonevox +{ + public class BrushVoxelSelection : IVoxelBrush + { + private bool active; + + public bool Active + { + get + { + return active; + } + + set + { + active = value; + } + } + + public VoxelBrushType BrushType + { + get + { + return VoxelBrushType.Select; + } + } + + public MouseCursor Cursor + { + get; + set; + } + + public string CursorPath + { + get + { + return ""; + } + } + + public void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + } + + public void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels) + { + } + + public void Disable() + { + } + + public void Enable() + { + } + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + } + + public bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e) + { + return true; + } + + public void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels) + { + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/IVoxelBrush.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/IVoxelBrush.cs new file mode 100644 index 00000000..28e70ce7 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/IVoxelBrush.cs @@ -0,0 +1,25 @@ +using OpenTK; +using OpenTK.Input; +using System.Collections.Generic; + +namespace stonevox +{ + public interface IVoxelBrush + { + VoxelBrushType BrushType { get; } + bool Active { get; set; } + string CursorPath { get; } + MouseCursor Cursor { get; set; } + + void Enable(); + void Disable(); + + bool OnRaycastHitchanged(Input input, QbMatrix matrix, RaycastHit hit, ref Colort color, MouseButtonEventArgs e); + + void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels); + void CleanLastVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, Dictionary modifiedVoxels); + + void AddVolume(VoxelVolume volume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels); + void RemoveVolume(VoxelVolume volume, QbMatrix matrix, Dictionary modifiedVoxels); + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/brush/VoxelBrushType.cs b/MinecraftUSkinEditor/Classes/StoneVOX/brush/VoxelBrushType.cs new file mode 100644 index 00000000..a54d7710 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/brush/VoxelBrushType.cs @@ -0,0 +1,15 @@ +namespace stonevox +{ + public enum VoxelBrushType + { + Add, + Remove, + Recolor, + MatrixSelect, + ColorSelect, + Select + //Area_Fill, + //Area_Remove, + //Area_Color, + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/Broadcaster.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/Broadcaster.cs new file mode 100644 index 00000000..6477f668 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/Broadcaster.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; + +namespace stonevox +{ + public enum Message + { + WidgetEnable, + WidgetMouseEnter, + WidgetMouseLeave, + WidgetKeyPress, + WidgetFocus, + WidgetFocusLost, + WidgetStartTranslation, + WidgetEndTranslation, + ColorSelectionChanged, + ColorSelectionUpdate, + ColorSelectionCommit, + + TextboxTextCommited, + + WindowOpened, + WindowClosed, + + StatusStripUpdate, + + ModelImported, + ModelRemoved, + ModelRenamed, + ActiveMatrixChanged, + ActiveModelChanged, + + WidgetMouseDown = 100, + WidgetMouseDoubleClick = 101, + WidgetMouseUp = 102, + WidgetMouseScroll = 103, + WidgetMouseOver = 104, + WidgetMouseMove = 105 + } + + // allows widgets or others to handle the broadcast and stop it from carring further + public enum BroadcastMessageReturn + { + Funnel, + Stop + } + + public class BroadcastMessage + { + public Message messgae; + public Widget widget; + public object[] args; + + public bool HasWidget { get { return widget != null; } } + + public T Arg(int index) + { + return (T)args[index]; + } + } + + public delegate void BroadcastMessageHandler(Message message, Widget windget, object[] args); + + public class Broadcaster : Singleton + { + GUI gui; + + public List handlers; + + public Broadcaster() + : base() + { + handlers = new List(); + } + + public void SetGUI(GUI gui) + { + this.gui = gui; + } + + public void Broadcast(Message message, params object[] args) + { + Broadcast(message, null, args); + } + + public void Broadcast(Message message, Widget widget, params object[] args) + { + if ((int)message < 100) + gui.Dirty = true; + + BroadcastMessage m = new BroadcastMessage() + { + messgae = message, + widget = widget, + args = args + }; + + handlers.ForEach(t => t(message, widget, args)); + + foreach (var guiWidget in gui.widgets) + { + if (m.HasWidget) + { + if (guiWidget.ID != m.widget.ID) + guiWidget.HandleMessageRecieved(m); + } + else + guiWidget.HandleMessageRecieved(m); + } + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/BrushManager.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/BrushManager.cs new file mode 100644 index 00000000..65865920 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/BrushManager.cs @@ -0,0 +1,155 @@ +using OpenTK.Input; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace stonevox +{ + public class BrushManager : Singleton + { + public Colort brushColor = new Colort(.3f, .2f, .8f); + public IVoxelBrush currentBrush; + public IVoxelBrush previousBrush; + + public Dictionary brushes; + + private GLWindow window; + + public BrushManager(GLWindow window, Input input) + : base() + { + this.window = window; + + brushes = new Dictionary(); + brushes.Add(VoxelBrushType.Add, new BrushAdd()); + brushes.Add(VoxelBrushType.Remove, new BrushRemove()); + brushes.Add(VoxelBrushType.Recolor, new BrushRecolor()); + brushes.Add(VoxelBrushType.MatrixSelect, new BrushMatrixSelection()); + brushes.Add(VoxelBrushType.ColorSelect, new BrushColorSelection()); + brushes.Add(VoxelBrushType.Select, new BrushVoxelSelection()); + + input.AddHandler(new InputHandler() + { + Keydownhandler = (e) => + { + if (e.Key == Key.Tab) + { + NextBrush(); + } + + var gui = Singleton.INSTANCE; + + + if (e.Key == Key.B) + SetCurrentBrush(VoxelBrushType.Add); + else if (e.Key == Key.R) + SetCurrentBrush(VoxelBrushType.Recolor); + else if (e.Key == Key.F) + SetCurrentBrush(VoxelBrushType.Remove); + } + }); + + window.SVReizeEvent += (e, o) => + { + var values = Enum.GetValues(typeof(VoxelBrushType)); + var enumer = values.GetEnumerator(); + while (enumer.MoveNext()) + { + string path = brushes[(VoxelBrushType)enumer.Current].CursorPath; + + if (string.IsNullOrEmpty(path)) + { + brushes[(VoxelBrushType)enumer.Current].Cursor = OpenTK.MouseCursor.Default; + continue; + } + + Bitmap bitmap = new Bitmap(path); + + if (window.Width <= 1280) + bitmap = bitmap.ResizeImage(new Size((int)(bitmap.Width * .75f), (int)(bitmap.Height * .75f))); + else if (window.Width <= 1400) + bitmap = bitmap.ResizeImage(new Size((int)(bitmap.Width * .8f), (int)(bitmap.Height * .8f))); + + bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); + var data = bitmap.LockBits( + new Rectangle(0, 0, bitmap.Width, bitmap.Height), + System.Drawing.Imaging.ImageLockMode.ReadOnly, + System.Drawing.Imaging.PixelFormat.Format32bppPArgb); + + // super hacks + if ((VoxelBrushType)enumer.Current == VoxelBrushType.MatrixSelect) + brushes[(VoxelBrushType)enumer.Current].Cursor = new OpenTK.MouseCursor( + data.Width / 2, data.Height / 2, data.Width, data.Height, data.Scan0); + else + brushes[(VoxelBrushType)enumer.Current].Cursor = new OpenTK.MouseCursor( + 0, 0, data.Width, data.Height, data.Scan0); + + bitmap.Dispose(); + } + }; + + NextBrush(); + } + + public void SetCurrentBrush(VoxelBrushType type) + { + if (previousBrush != null) + { + currentBrush.Disable(); + + //super super hacks + if (currentBrush.BrushType != VoxelBrushType.Select || currentBrush.BrushType != VoxelBrushType.MatrixSelect || currentBrush.BrushType != VoxelBrushType.ColorSelect) + previousBrush = currentBrush; + } + else + { + if (currentBrush?.BrushType != VoxelBrushType.Select || currentBrush?.BrushType != VoxelBrushType.MatrixSelect || currentBrush?.BrushType != VoxelBrushType.MatrixSelect) + previousBrush = currentBrush; + } + currentBrush = brushes[type]; + currentBrush.Enable(); + + // ensure onselectionchanged is called when changing brushes + // even if the raycaster isn't over a new voxel + if (Singleton.INSTANCE != null) + Singleton.INSTANCE.lastHit = new RaycastHit() { distance = 10000 }; + + if (Singleton.INSTANCE?.OverWidget == false) + window.Cursor = currentBrush.Cursor; + } + + public void NextBrush() + { + var values = Enum.GetValues(typeof(VoxelBrushType)); + var enumer = values.GetEnumerator(); + enumer.MoveNext(); + VoxelBrushType first = (VoxelBrushType)enumer.Current; + + if (currentBrush != null) + { + do + { + if ((VoxelBrushType)enumer.Current == currentBrush.BrushType) + { + if (enumer.MoveNext()) + { + // kinda hacky but so is the matrix selection tool... + if ((VoxelBrushType)enumer.Current == VoxelBrushType.Select || (VoxelBrushType)enumer.Current == VoxelBrushType.MatrixSelect || currentBrush.BrushType == VoxelBrushType.MatrixSelect) + continue; + SetCurrentBrush((VoxelBrushType)enumer.Current); + return; + } + } + } while (enumer.MoveNext()); + } + + SetCurrentBrush(first); + } + + public bool onselectionchanged(Input input, QbMatrix matrix, RaycastHit hit, MouseButtonEventArgs e = null) + { + if (matrix != null && !matrix.Visible) return true; + return currentBrush.OnRaycastHitchanged(input, matrix, hit, ref brushColor, e); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/Camera.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/Camera.cs new file mode 100644 index 00000000..3f6fdc09 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/Camera.cs @@ -0,0 +1,396 @@ +using OpenTK; +using OpenTK.Input; +using System; + +namespace stonevox +{ + public class Camera : Singleton + { + public Vector3 position; + public Vector3 direction; + + public Matrix4 projection; + public Matrix4 view; + public Matrix4 modelviewprojection; + + private Input input; + private QbManager manager; + + public Vector3 cameraright { get { return Vector3.Cross(direction, VectorUtils.UP); } } + public Vector3 cameraup { get { return Vector3.Cross(cameraright, direction); } } + + float fov = 45f; + float nearPlane = 1f; + float farPlane = 300; + + bool dotransition; + Vector3 startpos; + Vector3 startdir; + Vector3 _goto; + Vector3 centerposition; + float time = 0; + + public bool freecam; + + public Camera(GLWindow window, Input input, QbManager manager) + : base() + { + this.input = input; + this.manager = manager; + + window.Resize += (e, s) => + { + projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(fov), (float)window.Width / (float)window.Height, nearPlane, farPlane); + }; + + position = new Vector3(0f, 0f, 10f); + direction = new Vector3(0f, 0f, 1f); + direction.Normalize(); + + projection = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(fov), (float)window.Width / (float)window.Height, nearPlane, farPlane); + view = Matrix4.LookAt(position, position + direction, VectorUtils.UP); + modelviewprojection = projection * view; + + InputHandler handler = new InputHandler() + { + Keydownhandler = (e) => + { + var gui = Singleton.INSTANCE; + + + //freecam is super interesting... but need a bit of work + //if (e.Key == Key.C) + //{ + // freecam = !freecam; + + // if (freecam) + // { + // Client.window.CursorVisible = false; + // } + // else + // { + // Client.window.CursorVisible = true; + // } + //} + }, + + + mousewheelhandler = (e) => + { + if (!Singleton.INSTANCE.OverWidget) + { + if (e.Delta < 0) + { + position -= direction * 6 * 1f; + } + else if (e.Delta > 0) + { + position += direction * 6 * 1f; + } + } + else if (Singleton.INSTANCE.lastWidgetOver.Drag) + { + if (e.Delta < 0) + { + position -= direction * 6 * 1f; + } + else if (e.Delta > 0) + { + position += direction * 6 * 1f; + } + } + } + }; + input.AddHandler(handler); + } + + public void update(float delta) + { + if (!Singleton.INSTANCE.OverWidget) + { + if (input.Keydown(Key.Q)) + position += Vector3.UnitY * -15f * delta; + if (input.Keydown(Key.E)) + position -= Vector3.UnitY * -15f * delta; + + if (input.Keydown(Key.W) || input.Keydown(Key.Up)) + { + position += direction * 25f * delta; + } + + if (input.Keydown(Key.S) || input.Keydown(Key.Down)) + { + position -= direction * 25f * delta; + } + + if (input.Keydown(Key.A) || input.Keydown(Key.Left)) + { + Vector3 camerar = cameraright; + + position.X -= camerar.X * 25f * delta; + position.Z -= camerar.Z * 25f * delta; + } + + if (input.Keydown(Key.D) || input.Keydown(Key.Right)) + { + Vector3 camerar = cameraright; + + position.X += camerar.X * 25f * delta; + position.Z += camerar.Z * 25f * delta; + } + } + + if (input.mousedown(MouseButton.Right) || freecam) + { + Vector3 camright = cameraright; + Vector3 camup = cameraup; + + float roty = (float)MathHelper.DegreesToRadians(-input.mousedx * .15f); + float rotx = (float)MathHelper.DegreesToRadians(-input.mousedy * .15f); + + if (Math.Abs(direction.Y) >= .95f && Math.Sign(rotx) == Math.Sign(direction.Y)) + { + camright.Normalize(); + camup.Normalize(); + + Vector3 focus = position - manager.ActiveMatrix.centerposition; + float length = focus.Length; + focus.Normalize(); + + focus = Vector3.Transform(focus, Quaternion.FromAxisAngle(camup, roty)); + + position = focus * length + manager.ActiveMatrix.centerposition; + + direction = Vector3.Transform(direction, Quaternion.FromAxisAngle(camup, roty)); + direction.Normalize(); + } + else + { + camright.Normalize(); + camup.Normalize(); + + Vector3 focus = position - manager.ActiveMatrix.centerposition; + float length = focus.Length; + focus.Normalize(); + + if (!freecam) + { + focus = Vector3.Transform(focus, Quaternion.FromAxisAngle(camup, roty)); + focus = Vector3.Transform(focus, Quaternion.FromAxisAngle(camright, rotx)); + } + + position = focus * length + manager.ActiveMatrix.centerposition; + + direction = Vector3.Transform(direction, Quaternion.FromAxisAngle(camup, roty)); + direction = Vector3.Transform(direction, Quaternion.FromAxisAngle(camright, rotx)); + direction.Normalize(); + + direction.Y = direction.Y.Clamp(-.95f, .95f); + } + } + + if (input.mousedown(MouseButton.Middle)) + { + Vector3 camright = Vector3.Cross(direction, Vector3.UnitY); + + camright.Normalize(); + + Vector3 camup = Vector3.Cross(camright, direction); + + camup.Normalize(); + + camright *= -input.mousedx; + camup *= input.mousedy; + + camright += camup; + + position.X += camright.X * .06f; + position.Y += camright.Y * .06f; + position.Z += camright.Z * .06f; + } + + if (dotransition) + { + time += delta; + + if (time >= .5f) + { + position = _goto; + direction = (centerposition - position).Normalized(); + dotransition = false; + time = 0; + } + else + { + position = Vector3.Lerp(startpos, _goto, time / .5f); + direction = Vector3.Lerp(startdir, (centerposition - position).Normalized(), time / .5f); + direction.Normalize(); + } + } + + view = Matrix4.LookAt(position, position + direction, Vector3.UnitY); + modelviewprojection = view * projection; + } + + public void LookAtModel(bool skipVoxels = false) + { + if (!skipVoxels) + { + int minx = 10000; + int miny = 10000; + int minz = 10000; + int maxx = 0; + int maxy = 0; + int maxz = 0; + int sizex = 0; + int sizey = 0; + int sizez = 0; + + foreach (var matrix in manager.ActiveModel.matrices) + { + if (matrix.minx < minx) + minx = matrix.minx; + if (matrix.maxx > maxx) + maxx = matrix.maxx; + + if (matrix.miny < miny) + miny = matrix.miny; + if (matrix.maxy > maxy) + maxy = matrix.maxy; + + if (matrix.minz < minz) + minz = matrix.minz; + if (matrix.maxz > maxz) + maxz = matrix.maxz; + } + + sizex = maxx - minx; + sizey = maxy - miny; + sizez = maxz - minz; + + float backup = 0; + + if (sizey * 1.5f > 20) + backup = sizey * 1.5f; + else if (sizex * 1.5f > 20) + backup = sizex * 1.5f; + else backup = 20; + + var centerpos = new Vector3((minx + ((maxx - minx) / 2)), (miny + ((maxy - miny) / 2)), (minz + ((maxz - minz) / 2))); + position = centerpos + new Vector3(.5f, sizey * .65f, backup); + + Vector3.Subtract(ref centerpos, ref position, out direction); + direction.Normalize(); + + view = Matrix4.LookAt(position, position + direction, cameraup); + modelviewprojection = Matrix4.CreateScale(.1f) * projection * view; + } + else + { + float sizey = manager.ActiveMatrix.sizey; + float sizex = manager.ActiveMatrix.sizex; + + float backup = 0; + + if (sizey * 1.5f > 20) + backup = sizey * 1.5f; + else if (sizex * 1.5f > 20) + backup = sizex * 1.5f; + else backup = 20; + + var centerpos = manager.ActiveMatrix.centerposition; + position = centerpos + new Vector3(0, sizey * .65f, backup*.7f); + + Vector3.Subtract(ref centerpos, ref position, out direction); + direction.Normalize(); + + view = Matrix4.LookAt(position, position + direction, cameraup); + modelviewprojection = Matrix4.CreateScale(.1f) * projection * view; + } + } + + public void TransitionToMatrix() + { + startpos = position; + startdir = direction; + + var mat = manager.ActiveMatrix; + _goto = mat.centerposition; + centerposition = mat.centerposition; + + float height = (mat.maxy - mat.miny); + float width = (mat.maxx - mat.minx); + float length = (mat.maxz - mat.minz); + + float distance; + + if (height < 10 && width < 10 && length < 10) + { + height = (mat.maxy - mat.miny) * 2.5f; + width = (mat.maxx - mat.minx) * 2.5f; + length = (mat.maxz - mat.minz) * 2.5f; + + distance = Math.Max(Math.Max(height, width), length); + } + else if (height < 18 && width < 18 && length < 18) + { + height = (mat.maxy - mat.miny) * 3.5f; + width = (mat.maxx - mat.minx) * 3.5f; + length = (mat.maxz - mat.minz) * 3.5f; + + distance = Math.Max(Math.Max(height, width), length); + } + else + { + height = (mat.maxy - mat.miny) * 1.5f; + width = (mat.maxx - mat.minx) * 1.5f; + length = (mat.maxz - mat.minz) * 1.5f; + + distance = Math.Max(Math.Max(height, width), length); + } + + Vector3 offset = direction; + if (Math.Abs(offset.X) > .5f) + offset.X = 1f * -Math.Sign(direction.X); + else + offset.X = 0; + if (Math.Abs(offset.Z) > .5f) + offset.Z = 1f * -Math.Sign(direction.Z); + else + offset.Z = 0; + if (Math.Abs(offset.Y) > .5f) + offset.Y = 1f * -Math.Sign(direction.Y); + else + offset.Y = 0; + + if (offset.Y == 1 && offset.X == 0 && offset.Z == 0) + { + offset = direction; + + float starting = .5f; + bool b = true; + while (b) + { + if (Math.Abs(offset.X) > starting) + { + offset.X = 1f * -Math.Sign(direction.X); + b = false; + } + if (Math.Abs(offset.Z) > starting) + { + offset.Z = 1f * -Math.Sign(direction.Z); + b = false; + } + starting -= .07f; + } + + offset.Y = 0; + } + + offset.Normalize(); + + _goto += offset * distance; + dotransition = true; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/Colort.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/Colort.cs new file mode 100644 index 00000000..4b7345bc --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/Colort.cs @@ -0,0 +1,28 @@ +using OpenTK.Graphics; + +namespace stonevox +{ + public struct Colort + { + public float R; + public float G; + public float B; + + public Colort(float r, float g, float b) + { + this.R = r; + this.G = g; + this.B = b; + } + + public static implicit operator Colort(Color4 color) + { + return new Colort(color.R, color.G, color.B); + } + + public static implicit operator Color4(Colort color) + { + return new Color4(color.R, color.G, color.B, 1.0f); + } + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/ConsoleCommands.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/ConsoleCommands.cs new file mode 100644 index 00000000..3edf70f5 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/ConsoleCommands.cs @@ -0,0 +1,497 @@ +using OpenTK.Graphics.OpenGL4; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Windows.Forms; + +namespace stonevox +{ + public static class ConsoleCommands + { + [ConsoleCommand("help", 0)] + [ConsoleCommandDescription("Lists all console commands.")] + public static void help() + { + var cmds = typeof(ConsoleCommands).GetMethods(BindingFlags.Static | BindingFlags.Public); + + Console.WriteLine(); + + foreach (var c in cmds) + { + Console.ForegroundColor = ConsoleColor.Green; + ConsoleCommand command = (ConsoleCommand)c.GetCustomAttribute(typeof(ConsoleCommand)); + Console.WriteLine(command.name); + + ConsoleCommandDescription description = (ConsoleCommandDescription)c.GetCustomAttribute(typeof(ConsoleCommandDescription)); + if (description != null) + { + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(description.text); + } + + ConsoleArgs args = (ConsoleArgs)c.GetCustomAttribute(typeof(ConsoleArgs)); + + if (args != null) + { + Console.ForegroundColor = ConsoleColor.White; + foreach (var arg in args.args) + Console.WriteLine(" " + arg); + } + + Console.WriteLine(); + } + + Console.ForegroundColor = ConsoleColor.White; + } + + + [ConsoleCommand("getip", 0)] + [ConsoleCommandDescription("Copies your public ip address to the clipboard.")] + public static void getip() + { + string ip = NetworkUtil.getIP(); + //Clippz.PushAnsiStringToClipboard(ip); + Clipboard.SetText(ip); + Console.WriteLine(string.Format("your public ip address is {0}", ip)); + } + + [ConsoleCommand("getip", 1)] + [ConsoleCommandDescription("Copies your public ip address to the clipboard.")] + [ConsoleArgs("accurate - goes through slower but more accurate means to obtain your public IP.")] + public static void getipact(string act) + { + string ip = NetworkUtil.getIP_WEBREQUEST(); + //Clippz.PushAnsiStringToClipboard(ip); + Clipboard.SetText(ip); + Console.WriteLine(string.Format("your accurate public ip address is {0}", ip)); + } + + [ConsoleCommand("getips", 0)] + [ConsoleCommandDescription("Displays all IP addresses found on your network interface.")] + public static void getips() + { + string ip = NetworkUtil.getIP(); + //Clippz.PushAnsiStringToClipboard(ip); + Clipboard.SetText(ip); + Console.WriteLine(string.Format("your public ip address is {0}", ip)); + } + + [ConsoleCommand("encrypt", 1)] + [ConsoleCommandDescription("Encrypts the give text.")] + [ConsoleArgs("text - value to encrypt.")] + public static void encrypt(string text) + { + string e = Program.Encrypt(text); + //Clippz.PushAnsiStringToClipboard(e); + Clipboard.SetText(e); + Console.WriteLine(e); + } + + [ConsoleCommand("decrypt", 1)] + [ConsoleCommandDescription("Decrypts the give text. Value is copied to clipboard.")] + [ConsoleArgs("text - value to encrypt.")] + public static void decrypt(string text) + { + string e = Program.Decrypt(text); + //Clippz.PushAnsiStringToClipboard(e); + Clipboard.SetText(e); + Console.WriteLine(e); + } + + [ConsoleCommand("startclient", 0)] + [ConsoleCommandDescription("Starts up client, if a client is currently running it will be closed.")] + public static void startclient() + { + if (Client.net != null && Client.net.Status == Lidgren.Network.NetPeerStatus.Running) + { + Client.net.Shutdown("(Client) Shutting Down"); + Thread.Sleep(300); + + if (Program.clientthread.IsAlive) + { + Program.clientthread.Abort(); + Thread.Sleep(300); + } + } + + Program.clientthread = new Thread(_startclient); + Program.clientthread.SetApartmentState(ApartmentState.STA); + Program.clientthread.Start(); + } + + static void _startclient() + { + Client.defaultConfigure(); + Client.start(); + Client.beginstonevox(); + } + + [ConsoleCommand("startserver", 0)] + [ConsoleCommandDescription("Starts up server, if a server is currently running it will be closed.")] + public static void startserver() + { + if (Server.net != null && Server.net.Status == Lidgren.Network.NetPeerStatus.Running) + { + Server.net.Shutdown("(Server) Shutting Down"); + Thread.Sleep(300); + + if (Program.serverthread.IsAlive) + { + Program.serverthread.Abort(); + Thread.Sleep(300); + } + } + + Program.serverthread = new Thread(Program.startServer); + Program.serverthread.Start(); + + Thread.Sleep(20); + } + + [ConsoleCommand("connectlocal", 0)] + [ConsoleCommandDescription("Connects the client to server running on your local computer.")] + public static void connectlocal() + { + int count = 0; + while (count < 1000) + { + if (!Client.Initialized) + { + count += 10; + Thread.Sleep(10); + } + else + break; + } + + if (Client.net.ConnectionStatus == Lidgren.Network.NetConnectionStatus.Connected) + { + Client.disconnect(); + Thread.Sleep(300); + } + Client.connect(NetConfig.NETWORK_LOCALHOST, NetConfig.NETWORK_PORT); + } + + + [ConsoleCommand("connect", 3)] + [ConsoleCommandDescription("Connect the client to a server running at the specified web address")] + [ConsoleArgs("ip - ip address to connect to", + "port - port on server", + "encrypted - does the ip need to be decrypted : (true | false)")] + public static void connect(string ip, string port, string encrypted) + { + int count = 0; + while (count < 1000) + { + if (!Client.Initialized) + { + count += 10; + Thread.Sleep(10); + } + else + break; + } + + if (encrypted.ToLower() == "true") + { + ip = Program.Decrypt(ip); + } + + if (Client.net.ConnectionStatus == Lidgren.Network.NetConnectionStatus.Connected) + { + Client.disconnect(); + Thread.Sleep(300); + } + int nport = Convert.ToInt32(port); + + Client.connect(ip, nport); + } + + [ConsoleCommand("disconnect", 0)] + [ConsoleCommandDescription("Disconnects client from server. Value is copied to clipboard.")] + public static void disconnect() + { + if (Client.net.ConnectionStatus == Lidgren.Network.NetConnectionStatus.Connected) + { + Client.disconnect(); + Thread.Sleep(300); + } + } + + [ConsoleCommand("getclientid", 0)] + [ConsoleCommandDescription("Retreives your specific client ID used to connect with others through StoneVox 3D. Data is copy to the clipboard.")] + public static void getclientid() + { + string ip = Program.Encrypt(NetworkUtil.getIP()); + Console.WriteLine(string.Format("you client ID : {0}", ip)); + Clipboard.SetText(ip); + } + + [ConsoleCommand("getclientstatus", 0)] + [ConsoleCommandDescription("Displays the clients current network status.")] + public static void getclientstatus() + { + if (Client.net != null) + Client.print("info", "Connection Status " + Client.net.ConnectionStatus.ToString()); + } + + [ConsoleCommand("getserverconnectioncount", 0)] + [ConsoleCommandDescription("If a server is running on this computer, Displays the amount of clients connected to the server")] + public static void getserverconnectioncount() + { + if (Server.net != null) + Server.print("info", "Connection Count " + Server.net.ConnectionsCount.ToString()); + } + + [ConsoleCommand("loadqb", 1)] + [ConsoleCommandDescription("Loads a .qb file.")] + [ConsoleArgs("filepath - path to file to load")] + public static void loadqb(string path) + { + if (!File.Exists(path)) + { + Client.print("error", $"Error : loadqb -File {path}, was not found."); + return; + } + + Client.OpenGLContextThread.Add(() => + { + ImportExportUtil.Import(path); + }); + } + + [ConsoleCommand("loadqbnetworking", 1)] + [ConsoleCommandDescription("Loads a .qb file internally, then sends the loaded model to all clients connected.")] + [ConsoleArgs("filepath - path to file to load")] + public static void loadqbnetworking(string path) + { + if (Client.net != null && Client.net.ConnectionsCount == 0) + { + Client.print("error", "Error : loadqbnetworking - is meant for network loading of files. Connect your client to a server before running this command, or try using \"loadqb\""); + return; + } + + Client.OpenGLContextThread.Add(() => + { + + StopwatchUtil.startclient("internalqbread", "Begin Qb Read"); + ImporterQb importer = new ImporterQb(); + QbModel model = importer._read(path); + + StopwatchUtil.stopclient("internalqbread", "End Qb Read"); + + StopwatchUtil.startclient("clientwriteqbpacket", "Begin Qb Packet Write"); + var packet = PacketWriter.write(NetEndpoint.CLIENT); + packet.write(model); + packet.send(); + StopwatchUtil.stopclient("clientwriteqbpacket", "End qb Packet Write"); + + }); + } + + + [ConsoleCommand("fps", 1)] + [ConsoleCommandDescription("SV will try to match this frame-rate.")] + [ConsoleArgs("fps - frames per second to shoot for")] + public static void fps(string s_fps) + { + try + { + int fps = Convert.ToInt32(s_fps); + if (fps <= 0) + throw new Exception(); + Client.window.targetFps = fps; + Client.window.TargetRenderFrequency = fps; + Client.window.TargetUpdateFrequency = fps; + } + catch + { + Client.print("info", "input value must be non decimal and greater then 0"); + } + } + + [ConsoleCommand("openglsupport", 0)] + [ConsoleCommandDescription("Gives feedback about what version of OpenGL you GPU supports.")] + public static void openglsupport() + { + Client.OpenGLContextThread.Add(() => + { + Console.WriteLine(); + + var osversion= Environment.OSVersion.VersionString; + Client.print("debug", osversion); + + string majorminor = GL.GetString(StringName.Version); + Client.print("debug", string.Format("Available OpenGL version : {0}", majorminor)); + + string vendor = GL.GetString(StringName.Vendor); + Client.print("debug", string.Format("Vendor : {0}", vendor)); + + string renderer = GL.GetString(StringName.Renderer); + Client.print("debug", string.Format("Available Render version : {0}", renderer)); + + string glslversion = GL.GetString(StringName.ShadingLanguageVersion); + Client.print("debug", string.Format("Available Shader version : {0}", glslversion)); + + string numberextensions = GL.GetString(StringName.Extensions); + string[] supports = numberextensions.Split(' '); + + Console.WriteLine(); + Client.print("debug", string.Format("Available Extensions : {0}", supports.Length)); + + for (int i = 0; i < supports.Length; i++) + { + Client.print("debug_data", string.Format(" {0}", supports[i])); + } + + Client.print("debug", "Shader test"); + int shadererrorcount = 0; + + string vertexshaderpath = "./data/shaders/voxel.vs"; + string fragmentshaderpath = "./data/shaders/voxel.fs"; + + int vertexshaderid = GL.CreateShader(ShaderType.VertexShader); + int fragmentshaderid = GL.CreateShader(ShaderType.FragmentShader); + + using (StreamReader r = new StreamReader(vertexshaderpath)) + GL.ShaderSource(vertexshaderid, r.ReadToEnd()); + using (StreamReader r = new StreamReader(fragmentshaderpath)) + GL.ShaderSource(fragmentshaderid, r.ReadToEnd()); + + GL.CompileShader(vertexshaderid); + + string vertexshadererror = GL.GetShaderInfoLog(vertexshaderid); + + GL.CompileShader(fragmentshaderid); + + string fragmentshadererror = GL.GetShaderInfoLog(fragmentshaderid); + + int shaderid = GL.CreateProgram(); + GL.AttachShader(shaderid, vertexshaderid); + GL.AttachShader(shaderid, fragmentshaderid); + GL.LinkProgram(shaderid); + + string shadererror = GL.GetProgramInfoLog(shaderid); + + if (!string.IsNullOrEmpty(vertexshadererror)) + { + Client.print("debug_data", string.Format(" {0}", vertexshadererror)); + shadererrorcount++; + } + + if (!string.IsNullOrEmpty(fragmentshadererror)) + { + Client.print("debug_data", string.Format(" Fragment shader failed compiling : {0}", fragmentshadererror)); + shadererrorcount++; + } + + if (!string.IsNullOrEmpty(shadererror)) + { + Client.print("debug_data", string.Format(" Shader failed linking: {0}", shadererror)); + shadererrorcount++; + } + + if (shadererrorcount > 0) + { + Client.print("debug", "Shader test failed"); + } + else + Client.print("debug", "Shader completed with no errors"); + + Console.WriteLine(); + Client.print("info", "Would you like to export info to desktop. (yes/no)"); + string t = Console.ReadLine(); + if (t.ToLower().Contains("y") || t.ToLower().Contains("es")) + { + Client.print("debug", "Info exported to desktop."); + + using (FileStream file = new FileStream(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "//stonevox_log.txt", FileMode.Create)) + { + using (StreamWriter s = new StreamWriter(file)) + { + s.WriteLine(osversion); + s.WriteLine(string.Format("Available OpenGL version : {0}", majorminor)); + s.WriteLine(string.Format("Vendor : {0}", vendor)); + s.WriteLine(string.Format("Available Render version : {0}", renderer)); + s.WriteLine(string.Format("Available Shader version : {0}", glslversion)); + + s.WriteLine(); + s.WriteLine(string.Format("Available Extensions : {0}", supports.Length)); + + for (int i = 0; i < supports.Length; i++) + { + s.WriteLine(string.Format(" {0}", supports[i])); + } + + s.WriteLine(); + + s.WriteLine("Shader test"); + + + if (!string.IsNullOrEmpty(vertexshadererror)) + { + s.WriteLine(string.Format(" {0}", vertexshadererror)); + } + + if (!string.IsNullOrEmpty(fragmentshadererror)) + { + s.WriteLine(string.Format(" Fragment shader failed compiling : {0}", fragmentshadererror)); + } + + if (!string.IsNullOrEmpty(shadererror)) + { + s.WriteLine(string.Format(" Shader failed linking: {0}", shadererror)); + } + + if (shadererrorcount > 0) + { + s.WriteLine("Shader test failed"); + } + else + s.WriteLine("Shader completed with no errors"); + } + } + } + else + { + + } + }); + } + } + + public class ConsoleCommand : System.Attribute + { + public string name; + public int argcount; + + public ConsoleCommand(string cmdname, int argcount) + : base() + { + name = cmdname; + this.argcount = argcount; + } + } + + public class ConsoleCommandDescription : System.Attribute + { + public string text; + + public ConsoleCommandDescription(string text) + { + this.text = text; + } + } + + public class ConsoleArgs : System.Attribute + { + public string[] args; + + public ConsoleArgs(params string[] args) + { + this.args = args; + } + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/DragDropTarget.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/DragDropTarget.cs new file mode 100644 index 00000000..37709dd7 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/DragDropTarget.cs @@ -0,0 +1,87 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace stonevox +{ + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000122-0000-0000-C000-000000000046")] // This is the value of IID_IDropTarget from the Platform SDK. + [ComImport] + public interface IDropTarget + { + void DragEnter([In] IDataObject dataObject, [In] uint keyState, [In] Point pt, [In, Out] ref uint effect); + void DragOver([In] uint keyState, [In] Point pt, [In, Out] ref uint effect); + void DragLeave(); + void Drop([In] IDataObject dataObject, [In] uint keyState, [In] Point pt, [In, Out] ref uint effect); + } + + + static class NativeMethods + { + public const int CF_HDROP = 15; + + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + public static extern int DragQueryFile(HandleRef hDrop, int iFile, [Out] StringBuilder lpszFile, int cch); + + [DllImport("ole32.dll")] + internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); + } + + public class DragDropTarget : stonevox.IDropTarget + { + public void DragEnter(IDataObject dataObject, uint keyState, Point pt, ref uint effect) + { + } + + public void DragOver(uint keyState, Point pt, ref uint effect) + { + } + + public void DragLeave() + { + } + + public void Drop(IDataObject dataObject, uint keyState, Point pt, ref uint effect) + { + FORMATETC format = new FORMATETC() + { + cfFormat = NativeMethods.CF_HDROP, + dwAspect = DVASPECT.DVASPECT_CONTENT, + tymed = TYMED.TYMED_HGLOBAL + }; + STGMEDIUM medium; + string[] files; + dataObject.GetData(ref format, out medium); + try + { + IntPtr dropHandle = medium.unionmember; + int fileCount = NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), -1, null, 0); + files = new string[fileCount]; + for (int x = 0; x < fileCount; ++x) + { + int size = NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), x, null, 0); + if (size > 0) + { + StringBuilder fileName = new StringBuilder(size + 1); + if (NativeMethods.DragQueryFile(new HandleRef(this, dropHandle), x, fileName, fileName.Capacity) > 0) + files[x] = fileName.ToString(); + } + } + } + finally + { + NativeMethods.ReleaseStgMedium(ref medium); + } + + foreach (string file in files) + { + Client.OpenGLContextThread.Add(() => + { + ImportExportUtil.Import(file); + }); + } + } + } +} \ No newline at end of file diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/Floor.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/Floor.cs new file mode 100644 index 00000000..dd1947d9 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/Floor.cs @@ -0,0 +1,114 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using System; + +namespace stonevox +{ + public class Floor : Singleton + { + public static int MinSize = 15; + Camera camera; + Broadcaster broadcaster; + + public Color4 color = Color4.Transparent; + + float width =15; + float length = 15; + + public float x = 0; + public float y = 0; + public float z = 0; + + Vector3 up = new Vector3(0, 1, 0); + Vector3 h = Vector3.Zero; + + public Floor(Camera camera, Broadcaster broadcaster) + : base() + { + this.camera = camera; + this.broadcaster = broadcaster; + + broadcaster.handlers.Add((message, e, t) => + { + if (message == Message.ActiveMatrixChanged) + { + QbMatrix m = t[0] as QbMatrix; + m.MatchFloorToSize(); + } + }); + } + + public void SetFloorSize(float x, float y, float z, float width, float length) + { + this.x = x; + this.y = y; + this.z = z; + this.width = width; + this.length = length; + } + + public void Render() + { + float cubesize = .5f; + + GL.MatrixMode(MatrixMode.Projection); + GL.LoadMatrix(ref camera.projection); + + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadMatrix(ref camera.view); + + GL.Begin(PrimitiveType.Quads); + + GL.Color4(color); + GL.Vertex3(-cubesize + x, + -.5F + y, + cubesize*length*2f + z+cubesize); + + GL.Vertex3(cubesize*width*2f + x+cubesize, + -.5F + y, + cubesize*length*2f + z+cubesize); + + GL.Vertex3(cubesize*width*2f + x+cubesize, + -.5F + y, + -cubesize + z); + + GL.Vertex3(-cubesize*2f + x +cubesize, + -.5F + y, + -cubesize + z); + GL.End(); + } + + public bool RayTest(Raycaster raycaster, ref RaycastHit hit) + { + // 0 1 2 + // 0 2 3 + float cubesize = .5f; + + for (float x = this.x; x <= this.x+width; x++) + for (float z = this.z; z <= this.z + length; z++) + { + if (raycaster.RayTestTriangle(ref up, -cubesize + x, -.5F + y, cubesize + z, + cubesize + x, -.5F + y, cubesize + z, + cubesize + x, -.5F + y, -cubesize + z, + out h) || raycaster.RayTestTriangle(ref up, -cubesize + x, -.5F + y, cubesize + z, + cubesize + x, -.5F + y, -cubesize + z, + -cubesize + x, -.5F + y, -cubesize + z, out h)) + { + int hx, hy, hz; + hx = (int)Math.Round(h.X, 0); + hy = (int)Math.Round(h.Y, 0); + hz = (int)Math.Round(h.Z, 0); + + hit.distance = raycaster.distance(hx * .5f, hy * .5f + .5f, hz * .5f); + hit.x = hx; + hit.y = (int)y-1; + hit.z = hz; + hit.side = Side.Top; + return true; + } + } + return false; + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/GLWindow.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/GLWindow.cs new file mode 100644 index 00000000..908f109b --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/GLWindow.cs @@ -0,0 +1,560 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL4; +using OpenTK.Input; +using QuickFont; +using stonevox.gui.editor; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Collections.Generic; + +namespace stonevox +{ + // personally i'm thinking nothing should go here to access stuff, ie backcolor, input, gui, ect + // added singleton classes to start this change + public class GLWindow : SyncWindow + { + [DllImport("user32.dll", SetLastError = true)] + static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)] + public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName); + + [DllImport("ole32.dll")] + public static extern int OleInitialize(IntPtr pvReserved); + + [DllImport("ole32.dll")] + public static extern int RegisterDragDrop(IntPtr hwnd, stonevox.IDropTarget pDropTarget); + + // this goes along with the comment below + // these are all singeltons now + Input input; + Camera camera; + Selection selection; + BrushManager brushes; + GUI gui; + Broadcaster broadcaster; + UndoRedo undoredo; + Raycaster raycaster; + Floor floor; + QbManager manager; + IRenderer renderer; + + // not sure about these yet, need another place + // ... also the whole QbManager/Importer/Exporter thing i don't like + // thinking some sort of qb manager class should be put in the _client space + // following Singleton + //public QbModel model; + public Shader voxelShader; + + public Color4 backcolor; + + public bool isfocused = true; + + public QFont Qfont; + public QFont Qfont_1280; + public QFont Qfont_1400; + public QFont Qfont_1920; + + public event EventHandler SVReizeEvent; + private int lastWidth; + + public string CSMText = ""; + + public GLWindow(int width, int height, GraphicsMode graphicsmode, string CSMTextx) + : base(width, height, graphicsmode) + { + CSMText = CSMTextx; + } + + + + public DragDropTarget dnd; + + + + + VoxelLocation startPosition; + VoxelLocation endPosition; + VoxelVolume volume; + VoxelVolume lastvolume; + QbMatrix lastmatrix; + Dictionary modifiedVoxels = new Dictionary(); + + + protected override void OnLoad(EventArgs e) + { + string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + Title = String.Format("StoneVox 3D - version {0}", version); + + GL.Viewport(0, 0, Width, Height); + Qfont_1280 = new QFont("data\\fonts\\Bigfish.ttf", 11.2f, new QFontBuilderConfiguration(true, false)); + Qfont_1400 = new QFont("data\\fonts\\Bigfish.ttf", 12f, new QFontBuilderConfiguration(true, false)); + Qfont_1920 = new QFont("data\\fonts\\Bigfish.ttf", 15, new QFontBuilderConfiguration(true, false)); + if (Width <= 1280) + { + Qfont = Qfont_1280; + } + else if (Width < 1400) + { + Qfont = Qfont_1400; + } + else + { + Qfont = Qfont_1920; + } + + this.Qfont.Options.Colour = Color.White; + //this.Qfont.Options.TransformToViewport = new TransformViewport(-1,-1,2,2); + + Scale.SetHScaling(0, Width); + Scale.SetVScaling(0, Height); + + ShaderUtil.CreateShader("quad_interpolation", "./data/shaders/QuadInterpolation.vs", "./data/shaders/QuadInterpolation.fs"); + + broadcaster = new Broadcaster(); + manager = new QbManager(broadcaster); + input = new Input(this); + camera = new Camera(this, input, manager); + brushes = new BrushManager(this, input); + floor = new Floor(camera, broadcaster); + gui = new GUI(this, manager, input); + selection = new Selection(this,brushes, input, manager, floor, gui); + renderer = new Wireframe(camera, selection, floor, input); + undoredo = new UndoRedo(input); + + selection.GenerateVertexArray(); + + if(!manager.HasModel) + manager.AddEmpty(); + camera.LookAtModel(true); + + backcolor = new Color4(60, 60, 60, 0); + + GL.Enable(EnableCap.DepthTest); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); + + GL.Enable(EnableCap.CullFace); + GL.CullFace(CullFaceMode.Back); + + int ole_hresult = OleInitialize(IntPtr.Zero); + IntPtr handle = FindWindowByCaption(IntPtr.Zero, Title); + dnd = new DragDropTarget(); + int dnd_hresult = RegisterDragDrop(handle, dnd); + + raycaster = new Raycaster(this, camera, selection, floor, input, manager, gui); + selection.raycaster = raycaster; + + Client.Initialized = true; + base.OnLoad(e); + + CSM csm = new CSM(CSMText); + foreach(Box box in csm.boxes) + { + CreateBox(box.ParentName, -box.x, -box.y, -box.z, box.SizeX, box.SizeY, box.SizeZ, new Colort(.3f, .5f, .8f)); + Console.WriteLine("CreateBox(\"" + box.ParentName + "\", " + -box.x + ", " + -box.y + ", " + -box.z + ", " + box.SizeX + ", " + box.SizeY + ", " + box.SizeZ + ", new Colort(.3f, .5f, .8f));"); + } + + + + SetForegroundWindow(WindowInfo.Handle); + + } + + public void CreateBox(string Parent, int x, int y, int z, int sizeX, int sizeY, int sizeZ, Colort col) + { + sizeX -= 1; + sizeY -= 1; + sizeZ -= 1; + switch (Parent) + { + case ("HEAD"): + y += 16; + col = new Colort(.3f, .5f, .8f); + break; + case ("BODY"): + z += 4; + y += 12; + col = new Colort(.5f, .2f, .8f); + break; + case ("ARM0"): + z += 4; + y += 10; + x -= 3; + col = new Colort(.3f, .2f, .5f); + break; + case ("ARM1"): + z += 4; + y += 10; + x += 11; + col = new Colort(.3f, .5f, .5f); + break; + case ("LEG0"): + x += 2; + z += 4; + col = new Colort(.3f, .2f, .1f); + break; + case ("LEG1"): + x += 6; + z += 4; + col = new Colort(.1f, .2f, .5f); + break; + } + + + + RaycastHit hit = new RaycastHit(); + hit.distance = 10000; + hit.x = x; + hit.y = y; + hit.z = z; + + RaycastHit hit2 = new RaycastHit(); + hit2.distance = hit.distance - (sizeX + sizeZ + sizeY); + hit2.y = y + sizeY; + hit2.x = x + sizeX; + hit2.z = z + sizeZ; + + int index = 0; + + for (int i = 0; i < manager.ActiveModel.numMatrices; i++) + { + if (!manager.ActiveModel.matrices[i].Visible) continue; + RaycastHit tempHit = raycaster.RaycastTest(camera.position, manager.ActiveModel.matrices[i]); + + if (tempHit.distance != 10000 && tempHit.distance < hit.distance && !tempHit.matches(hit)) + { + index = i; + } + } + + + startPosition = new VoxelLocation(hit); + endPosition = new VoxelLocation(hit2); + volume = new VoxelVolume(startPosition, endPosition); + modifiedVoxels.Clear(); + EnumerateVolume(lastvolume, volume, manager.ActiveModel.matrices[index], ref col, modifiedVoxels); + + } + + public void EnumerateVolume(VoxelVolume volume, VoxelVolume currentVolume, QbMatrix matrix, ref Colort color, Dictionary modifiedVoxels) + { + for (int z = currentVolume.minz; z <= currentVolume.maxz; z++) + for (int y = currentVolume.miny; y <= currentVolume.maxy; y++) + for (int x = currentVolume.minx; x <= currentVolume.maxx; x++) + { + if (!volume.ContainsPoint(x, y, z) && !modifiedVoxels.ContainsKey(matrix.GetHash(x, y, z))) + modifiedVoxels.Add(matrix.GetHash(x, y, z), new VoxelUndoData(matrix.Add(x, y, z, color))); + + } + } + + + protected override void OnClosing(CancelEventArgs e) + { + Environment.Exit(0); + base.OnClosing(e); + } + public override void Dispose() + { + Qfont_1280.Dispose(); + Qfont_1400.Dispose(); + Qfont_1920.Dispose(); + manager.Dispose(); + base.Dispose(); + } + + protected override void OnFocusedChanged(EventArgs e) + { + if (!Focused) + { + isfocused = false; + raycaster.Enabled = false; + } + else + { + isfocused = true; + raycaster.Enabled = true; + } + + var handle = FindWindowByCaption(IntPtr.Zero, "StoneVox - Open File"); + if (handle != IntPtr.Zero) + SetForegroundWindow(handle); + + handle = FindWindowByCaption(IntPtr.Zero, "StoneVox - Save File"); + if (handle != IntPtr.Zero) + SetForegroundWindow(handle); + + base.OnFocusedChanged(e); + } + protected override void OnWindowStateChanged(EventArgs e) + { + if (WindowState == WindowState.Minimized) + { + raycaster.Enabled = false; + + } + else if (WindowState == WindowState.Normal || WindowState == WindowState.Maximized) + { + raycaster.Enabled = true; + } + base.OnWindowStateChanged(e); + } + + protected override void OnResize(EventArgs e) + { + GL.Viewport(0, 0, Width, Height); + QFont.ForceViewportRefresh(); + + Scale.SetHScaling(0, Width); + Scale.SetVScaling(0, Height); + + if (Width <= 1280 && lastWidth != 1280) + { + lastWidth = 1280; + Qfont = Qfont_1280; + SVReizeEvent?.Invoke(this, EventArgs.Empty); + } + else if (Width >1280 && Width <= 1400 && lastWidth !=1400) + { + lastWidth = 1400; + Qfont = Qfont_1400; + SVReizeEvent?.Invoke(this, EventArgs.Empty); + } + else if (Width > 1400 && lastWidth != 1920) + { + lastWidth = 1920; + Qfont = Qfont_1920; + SVReizeEvent?.Invoke(this, EventArgs.Empty); + } + + base.OnResize(e); + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + input.handleKeydown(e); + + if (e.Control && e.Key == Key.O) + { + var open = new OpenFileDialog(); + open.Multiselect = false; + open.Title = "StoneVox - Open File"; + open.DefaultExt = ".qb"; + + open.FileOk += (s, o) => + { + Client.OpenGLContextThread.Add(() => + { + ImportExportUtil.Import(open.FileName); + }); + }; + + Thread thread = new Thread(() => + { + open.ShowDialog(); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + if (e.Control && e.Key == Key.S) + { + var save = new SaveFileDialog(); + save.Title = "StoneVox - Save File"; + save.Filter = "StoneVox Project (.svp)|*.svp|Qubicle Binary (.qb)|*.qb|Wavefront OBJ (.obj)|*.obj|All files (*.*)|*.*"; + save.DefaultExt = ".svp"; + + save.FileOk += (s, t) => + { + QbModel model = manager.ActiveModel; + model.name = save.FileName.Split('\\').Last(); + if (model.name.Contains('.')) + model.name = model.name.Split('.').First(); + broadcaster.Broadcast(Message.ModelRenamed, model, model.name); + ImportExportUtil.Export(save.FileName.Split('\\').Last().Replace(model.name, ""), model.name, Path.GetDirectoryName(save.FileName), model); + }; + + Thread thread = new Thread(() => + { + save.ShowDialog(); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + else if (e.Control && e.Key == Key.F12) + { + GUIEditor editor = new GUIEditor(); + editor.Show(); + } + //else if (e.Key == Key.T) + //{ + // Stopwatch w = Stopwatch.StartNew(); + // var model = Singleton.INSTANCE.ActiveMatrix; + + // Colort color = new Colort(1f, 0f, 0f); + + // var volume = new VoxelVolume() + // { + // minx = 0, + // maxx = 100, + // miny = 0, + // maxy = 100, + // minz = 0, + // maxz = 100 + // }; + + // model.Add(volume, ref color); + + // w.Stop(); + // Console.WriteLine("add " + w.ElapsedMilliseconds.ToString()); + //} + //else if (e.Key == Key.T) + //{ + // Stopwatch w = Stopwatch.StartNew(); + // var model = Singleton.INSTANCE.ActiveMatrix; + + // Colort color = new Colort(1f, 0f, 0f); + // for (int x = 0; x < 100; x++) + // for (int z = 0; z < 100; z++) + // for (int y = 0; y< 100; y++) + // model.Add(x, y, z, color); + + // w.Stop(); + // Console.WriteLine("add " +w.ElapsedMilliseconds.ToString()); + //} + + //else if (e.Key == Key.Y) + //{ + // Stopwatch w = Stopwatch.StartNew(); + // var model = Singleton.INSTANCE.ActiveMatrix; + + // Colort color = new Colort(1f, 0f, 0f); + // for (int x = 0; x < 100; x++) + // for (int z = 0; z < 100; z++) + // for (int y = 0; y < 100; y++) + + // model.Remove(x, y, z); + + // w.Stop(); + // Console.WriteLine("earse " + w.ElapsedMilliseconds.ToString()); + //} + } + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + input.handleKeyup(e); + base.OnKeyUp(e); + } + protected override void OnKeyPress(OpenTK.KeyPressEventArgs e) + { + input.handleKeypress(e); + base.OnKeyPress(e); + } + + // mouse + protected override void OnMouseDown(MouseButtonEventArgs e) + { + input.handlemousedown(e); + base.OnMouseDown(e); + } + protected override void OnMouseUp(MouseButtonEventArgs e) + { + input.handlemouseup(e); + base.OnMouseUp(e); + } + protected override void OnMouseMove(MouseMoveEventArgs e) + { + if (input != null) + { + input.mousex = e.X; + input.mousey = Height - e.Y; + + input.mousedx = e.XDelta; + input.mousedy = e.YDelta; + + // when i implement something good for following the mouse around quickly + //Raycaster.testlocations.Push(new Vector2(e.X, Height - e.Y)); + + input.handlemousemove(e); + + } + + base.OnMouseMove(e); + } + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + input.handlemousewheel(e); + base.OnMouseWheel(e); + } + + protected override void OnUpdateFrame(FrameEventArgs e) + { + if (!isfocused) + { + input.update(); + base.OnUpdateFrame(e); + return; + } + + input.update(); + gui.Update(e); + + camera.update((float)e.Time); + selection.update(); + + base.OnUpdateFrame(e); + } + + double ee = 0; + int fps = 0; + + protected override void OnRenderFrame(FrameEventArgs e) + { + if (!isfocused) + { + Client.update(); + base.OnRenderFrame(e); + return; + } + // Console.WriteLine(raycaster.Enabled.ToString()); + GL.ClearColor(backcolor.R, backcolor.G, backcolor.B, backcolor.A); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + ee += e.Time; + fps++; + + if (ee > 1) + { + ee = 0; + Title = "StoneVox fps : " + fps.ToString(); + fps = 0; + } + + renderer.Render(manager.ActiveModel); + + ShaderUtil.ResetShader(); + + floor.Render(); + + gui.Render(); + + SwapBuffers(); + + Client.update(); + + base.OnRenderFrame(e); + } + } +} diff --git a/MinecraftUSkinEditor/Classes/StoneVOX/client/GUI.cs b/MinecraftUSkinEditor/Classes/StoneVOX/client/GUI.cs new file mode 100644 index 00000000..3fb7b268 --- /dev/null +++ b/MinecraftUSkinEditor/Classes/StoneVOX/client/GUI.cs @@ -0,0 +1,490 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Graphics.OpenGL; +using OpenTK.Input; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace stonevox +{ + public class GUIID + { + public const int HSV_H = 500; + public const int HSV_S = 501; + public const int HSV_V = 502; + public const int RGB_R = 503; + public const int RGB_G = 504; + public const int RGB_B = 505; + + public const int MATRIX_LISTBOX_WINDOW = 700; + public const int MATRIX_SIZE_TEXTBOX = 701; + public const int IO_WINDOW = 900; + public const int COLOR_PICKER_WINDOW = 600; + + public const int COLORQUAD = 601; + + public const int START_COLOR_SELECTORS = 1000; + + public const int STATUS_TEXT = 750; + + public const int GRIDOPTIONS = 800; + + public const int ACTIVE_MATRIX_NAME = 850; + + public const int HACKYSELECTIONTOOL = 950; + } + + public class GUI : Singleton + { + public float scale = 1.0f; + + public List widgets; + private Input input; + private GLWindow window; + private QbManager manager; + + private int widgetIDs = 100000; + public int NextAvailableWidgeID { get { widgetIDs++; return widgetIDs; } } + + private int lastWidgetOverIndex = -1; + public Widget lastWidgetOver { get { return widgets[lastWidgetOverIndex]; } } + + private int lastWidgetFocusedID = -1; + public Widget lastWidgetFocused { get { return widgets[lastWidgetFocusedID]; } } + + public bool OverWidget { get { return lastWidgetOverIndex != -1; } } + public bool FocusingWidget { get { return lastWidgetFocusedID != -1; } } + + public bool Visible = true; + + int activecolorindex = 9; + List colorpallete = new List(); + + public bool Dirty = true; + int framebuffer; + int color; + + public GUI(GLWindow window, QbManager manager, Input input) + : base() + { + this.window = window; + this.manager = manager; + Singleton.INSTANCE.SetGUI(this); + + framebuffer = GL.GenFramebuffer(); + GL.BindFramebuffer(FramebufferTarget.FramebufferExt, framebuffer); + + color = GL.GenTexture(); + GL.BindTexture(TextureTarget.Texture2D, color); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, window.Width, window.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + GL.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, color, 0); + + GL.BindFramebuffer(FramebufferTarget.FramebufferExt, 0); + + window.Resize += (e, o) => + { + UISaveState(); + ConfigureUI(window.Width); + UILoadState(); + + GL.BindTexture(TextureTarget.Texture2D, color); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, window.Width, window.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + GL.BindTexture(TextureTarget.Texture2D, 0); + }; + + this.input = input; + widgets = new List(); + + input.AddHandler(new InputHandler() + { + Keydownhandler = (e) => + { + if (lastWidgetFocusedID != -1 && lastWidgetFocused.Enable) + { + lastWidgetFocused.HandleKeyDown(e); + } + }, + Keypresshandler = (e) => + { + if (lastWidgetFocusedID != -1 && lastWidgetFocused.Enable) + { + lastWidgetFocused.HandleKeyPress(e); + } + }, + + mousedownhandler = (e) => + { + if (lastWidgetOverIndex != -1 && lastWidgetOver.Enable) + { + if (lastWidgetOverIndex != lastWidgetFocusedID) + { + if (lastWidgetFocusedID != -1) + lastWidgetFocused.HandleFocusedLost(); + + lastWidgetFocusedID = lastWidgetOverIndex; + lastWidgetOver?.HandleFocusedGained(); + } + + lastWidgetOver.HandleMouseDown(e); + } + else + { + if (lastWidgetFocusedID != -1 && lastWidgetFocused.Enable) + { + lastWidgetFocused.HandleFocusedLost(); + lastWidgetFocusedID = -1; + } + } + }, + mousemovehandler = (e) => + { + if (lastWidgetFocusedID != -1 && lastWidgetFocused.Enable) + { + if (lastWidgetFocused.Drag) + { + lastWidgetFocused.HandleMouseMove(e); + } + } + }, + mouseuphandler = (e) => + { + if (lastWidgetOverIndex != -1 && lastWidgetOver.Enable) + { + lastWidgetOver.Drag = false; + lastWidgetOver.HandleMouseUp(e); + + //float scaledmouseX = (float)Scale.hPosScale(input.mousex); + //float scaledmousez = (float)Scale.vPosScale(input.mouseY); + + //if (!isMouseWithin(scaledmouseX, scaledmousez, lastWidgetOver)) + //{ + // Console.WriteLine("mouse leasve"); + // lastWidgetOver.HandleMouseLeave(); + // lastWidgetOverIndex = -1; + //} + } + }, + mousewheelhandler = (e) => + { + if (lastWidgetOverIndex != -1 && lastWidgetOver.Enable) + { + lastWidgetOver.HandleMouseWheel(e); + } + } + }); + + //SharpSerializer s = new SharpSerializer(); + + //if (File.Exists(Application.StartupPath + @"\data\gui\Standard.svui")) + //{ + // GUIData data = s.Deserialize(Application.StartupPath + @"\data\gui\Standard.svui") as GUIData; + // foreach (var widget in data.widgets) + // { + // //widgets.Add(widget.) + // } + //} + } + + public void Update(FrameEventArgs e) + { + for (int i = widgets.Count - 1; i > -1; i--) + { + if (widgets[i].Enable) + widgets[i].Update(e); + } + + if (input.mousedx != 0 || input.mousedy != 0) + { + float scaledmouseX = (float)Scale.hPosScale(input.mousex); + float scaledmouseY = (float)Scale.vPosScale(input.mousey); + + + // we were over a control + if (lastWidgetOverIndex != -1) + { + if (!lastWidgetOver.Drag) + { + bool change = false; + if (!isMouseWithin(scaledmouseX, scaledmouseY, lastWidgetOver) || !lastWidgetOver.Enable) + { + change = true; + } + + // are within our lastoverwidget + if (!change) + { + // check widgets above, if we're over then reset lastover + for (int i = widgets.Count - 1; i > lastWidgetOverIndex; i--) + { + if (widgets[i].Enable) + if (isMouseWithin(scaledmouseX, scaledmouseY, widgets[i])) + { + lastWidgetOver.HandleMouseLeave(); + lastWidgetOverIndex = i; + lastWidgetOver.HandleMouseEnter(); + lastWidgetOver.HandleMouseOver(); + change = true; + break; + } + } + } + // not within our lastoverwidget + else + { + change = false; + // check all widgets including lower ones... + for (int i = widgets.Count - 1; i > -1; i--) + { + if (widgets[i].Enable) + + if (i != lastWidgetOverIndex && isMouseWithin(scaledmouseX, scaledmouseY, widgets[i])) + { + lastWidgetOver.HandleMouseLeave(); + lastWidgetOverIndex = i; + lastWidgetOver.HandleMouseEnter(); + lastWidgetOver.HandleMouseOver(); + change = true; + break; + } + } + // not over anything + if (!change) + { + lastWidgetOver.HandleMouseLeave(); + lastWidgetOverIndex = -1; + } + } + } + else + { + // were dragging... release left mouse to let go + if (!input.mousedown(MouseButton.Left)) + { + lastWidgetOver.HandleMouseLeave(); + lastWidgetOverIndex = -1; + + // if we don't test again we'd have to wait till next frame to mouse over something... + // i think there a potential double mouse over / mouse enter here?? + for (int i = widgets.Count - 1; i > -1; i--) + { + if (widgets[i].Enable) + + if (isMouseWithin(scaledmouseX, scaledmouseY, widgets[i])) + { + lastWidgetOverIndex = i; + lastWidgetOver.HandleMouseEnter(); + lastWidgetOver.HandleMouseOver(); + break; + } + } + } + } + } + else + { + for (int i = widgets.Count - 1; i > -1; i--) + { + if (widgets[i].Enable) + + if (isMouseWithin(scaledmouseX, scaledmouseY, widgets[i])) + { + lastWidgetOverIndex = i; + lastWidgetOver.HandleMouseEnter(); + lastWidgetOver.HandleMouseOver(); + break; + } + } + } + + if (lastWidgetOverIndex == -1) + { + Singleton.INSTANCE.Enabled = true; + + if (Client.window.Cursor != Singleton.INSTANCE.currentBrush.Cursor) + Client.window.Cursor = Singleton.INSTANCE.currentBrush.Cursor; + } + else + { + Singleton.INSTANCE.Enabled = false; + + MouseCursor cursor = lastWidgetOver.cursor != null ? lastWidgetOver.cursor : MouseCursor.Default; + + if (Client.window.Cursor != cursor) + Client.window.Cursor = cursor; + } + } + } + + public void Render() + { + if (!Visible) return; + + GL.Disable(EnableCap.DepthTest); + + Setup2D(); + + if (Dirty) + { + Dirty = false; + + //if (lastWidgetOverIndex >-1) + //{ + // Label status = Get