├── Examples └── readme.md ├── Images ├── readme.md └── ExampleFpsMgSbTextBounder.png ├── LICENSE ├── CODE_OF_CONDUCT.md ├── README.md ├── Game1_FpsMgStringBuilderTextBounding.cs ├── MakeMeAnAssetClassFile.cs ├── MgFrameRate.cs ├── SurfaceMesh.cs ├── TextWrapping.cs ├── MouseUserInput.cs ├── PerspectiveProjectionSpriteBatch.cs ├── MgTextBounder.cs ├── SpriteFontConverter.cs └── MgStringBuilder.cs /Examples/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Images/readme.md: -------------------------------------------------------------------------------- 1 | This will just list the images i have placed here. 2 | -------------------------------------------------------------------------------- /Images/ExampleFpsMgSbTextBounder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willmotil/MonoGameUtilityClasses/HEAD/Images/ExampleFpsMgSbTextBounder.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 willmotil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at willmotil@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MonoGameUtilityClasses 2 | 3 | Utility classes for monogame or c# 4 | 5 | Note: (the FrameRate class uses the MgStringBuilder to avoid garbage collections). 6 | 7 | Listing... 8 | 9 | _____________________________________ 10 | 11 | Game1_Fps... 12 | 13 | This class shows the usage of the MgFrameRate. MgStringBuilder. MgTextBounder. 14 | 15 | You can put all of these into a MonoGameProject change the namespace for game1 16 | Make a font named MgFont and try it out. 17 | This project was tested on GL it should work on Dx as well. 18 | 19 | 20 | 21 | The old gl project 22 | 23 | https://drive.google.com/open?id=1KSrPglaYow8pVORL315pYaV6fb6MlpU0 24 | 25 | _____________________________________ 26 | 27 | MgStringBuilder. 28 | 29 | This class is a wrapper around stringbuilder it can be used in place of stringbuilder in most cases. 30 | It can take or return a stringbuilder to work with it were necessary. 31 | It also has quite a few convenient operator overloads. 32 | This class can bypass many garbage collections that stingbuilder doesn't, especially for numerical appends and inserts. 33 | This class bypasses numeric.ToString() which causes garbage collections in C# itself, which in turn affect monogame. 34 | The performance of inserting is not as great due to the work arounds that are done using the stringbuilder indexer. 35 | The class despite the size which is unrolled, is pretty performant otherwise, i use this myself constantly. 36 | 37 | If you were to rip out the Monogame specific Append Methods. (Vectors Rectangle Color) 38 | Then this class can be used as a standalone c# class. 39 | In that case you should be aware that super high precision or extremely large values may cause garbage as i have limited the number of digits checked reasonably to trade off for perfomance before defaulting to just letting stringbuilder append. 40 | In practice this is doesn't happen as the number of digits set is pretty high, but it could. 41 | 42 | _____________________________________ 43 | 44 | MgFrameRate. 45 | 46 | This class is used to display framerate. 47 | The class displays basic information about fps frame and update rates draw. 48 | It also displays useful memory usage info in the game window, when collections occur and how much is being lost. 49 | The class is setup typically in load. 50 | You call to it in update and draw, that is all that is required other then that you have loaded a font. 51 | 52 | _____________________________________ 53 | 54 | MgTextBounder. 55 | 56 | This class (like word wrap) takes a StringBuilder and wraps it to fit into a specified rectangle. 57 | This class is new, and will probably see many more additions and revisions. 58 | Unlike measure string this class copys or directly alters a stringbuilder to format it to fit on the fly. 59 | This class was and is in another form a experimental prototype for a direct DrawString() overload. 60 | 61 | _____________________________________ 62 | 63 | TextWrapping 64 | 65 | This class is very similar to the above but it is a real time stand alone class and method. 66 | Measure string is not required as the method effectively includes it in the run but in a modified manner. 67 | Which instead word wraps a altered version then draws it. 68 | If used in combination with the mgstringbuilder no garbage allocations will be generated for dynamic text. 69 | 70 | _____________________________________ 71 | 72 | SurfaceMesh. 73 | 74 | Still not fully completed as its companion class is not done, who knows if i will ever get it done. 75 | This class takes a array of vector4's and treats them as if they are to be made into a VertexPositionNormalTexture array. 76 | It creates the u,v's along the surface proportionally to fit a single texture and creates smooth normals. 77 | The smooth normals are best used when the surface area has curvature for light reflection. 78 | 79 | _____________________________________ 80 | 81 | SpriteFontConverter. 82 | 83 | Takes a loaded SpriteFont and turns it into a class that can self load a instance of spritefont. 84 | This maybe useful maybe as a default font if something goes wrong. 85 | While im not too sure it's really useful at all it is a neet little class. 86 | It uses a simple RLE algorithm to compress the pixel data values so the texture is also in the resulting class file. 87 | 88 | The class that is created is saved as a text file with a .cs extention that can be pulled into a visual studio project. 89 | When you run the project after instatiating the class instance. 90 | Load can be called on the instance to load the hardcoded spritefont. 91 | 92 | _____________________________________ 93 | 94 | WinFullscreenModeChangeTestApp. 95 | 96 | In the Tests Folder is also a full screen testing class. 97 | The full project for that can be found here. 98 | 99 | https://drive.google.com/open?id=1SLpOZWz_whcPEcxz4fTk9OUd8wu9xPbw 100 | -------------------------------------------------------------------------------- /Game1_FpsMgStringBuilderTextBounding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Microsoft.Xna.Framework.Input; 6 | using System.Collections.Generic; 7 | 8 | namespace YourNameSpaceHere 9 | { 10 | /// 11 | /// This MonoGame class demonstrates how to setup... 12 | /// The MgFrameRate class to see the fps and the associated garbage collection rates. 13 | /// The text bounding class to wrap text. 14 | /// The MgStringBuilder class is shown to be used to avoid garbage collections at runtime. 15 | /// 16 | public class Game1 : Game 17 | { 18 | private GraphicsDeviceManager graphics; 19 | private SpriteBatch spriteBatch; 20 | SpriteFont font; 21 | 22 | MgFrameRate fps = new MgFrameRate(); 23 | Rectangle textBoundedArea; 24 | MgStringBuilder originalText = "This is a MgStringBuilder a wrapper around string builder"; 25 | 26 | public Game1() 27 | { 28 | graphics = new GraphicsDeviceManager(this); 29 | Content.RootDirectory = "Content"; 30 | } 31 | 32 | protected override void Initialize() 33 | { 34 | base.Initialize(); 35 | } 36 | 37 | protected override void LoadContent() 38 | { 39 | spriteBatch = new SpriteBatch(GraphicsDevice); 40 | 41 | // 42 | // Add a font this ones created thru the pipeline 43 | // 44 | font = Content.Load("MgFont"); 45 | 46 | // 47 | // Set the current font to the text bounding font so it can operate on the text. 48 | // 49 | MgTextBounder.SetSpriteFont(font); 50 | 51 | // 52 | // Set up the fps class. 53 | // by passing your desired settings for, 54 | // showing the mouse, resizing, desired frames per second ect. 55 | // 56 | fps.LoadSetUp(this, graphics, spriteBatch, true, true, false, false, 100d); 57 | 58 | } 59 | 60 | protected override void UnloadContent() { } 61 | 62 | protected override void Update(GameTime gameTime) 63 | { 64 | KeyboardState state = Keyboard.GetState(); 65 | if (state.IsKeyDown(Keys.Escape)) 66 | Exit(); 67 | 68 | // Update fps 69 | // 70 | fps.Update(gameTime); 71 | 72 | base.Update(gameTime); 73 | } 74 | 75 | protected override void Draw(GameTime gameTime) 76 | { 77 | GraphicsDevice.Clear(Color.Moccasin); 78 | 79 | spriteBatch.Begin(); 80 | 81 | // 82 | // Draw the fps msg 83 | // 84 | fps.DrawFps(spriteBatch, font, new Vector2(10f, 10f), Color.MonoGameOrange); 85 | 86 | // 87 | // Uncomment to test no garbage word wrapping. 88 | // This is of course faster if it is pre-computed where possible. 89 | // 90 | RunTimeWordWrapping(gameTime); 91 | 92 | spriteBatch.End(); 93 | 94 | base.Draw(gameTime); 95 | } 96 | 97 | // Usage of the MgStringBuilder wrapper to make some text. 98 | public void RunTimeWordWrapping(GameTime gameTime) 99 | { 100 | // ______________________________________________________________________ 101 | // Here we do some word wrapping. 102 | 103 | // 104 | // Make a rectangle text area and some text. 105 | // 106 | textBoundedArea = new Rectangle(200, 200, 300, 90); 107 | 108 | // 109 | // Repeatedly make a messege every frame. 110 | // 111 | originalText.Clear(); 112 | var gt = (float)(gameTime.TotalGameTime.TotalSeconds); 113 | originalText.Append("Hello this is a mgsb test. ").AppendLine("The pupose of which is to demonstrate the fps and text rectangle wrapping functions. ").Append("In a Dynamic context ").AppendTrim(gt); 114 | 115 | // 116 | // Call the function on a MgStringBuilder object. 117 | // Note while this can work for stringbuilder... 118 | // You have to do it a specific way to not get garbage specifically precalculate it. 119 | // 120 | MgTextBounder.WordWrapTextWithinBounds(ref originalText, Vector2.One, textBoundedArea); 121 | 122 | // 123 | // Draw the background rectangle and the wrapped text. 124 | // 125 | spriteBatch.Draw(fps.dotTexture, textBoundedArea, Color.AntiqueWhite); 126 | 127 | // 128 | // Draw the wrapped text to the bounded xy position. 129 | // 130 | Vector2 pos = new Vector2(textBoundedArea.X, textBoundedArea.Y); 131 | 132 | spriteBatch.DrawString(font, originalText, pos, Color.Blue); 133 | } 134 | 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /MakeMeAnAssetClassFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | 6 | namespace Microsoft.Xna.Framework//SpriteFontEditToClassSomethingOrOther 7 | { 8 | 9 | //// the file is used in game 1 as a method like so it will build a class that will list the content 10 | ///// 11 | ///// It probably needs a bit more work but its not super important just a nicety. 12 | ///// I should loop thru the content sub folders but for now i just have to specify each folder to load. 13 | ///// Still it's pretty handy because when i use it i just have to type Assets. 14 | ///// It also puts copys of the types into lists 15 | ///// 16 | //public void CreateOpenAssetClassTxtFile() 17 | //{ 18 | // // This is just for a new project its pretty cool 19 | // 20 | // MakeMeAnAssetClassFile.FolderToAddToTheAssets(Content); 21 | // 22 | // // or 23 | // 24 | // //MakeMeAnAssetClassFile.Prefix = "Ui_"; 25 | // //MakeMeAnAssetClassFile.FolderToAddToTheAssets("UiImages", Content); 26 | // //MakeMeAnAssetClassFile.WriteTheFileAndDisplayIt(); 27 | //} 28 | 29 | /// 30 | /// 31 | /// 32 | public static class MakeMeAnAssetClassFile 33 | { 34 | public static string Prefix { get; set; } = ""; 35 | //public static string AssetType { get; set; } = "Texture2D" ; 36 | // they all end up being xnbs so this doesn't work out to well to specify types. 37 | public static string[] FileTypesToGet { get; set; } = { "any" }; 38 | 39 | public static string header = 40 | "\n" + "using System;" 41 | +"\n" + "using Microsoft.Xna.Framework;" 42 | +"\n" + "using Microsoft.Xna.Framework.Graphics; " 43 | +"\n" + "using System.Collections.Generic;" 44 | + "\n" 45 | + "\n" +"namespace Microsoft.Xna.Framework" 46 | +"\n{" 47 | +"\n public static class Asset" 48 | +"\n {" 49 | ; 50 | public static string closing = 51 | "\n\n } \n}" 52 | ; 53 | public static string msg = ""; 54 | 55 | /// 56 | /// using System; 57 | /// using System.IO; 58 | /// This is a little cheating method so you don't have to type out everything in the content folder. 59 | /// you get a temp.txt file in my documents that is formatted to copy paste below game1 or into a new class. 60 | /// The file should automatically open and display. 61 | /// 62 | public static void GenerateContentAssetsFile(Microsoft.Xna.Framework.Content.ContentManager content) 63 | { 64 | FolderToAddToTheAssets("", content); 65 | WriteTheFileAndDisplayIt(); 66 | } 67 | /// 68 | /// using System; 69 | /// using System.IO; 70 | /// This is a little cheating method so you don't have to type out everything in the content folder. 71 | /// you get a temp.txt file in my documents that is formatted to copy paste into game 1 72 | /// 73 | public static void FolderToAddToTheAssets(string folder, Microsoft.Xna.Framework.Content.ContentManager content) 74 | { 75 | string envpath = Environment.CurrentDirectory; 76 | string contentpath = content.RootDirectory; 77 | string path = Path.Combine(contentpath, folder); 78 | content.RootDirectory = path; 79 | string FileSearchPath = Path.Combine(envpath, path); 80 | string[] filesPrefixs; 81 | string[] filesClassTypes; 82 | string[] files; 83 | GetFileNamesInFolderWithoutExt(FileSearchPath, out files, out filesClassTypes, out filesPrefixs); 84 | content.RootDirectory = contentpath; 85 | string itemsprefix = Prefix; 86 | 87 | string tempword = path.Replace('/', '_'); 88 | tempword = tempword.Replace(Path.DirectorySeparatorChar, '_'); 89 | tempword = tempword.Replace(Path.AltDirectorySeparatorChar, '_'); 90 | tempword = tempword.Replace(Path.PathSeparator, '_'); 91 | 92 | msg += "\n\n"; 93 | msg += "\n #region "+Prefix+" assets in " + tempword; 94 | msg += "\n"; 95 | msg += "\n " + "public static ListTexture2DList = new List();"; 96 | msg += "\n " + "public static ListSpriteFontList = new List();"; 97 | msg += "\n " + "public static ListEffectList = new List();"; 98 | msg += "\n"; 99 | for (int i = 0; i < files.Length; i++) 100 | { 101 | msg += "\n " + "public static "+ filesClassTypes[i] + " " + Prefix + filesPrefixs[i] + files[i] + ";"; 102 | } 103 | msg += "\n"; 104 | // the part were its loaded 105 | msg += "\n" + " public static void LoadFrom_"+ tempword + "( Microsoft.Xna.Framework.Content.ContentManager Content )"; 106 | msg += "\n {"; 107 | msg += "\n Content.RootDirectory = " + '@' + '"' + path + '"' + ";"; 108 | msg += "\n"; 109 | for (int i = 0; i < files.Length; i++) 110 | { 111 | msg += "\n " + Prefix + filesPrefixs[i] + files[i] + " = Content.Load<"+ filesClassTypes[i] + ">( " + '"' + files[i] + '"' + ");"; 112 | } 113 | msg += "\n"; 114 | for (int i = 0; i < files.Length; i++) 115 | { 116 | msg += "\n " + filesClassTypes[i] + "List.Add("+Prefix + filesPrefixs[i] + files[i] + ");"; 117 | } 118 | msg += "\n"; 119 | msg += "\n\n Content.RootDirectory = " + '@' + '"' + contentpath + '"' + ";"; 120 | msg += "\n }"; 121 | msg += "\n\n #endregion"; 122 | } 123 | 124 | public static void WriteTheFileAndDisplayIt() 125 | { 126 | string fullpath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "temp.txt"); 127 | File.WriteAllText(fullpath, (header + msg + closing)); 128 | Process.Start(fullpath); 129 | } 130 | 131 | /// 132 | /// Gets the files in a specific directory. 133 | /// 134 | private static void GetFileNamesInFolderWithoutExt(string path, out string[] nameArray, out string[] xnbTypeArray, out string[] prefixArray) 135 | { 136 | string[] prefixarray; 137 | string[] typearray; 138 | string[] namearray; 139 | if (path != null) 140 | { 141 | if (File.Exists(path)) 142 | path = PathRemoveFileName(path); 143 | // Use any all to get all the file types. 144 | namearray = Directory.GetFiles(path); // this returns the full path 145 | typearray = new string[namearray.Length]; 146 | prefixarray = new string[namearray.Length]; 147 | int i = 0; 148 | while (i < namearray.Length) 149 | { 150 | namearray[i] = Path.GetFileName(namearray[i]); 151 | string a; 152 | string b; 153 | DetermineXnbType(path, namearray[i], out a, out b); 154 | typearray[i] = a; 155 | prefixarray[i] = b; 156 | // amend the name array. 157 | namearray[i] = Path.GetFileNameWithoutExtension(namearray[i]); 158 | i++; 159 | } 160 | } 161 | else 162 | { 163 | namearray = new string[0]; 164 | typearray = new string[0]; 165 | prefixarray = new string[0]; 166 | } 167 | nameArray = namearray; 168 | xnbTypeArray = typearray; 169 | prefixArray = prefixarray; 170 | } 171 | /// 172 | /// Gets the files of a given type (ie.. png ect...) in a specific directory use "any" to get them all. 173 | /// 174 | private static string[] GetFileNamesInFolderWithoutExt(string path, string[] filetypes) 175 | { 176 | string[] namearray; 177 | List parsedarray = new List(); 178 | if (path != null && filetypes.Length > 0) 179 | { 180 | if (File.Exists(path)) 181 | path = PathRemoveFileName(path); 182 | // Use any all to get all the file types. 183 | namearray = Directory.GetFiles(path); // this returns the full path 184 | int i = 0; 185 | while (i < namearray.Length) 186 | { 187 | int j = 0; 188 | while (j < filetypes.Length) 189 | { 190 | namearray[i] = Path.GetFileName(namearray[i]); 191 | string comp = Path.GetExtension(namearray[i]); 192 | if (filetypes[j] == comp) 193 | { 194 | namearray[i] = Path.GetFileNameWithoutExtension(namearray[i]); 195 | parsedarray.Add(namearray[i]); 196 | } 197 | j++; 198 | } 199 | i++; 200 | } 201 | } 202 | else 203 | { 204 | namearray = new string[0]; 205 | } 206 | return parsedarray.ToArray(); 207 | } 208 | /// 209 | /// return just the path without the filename 210 | /// 211 | private static string PathRemoveFileName(string filenameorfolder) 212 | { 213 | return Path.GetDirectoryName(filenameorfolder); 214 | } 215 | 216 | /// 217 | /// just opens the xnb and searches for a string that matches a known type not the best way to do it but its fine. 218 | /// 219 | private static void DetermineXnbType(string directoryPath, string filename, out string filetype, out string prefix) 220 | { 221 | string xnbfiletype = "- Unknown"; 222 | string prefixAbr = "- Unknown_"; 223 | string fullfilePath = Path.Combine(directoryPath, filename); 224 | var textLinesArray = File.ReadAllLines(fullfilePath); 225 | string test = ""; 226 | if (textLinesArray.Length > 0) 227 | test = textLinesArray[0]; 228 | if (textLinesArray.Length > 1) 229 | test += textLinesArray[1]; 230 | 231 | if (test.Contains("Texture2D")) 232 | { 233 | xnbfiletype = "Texture2D"; 234 | prefixAbr = "texture_"; 235 | } 236 | if (test.Contains("Effect")) 237 | { 238 | xnbfiletype = "Effect"; 239 | prefixAbr = "effect_"; 240 | } 241 | if (test.Contains("SpriteFont")) 242 | { 243 | xnbfiletype = "SpriteFont"; 244 | prefixAbr = "font_"; 245 | } 246 | filetype = xnbfiletype; 247 | prefix = prefixAbr; 248 | 249 | Console.WriteLine(filename + " xnb Type:" + xnbfiletype);// + " data: " + test); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /MgFrameRate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | //using System.Text; 4 | //using System.Linq; 5 | //using System.Collections.Generic; 6 | //using Microsoft.Xna.Framework; 7 | using Microsoft.Xna.Framework.Graphics; 8 | using Microsoft.Xna.Framework.Input; 9 | using System.Diagnostics; 10 | //using System.Reflection; 11 | ////using System.Xml.Serialization; 12 | 13 | namespace Microsoft.Xna.Framework 14 | { 15 | /// 16 | /// A more detailed frameRate class. 17 | /// 18 | public class MgFrameRate 19 | { 20 | public Texture2D dotTexture; 21 | SpriteBatch spriteBatch; 22 | MgStringBuilder msg = new MgStringBuilder(); 23 | MgStringBuilder fpsmsg = new MgStringBuilder(); 24 | MgStringBuilder gcmsg = new MgStringBuilder(); 25 | 26 | public bool DisplayFrameRate = true; 27 | public bool DisplayGarbageCollectionRate = true; 28 | public bool DisplayVisualizations = true; 29 | public double DisplayedMessageFrequency = 1.0d; 30 | public bool DisplayCollectionAlert = true; 31 | 32 | //private long gcDisplayDelay = 0; 33 | 34 | private double fps = 0d; 35 | private double frames = 0; 36 | private double updates = 0; 37 | private double ufRatio = 1f; 38 | private double elapsed = 0; 39 | private double last = 0; 40 | private double now = 0; 41 | 42 | private long gcNow = 0; 43 | private long gcLast = 0; 44 | private long gcDiff = 0; 45 | private long gcAccumulatedInc = 0; 46 | private long gcAccumulatedSinceLastCollect = 0; 47 | private long gcTotalLost = 0; 48 | private long gcSizeOfLastCollect = 0; 49 | private long gcRecordedCollects = 0; 50 | 51 | 52 | private const double MEGABYTE = 1048576d; 53 | 54 | 55 | public void LoadSetUp(Game pass_in_this, GraphicsDeviceManager gdm, SpriteBatch spriteBatch, bool allowresizing, bool showmouse, bool fixedon, bool vsync, double desiredFramesPerSecond, bool displayGc) 56 | { 57 | this.spriteBatch = spriteBatch; 58 | DisplayGarbageCollectionRate = displayGc; 59 | dotTexture = TextureDotCreate(pass_in_this.GraphicsDevice); 60 | pass_in_this.Window.AllowUserResizing = allowresizing; 61 | pass_in_this.IsMouseVisible = showmouse; 62 | pass_in_this.IsFixedTimeStep = fixedon; 63 | if (fixedon) 64 | { 65 | //pass_in_this.TargetElapsedTime = TimeSpan.FromSeconds(1d / desiredFramesPerSecond); 66 | pass_in_this.TargetElapsedTime = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / desiredFramesPerSecond)); 67 | } 68 | gdm.SynchronizeWithVerticalRetrace = vsync; 69 | gdm.ApplyChanges(); 70 | } 71 | 72 | /// 73 | /// The msgFrequency here is the reporting time to update the message. 74 | /// 75 | public void Update(GameTime gameTime) 76 | { 77 | //now = gameTime.TotalGameTime.TotalSeconds; // TimeSpan.FromTicks(166666); 78 | now = (double)gameTime.TotalGameTime.Ticks / (double)TimeSpan.TicksPerSecond; 79 | elapsed = (double)(now - last); 80 | 81 | // fps msg's 82 | if (elapsed >= DisplayedMessageFrequency) // || (gcDiff != 0) 83 | { 84 | // time 85 | if (DisplayFrameRate) 86 | { 87 | fps = (frames / elapsed); 88 | ufRatio = (float)frames / (float)updates; 89 | 90 | fpsmsg.Clear(); 91 | fpsmsg.Append(" Minutes Running: ").AppendTrim(now / 60d, 3).AppendLine(); // .Append(now).AppendLine(); 92 | fpsmsg.Append(" Fps: ").AppendTrim(fps).AppendLine(); 93 | fpsmsg.Append(" Draw to Update Ratio: ").AppendTrim(ufRatio).AppendLine(); 94 | fpsmsg.Append(" Elapsed interval: ").AppendTrim(elapsed).AppendLine(); 95 | fpsmsg.Append(" Interval error: ").Append(elapsed - DisplayedMessageFrequency).AppendLine(); 96 | fpsmsg.Append(" Updates: ").Append(updates).AppendLine(); 97 | fpsmsg.Append(" Frames: ").Append(frames).AppendLine(); 98 | 99 | //elapsed -= msgFrequency; 100 | frames = 0; 101 | updates = 0; 102 | last = now; 103 | } 104 | // Gc Messages 105 | if (DisplayGarbageCollectionRate) 106 | { 107 | gcLast = gcNow; 108 | gcNow = GC.GetTotalMemory(false); 109 | gcDiff = gcNow - gcLast; 110 | 111 | // give the app a little time to load and let the gc run a bit. 112 | if (now < 5d) 113 | { 114 | gcSizeOfLastCollect = 0; 115 | gcTotalLost = 0; 116 | gcRecordedCollects = 0; 117 | gcAccumulatedInc = 0; 118 | gcAccumulatedSinceLastCollect = 0; 119 | gcLast = gcNow; 120 | } 121 | 122 | gcmsg.Clear(); 123 | gcmsg.Append(" GC Memory(Mb) Now: ").AppendTrim((double)gcNow / MEGABYTE).AppendLine(); 124 | 125 | if (gcDiff == 0) 126 | { 127 | gcmsg.AppendLine(" GC Memory(Mb) No change"); 128 | } 129 | if (gcDiff < 0) 130 | { 131 | gcmsg.AppendLine(" !!! COLLECTION OCCURED !!! ").Append(" GC Memory(Mb) Lost: ").AppendTrim((double)(gcDiff) / MEGABYTE).AppendLine(); 132 | var tmp = -gcDiff; 133 | gcSizeOfLastCollect = tmp; 134 | gcTotalLost += tmp; 135 | gcRecordedCollects++; 136 | gcAccumulatedInc += tmp; 137 | gcAccumulatedSinceLastCollect = 0; 138 | gcDiff = 0; 139 | gcLast = gcNow; 140 | } 141 | if (gcDiff > 0) 142 | { 143 | gcmsg.Append(" GC Memory(Mb) Increased: ").AppendTrim((double)(gcDiff) / MEGABYTE).AppendLine(); 144 | // 145 | //if (GcTrackingDelayCompleted) 146 | //{ 147 | gcAccumulatedInc += gcDiff; 148 | gcAccumulatedSinceLastCollect += gcDiff; 149 | gcLast = gcNow; 150 | //} 151 | } 152 | gcmsg.Append(" GC Memory(Mb) AccumulatedInc: ").AppendTrim(gcAccumulatedInc / MEGABYTE, 6).AppendLine(); 153 | if (gcRecordedCollects > 0) 154 | { 155 | gcmsg.Append(" GC Memory(Mb) Accumulated Since Collect: ").AppendTrim(gcAccumulatedSinceLastCollect / MEGABYTE).AppendLine(); 156 | gcmsg.Append(" GC Memory(Mb) Last Lost Collection: ").AppendTrim(gcSizeOfLastCollect / MEGABYTE).AppendLine(); 157 | gcmsg.Append(" GC Memory(Mb) Total Lost to Collections: ").AppendTrim(gcTotalLost / MEGABYTE).AppendLine(); 158 | } 159 | gcmsg.Append(" GC Number Of Memory Collections: ").AppendTrim(gcRecordedCollects).AppendLine(); 160 | } 161 | msg.Clear(); 162 | msg.Append(fpsmsg).Append(gcmsg); 163 | } 164 | updates++; 165 | } 166 | 167 | public void DrawFps(SpriteBatch spritebatch, SpriteFont font, Vector2 fpsDisplayPosition, Color fpsTextColor) 168 | { 169 | if (DisplayVisualizations) 170 | { 171 | DrawVisualizations(fpsTextColor); 172 | fpsDisplayPosition.Y += 10; 173 | } 174 | spriteBatch = spritebatch; 175 | if (DisplayCollectionAlert && gcRecordedCollects > 0) 176 | spriteBatch.DrawString(font, msg, fpsDisplayPosition, new Color(1f, fpsTextColor.G, fpsTextColor.B, fpsTextColor.A)); 177 | else 178 | spriteBatch.DrawString(font, msg, fpsDisplayPosition, fpsTextColor); 179 | frames++; 180 | } 181 | 182 | private void DrawVisualizations(Color fpsTextColor) 183 | { 184 | var dist = 600d; 185 | var vmsrheight = 4; 186 | var vmsrWidth = 30; 187 | Rectangle visualMotionStutterRect = new Rectangle((int)(dist * elapsed) + 5, 2, vmsrWidth, vmsrheight); 188 | Rectangle visualMotionStutterRect2 = new Rectangle((int)(dist - (dist * elapsed)) + 5, 2, vmsrWidth, vmsrheight); 189 | DrawFilledSquare(visualMotionStutterRect, Color.LemonChiffon); 190 | DrawFilledSquare(visualMotionStutterRect2, Color.LemonChiffon); 191 | 192 | var visualFps = fps; 193 | if (visualFps > 120) { visualFps = 120d + (visualFps / 20d); } 194 | Rectangle visualFpsRect = new Rectangle(10 + (int)(visualFps), 10, 4, 2); 195 | 196 | var visualFrame = frames / elapsed; 197 | if (visualFrame > 120) { visualFrame = 120d + (visualFrame / 20d); } 198 | Rectangle visualFrameRect = new Rectangle(10 + (int)(visualFrame), 12, 4, 2); 199 | 200 | DrawSquare(visualFpsRect, 3, Color.Aqua); 201 | DrawSquare(visualFrameRect, 3, Color.Blue); 202 | DrawRaySegment(new Vector2(15, 15), 15, 2, (float)( (elapsed / DisplayedMessageFrequency) * Math.PI *2d), Color.Gray); 203 | 204 | var gcCoefficient = (150d / gcNow); 205 | var visualGcIncrease = (double)(gcAccumulatedInc * gcCoefficient); 206 | Rectangle visualGcRect = new Rectangle(10, 14, 150, 4); 207 | Rectangle visualGcIncreaseRect = new Rectangle(10, 15, (int)(visualGcIncrease), 2); 208 | 209 | DrawSquare(visualGcRect, 1, Color.Orange); 210 | DrawSquare(visualGcIncreaseRect, 1, Color.Red); 211 | } 212 | 213 | 214 | public static Texture2D TextureDotCreate(GraphicsDevice device) 215 | { 216 | Color[] data = new Color[1]; 217 | data[0] = new Color(255, 255, 255, 255); 218 | return TextureFromColorArray(device, data, 1, 1); 219 | } 220 | public static Texture2D TextureFromColorArray(GraphicsDevice device, Color[] data, int width, int height) 221 | { 222 | Texture2D tex = new Texture2D(device, width, height); 223 | tex.SetData(data); 224 | return tex; 225 | } 226 | public void DrawSquare(Rectangle r, int lineThickness, Color c) 227 | { 228 | Rectangle TLtoR = new Rectangle(r.Left, r.Top, r.Width, lineThickness); 229 | Rectangle BLtoR = new Rectangle(r.Left, r.Bottom - lineThickness, r.Width, lineThickness); 230 | Rectangle LTtoB = new Rectangle(r.Left, r.Top, lineThickness, r.Height); 231 | Rectangle RTtoB = new Rectangle(r.Right - lineThickness, r.Top, lineThickness, r.Height); 232 | spriteBatch.Draw(dotTexture, TLtoR, c); 233 | spriteBatch.Draw(dotTexture, BLtoR, c); 234 | spriteBatch.Draw(dotTexture, LTtoB, c); 235 | spriteBatch.Draw(dotTexture, RTtoB, c); 236 | } 237 | public void DrawFilledSquare(Rectangle r, Color c) 238 | { 239 | spriteBatch.Draw(dotTexture, r, c); 240 | } 241 | public void DrawRaySegment(Vector2 postion, int length, int linethickness, float rot, Color c) 242 | { 243 | rot += 3.141592f; 244 | Rectangle screendrawrect = new Rectangle((int)postion.X, (int)postion.Y, linethickness, length); 245 | spriteBatch.Draw(dotTexture, screendrawrect, new Rectangle(0, 0, 1, 1), c, rot, Vector2.Zero, SpriteEffects.None, 0); 246 | } 247 | } 248 | } 249 | 250 | -------------------------------------------------------------------------------- /SurfaceMesh.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | using Microsoft.Xna.Framework.Input; 5 | 6 | namespace Microsoft.Xna.Framework 7 | { 8 | /// 9 | /// Will Motill 2016. - 2018 10 | /// This class provides methods to build indexed triangle meshes from a grid of positional points. 11 | /// It also provides the ability to create smooth normals and generate simple tangents for normal mapping. 12 | /// 13 | public class SurfaceMesh 14 | { 15 | public bool showConsoleInfo = false; 16 | public bool invertXtextureCoordinatesCreation = false; 17 | public bool invertYtextureCoordinatesCreation = true; 18 | public bool invertNormalsOnCreation = false; 19 | int surfacePointWidth = 0; 20 | 21 | public VertexPositionNormalTextureTangent[] vertices; 22 | public int[] indices; 23 | 24 | public void CreateSurfaceMesh(Vector4[] inputSurfaceVectors, int w, int h) 25 | { 26 | surfacePointWidth = w; 27 | indices = SetUpIndices(w, h); 28 | vertices = VertexFromVectorArray(w, h, inputSurfaceVectors); 29 | vertices = CreateSmoothNormals(vertices, indices); 30 | CreateTangents(); 31 | } 32 | 33 | public void CreateSurfaceMesh(Vector4[] inputSurfaceVectors, int w, int h, out VertexPositionNormalTextureTangent[] verts, out int[] indexs) 34 | { 35 | surfacePointWidth = w; 36 | indices = SetUpIndices(w, h); 37 | vertices = VertexFromVectorArray(w, h, inputSurfaceVectors); 38 | vertices = CreateSmoothNormals(vertices, indices); 39 | CreateTangents(); 40 | verts = vertices; 41 | indexs = indices; 42 | } 43 | 44 | public Vector4[] Vector4FromVector3Array(Vector3[] inputSurfaceVectors) 45 | { 46 | var len = inputSurfaceVectors.Length; 47 | Vector4[] vector4_surface_pos_ary = new Vector4[len]; 48 | for (int i = 0; i < inputSurfaceVectors.Length; i++) 49 | { 50 | vector4_surface_pos_ary[i] = new Vector4( 51 | inputSurfaceVectors[i].X, 52 | inputSurfaceVectors[i].Y, 53 | inputSurfaceVectors[i].Z, 54 | 1f 55 | ); 56 | } 57 | return vector4_surface_pos_ary; 58 | } 59 | 60 | int[] SetUpIndices(int w, int h) 61 | { 62 | int counter = 0; 63 | indices = new int[(w - 1) * (h - 1) * 6]; 64 | for (int y = 0; y < h - 1; y++) 65 | { 66 | for (int x = 0; x < w - 1; x++) 67 | { 68 | int topLeft = x + (y + 1) * w; 69 | int topRight = (x + 1) + (y + 1) * w; 70 | int lowerRight = (x + 1) + y * w; 71 | int lowerLeft = x + y * w; 72 | indices[counter++] = (int)topLeft; 73 | indices[counter++] = (int)lowerRight; 74 | indices[counter++] = (int)lowerLeft; 75 | indices[counter++] = (int)topLeft; 76 | indices[counter++] = (int)topRight; 77 | indices[counter++] = (int)lowerRight; 78 | } 79 | } 80 | return indices; 81 | } 82 | 83 | /// 84 | /// Couldn't really decide if i wanted this method to be public or not. 85 | /// 86 | VertexPositionNormalTextureTangent[] VertexFromVectorArray(int verts_width, int verts_height, Vector4[] inputSurfaceVectors) 87 | { 88 | surfacePointWidth = verts_width; 89 | VertexPositionNormalTextureTangent[] MyMeshVertices = new VertexPositionNormalTextureTangent[verts_width * verts_height]; 90 | vertices = new VertexPositionNormalTextureTangent[verts_width * verts_height]; 91 | for (int y = 0; y < verts_height; y++) 92 | { 93 | for (int x = 0; x < verts_width; x++) 94 | { 95 | int index = x + y * verts_width; 96 | float u = (float)x / (verts_width - 1); 97 | float v = (float)y / (verts_height - 1); 98 | if (invertXtextureCoordinatesCreation) { u = 1f - u; } 99 | if (invertYtextureCoordinatesCreation) { v = 1f - v; } 100 | var tmp = inputSurfaceVectors[index]; 101 | Vector3 vec = new Vector3(tmp.X, tmp.Y, tmp.Z); 102 | MyMeshVertices[index].Position = vec; 103 | MyMeshVertices[index].TextureCoordinate.X = u; 104 | MyMeshVertices[index].TextureCoordinate.Y = v; 105 | } 106 | } 107 | return MyMeshVertices; 108 | } 109 | 110 | /// 111 | /// This method creates smoothed normals from a indexed vertice mesh array. 112 | /// This loops thru the index array finding the each triangle connected to a vertice. 113 | /// It then calculates the normal for those triangles and averages them for the vertice in question. 114 | /// This method can deal with abritrary numbers of connected triangles 0 to n connections. 115 | /// 116 | VertexPositionNormalTextureTangent[] CreateSmoothNormals(VertexPositionNormalTextureTangent[] vertices, int[] indexs) 117 | { 118 | // For each vertice we must calculate the surrounding triangles normals, average them and set the normal. 119 | int tvertmultiplier = 3; 120 | int triangles = (int)(indexs.Length / tvertmultiplier); 121 | for (int currentTestedVerticeIndex = 0; currentTestedVerticeIndex < vertices.Length; currentTestedVerticeIndex++) 122 | { 123 | Vector3 sum = Vector3.Zero; 124 | float total = 0; 125 | for (int t = 0; t < triangles; t++) 126 | { 127 | int tvstart = t * tvertmultiplier; 128 | int tindex0 = tvstart + 0; 129 | int tindex1 = tvstart + 1; 130 | int tindex2 = tvstart + 2; 131 | var vindex0 = indices[tindex0]; 132 | var vindex1 = indices[tindex1]; 133 | var vindex2 = indices[tindex2]; 134 | if (vindex0 == currentTestedVerticeIndex || vindex1 == currentTestedVerticeIndex || vindex2 == currentTestedVerticeIndex) 135 | { 136 | var n0 = (vertices[vindex1].Position - vertices[vindex0].Position) * 10f; // * 10 math artifact avoidance. 137 | var n1 = (vertices[vindex2].Position - vertices[vindex1].Position) * 10f; 138 | var cnorm = Vector3.Cross(n0, n1); 139 | sum += cnorm; 140 | total += 1; 141 | } 142 | } 143 | if (total > 0) 144 | { 145 | var averagednormal = sum / total; 146 | averagednormal.Normalize(); 147 | if (invertNormalsOnCreation) 148 | averagednormal = -averagednormal; 149 | vertices[currentTestedVerticeIndex].Normal = averagednormal; 150 | } 151 | } 152 | return vertices; 153 | } 154 | 155 | void CreateTangents() 156 | { 157 | for(int i = 0; i < vertices.Length;i++) 158 | { 159 | int y = i / surfacePointWidth; 160 | int x = i - (y * surfacePointWidth); 161 | int up = (y - 1) * surfacePointWidth + x; 162 | int down = (y + 1) * surfacePointWidth + x; 163 | Vector3 tangent = new Vector3(); 164 | if(down >= vertices.Length) 165 | { 166 | tangent = vertices[up].Position - vertices[i].Position; 167 | tangent.Normalize(); 168 | vertices[i].Tangent = tangent; 169 | } 170 | else 171 | { 172 | tangent = vertices[i].Position - vertices[down].Position; 173 | tangent.Normalize(); 174 | vertices[i].Tangent = tangent; 175 | } 176 | } 177 | } 178 | 179 | /// 180 | /// Draws this surface mesh using user index primitives.. 181 | /// Doesn't set effect parameters or techniques. 182 | /// 183 | public void Draw(GraphicsDevice gd, Effect effect) 184 | { 185 | foreach (EffectPass pass in effect.CurrentTechnique.Passes) 186 | { 187 | pass.Apply(); 188 | gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionNormalTextureTangent.VertexDeclaration); 189 | } 190 | } 191 | 192 | public struct VertexPositionNormalTextureTangent : IVertexType 193 | { 194 | public Vector3 Position; 195 | public Vector3 Normal; 196 | public Vector2 TextureCoordinate; 197 | public Vector3 Tangent; 198 | 199 | public static VertexDeclaration VertexDeclaration = new VertexDeclaration 200 | ( 201 | new VertexElement(VertexElementByteOffset.PositionStartOffset(), VertexElementFormat.Vector3, VertexElementUsage.Position, 0), 202 | new VertexElement(VertexElementByteOffset.OffsetVector3(), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0), 203 | new VertexElement(VertexElementByteOffset.OffsetVector2(), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), 204 | new VertexElement(VertexElementByteOffset.OffsetVector3(), VertexElementFormat.Vector3, VertexElementUsage.Normal, 1) 205 | ); 206 | VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } } 207 | } 208 | /// 209 | /// This is a helper struct for tallying byte offsets 210 | /// 211 | public struct VertexElementByteOffset 212 | { 213 | public static int currentByteSize = 0; 214 | [STAThread] 215 | public static int PositionStartOffset() { currentByteSize = 0; var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; } 216 | public static int Offset(float n) { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; } 217 | public static int Offset(Vector2 n) { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; } 218 | public static int Offset(Color n) { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; } 219 | public static int Offset(Vector3 n) { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; } 220 | public static int Offset(Vector4 n) { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; } 221 | 222 | public static int OffsetFloat() { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; } 223 | public static int OffsetColor() { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; } 224 | public static int OffsetVector2() { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; } 225 | public static int OffsetVector3() { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; } 226 | public static int OffsetVector4() { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /TextWrapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | //using System.Text; 3 | using System.Collections.Generic; 4 | //using Microsoft.Xna.Framework; 5 | using Microsoft.Xna.Framework.Graphics; 6 | //using Microsoft.Xna.Framework.Input; 7 | 8 | 9 | namespace Microsoft.Xna.Framework 10 | { 11 | public static class TextWrapping 12 | { 13 | private static string newline = Environment.NewLine; 14 | private static MgStringBuilder text = new MgStringBuilder(); 15 | private static Dictionary spriteFonts = new Dictionary(); 16 | private static SpriteFontItem lastSpriteFontItem; 17 | private static int lastHashCode = -1; 18 | 19 | /// 20 | /// This method wraps and cuts off the text within the Rectangle boundry this is a real time function. 21 | /// It is not as efficient as precomputing a altered stringbuilder then just drawing it. 22 | /// Effectively this can be considered at a cost of two drawstrings 23 | /// 24 | public static void DrawWordWrapTextWithinBounds(MgStringBuilder stringText, SpriteBatch sb, SpriteFont sf, Vector2 scale, Rectangle boundRect, Color color) 25 | { 26 | var sfc = SpriteFontItem.PrepFont(sf); 27 | text.Clear(); 28 | text.Append(stringText); 29 | var _glyphs = sfc._glyphs; 30 | var Spacing = sfc.Spacing; 31 | var lineHeight = sfc.LineSpacing * scale.Y; 32 | Vector2 offset = Vector2.Zero; 33 | float yextent = offset.Y + lineHeight; 34 | Vector2 redrawOffset = Vector2.Zero; 35 | Rectangle dest = new Rectangle(); 36 | var currentGlyph = SpriteFont.Glyph.Empty; 37 | var firstGlyphOfLine = true; 38 | 39 | int lastWordBreakCharPos = 0; 40 | bool firstwordonline = true; 41 | Vector2 rewindOffset = Vector2.Zero; 42 | 43 | for (int i = 0; i < text.Length; i++) 44 | { 45 | char c = text[i]; 46 | 47 | if (c == '\r') 48 | continue; 49 | if (c == '\n') 50 | { 51 | offset.X = 0; 52 | offset.Y += lineHeight; 53 | yextent = offset.Y + lineHeight; 54 | firstGlyphOfLine = true; 55 | firstwordonline = true; 56 | continue; 57 | } 58 | 59 | if (_glyphs.ContainsKey(c)) 60 | currentGlyph = _glyphs[c]; 61 | else 62 | if (!sfc.tsf.DefaultCharacter.HasValue) 63 | throw new ArgumentException("Text Contains a Unresolvable Character"); 64 | else 65 | currentGlyph = sfc.defaultGlyph; 66 | 67 | // Solves the problem- the first character on a line with a negative left side bearing. 68 | if (firstGlyphOfLine) 69 | { 70 | offset.X = Math.Max(currentGlyph.LeftSideBearing, 0); 71 | firstGlyphOfLine = false; 72 | } 73 | else 74 | offset.X += Spacing + currentGlyph.LeftSideBearing; 75 | 76 | // matrix calculations unrolled rotation is excluded for this version 77 | var m = offset; 78 | m.X += currentGlyph.Cropping.X; 79 | m.Y += currentGlyph.Cropping.Y; 80 | 81 | dest = new Rectangle( 82 | (int)(m.X * scale.X), 83 | (int)(m.Y * scale.Y), 84 | (int)(currentGlyph.BoundsInTexture.Width * scale.X), 85 | (int)(currentGlyph.BoundsInTexture.Height * scale.Y) 86 | ); 87 | 88 | // Begin word wrapping operations. 89 | // if char is a word break character e.g. white space 90 | if (c == ' ') 91 | { 92 | lastWordBreakCharPos = i; 93 | rewindOffset = offset; 94 | if (firstwordonline) 95 | firstwordonline = false; 96 | } 97 | // word wrapping calculations. 98 | bool executeEndOffset = true; 99 | if (yextent > boundRect.Height) 100 | { 101 | // this essentially is function termination due to height boundry 102 | text.Length = i; 103 | i = text.Length; 104 | } 105 | else 106 | { 107 | if (dest.Right > boundRect.Width) 108 | { 109 | if (text.Length > (i + 1)) 110 | { 111 | if (firstwordonline == false) 112 | { 113 | if (text[lastWordBreakCharPos + 1] != '\n') 114 | { 115 | // i also pulled the +1 here which is i think inserting a improperly positioned like break using append at. 116 | text.AppendAt(lastWordBreakCharPos + 1, '\n'); // !!! insert here is a gcollection problem... fixed 117 | if (text[lastWordBreakCharPos + 1] != ' ') 118 | { 119 | offset = rewindOffset; 120 | i = lastWordBreakCharPos; 121 | } 122 | } 123 | } 124 | else // first word on the line true 125 | { 126 | if (text[i + 1] != '\n') // if its not already a new line char 127 | { 128 | text.AppendAt(i + 1, '\n'); 129 | } 130 | } 131 | } 132 | else 133 | { 134 | // handles the case were we cannot check the next char for a newline because it doesn't exist. 135 | text.AppendAt(lastWordBreakCharPos + 1, '\n'); 136 | if (text[lastWordBreakCharPos + 1] != ' ') 137 | { 138 | offset = rewindOffset; 139 | i = lastWordBreakCharPos; 140 | executeEndOffset = false; 141 | } 142 | } 143 | } 144 | } 145 | if (executeEndOffset) 146 | offset.X += currentGlyph.Width + currentGlyph.RightSideBearing; 147 | } 148 | sb.DrawString(sfc.tsf, text, boundRect.Location.ToVector2(), color); 149 | } 150 | 151 | /// 152 | /// This method wraps and cuts off the text within the Rectangle boundry. 153 | /// This method copys the textIn to Out and alters the textOut which can then be feed to spriteBatch. 154 | /// This isn't the greatest method for dynamic text but it will in general with prudence be a simple precomputation. 155 | /// 156 | public static void WordWrapTextWithinBounds(MgStringBuilder stringTextIn, MgStringBuilder stringTextOut, SpriteFont sf, Vector2 scale, Rectangle boundRect) 157 | { 158 | var sfc = SpriteFontItem.PrepFont(sf); 159 | stringTextOut.Clear(); 160 | stringTextOut.Append(stringTextIn); 161 | var _glyphs = sfc._glyphs; 162 | var Spacing = sfc.Spacing; 163 | var lineHeight = sfc.LineSpacing * scale.Y; 164 | Vector2 offset = Vector2.Zero; 165 | float yextent = offset.Y + lineHeight; 166 | Vector2 redrawOffset = Vector2.Zero; 167 | Rectangle dest = new Rectangle(); 168 | var currentGlyph = SpriteFont.Glyph.Empty; 169 | var firstGlyphOfLine = true; 170 | 171 | int lastWordBreakCharPos = 0; 172 | bool firstwordonline = true; 173 | Vector2 rewindOffset = Vector2.Zero; 174 | 175 | for (int i = 0; i < stringTextOut.Length; i++) 176 | { 177 | char c = stringTextOut[i]; 178 | 179 | if (c == '\r') 180 | continue; 181 | if (c == '\n') 182 | { 183 | offset.X = 0; 184 | offset.Y += lineHeight; 185 | yextent = offset.Y + lineHeight; 186 | firstGlyphOfLine = true; 187 | firstwordonline = true; 188 | continue; 189 | } 190 | 191 | if (_glyphs.ContainsKey(c)) 192 | currentGlyph = _glyphs[c]; 193 | else 194 | if (!sfc.tsf.DefaultCharacter.HasValue) 195 | throw new ArgumentException("Text Contains a Unresolvable Character"); 196 | else 197 | currentGlyph = sfc.defaultGlyph; 198 | 199 | // Solves the problem- the first character on a line with a negative left side bearing. 200 | if (firstGlyphOfLine) 201 | { 202 | offset.X = Math.Max(currentGlyph.LeftSideBearing, 0); 203 | firstGlyphOfLine = false; 204 | } 205 | else 206 | offset.X += Spacing + currentGlyph.LeftSideBearing; 207 | 208 | // matrix calculations unrolled rotation is excluded for this version 209 | var m = offset; 210 | m.X += currentGlyph.Cropping.X; 211 | m.Y += currentGlyph.Cropping.Y; 212 | 213 | dest = new Rectangle( 214 | (int)(m.X * scale.X), 215 | (int)(m.Y * scale.Y), 216 | (int)(currentGlyph.BoundsInTexture.Width * scale.X), 217 | (int)(currentGlyph.BoundsInTexture.Height * scale.Y) 218 | ); 219 | 220 | // Begin word wrapping operations. 221 | // if char is a word break character e.g. white space. 222 | // atm only spaces are legitiment line breaks 223 | if (c == ' ') 224 | { 225 | lastWordBreakCharPos = i; 226 | rewindOffset = offset; 227 | if (firstwordonline) 228 | firstwordonline = false; 229 | } 230 | // word wrapping calculations. 231 | bool executeEndOffset = true; 232 | if (yextent > boundRect.Height) 233 | { 234 | // this essentially is function termination due to height boundry 235 | stringTextOut.Length = i; 236 | i = stringTextOut.Length; 237 | } 238 | else 239 | { 240 | if (dest.Right > boundRect.Width) 241 | { 242 | // is there enough length for the next character is a new line check 243 | if (stringTextOut.Length > (i + 1)) 244 | { 245 | if (firstwordonline == false) 246 | { 247 | if (stringTextOut[lastWordBreakCharPos + 1] != '\n') // skips appending new line if the next line is a line break. 248 | { 249 | stringTextOut.AppendAt(lastWordBreakCharPos + 1, '\n'); // !!! insert here is a gcollection problem... fixed 250 | if (stringTextOut[lastWordBreakCharPos + 1] != ' ') 251 | { 252 | offset = rewindOffset; 253 | i = lastWordBreakCharPos; 254 | executeEndOffset = false; 255 | } 256 | } 257 | } 258 | else // first word on the line true 259 | { 260 | if (stringTextOut[i + 1] != '\n') // if its not already a new line char 261 | { 262 | stringTextOut.AppendAt(i + 1, '\n'); // !!! insert here is a gcollection problem... fixed via mystringbuilder 263 | } 264 | } 265 | } 266 | else 267 | { 268 | stringTextOut.AppendAt(lastWordBreakCharPos + 1, '\n'); // !!! insert here is a gcollection problem... fixed via mystringbuilder 269 | if (stringTextOut[lastWordBreakCharPos + 1] != ' ') 270 | { 271 | offset = rewindOffset; 272 | i = lastWordBreakCharPos; 273 | executeEndOffset = false; 274 | } 275 | } 276 | } 277 | } 278 | if (executeEndOffset) 279 | offset.X = offset.X + currentGlyph.Width + currentGlyph.RightSideBearing; 280 | } 281 | // we could return the string we sent in but i don't think i need to. 282 | } 283 | 284 | private class SpriteFontItem 285 | { 286 | public SpriteFont tsf; 287 | public Dictionary _glyphs; 288 | public SpriteFont.Glyph defaultGlyph; 289 | public char defaultfontchar = ' '; 290 | public float Spacing = 0f; 291 | public int LineSpacing = 10; 292 | public string newline = Environment.NewLine; 293 | 294 | 295 | public void SetSpriteFont(SpriteFont s) 296 | { 297 | tsf = s; 298 | _glyphs = tsf.GetGlyphs(); 299 | defaultGlyph = new SpriteFont.Glyph(); 300 | Spacing = tsf.Spacing; 301 | LineSpacing = tsf.LineSpacing; 302 | if (tsf.DefaultCharacter.HasValue) 303 | { 304 | defaultfontchar = (char)(tsf.DefaultCharacter.Value); 305 | defaultGlyph = _glyphs[defaultfontchar]; 306 | } 307 | } 308 | 309 | public static SpriteFontItem PrepFont(SpriteFont sf) 310 | { 311 | var hcode = sf.GetHashCode(); 312 | if (hcode != lastHashCode) 313 | { 314 | if (spriteFonts.ContainsKey(hcode) == false) 315 | { 316 | SpriteFontItem nval = new SpriteFontItem(); 317 | nval.SetSpriteFont(sf); 318 | spriteFonts.Add(hcode, nval); 319 | } 320 | lastSpriteFontItem = spriteFonts[hcode]; 321 | } 322 | return lastSpriteFontItem; 323 | } 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /MouseUserInput.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | using Microsoft.Xna.Framework.Input; 3 | 4 | namespace Microsoft.Xna.Framework 5 | { 6 | // mouse keyboard and text 7 | 8 | /// 9 | /// Will Motil 2010 last updated 2018 10 | /// 11 | public static class UserMouseInput 12 | { 13 | #region private methods or variables 14 | 15 | private static Vector2 ScreenSize { get; set; } 16 | private static Vector2 ScreenSizeMultiplier { get; set; } 17 | 18 | /// 19 | /// this is used in combination with another bool to prevent secondary updates per frame loop 20 | /// 21 | private static bool preventUpdatesRestOfThisFrame = false; 22 | /// 23 | /// when we actually are useing the preventSecondaryUpdates 24 | /// method we set this to true that allows for the prevent_update_rest_ofthisframe bool 25 | /// to be turned on and off as needed 26 | /// 27 | private static bool autoPreventMultipleUpdates_Flag = false; 28 | 29 | /// 30 | /// scroll wheel this will mainly affect the zoomvalue its multiplyed by a small number to get the below 31 | /// 32 | private static int mouseWheelValue = 0; 33 | /// 34 | /// the old mouse wheel value depending on the frequency of this call 35 | /// the discrepency between the two can be different 36 | /// 37 | private static int mouseOldWheelValue = 0; 38 | 39 | #endregion 40 | 41 | #region public methods or variables 42 | 43 | public static MouseState mouseState; 44 | 45 | /// 46 | /// set or get the X mouse position as a int 47 | /// 48 | public static int X 49 | { 50 | get; 51 | set; 52 | } 53 | /// 54 | /// set or get the Y mouse position as a int 55 | /// 56 | public static int Y 57 | { 58 | get; 59 | set; 60 | } 61 | /// 62 | /// This is calculated as the mouse position in relation to the size of the screens width height. 63 | /// 64 | public static Vector2 VirtualPos = new Vector2(0.0f, 0.0f);// could use 2 floats here instead but 65 | /// 66 | /// the position of the mouse as a point 67 | /// 68 | public static Point Pos = new Point(-1, -1); 69 | /// 70 | /// this can be used instead of the bool methods 71 | /// +1 if the wheel is getting rolled upwards 72 | /// -1 if the wheel is getting rolled downwards 73 | /// and 0 when wheel is not being rolled 74 | /// 75 | public static int mouseWheelUpOrDown = 0;// if its +1 mouse scoll wheel got moved up if its -1 it got scrolled down 76 | 77 | /// 78 | /// returns true if the wheel is being wheeled up 79 | /// 80 | public static bool WheelingUp 81 | { 82 | get 83 | { 84 | if (mouseWheelUpOrDown > 0) 85 | return true; 86 | else 87 | return false; 88 | } 89 | } 90 | /// 91 | /// returns true if the wheel is wheeling down 92 | /// 93 | public static bool WheelingDown 94 | { 95 | get 96 | { 97 | if (mouseWheelUpOrDown < 0) 98 | return true; 99 | else 100 | return false; 101 | } 102 | } 103 | /// 104 | /// if the mouse wheel is not being wheeled up or down we return true 105 | /// 106 | public static bool WheelAtRest() 107 | { 108 | if (mouseWheelUpOrDown == 0) 109 | return true; 110 | else 111 | return false; 112 | } 113 | 114 | #endregion 115 | 116 | #region Left ______________________________ 117 | 118 | /// 119 | /// is left being pressed now 120 | /// 121 | public static bool IsLeftDown = false; 122 | /// 123 | /// is left just clicked 124 | /// 125 | public static bool IsLeftClicked = false; 126 | /// 127 | /// is left being held down now 128 | /// 129 | public static bool IsLeftHeld = false; 130 | /// 131 | /// is true only in one single frame is the mouse just released 132 | /// 133 | public static bool IsLeftJustReleased = false; 134 | /// 135 | /// has the left mouse been dragged 136 | /// 137 | public static bool IsLeftDragged = false; 138 | /// 139 | /// left last position pressed while useing left mouse button 140 | /// 141 | public static Vector2 LastLeftPressedAt; 142 | /// 143 | /// left last position draged from before release while useing left mouse button 144 | /// 145 | public static Vector2 LastLeftDragReleased; 146 | /// 147 | /// Gets the direction and magnitude of left drag press to drag released 148 | /// 149 | public static Vector2 GetLeftDragVector() 150 | { 151 | return LastLeftDragReleased - LastLeftPressedAt; 152 | } 153 | /// 154 | /// Gets the left drag rectangle 155 | /// this method doesn't ensure there has been one 156 | /// 157 | public static Rectangle GetLeftDragRectangle() 158 | { 159 | int x1 = (int)LastLeftPressedAt.X, x2 = (int)LastLeftDragReleased.X, y1 = (int)LastLeftPressedAt.Y, y2 = (int)LastLeftDragReleased.Y; 160 | if (x1 > x2) { int temp = x1; x1 = x2; x2 = temp; } 161 | if (y1 > y2) { int temp = y1; y1 = y2; y2 = temp; } 162 | return new Rectangle(x1, y1, x2 - x1, y2 - y1); 163 | } 164 | 165 | #endregion 166 | 167 | #region Right ______________________________ 168 | 169 | /// 170 | /// is right being pressed now 171 | /// 172 | public static bool IsRghtDown = false; 173 | /// 174 | /// is left just clicked 175 | /// 176 | public static bool IsRightClicked = false; 177 | /// 178 | /// is right being held down now 179 | /// 180 | public static bool IsRightHeld = false; 181 | /// 182 | /// right is true only in one single frame is the mouse just released 183 | /// 184 | public static bool IsRightJustReleased = false; 185 | /// 186 | /// has the right mouse been dragged 187 | /// 188 | public static bool IsRightDragged = false; 189 | /// 190 | /// right last position pressed while useing left mouse button 191 | /// 192 | public static Vector2 LastRightPressedAt; 193 | /// 194 | /// right last position draged from before release while useing left mouse button 195 | /// 196 | public static Vector2 LastRghtDragReleased; 197 | /// 198 | /// Gets the direction and magnitude of left drag press to drag released 199 | /// 200 | public static Vector2 GetRightDragVector() 201 | { 202 | return LastRghtDragReleased - LastRightPressedAt; 203 | } 204 | /// 205 | /// Gets the left drag rectangle 206 | /// this method doesn't ensure there has been one 207 | /// 208 | public static Rectangle GetRightDragRectangle() 209 | { 210 | int x1 = (int)LastRightPressedAt.X, x2 = (int)LastRghtDragReleased.X, y1 = (int)LastRightPressedAt.Y, y2 = (int)LastRghtDragReleased.Y; 211 | if (x1 > x2) { int temp = x1; x1 = x2; x2 = temp; } 212 | if (y1 > y2) { int temp = y1; y1 = y2; y2 = temp; } 213 | return new Rectangle(x1, y1, x2 - x1, y2 - y1); 214 | } 215 | 216 | #endregion 217 | 218 | public static void PassTheScreenSize(int w, int h) 219 | { 220 | ScreenSize = new Vector2(w, h); 221 | ScreenSizeMultiplier = Vector2.One / ScreenSize; 222 | } 223 | 224 | /// 225 | /// When used this must be called seperately from update typically just before it 226 | /// 227 | public static void PreventMultipleUpdates() 228 | { 229 | autoPreventMultipleUpdates_Flag = true; 230 | preventUpdatesRestOfThisFrame = false; 231 | } 232 | 233 | /// 234 | /// Multi Frame logic function ( we dont time things here we rely on frame pass logic ) 235 | /// this fuction ONLY needs to and should be called 1 time per frame 236 | /// more then that will mess up the 237 | /// tracking of the mousejustreleased value 238 | /// get the mouse values and save them to track what the mouse is doing exactly 239 | /// 240 | public static void Update() 241 | { 242 | if (preventUpdatesRestOfThisFrame == false) 243 | { 244 | // grab the mouse state from the input class 245 | mouseState = Mouse.GetState(); 246 | // save the current mouse position into another variable for later use 247 | X = mouseState.Position.X; 248 | Y = mouseState.Position.Y; 249 | Pos = mouseState.Position; 250 | VirtualPos.X = (float)X * ScreenSizeMultiplier.X; 251 | VirtualPos.Y = (float)Y * ScreenSizeMultiplier.Y; 252 | 253 | // mouse wheel 254 | // 255 | mouseWheelUpOrDown = 0;// 0 is the state of no action on the wheel 256 | mouseWheelValue = mouseState.ScrollWheelValue; 257 | // 258 | if (mouseWheelValue < mouseOldWheelValue) 259 | { 260 | mouseWheelUpOrDown = -1; 261 | mouseOldWheelValue = mouseWheelValue; 262 | } 263 | else 264 | { 265 | if (mouseWheelValue > mouseOldWheelValue) 266 | { 267 | mouseWheelUpOrDown = +1; 268 | mouseOldWheelValue = mouseWheelValue; 269 | } 270 | } 271 | 272 | // mouse buttons 273 | // Processing when carrying out the left click of the mouse 274 | //_______________________________________________ 275 | if (mouseState.LeftButton == ButtonState.Pressed) 276 | { 277 | // NOTE THESE ARE PLACED IN A SPECIFIC ORDER TO ENSURE THAT DRAG CLICK LOGIC IS CORRECT 278 | // note to self thanks Never muck it up , (this is a composite multi frame logic function) 279 | // on the first pass the below will set the lastposmousepressed to the current position 280 | if (IsLeftHeld == false) // this executes on the first pass and the second pass but not the third pass 281 | { 282 | IsLeftClicked = true; 283 | if (IsLeftDown == true) // this condition will not execute untill the second pass thru the function 284 | { 285 | IsLeftHeld = true; // now we mark it as being held on the second pass 286 | IsLeftClicked = false; 287 | } 288 | else // this executes on the first pass only 289 | { 290 | LastLeftPressedAt.X = X; // we save the click of the first point of holding 291 | LastLeftPressedAt.Y = Y; // so when released we know were we draged from 292 | } 293 | } 294 | // set at the end of but still in the first pass 295 | IsLeftDown = true;// we SET this after the call to is leftbutton pressed now to ensure next pass is active 296 | } 297 | else 298 | { // mouse itself is no longer registering the button pressed so.. toggle held and button pressed off 299 | IsLeftDown = false; 300 | IsLeftJustReleased = false; // added this so i can get a ... just now released value 301 | IsLeftDragged = false; 302 | if (IsLeftHeld == true) 303 | { 304 | LastLeftDragReleased.X = X; 305 | LastLeftDragReleased.Y = Y; 306 | IsLeftJustReleased = true; // this gets reset to zero on next pass its good for one frame 307 | var ldr = GetLeftDragRectangle(); 308 | if (ldr.Width != 0 || ldr.Height != 0) 309 | IsLeftDragged = true; 310 | } 311 | IsLeftHeld = false; 312 | } 313 | 314 | // Processing when carrying out the right click of the mouse 315 | //________________________________________________ 316 | if (mouseState.RightButton == ButtonState.Pressed) 317 | { 318 | //NOTE THESE ARE PLACED IN A SPECIFIC ORDER TO ENSURE THAT DRAG CLICK LOGIC IS CORRECT 319 | // on the first pass the below will set the lastposmousepressed to the current position 320 | if (IsRightHeld == false) // this executes on the first pass and the second pass but not the third pass 321 | { 322 | IsRightClicked = true; 323 | if (IsRghtDown == true) // this condition will not execute untill the second pass thru the function 324 | { 325 | IsRightHeld = true; // now we mark it as being held on the second pass 326 | IsRightClicked = false; 327 | } 328 | else // this executes on the first pass only 329 | { 330 | LastRightPressedAt.X = X; // we save the click of the first point of holding 331 | LastRightPressedAt.Y = Y; // so when released we know were we draged from 332 | } 333 | } 334 | // set at the end of the first pass 335 | IsRghtDown = true;// we SET this after the call to is rightbutton pressed now to to ensure next pass is active 336 | } 337 | else 338 | { // right mouse button itself is no longer registering the button pressed so.. toggle held and button pressed off 339 | IsRghtDown = false; 340 | IsRightJustReleased = false; 341 | IsRightDragged = false; 342 | if (IsRightHeld == true) 343 | { 344 | LastRghtDragReleased.X = X; 345 | LastRghtDragReleased.Y = Y; 346 | IsRightJustReleased = true; 347 | var rdr = GetRightDragRectangle(); 348 | if (rdr.Width != 0 || rdr.Height != 0) 349 | IsRightDragged = true; 350 | } 351 | IsRightHeld = false; 352 | } 353 | } 354 | if (autoPreventMultipleUpdates_Flag) 355 | { 356 | preventUpdatesRestOfThisFrame = true; 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /PerspectiveProjectionSpriteBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Microsoft.Xna.Framework.Input; 6 | 7 | // https://community.monogame.net/t/minimal-example-of-drawing-a-quad-into-2d-space/11063/3 8 | 9 | namespace PerspectiveQuadToSpritebatchAlignment 10 | { 11 | public class Game_QuadDrawExample : Game 12 | { 13 | public static GraphicsDeviceManager graphics; 14 | public static SpriteBatch spriteBatch; 15 | public static SpriteFont font; 16 | public static Effect effect; 17 | Matrix World, View , Projection = Matrix.Identity; 18 | float fieldOfView = 1.4f; 19 | public static Texture2D generatedTexture; 20 | public static Vector3 cameraScrollPosition = new Vector3(0, 0, 0); 21 | 22 | public static bool UsePerspectiveSpriteBatchEquivillent { get; set; } = true; 23 | 24 | 25 | public Game_QuadDrawExample() 26 | { 27 | graphics = new GraphicsDeviceManager(this); 28 | graphics.GraphicsProfile = GraphicsProfile.HiDef; 29 | Content.RootDirectory = "Content"; 30 | IsMouseVisible = true; 31 | Window.AllowUserResizing = true; 32 | this.Window.ClientSizeChanged += Resize; 33 | } 34 | 35 | protected override void Initialize() 36 | { 37 | graphics.PreferredBackBufferWidth = 800; 38 | graphics.PreferredBackBufferHeight = 500; 39 | graphics.ApplyChanges(); 40 | 41 | base.Initialize(); 42 | } 43 | 44 | protected override void LoadContent() 45 | { 46 | spriteBatch = new SpriteBatch(GraphicsDevice); 47 | font = Content.Load("MgGenFont"); 48 | effect = Content.Load("CameraTestsEffect"); 49 | generatedTexture = GenerateTexture2DWithTopLeftDiscoloration(); 50 | } 51 | 52 | protected override void UnloadContent() 53 | { 54 | generatedTexture.Dispose(); 55 | } 56 | 57 | public void Resize(object sender, EventArgs e) { } 58 | 59 | protected override void Update(GameTime gameTime) 60 | { 61 | var elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; 62 | 63 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 64 | Exit(); 65 | 66 | float pixelsPerSecond = 100f; 67 | if (Keyboard.GetState().IsKeyDown(Keys.Up)) 68 | cameraScrollPosition.Y += -pixelsPerSecond * elapsed; 69 | if (Keyboard.GetState().IsKeyDown(Keys.Down)) 70 | cameraScrollPosition.Y += pixelsPerSecond * elapsed; 71 | if (Keyboard.GetState().IsKeyDown(Keys.Left)) 72 | cameraScrollPosition.X += -pixelsPerSecond * elapsed; 73 | if (Keyboard.GetState().IsKeyDown(Keys.Right)) 74 | cameraScrollPosition.X += pixelsPerSecond * elapsed; 75 | 76 | if (Keyboard.GetState().IsKeyDown(Keys.F1)) 77 | UsePerspectiveSpriteBatchEquivillent = true; 78 | if (Keyboard.GetState().IsKeyDown(Keys.F2)) 79 | UsePerspectiveSpriteBatchEquivillent = false; 80 | 81 | SetCameraPosition2D(cameraScrollPosition.X, cameraScrollPosition.Y); 82 | 83 | base.Update(gameTime); 84 | } 85 | 86 | protected override void Draw(GameTime gameTime) 87 | { 88 | this.GraphicsDevice.Clear(Color.Transparent); 89 | 90 | Rectangle myColoredRectangle = new Rectangle(10, 40, 450, 260); 91 | 92 | SetStates(); 93 | 94 | SetUpEffect(); 95 | 96 | CreateAndThenDrawVertexRectangle(myColoredRectangle); 97 | 98 | //DrawOutMatrixInformationWithSpriteBatch(); 99 | 100 | base.Draw(gameTime); 101 | } 102 | 103 | public void SetUpEffect() 104 | { 105 | effect.CurrentTechnique = effect.Techniques["QuadDraw"]; 106 | World = Matrix.Identity; 107 | 108 | if (UsePerspectiveSpriteBatchEquivillent) 109 | PerspectiveAligned(GraphicsDevice, cameraScrollPosition, fieldOfView, out View, out Projection); 110 | else 111 | OrthographicAligned(GraphicsDevice, cameraScrollPosition, out View, out Projection); 112 | 113 | effect.Parameters["World"].SetValue(World); 114 | effect.Parameters["View"].SetValue(View); 115 | effect.Parameters["Projection"].SetValue(Projection); 116 | effect.Parameters["TextureA"].SetValue(generatedTexture); 117 | } 118 | 119 | public void PerspectiveAligned(GraphicsDevice device, Vector3 scollPositionOffset, float fieldOfView, out Matrix view, out Matrix projection) 120 | { 121 | var dist = -((1f / (float)Math.Tan(fieldOfView / 2)) * (device.Viewport.Height / 2)); 122 | var pos = new Vector3(device.Viewport.Width / 2, device.Viewport.Height / 2, dist) + scollPositionOffset; 123 | var target = new Vector3(0, 0, 1) + pos; 124 | var cameraWorld = Matrix.CreateWorld(pos, target - pos, Vector3.Down); 125 | view = Matrix.Invert(cameraWorld); 126 | projection = CreateInfinitePerspectiveFieldOfViewRHLH(fieldOfView, device.Viewport.AspectRatio, 0, 2000, true); 127 | this.Window.Title = "QuadToSpritebatchAlignment (F1 or F2) Perspective use arrow keys to scroll"; 128 | } 129 | public void OrthographicAligned(GraphicsDevice device, Vector3 scollPositionOffset, out Matrix view, out Matrix projection) 130 | { 131 | float forwardDepthDirection = 1f; 132 | view = Matrix.Invert(Matrix.CreateWorld(scollPositionOffset, new Vector3(0, 0, 1), Vector3.Down)); 133 | projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, -device.Viewport.Height, 0, forwardDepthDirection * 0, forwardDepthDirection * 1f); 134 | this.Window.Title = "QuadToSpritebatchAlignment (F1 or F2) Othographic use arrow keys to scroll"; 135 | } 136 | 137 | public void CreateAndThenDrawVertexRectangle(Rectangle r) 138 | { 139 | VertexPositionColorTexture[] quad = new VertexPositionColorTexture[6]; 140 | if (GraphicsDevice.RasterizerState == RasterizerState.CullClockwise) 141 | { 142 | quad[0] = new VertexPositionColorTexture(new Vector3(r.Left, r.Top, 0f), Color.White, new Vector2(0f, 0f)); // p1 143 | quad[1] = new VertexPositionColorTexture(new Vector3(r.Left, r.Bottom, 0f), Color.Red, new Vector2(0f, 1f)); // p0 144 | quad[2] = new VertexPositionColorTexture(new Vector3(r.Right, r.Bottom, 0f), Color.Green, new Vector2(1f, 1f));// p3 145 | 146 | quad[3] = new VertexPositionColorTexture(new Vector3(r.Right, r.Bottom, 0f), Color.Green, new Vector2(1f, 1f));// p3 147 | quad[4] = new VertexPositionColorTexture(new Vector3(r.Right, r.Top, 0f), Color.Blue, new Vector2(1f, 0f));// p2 148 | quad[5] = new VertexPositionColorTexture(new Vector3(r.Left, r.Top, 0f), Color.White, new Vector2(0f, 0f)); // p1 149 | } 150 | else 151 | { 152 | quad[0] = new VertexPositionColorTexture(new Vector3(r.Left, r.Top, 0f), Color.White, new Vector2(0f, 0f)); // p1 153 | quad[2] = new VertexPositionColorTexture(new Vector3(r.Left, r.Bottom, 0f), Color.Red, new Vector2(0f, 1f)); // p0 154 | quad[1] = new VertexPositionColorTexture(new Vector3(r.Right, r.Bottom, 0f), Color.Green, new Vector2(1f, 1f));// p3 155 | 156 | quad[4] = new VertexPositionColorTexture(new Vector3(r.Right, r.Bottom, 0f), Color.Green, new Vector2(1f, 1f));// p3 157 | quad[3] = new VertexPositionColorTexture(new Vector3(r.Right, r.Top, 0f), Color.Blue, new Vector2(1f, 0f));// p2 158 | quad[5] = new VertexPositionColorTexture(new Vector3(r.Left, r.Top, 0f), Color.White, new Vector2(0f, 0f)); // p1 159 | } 160 | foreach (EffectPass pass in effect.CurrentTechnique.Passes) 161 | { 162 | pass.Apply(); 163 | this.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, quad, 0, 2); 164 | } 165 | } 166 | 167 | public void SetStates() 168 | { 169 | GraphicsDevice.BlendState = BlendState.Opaque; 170 | GraphicsDevice.DepthStencilState = DepthStencilState.Default; 171 | GraphicsDevice.SamplerStates[0] = SamplerState.PointWrap; 172 | GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; 173 | } 174 | 175 | public void SetCameraPosition2D(float x, float y) 176 | { 177 | cameraScrollPosition.X = x; 178 | cameraScrollPosition.Y = y; 179 | cameraScrollPosition.Z = 0; 180 | } 181 | 182 | public Texture2D GenerateTexture2DWithTopLeftDiscoloration() 183 | { 184 | Texture2D t = new Texture2D(this.GraphicsDevice, 250, 250); 185 | var cdata = new Color[250 * 250]; 186 | for (int i = 0; i < 250; i++) 187 | { 188 | for (int j = 0; j < 250; j++) 189 | { 190 | if (i < 50 && j < 50) 191 | cdata[i * 250 + j] = new Color(120, 120, 120, 250); 192 | else 193 | cdata[i * 250 + j] = Color.White; 194 | } 195 | } 196 | t.SetData(cdata); 197 | return t; 198 | } 199 | 200 | public static Matrix CreateInfinitePerspectiveFieldOfViewRHLH(float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance, bool isRightHanded) 201 | { 202 | /* RH 203 | m11= xscale m12= 0 m13= 0 m14= 0 204 | m21= 0 m22= yscale m23= 0 m24= 0 205 | m31= 0 0 m33= f/(f-n) ~ m34= -1 ~ 206 | m41= 0 m42= 0 m43= n*f/(n-f) ~ m44= 0 207 | where: 208 | yScale = cot(fovY/2) 209 | xScale = yScale / aspect ratio 210 | */ 211 | if ((fieldOfView <= 0f) || (fieldOfView >= 3.141593f)){ throw new ArgumentException("fieldOfView <= 0 or >= PI"); } 212 | 213 | Matrix result = new Matrix(); 214 | float yscale = 1f / ((float)Math.Tan((double)(fieldOfView * 0.5f))); 215 | float xscale = yscale / aspectRatio; 216 | var negFarRange = float.IsPositiveInfinity(farPlaneDistance) ? -1.0f : farPlaneDistance / (nearPlaneDistance - farPlaneDistance); 217 | result.M11 = xscale; 218 | result.M12 = result.M13 = result.M14 = 0; 219 | result.M22 = yscale; 220 | result.M21 = result.M23 = result.M24 = 0; 221 | result.M31 = result.M32 = 0f; 222 | if (isRightHanded) 223 | { 224 | result.M33 = negFarRange; 225 | result.M34 = -1; 226 | result.M43 = nearPlaneDistance * negFarRange; 227 | } 228 | else 229 | { 230 | result.M33 = negFarRange; 231 | result.M34 = 1; 232 | result.M43 = -nearPlaneDistance * negFarRange; 233 | } 234 | result.M41 = result.M42 = result.M44 = 0; 235 | return result; 236 | } 237 | 238 | public void DrawOutMatrixInformationWithSpriteBatch() 239 | { 240 | spriteBatch.Begin(); 241 | var drawpos = new Vector2(10, 10); 242 | drawpos = MatrixSpriteBatchOut(spriteBatch, font, drawpos, Color.White, World, "World"); 243 | drawpos = MatrixSpriteBatchOut(spriteBatch, font, drawpos, Color.White, View, "View"); 244 | drawpos = MatrixSpriteBatchOut(spriteBatch, font, drawpos, Color.White, Projection, "Projection"); 245 | drawpos = MatrixSpriteBatchOut(spriteBatch, font, drawpos, Color.White, View * Projection, "VP"); 246 | spriteBatch.End(); 247 | } 248 | 249 | public static Vector2 MatrixSpriteBatchOut(SpriteBatch spriteBatch, SpriteFont font, Vector2 textPosition, Color col, Matrix m, string name) 250 | { 251 | var textPos = textPosition; 252 | spriteBatch.DrawString(font, name, textPos, col); 253 | textPos.Y += font.LineSpacing; 254 | float spacing = 110; 255 | spriteBatch.DrawString(font, " M11: " + m.M11.ToString("#0.000"), textPos, col); textPos.X += spacing; 256 | spriteBatch.DrawString(font, " M12: " + m.M12.ToString("#0.000"), textPos, col); textPos.X += spacing; 257 | spriteBatch.DrawString(font, " M13: " + m.M13.ToString("#0.000"), textPos, col); textPos.X += spacing; 258 | spriteBatch.DrawString(font, " M14: " + m.M14.ToString("#0.000"), textPos, col); textPos.X += spacing; 259 | spriteBatch.DrawString(font, " Right ", textPos, col); textPos.X += spacing; 260 | textPos.X = textPosition.X; textPos.Y += font.LineSpacing; 261 | spriteBatch.DrawString(font, " M21: " + m.M21.ToString("#0.000"), textPos, col); textPos.X += spacing; 262 | spriteBatch.DrawString(font, " M22: " + m.M22.ToString("#0.000"), textPos, col); textPos.X += spacing; 263 | spriteBatch.DrawString(font, " M23: " + m.M23.ToString("#0.000"), textPos, col); textPos.X += spacing; 264 | spriteBatch.DrawString(font, " M24: " + m.M24.ToString("#0.000"), textPos, col); textPos.X += spacing; 265 | spriteBatch.DrawString(font, " Up ", textPos, col); textPos.X += spacing; 266 | textPos.X = textPosition.X; textPos.Y += font.LineSpacing; 267 | spriteBatch.DrawString(font, " M31: " + m.M31.ToString("#0.000"), textPos, col); textPos.X += spacing; 268 | spriteBatch.DrawString(font, " M32: " + m.M32.ToString("#0.000"), textPos, col); textPos.X += spacing; 269 | spriteBatch.DrawString(font, " M33: " + m.M33.ToString("#0.000"), textPos, col); textPos.X += spacing; 270 | spriteBatch.DrawString(font, " M34: " + m.M34.ToString("#0.000"), textPos, col); textPos.X += spacing; 271 | spriteBatch.DrawString(font, " Forward ", textPos, col); textPos.X += spacing; 272 | textPos.X = textPosition.X; textPos.Y += font.LineSpacing; 273 | spriteBatch.DrawString(font, " M41: " + m.M41.ToString("#0.000"), textPos, col); textPos.X += spacing; 274 | spriteBatch.DrawString(font, " M42: " + m.M42.ToString("#0.000"), textPos, col); textPos.X += spacing; 275 | spriteBatch.DrawString(font, " M43: " + m.M43.ToString("#0.000"), textPos, col); textPos.X += spacing; 276 | spriteBatch.DrawString(font, " M44: " + m.M44.ToString("#0.000"), textPos, col); textPos.X += spacing; 277 | spriteBatch.DrawString(font, " Position ", textPos, col); textPos.X += spacing; 278 | textPos.X = textPosition.X; textPos.Y += font.LineSpacing; 279 | return textPos; 280 | } 281 | 282 | } 283 | } 284 | 285 | /* shader that goes with it although you can just use basic effect. 286 | 287 | #if OPENGL 288 | #define SV_POSITION POSITION 289 | #define VS_SHADERMODEL vs_3_0 290 | #define PS_SHADERMODEL ps_3_0 291 | #else 292 | #define VS_SHADERMODEL vs_4_0 //_level_9_1 293 | #define PS_SHADERMODEL ps_4_0 //_level_9_1 294 | #endif 295 | 296 | matrix World; 297 | matrix View; 298 | matrix Projection; 299 | 300 | Texture TextureA; // primary texture. 301 | sampler TextureSamplerA = sampler_state 302 | { 303 | texture = ; 304 | //magfilter = LINEAR; //minfilter = LINEAR; //mipfilter = LINEAR; //AddressU = mirror; //AddressV = mirror; 305 | }; 306 | //_______________________________________________________________ 307 | // techniques 308 | // Quad Draw Position Color Texture 309 | //_______________________________________________________________ 310 | struct VsInputQuad 311 | { 312 | float4 Position : POSITION0; 313 | float4 Color : COLOR0; 314 | float2 TexureCoordinateA : TEXCOORD0; 315 | }; 316 | struct VsOutputQuad 317 | { 318 | float4 Position : SV_Position; 319 | float4 Color : COLOR0; 320 | float2 TexureCoordinateA : TEXCOORD0; 321 | }; 322 | struct PsOutputQuad 323 | { 324 | float4 Color : COLOR0; 325 | }; 326 | // ____________________________ 327 | VsOutputQuad VertexShaderQuadDraw(VsInputQuad input) 328 | { 329 | VsOutputQuad output; 330 | float4x4 vp = mul(View, Projection); 331 | float4 pos = mul(input.Position, World); 332 | output.Position = mul(pos, vp); 333 | output.Color = input.Color; 334 | output.TexureCoordinateA = input.TexureCoordinateA; 335 | return output; 336 | } 337 | PsOutputQuad PixelShaderQuadDraw(VsOutputQuad input) 338 | { 339 | PsOutputQuad output; 340 | output.Color = tex2D(TextureSamplerA, input.TexureCoordinateA) * input.Color; 341 | return output; 342 | } 343 | 344 | technique QuadDraw 345 | { 346 | pass P0 347 | { 348 | VertexShader = compile VS_SHADERMODEL 349 | VertexShaderQuadDraw(); 350 | PixelShader = compile PS_SHADERMODEL 351 | PixelShaderQuadDraw(); 352 | } 353 | } 354 | 355 | */ 356 | -------------------------------------------------------------------------------- /MgTextBounder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Xna.Framework; 5 | using Microsoft.Xna.Framework.Graphics; 6 | 7 | namespace Microsoft.Xna.Framework 8 | { 9 | /// 10 | /// Wraps a stringbuilder to a bounding box. 11 | /// This class of course works for a no garbage MgStringBuilder as well. 12 | /// 13 | public static class MgTextBounder 14 | { 15 | private static SpriteFont tsf; 16 | private static Dictionary _glyphs; 17 | private static SpriteFont.Glyph defaultGlyph; 18 | private static char defaultfontchar = ' '; 19 | private static string newline = Environment.NewLine; 20 | 21 | /// 22 | /// Set the spritefont this needs to be done before the class is used. 23 | /// 24 | public static void SetSpriteFont(SpriteFont s) 25 | { 26 | tsf = s; 27 | _glyphs = tsf.GetGlyphs(); 28 | defaultGlyph = new SpriteFont.Glyph(); 29 | if (tsf.DefaultCharacter.HasValue) 30 | { 31 | defaultfontchar = (char)(tsf.DefaultCharacter.Value); 32 | defaultGlyph = _glyphs[defaultfontchar]; 33 | } 34 | } 35 | 36 | /// 37 | /// New i just added this here for now. 38 | /// Alter a characters width in this spritefont. 39 | /// requires ... using System.Collections.Generic; 40 | /// 41 | public static SpriteFont AlterSpriteFontWidth(SpriteFont sf, char chartoalter, float width_amount_to_add) 42 | { 43 | Dictionary dgyphs; 44 | SpriteFont.Glyph defaultglyph; 45 | char defaultchar = ' '; 46 | // Alter one of my methods a bit here for this purpose. 47 | // just drop all the alterd values into a new spritefont 48 | dgyphs = sf.GetGlyphs(); 49 | defaultglyph = new SpriteFont.Glyph(); 50 | if (sf.DefaultCharacter.HasValue) 51 | { 52 | defaultchar = (char)(sf.DefaultCharacter.Value); 53 | defaultglyph = dgyphs[defaultchar]; 54 | } 55 | else 56 | { 57 | // we could create a default value from like a pixel in the sprite font and add the glyph. 58 | } 59 | var altered = dgyphs[chartoalter]; 60 | altered.Width = altered.Width + width_amount_to_add; // ect 61 | dgyphs.Remove(chartoalter); 62 | dgyphs.Add(chartoalter, altered); 63 | 64 | //sf.Glyphs = _glyphs; // cant do it as its readonly private that sucks hard we would of been done 65 | 66 | List glyphBounds = new List(); 67 | List cropping = new List(); 68 | List characters = new List(); 69 | List kerning = new List(); 70 | foreach (var item in dgyphs) 71 | { 72 | glyphBounds.Add(item.Value.BoundsInTexture); 73 | cropping.Add(item.Value.Cropping); 74 | characters.Add(item.Value.Character); 75 | kerning.Add(new Vector3(item.Value.LeftSideBearing, item.Value.Width, item.Value.RightSideBearing)); 76 | } 77 | List b = new List(); 78 | sf = new SpriteFont(sf.Texture, glyphBounds, cropping, characters, sf.LineSpacing, sf.Spacing, kerning, defaultchar); 79 | return sf; 80 | } 81 | 82 | /// 83 | /// This is the primary working method the others only will generate garbage during runtime. 84 | /// That may or may not be a problem depending on if you are just pre-calculating. 85 | /// 86 | public static void WordWrapTextWithinBounds(ref MgStringBuilder text, Vector2 scale, Rectangle boundRect) 87 | { 88 | var Spacing = tsf.Spacing; 89 | var lineHeight = tsf.LineSpacing * scale.Y; 90 | Vector2 offset = Vector2.Zero; 91 | float yextent = offset.Y + lineHeight; 92 | Vector2 redrawOffset = Vector2.Zero; 93 | Rectangle dest = new Rectangle(); 94 | var currentGlyph = SpriteFont.Glyph.Empty; 95 | var firstGlyphOfLine = true; 96 | 97 | int lastWordBreakCharPos = 0; 98 | bool firstwordonline = true; 99 | Vector2 rewindOffset = Vector2.Zero; 100 | 101 | for (int i = 0; i < text.Length; i++) 102 | { 103 | char c = text[i]; 104 | 105 | if (c == '\r') 106 | continue; 107 | if (c == '\n') 108 | { 109 | offset.X = 0; 110 | offset.Y += lineHeight; 111 | yextent = offset.Y + lineHeight; 112 | firstGlyphOfLine = true; 113 | firstwordonline = true; 114 | continue; 115 | } 116 | 117 | if (_glyphs.ContainsKey(c)) 118 | currentGlyph = _glyphs[c]; 119 | else 120 | if (!tsf.DefaultCharacter.HasValue) 121 | throw new ArgumentException("Text Contains a Unresolvable Character"); 122 | else 123 | currentGlyph = defaultGlyph; 124 | 125 | // Solves the problem- the first character on a line with a negative left side bearing. 126 | if (firstGlyphOfLine) 127 | { 128 | offset.X = Math.Max(currentGlyph.LeftSideBearing, 0); 129 | firstGlyphOfLine = false; 130 | } 131 | else 132 | offset.X += Spacing + currentGlyph.LeftSideBearing; 133 | 134 | // matrix calculations unrolled rotation is excluded for this version 135 | var m = offset; 136 | m.X += currentGlyph.Cropping.X; 137 | m.Y += currentGlyph.Cropping.Y; 138 | 139 | dest = new Rectangle( 140 | (int)(m.X * scale.X), 141 | (int)(m.Y * scale.Y), 142 | (int)(currentGlyph.BoundsInTexture.Width * scale.X), 143 | (int)(currentGlyph.BoundsInTexture.Height * scale.Y) 144 | ); 145 | 146 | // Begin word wrapping operations. 147 | // if char is a word break character e.g. white space 148 | if (c == ' ') 149 | { 150 | lastWordBreakCharPos = i; 151 | rewindOffset = offset; 152 | if (firstwordonline) 153 | firstwordonline = false; 154 | } 155 | // word wrapping calculations. 156 | if (yextent >= boundRect.Height) 157 | { 158 | // this essentially is function termination due to height boundry 159 | text.Length = i; 160 | i = text.Length; 161 | } 162 | else 163 | { 164 | if (dest.Right > boundRect.Width) 165 | { 166 | if (text.Length > (i + 1)) 167 | { 168 | if (firstwordonline == false) 169 | { 170 | if (text[lastWordBreakCharPos + 1] != '\n') 171 | { 172 | // i also pulled the +1 here which is i think inserting a improperly positioned like break using append at. 173 | text.AppendAt(lastWordBreakCharPos + 1, '\n'); // !!! insert here is a gcollection problem... fixed 174 | if (text[lastWordBreakCharPos + 1] != ' ') 175 | { 176 | offset = rewindOffset; 177 | i = lastWordBreakCharPos; 178 | } 179 | } 180 | } 181 | else // first word on the line true 182 | { 183 | if (text[i + 1] != '\n') // if its not already a new line char 184 | { 185 | text.AppendAt(i + 1, '\n'); // !!! insert here is a gcollection problem... fixed 186 | } 187 | } 188 | } 189 | } 190 | } 191 | offset.X += currentGlyph.Width + currentGlyph.RightSideBearing; 192 | } 193 | } 194 | 195 | /// 196 | /// This changes the ref stringbuilder by word wrapping it to a bounding box. 197 | /// 198 | public static void WordWrapTextWithinBounds(ref StringBuilder text, Vector2 scale, Rectangle boundRect) 199 | { 200 | var Spacing = tsf.Spacing; 201 | var lineHeight = tsf.LineSpacing * scale.Y; 202 | Vector2 offset = Vector2.Zero; 203 | float yextent = offset.Y + lineHeight; 204 | Vector2 redrawOffset = Vector2.Zero; 205 | Rectangle dest = new Rectangle(); 206 | var currentGlyph = SpriteFont.Glyph.Empty; 207 | var firstGlyphOfLine = true; 208 | 209 | int lastWordBreakCharPos = 0; 210 | bool firstwordonline = true; 211 | Vector2 rewind = Vector2.Zero; 212 | 213 | //int i = -1; 214 | 215 | //Console.WriteLine(" text.StringBuilder.Length " + text.StringBuilder.Length.ToString() + " "); 216 | 217 | //while (i < text.Length) 218 | //{ i++; 219 | for (int i = 0; i < text.Length; i++) 220 | { 221 | char c = text[i]; 222 | //Console.Write(" text[" + i + "]"); 223 | //Console.Write(" = " + c + " "); 224 | 225 | if (c == '\r') 226 | continue; 227 | if (c == '\n') 228 | { 229 | offset.X = 0; 230 | offset.Y += lineHeight; 231 | yextent = offset.Y + lineHeight; 232 | //Console.Write(" >> [" + i.ToString() + "] newline is set. lineHeight:" + lineHeight.ToString() + " y offset:" + offset.Y.ToString() + " the y extent is " + yextent.ToString()); 233 | firstGlyphOfLine = true; 234 | firstwordonline = true; 235 | continue; 236 | } 237 | 238 | if (_glyphs.ContainsKey(c)) 239 | currentGlyph = _glyphs[c]; 240 | else 241 | if (!tsf.DefaultCharacter.HasValue) 242 | throw new ArgumentException("Text Contains a Unresolvable Character"); 243 | else 244 | currentGlyph = defaultGlyph; 245 | 246 | // Solves the problem- the first character on a line with a negative left side bearing. 247 | if (firstGlyphOfLine) 248 | { 249 | offset.X = Math.Max(currentGlyph.LeftSideBearing, 0); 250 | firstGlyphOfLine = false; 251 | } 252 | else 253 | offset.X += Spacing + currentGlyph.LeftSideBearing; 254 | 255 | // matrix calculations unrolled varys max 8 mults and up to 10 add subs. 256 | var m = offset; 257 | m.X += currentGlyph.Cropping.X; 258 | m.Y += currentGlyph.Cropping.Y; 259 | 260 | dest = new Rectangle( 261 | (int)(m.X * scale.X), 262 | (int)(m.Y * scale.Y), 263 | (int)(currentGlyph.BoundsInTexture.Width * scale.X), 264 | (int)(currentGlyph.BoundsInTexture.Height * scale.Y) 265 | ); 266 | 267 | //Console.WriteLine(" >>> dest.Height " + dest.Height + " , dest.Bottom " + dest.Bottom); 268 | 269 | // if char == a word break character white space 270 | if (c == ' ') 271 | { 272 | lastWordBreakCharPos = i; 273 | rewind = offset; 274 | if (firstwordonline) 275 | firstwordonline = false; 276 | } 277 | 278 | // Begin word wrapping calculations. 279 | if (yextent >= boundRect.Height) 280 | { 281 | //Console.WriteLine(" >> dest.Bottom " + dest.Bottom + " >= boundRect.Height " + boundRect.Height); 282 | //Console.WriteLine(" >> text.Length = i + 1; i = text.Length;"); 283 | text.Length = i; 284 | i = text.Length; 285 | } 286 | else 287 | { 288 | if (dest.Right > boundRect.Width) 289 | { 290 | if (text.Length > (i + 1)) 291 | { 292 | if (firstwordonline == false) 293 | { 294 | if (text[lastWordBreakCharPos + 1] != '\n') 295 | { 296 | //Console.WriteLine(" >> (" + dest.Right + " > " + boundRect.Width + "), text.Insert(lastWordBreakCharPos " + lastWordBreakCharPos + " + 1, newline); offset = rewind; i = lastWordBreakCharPos; "); 297 | text.Insert(lastWordBreakCharPos + 1, '\n'); 298 | if (text[lastWordBreakCharPos + 1] != ' ') 299 | { 300 | offset = rewind; 301 | i = lastWordBreakCharPos; 302 | } 303 | } 304 | } 305 | else // first word on the line true 306 | { 307 | if (text[i + 1] != '\n') 308 | { 309 | //Console.WriteLine(" >> text.Insert(i + 1, newline);'"); 310 | text.Insert(i + 1, '\n'); 311 | } 312 | } 313 | } 314 | } 315 | } 316 | offset.X += currentGlyph.Width + currentGlyph.RightSideBearing; 317 | } 318 | //return lastLineBreakCharPos; 319 | } 320 | 321 | /// 322 | /// This vesion copys to a new stringbuilder and then wraps it before returning it. 323 | /// 324 | public static StringBuilder WordWrapTextWithinBounds(StringBuilder text, Vector2 scale, Rectangle boundRect) 325 | { 326 | var Spacing = tsf.Spacing; 327 | var lineHeight = tsf.LineSpacing * scale.Y; 328 | Vector2 offset = Vector2.Zero; 329 | float yextent = offset.Y + lineHeight; 330 | Vector2 redrawOffset = Vector2.Zero; 331 | Rectangle dest = new Rectangle(); 332 | var currentGlyph = SpriteFont.Glyph.Empty; 333 | var firstGlyphOfLine = true; 334 | 335 | int lastWordBreakCharPos = 0; 336 | bool firstwordonline = true; 337 | Vector2 rewind = Vector2.Zero; 338 | 339 | //Console.WriteLine(" text.StringBuilder.Length " + text.StringBuilder.Length.ToString() + " "); 340 | StringBuilder tmp = new StringBuilder(); 341 | tmp.Append(text); 342 | 343 | for (int i = 0; i < tmp.Length; i++) 344 | { 345 | char c = tmp[i]; 346 | //Console.Write(" text[" + i + "]"); 347 | //Console.Write(" = " + c + " "); 348 | 349 | if (c == '\r') 350 | continue; 351 | if (c == '\n') 352 | { 353 | offset.X = 0; 354 | offset.Y += lineHeight; 355 | yextent = offset.Y + lineHeight; 356 | //Console.Write(" >> [" + i.ToString() + "] newline is set. lineHeight:" + lineHeight.ToString() + " y offset:" + offset.Y.ToString() + " the y extent is " + yextent.ToString()); 357 | firstGlyphOfLine = true; 358 | firstwordonline = true; 359 | continue; 360 | } 361 | 362 | if (_glyphs.ContainsKey(c)) 363 | currentGlyph = _glyphs[c]; 364 | else 365 | if (!tsf.DefaultCharacter.HasValue) 366 | throw new ArgumentException("Text Contains a Unresolvable Character"); 367 | else 368 | currentGlyph = defaultGlyph; 369 | 370 | // Solves the problem- the first character on a line with a negative left side bearing. 371 | if (firstGlyphOfLine) 372 | { 373 | offset.X = Math.Max(currentGlyph.LeftSideBearing, 0); 374 | firstGlyphOfLine = false; 375 | } 376 | else 377 | offset.X += Spacing + currentGlyph.LeftSideBearing; 378 | 379 | // matrix calculations unrolled varys max 8 mults and up to 10 add subs. 380 | var m = offset; 381 | m.X += currentGlyph.Cropping.X; 382 | m.Y += currentGlyph.Cropping.Y; 383 | 384 | dest = new Rectangle( 385 | (int)(m.X * scale.X), 386 | (int)(m.Y * scale.Y), 387 | (int)(currentGlyph.BoundsInTexture.Width * scale.X), 388 | (int)(currentGlyph.BoundsInTexture.Height * scale.Y) 389 | ); 390 | 391 | //Console.WriteLine(" >>> dest.Height " + dest.Height + " , dest.Bottom " + dest.Bottom); 392 | 393 | // if char == a word break character white space 394 | if (c == ' ') 395 | { 396 | lastWordBreakCharPos = i; 397 | rewind = offset; 398 | if (firstwordonline) 399 | firstwordonline = false; 400 | } 401 | 402 | // Begin word wrapping calculations. 403 | if (yextent >= boundRect.Height) 404 | { 405 | //Console.WriteLine(" >> dest.Bottom " + dest.Bottom + " >= boundRect.Height " + boundRect.Height); 406 | //Console.WriteLine(" >> text.Length = i + 1; i = text.Length;"); 407 | tmp.Length = i; 408 | i = tmp.Length; 409 | } 410 | else 411 | { 412 | if (dest.Right > boundRect.Width) 413 | { 414 | if (tmp.Length > (i + 1)) 415 | { 416 | if (firstwordonline == false) 417 | { 418 | if (tmp[lastWordBreakCharPos + 1] != '\n') 419 | { 420 | //Console.WriteLine(" >> (" + dest.Right + " > " + boundRect.Width + "), text.Insert(lastWordBreakCharPos " + lastWordBreakCharPos + " + 1, newline); offset = rewind; i = lastWordBreakCharPos; "); 421 | tmp.Insert(lastWordBreakCharPos + 1, '\n'); 422 | if (tmp[lastWordBreakCharPos + 1] != ' ') 423 | { 424 | offset = rewind; 425 | i = lastWordBreakCharPos; 426 | } 427 | } 428 | } 429 | else // first word on the line true 430 | { 431 | if (tmp[i + 1] != '\n') 432 | { 433 | //Console.WriteLine(" >> text.Insert(i + 1, newline);'"); 434 | tmp.Insert(i + 1, '\n'); 435 | } 436 | } 437 | } 438 | } 439 | } 440 | offset.X += currentGlyph.Width + currentGlyph.RightSideBearing; 441 | } 442 | return tmp; 443 | } 444 | 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /SpriteFontConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | //using System.Text; 4 | using System.Collections.Generic; 5 | //using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Graphics; 7 | //using Microsoft.Xna.Framework.Input; 8 | 9 | namespace Microsoft.Xna.Framework 10 | { 11 | 12 | public class SpriteFontConverter 13 | { 14 | /// 15 | /// This holds the deconstructed sprite font data including a pixel color array. 16 | /// 17 | public SpriteFontData spriteFontData; 18 | private SpriteFont spriteFont; 19 | private SpriteFontToClassFileEncoderDecoder spriteFontToClassWriter = new SpriteFontToClassFileEncoderDecoder(); 20 | 21 | /// 22 | /// Give this a sprite font to deconstruct it to data. 23 | /// 24 | public SpriteFontConverter(SpriteFont spriteFont) 25 | { 26 | this.spriteFont = spriteFont; // how to handle disposing the fonts texture in this particular case im not sure. 27 | spriteFontData = new SpriteFontData(); 28 | DeConstruct(spriteFontData); 29 | } 30 | 31 | /// 32 | /// Call this to write the font the file will be saved to the full path file 33 | /// This writes the file as a cs text file that can be loaded by another project as a actual c sharp class file into visual studio. 34 | /// When that generated class is instantiated and load is called on it, it then returns a hardcoded spritefont. 35 | /// 36 | public void WriteFileAsCs(string fullFilePath) 37 | { 38 | spriteFontToClassWriter.WriteToFileAsCs(fullFilePath, spriteFontData); 39 | } 40 | 41 | // Deconstructs a spritefont. 42 | private void DeConstruct(SpriteFontData spriteFontData) 43 | { 44 | spriteFontData.fontTexture = spriteFont.Texture; 45 | spriteFontData.lineHeightSpaceing = spriteFont.LineSpacing; 46 | spriteFontData.spaceing = spriteFont.Spacing; 47 | spriteFontData.dgyphs = spriteFont.GetGlyphs(); 48 | spriteFontData.defaultglyph = new SpriteFont.Glyph(); 49 | if (spriteFont.DefaultCharacter.HasValue) 50 | { 51 | spriteFontData.defaultchar = (char)(spriteFont.DefaultCharacter.Value); 52 | spriteFontData.defaultglyph = spriteFontData.dgyphs[spriteFontData.defaultchar]; 53 | } 54 | else 55 | { 56 | // we could create a default value from like a pixel in the sprite font and add the glyph. 57 | } 58 | foreach (var item in spriteFontData.dgyphs) 59 | { 60 | spriteFontData.glyphBounds.Add(item.Value.BoundsInTexture); 61 | spriteFontData.glyphCroppings.Add(item.Value.Cropping); 62 | spriteFontData.glyphCharacters.Add(item.Value.Character); 63 | spriteFontData.glyphKernings.Add(new Vector3(item.Value.LeftSideBearing, item.Value.Width, item.Value.RightSideBearing)); 64 | } 65 | spriteFontData.numberOfGlyphs = spriteFontData.glyphCharacters.Count; 66 | 67 | spriteFontData.width = spriteFont.Texture.Width; 68 | spriteFontData.height = spriteFont.Texture.Height; 69 | 70 | Color[] colorarray = new Color[spriteFont.Texture.Width * spriteFont.Texture.Height]; 71 | spriteFont.Texture.GetData(colorarray); //,0, width* height 72 | List pixels = new List(); 73 | foreach (var c in colorarray) 74 | pixels.Add(c); 75 | spriteFontData.pixelColorData = pixels; 76 | } 77 | 78 | /// 79 | /// Holds the sprite fonts data including the pixel data. 80 | /// 81 | public class SpriteFontData 82 | { 83 | public Texture2D fontTexture; 84 | public List pixelColorData; 85 | public int width; 86 | public int height; 87 | 88 | public int numberOfGlyphs = 0; 89 | public Dictionary dgyphs; 90 | public SpriteFont.Glyph defaultglyph; 91 | public float spaceing = 0; 92 | public int lineHeightSpaceing = 0; 93 | public char defaultchar = '*'; 94 | public List glyphBounds = new List(); 95 | public List glyphCroppings = new List(); 96 | public List glyphCharacters = new List(); 97 | public List glyphKernings = new List(); // left width right and side bering 98 | } 99 | 100 | /// 101 | /// Encoding and Decoding methods however the decoder is placed within the output class file. 102 | /// This allows the output class to be decoupled from the pipeline or even a ttf. 103 | /// This class doesn't have to be called unless you wish to do editing on a spriteFontData Instance. 104 | /// Then rewrite the encoded data to file later at a time of your own choosing 105 | /// 106 | public class SpriteFontToClassFileEncoderDecoder 107 | { 108 | MgStringBuilder theCsText = new MgStringBuilder(); 109 | public List rleEncodedByteData = new List(); 110 | public int width = 0; 111 | public int height = 0; 112 | 113 | // this is just a spacing thing to show the data a bit easier on the eyes. 114 | int dividerAmount = 10; 115 | int dividerCharSingleValueAmount = 50; 116 | int dividerSingleValueAmount = 200; 117 | private int pixels = 0; 118 | private int bytestallyed = 0; 119 | private int bytedataCount = 0; 120 | 121 | public void WriteToFileAsCs(string fullFilePath, SpriteFontData sfData) 122 | { 123 | var filename = Path.GetFileNameWithoutExtension(fullFilePath); 124 | var colordata = sfData.pixelColorData; 125 | width = sfData.width; 126 | height = sfData.height; 127 | rleEncodedByteData = EncodeColorArrayToDataRLE(colordata, sfData.width, sfData.height); 128 | 129 | var charsAsString = CharArrayToStringClassFormat("chars", sfData.glyphCharacters.ToArray()); 130 | var boundsAsString = RectangleToStringClassFormat("bounds", sfData.glyphBounds.ToArray()); 131 | var croppingsAsString = RectangleToStringClassFormat("croppings", sfData.glyphCroppings.ToArray()); 132 | var glyphKernings = Vector3ToStringClassFormat("kernings", sfData.glyphKernings.ToArray()); 133 | var rleDataAsString = ByteArrayToStringClassFormat("rleByteData", rleEncodedByteData.ToArray()); 134 | 135 | 136 | theCsText = 137 | "\n//" + 138 | "\n// This file is programatically generated this class is hard coded instance data for a instance of a spritefont." + 139 | "\n// Use the LoadHardCodeSpriteFont to load it." + 140 | "\n// Be aware i believe you should dispose its texture in game1 unload as this won't have been loaded thru the content manager." + 141 | "\n//" + 142 | "\n//"; 143 | 144 | theCsText 145 | .Append("\n using System;") 146 | .Append("\n using System.Text;") 147 | .Append("\n using System.Collections.Generic;") 148 | .Append("\n using Microsoft.Xna.Framework;") 149 | .Append("\n using Microsoft.Xna.Framework.Graphics;") 150 | .Append("\n using Microsoft.Xna.Framework.Input;") 151 | .Append("\n") 152 | .Append("\n namespace Microsoft.Xna.Framework") 153 | .Append("\n {") 154 | .Append("\n ") 155 | .Append("\n public class SpriteFontAsClassFile_").Append(filename) 156 | .Append("\n {") 157 | .Append("\n ") 158 | .Append("\n int width=").Append(width).Append(";") 159 | .Append("\n int height=").Append(height).Append(";") 160 | .Append("\n char defaultChar = Char.Parse(\"").Append(sfData.defaultchar).Append("\");") 161 | .Append("\n int lineHeightSpaceing =").Append(sfData.lineHeightSpaceing).Append(";") 162 | .Append("\n float spaceing =").Append(sfData.spaceing).Append(";") 163 | .Append("\n ") 164 | .Append("\n public SpriteFont LoadHardCodeSpriteFont(GraphicsDevice device)") 165 | .Append("\n {") 166 | .Append("\n Texture2D t = DecodeToTexture(device, rleByteData, width, height);") 167 | .Append("\n return new SpriteFont(t, bounds, croppings, chars, lineHeightSpaceing, spaceing, kernings, defaultChar);") 168 | .Append("\n }") 169 | .Append("\n ") 170 | .Append("\n private Texture2D DecodeToTexture(GraphicsDevice device, List rleByteData, int _width, int _height)") 171 | .Append("\n {") 172 | .Append("\n Color[] colData = DecodeDataRLE(rleByteData);") 173 | .Append("\n Texture2D tex = new Texture2D(device, _width, _height);") 174 | .Append("\n tex.SetData(colData);") 175 | .Append("\n return tex;") 176 | .Append("\n }") 177 | .Append("\n ") 178 | .Append("\n private Color[] DecodeDataRLE(List rleByteData)") 179 | .Append("\n {") 180 | .Append("\n List colAry = new List();") 181 | .Append("\n for (int i = 0; i < rleByteData.Count; i++)") 182 | .Append("\n {") 183 | .Append("\n var val = (rleByteData[i] & 0x7F) * 2;") 184 | .Append("\n if (val > 252)") 185 | .Append("\n val = 255;") 186 | .Append("\n Color color = new Color();") 187 | .Append("\n if (val > 0)") 188 | .Append("\n color = new Color(val, val, val, val);") 189 | .Append("\n if ((rleByteData[i] & 0x80) > 0)") 190 | .Append("\n {") 191 | .Append("\n var runlen = rleByteData[i + 1];") 192 | .Append("\n for (int j = 0; j < runlen; j++)") 193 | .Append("\n colAry.Add(color);") 194 | .Append("\n i += 1;") 195 | .Append("\n }") 196 | .Append("\n colAry.Add(color);") 197 | .Append("\n }") 198 | .Append("\n return colAry.ToArray();") 199 | .Append("\n }") 200 | .Append("\n ") 201 | .Append("\n ").Append(charsAsString) 202 | .Append("\n ").Append(boundsAsString) 203 | .Append("\n ").Append(croppingsAsString) 204 | .Append("\n ").Append(glyphKernings) 205 | .Append("\n ") 206 | .Append("\n // pixelsCompressed: ").Append(pixels).Append(" bytesTallied: ").Append(bytestallyed).Append(" byteDataCount: ").Append(bytedataCount) 207 | .Append("\n ").Append(rleDataAsString) 208 | .Append("\n ") 209 | .Append("\n ") 210 | .Append("\n }") 211 | .Append("\n ") 212 | .Append("\n }") // end of namespace 213 | .Append("\n ").Append(" ") 214 | ; 215 | 216 | //MgPathFolderFileOps.WriteStringToFile(fullFilePath, theCsText.ToString()); 217 | File.WriteAllText(fullFilePath, theCsText.ToString()); 218 | } 219 | 220 | public string CharArrayToStringClassFormat(string variableName, char[] c) 221 | { 222 | string s = 223 | "\n // Item count = " + c.Length + 224 | "\n List " + variableName + " = new List " + 225 | "\n {" + 226 | "\n " 227 | ; 228 | int divider = 0; 229 | for (int i = 0; i < c.Length; i++) 230 | { 231 | divider++; 232 | if (divider > dividerCharSingleValueAmount) 233 | { 234 | divider = 0; 235 | s += "\n "; 236 | } 237 | //s += $"new char(\"{c[i]})\""; 238 | s += "(char)" + (int)c[i] + ""; 239 | if (i < c.Length - 1) 240 | s += ","; 241 | } 242 | s += "\n };"; 243 | return s; 244 | } 245 | public string RectangleToStringClassFormat(string variableName, Rectangle[] r) 246 | { 247 | string s = 248 | "\n List < Rectangle > " + variableName + " = new List" + 249 | "\n {" + 250 | "\n " 251 | ; 252 | int divider = 0; 253 | for (int i = 0; i < r.Length; i++) 254 | { 255 | divider++; 256 | if (divider > dividerAmount) 257 | { 258 | divider = 0; 259 | s += "\n "; 260 | } 261 | s += $"new Rectangle({r[i].X},{r[i].Y},{r[i].Width},{r[i].Height})"; 262 | if (i < r.Length - 1) 263 | s += ","; 264 | } 265 | s += "\n };"; 266 | return s; 267 | } 268 | public string Vector3ToStringClassFormat(string variableName, Vector3[] v) 269 | { 270 | string s = 271 | "\n List " + variableName + " = new List" + 272 | "\n {" + 273 | "\n " 274 | ; 275 | int divider = 0; 276 | for (int i = 0; i < v.Length; i++) 277 | { 278 | divider++; 279 | if (divider > dividerAmount) 280 | { 281 | divider = 0; 282 | s += "\n "; 283 | } 284 | s += $"new Vector3({v[i].X},{v[i].Y},{v[i].Z})"; 285 | if (i < v.Length - 1) 286 | s += ","; 287 | } 288 | s += "\n };"; 289 | return s; 290 | } 291 | public string ByteArrayToStringClassFormat(string variableName, byte[] b) 292 | { 293 | string s = 294 | "\n List " + variableName + " = new List" + 295 | "\n {" + 296 | "\n " 297 | ; 298 | int divider = 0; 299 | for (int i = 0; i < b.Length; i++) 300 | { 301 | divider++; 302 | if (divider > dividerSingleValueAmount) 303 | { 304 | divider = 0; 305 | s += "\n "; 306 | } 307 | s += b[i]; 308 | if (i < b.Length - 1) 309 | s += ","; 310 | } 311 | s += "\n };"; 312 | return s; 313 | } 314 | 315 | /// 316 | /// Turns the pixel data into run length encode text for a cs file. 317 | /// Technically this is only compressing the alpha byte of a array. 318 | /// I could pull out my old bit ziping algorithm but that is probably overkill. 319 | /// 320 | public List EncodeColorArrayToDataRLE(List colorArray, int pkdcolaryWidth, int pkdcolaryHeight) 321 | { 322 | List rleAry = new List(); 323 | int colaryIndex = 0; 324 | int colaryLen = colorArray.Count; 325 | int templen = pkdcolaryWidth * pkdcolaryHeight; 326 | 327 | int pixelsAccountedFor = 0; 328 | 329 | while (colaryIndex < colaryLen) 330 | { 331 | var colorMasked = (colorArray[colaryIndex].A / 2); //(pkdcolAry[colaryIndex].A & 0xFE); 332 | var encodedValue = colorMasked; 333 | var runLength = 0; 334 | 335 | // find the run length for this pixel. 336 | for (int i = 1; i < 255; i++) 337 | { 338 | var indiceToTest = colaryIndex + i; 339 | if (indiceToTest < colaryLen) 340 | { 341 | var testColorMasked = (colorArray[indiceToTest].A / 2); //(pkdcolAry[indiceToTest].A & 0xFE); 342 | if (testColorMasked == colorMasked) 343 | runLength = i; 344 | else 345 | i = 256; // break on diff 346 | } 347 | else 348 | i = 256; // break on maximum run length 349 | } 350 | Console.WriteLine("colaryIndex: " + colaryIndex + " rleAry index " + rleAry.Count + " Alpha: " + colorMasked * 2 + " runLength: " + runLength); 351 | 352 | if (runLength > 0) 353 | { 354 | encodedValue += 0x80; 355 | if (colaryIndex < colaryLen) 356 | { 357 | rleAry.Add((byte)encodedValue); 358 | rleAry.Add((byte)runLength); 359 | pixelsAccountedFor += 1 + runLength; 360 | } 361 | else 362 | throw new Exception("bug check index write out of bounds"); 363 | colaryIndex = colaryIndex + 1 + runLength; 364 | } 365 | else 366 | { 367 | if (colaryIndex < colaryLen) 368 | { 369 | rleAry.Add((byte)encodedValue); 370 | pixelsAccountedFor += 1; 371 | } 372 | else 373 | throw new Exception("Encoding bug check index write out of bounds"); 374 | colaryIndex = colaryIndex + 1; 375 | } 376 | } 377 | // 378 | pixels = pixelsAccountedFor; 379 | bytestallyed = pixelsAccountedFor * 4; 380 | bytedataCount = rleAry.Count; 381 | Console.WriteLine("EncodeColorArrayToDataRLE: rleAry.Count " + rleAry.Count + " pixels accounted for " + pixelsAccountedFor + " bytes tallied " + pixelsAccountedFor * 4); 382 | return rleAry; 383 | } 384 | 385 | /// 386 | /// This decodes the cs files hard coded rle pixel data to a texture. 387 | /// 388 | public Texture2D DecodeRleDataToTexture(GraphicsDevice device, List rleByteData, int _width, int _height) 389 | { 390 | Color[] colData = DecodeRleDataToPixelData(rleByteData); 391 | Texture2D tex = new Texture2D(device, _width, _height); 392 | tex.SetData(colData); 393 | return tex; 394 | } 395 | /// 396 | /// Decodes the class file hardcoded rle byte data to a color array. 397 | /// 398 | private Color[] DecodeRleDataToPixelData(List rleByteData) 399 | { 400 | List colAry = new List(); 401 | for (int i = 0; i < rleByteData.Count; i++) 402 | { 403 | var val = (rleByteData[i] & 0x7F) * 2; 404 | if (val > 252) 405 | val = 255; 406 | Color color = new Color(); 407 | if (val > 0) 408 | color = new Color(val, val, val, val); 409 | if ((rleByteData[i] & 0x80) > 0) 410 | { 411 | var runlen = rleByteData[i + 1]; 412 | for (int j = 0; j < runlen; j++) 413 | colAry.Add(color); 414 | i += 1; 415 | } 416 | colAry.Add(color); 417 | } 418 | return colAry.ToArray(); 419 | } 420 | } 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /MgStringBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | //using System.IO; 3 | using System.Text; 4 | //using System.Linq; 5 | //using System.Collections.Generic; 6 | //using Microsoft.Xna.Framework; 7 | //using Microsoft.Xna.Framework.Graphics; 8 | //using Microsoft.Xna.Framework.Input; 9 | //using System.Diagnostics; 10 | //using System.Reflection; 11 | ////using System.Xml.Serialization; 12 | 13 | namespace Microsoft.Xna.Framework 14 | { 15 | /// 16 | /// No garbage stringbuilder William Motill 2017, last fix or change Feb 10, 2019. 17 | /// 18 | /// The purpose of this class is to eliminate garbage collections. 19 | /// Primarily bypassing numeric conversions to string. 20 | /// While this is not for high precision, Performance is to be considered. 21 | /// This class can be used in place of stringbuilder it was primarily designed for use with monogame. 22 | /// 23 | /// Notes 24 | /// To use this as a regular c# class simply remove the vector and color overloads. 25 | /// It's more like a string now ability wise, since over time ive slowly changed things to add overloads properly. 26 | /// So while you can use it with the + "" + operators id avoid that when adding dynamic number variable and stick with .append() 27 | /// 28 | /// ... 29 | /// Change log 2017 30 | /// ... 31 | /// March to December 32 | /// Added chained append funtionality that was getting annoying not having it. 33 | /// Cut out excess trailing float and double zeros. This was a partial fix. 34 | /// Fixed a major floating point error. Shifted the remainder of floats doubles into the higher integer range. 35 | /// Fixed a second edge case for trailing zeros. 36 | /// Fixed n = -n; when values were negative in float double appends. 37 | /// that would lead to a bug were - integer portions didn't get returned. 38 | /// yanked some redundant stuff for a un-needed reference swap hack. 39 | /// ... 40 | /// Change log 2018 41 | /// ... 42 | /// Added a Indexer to index into the underlying stringbuilder to directly access chars. 43 | /// Added a insert char overload. 44 | /// Appendline was adding the new line to the beginning not the end. 45 | /// Added a method to directly link via reference to the internal string builder this will probably stay in. 46 | /// The original AppendAt was fixed and renamed to OverWriteAt, The new AppendAt's works as a Insert. 47 | /// Multiple overloads were added and tested in relation. 48 | /// Standardized capacity and length checks to a method. 49 | /// Added AppendTrim overloads to allow setting the decimal place. 50 | /// Added a rectangle why i never added this before. 51 | /// ... 52 | /// Change log 2019 53 | /// ... 54 | /// Feb 10 55 | /// Added Familiar insert methods that use the AppendAt 56 | /// ... 57 | /// 58 | public sealed class MgStringBuilder 59 | { 60 | private static char decimalseperator = '.'; 61 | private static char minus = '-'; 62 | private static char plus = '+'; 63 | private StringBuilder stringbuilder; 64 | 65 | /// 66 | /// This was sort of a iffy thing to add i was superstitious it might make garbage, after all this time i guess its pretty safe. 67 | /// 68 | public StringBuilder StringBuilder 69 | { 70 | get { return stringbuilder; } 71 | private set { if (stringbuilder == null) { stringbuilder = value; } else { stringbuilder.Clear(); stringbuilder.Append(value); } } 72 | } 73 | /// 74 | /// Clears the length without clearing the capacity to prevent garbage deallocation. 75 | /// 76 | public int Length 77 | { 78 | get { return stringbuilder.Length; } 79 | set { stringbuilder.Length = value; } 80 | } 81 | /// 82 | /// Clears the length without clearing the capacity to prevent garbage deallocation. 83 | /// 84 | public void Clear() 85 | { 86 | stringbuilder.Length = 0; 87 | } 88 | /// 89 | /// Typically you only increase this setting it to say zero would cause a garbage collection to occur. 90 | /// 91 | public int Capacity 92 | { 93 | get { return stringbuilder.Capacity; } 94 | set { stringbuilder.Capacity = value; } 95 | } 96 | 97 | public static void CheckSeperator() 98 | { 99 | decimalseperator = Convert.ToChar(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator); 100 | } 101 | 102 | // indexer 103 | 104 | /// 105 | /// Indexer to chars in the underlying array 106 | /// 107 | public char this[int i] 108 | { 109 | get { return stringbuilder[i]; } 110 | set { stringbuilder[i] = value; } 111 | } 112 | 113 | // constructors 114 | 115 | public MgStringBuilder() 116 | { 117 | StringBuilder = StringBuilder; 118 | if (stringbuilder == null) { stringbuilder = new StringBuilder(); } 119 | } 120 | public MgStringBuilder(int capacity) 121 | { 122 | StringBuilder = new StringBuilder(capacity); 123 | if (stringbuilder == null) { stringbuilder = new StringBuilder(); } 124 | } 125 | public MgStringBuilder(StringBuilder sb) 126 | { 127 | StringBuilder = sb; 128 | if (sb == null) { sb = new StringBuilder(); } 129 | } 130 | public MgStringBuilder(string s) 131 | { 132 | StringBuilder = new StringBuilder(s); 133 | if (stringbuilder == null) { stringbuilder = new StringBuilder(); } 134 | } 135 | 136 | // operators 137 | 138 | public static implicit operator MgStringBuilder(String s) 139 | { 140 | return new MgStringBuilder(s); 141 | } 142 | public static implicit operator MgStringBuilder(StringBuilder sb) 143 | { 144 | return new MgStringBuilder(sb); 145 | } 146 | public static implicit operator StringBuilder(MgStringBuilder msb) 147 | { 148 | return msb.StringBuilder; 149 | } 150 | public static MgStringBuilder operator +(MgStringBuilder sbm, MgStringBuilder s) 151 | { 152 | sbm.StringBuilder.Append(s); 153 | return sbm; 154 | } 155 | public static MgStringBuilder operator +(MgStringBuilder sbm, string s) 156 | { 157 | sbm.StringBuilder.Append(s); 158 | return sbm; 159 | } 160 | 161 | // Methods all the appends are unrolled to squeeze out speed. 162 | 163 | public MgStringBuilder Append(StringBuilder s) 164 | { 165 | int len = this.StringBuilder.Length; 166 | CheckAppendCapacityAndLength(stringbuilder.Length, s.Length); 167 | for (int i = 0; i < s.Length; i++) 168 | { 169 | this.StringBuilder[i + len] = (char)(s[i]); 170 | } 171 | return this; 172 | } 173 | public MgStringBuilder Append(string s) 174 | { 175 | stringbuilder.Append(s); 176 | return this; 177 | } 178 | public MgStringBuilder Append(char value) 179 | { 180 | stringbuilder.Append(value); 181 | return this; 182 | } 183 | public MgStringBuilder Append(bool value) 184 | { 185 | stringbuilder.Append(value); 186 | return this; 187 | } 188 | public MgStringBuilder Append(byte value) 189 | { 190 | // basics 191 | int num = value; 192 | if (num == 0) 193 | { 194 | stringbuilder.Append('0'); 195 | return this; 196 | } 197 | int place = 100; 198 | if (num >= place * 10) 199 | { 200 | // just append it 201 | stringbuilder.Append(num); 202 | return this; 203 | } 204 | // part 1 pull integer digits 205 | bool addzeros = false; 206 | while (place > 0) 207 | { 208 | if (num >= place) 209 | { 210 | addzeros = true; 211 | int modulator = place * 10; 212 | int val = num % modulator; 213 | int dc = val / place; 214 | stringbuilder.Append((char)(dc + 48)); 215 | } 216 | else 217 | { 218 | if (addzeros) { stringbuilder.Append('0'); } 219 | } 220 | place = (int)(place * .1); 221 | } 222 | return this; 223 | } 224 | public MgStringBuilder Append(short value) 225 | { 226 | int num = value; 227 | // basics 228 | if (num < 0) 229 | { 230 | // Negative. 231 | stringbuilder.Append(minus); 232 | num = -num; 233 | } 234 | if (value == 0) 235 | { 236 | stringbuilder.Append('0'); 237 | return this; 238 | } 239 | 240 | int place = 10000; 241 | if (num >= place * 10) 242 | { 243 | // just append it, if its this big, this isn't a science calculator, its a edge case. 244 | stringbuilder.Append(num); 245 | return this; 246 | } 247 | // part 1 pull integer digits 248 | bool addzeros = false; 249 | while (place > 0) 250 | { 251 | if (num >= place) 252 | { 253 | addzeros = true; 254 | int modulator = place * 10; 255 | int val = num % modulator; 256 | int dc = val / place; 257 | stringbuilder.Append((char)(dc + 48)); 258 | } 259 | else 260 | { 261 | if (addzeros) { stringbuilder.Append('0'); } 262 | } 263 | place = (int)(place * .1); 264 | } 265 | return this; 266 | } 267 | public MgStringBuilder Append(int value) 268 | { 269 | // basics 270 | if (value < 0) 271 | { 272 | // Negative. 273 | stringbuilder.Append(minus); 274 | value = -value; 275 | } 276 | if (value == 0) 277 | { 278 | stringbuilder.Append('0'); 279 | return this; 280 | } 281 | 282 | int place = 1000000000; 283 | if (value >= place * 10) 284 | { 285 | // just append it 286 | stringbuilder.Append(value); 287 | return this; 288 | } 289 | // part 1 pull integer digits 290 | int n = (int)(value); 291 | bool addzeros = false; 292 | while (place > 0) 293 | { 294 | if (n >= place) 295 | { 296 | addzeros = true; 297 | int modulator = place * 10; 298 | int val = n % modulator; 299 | int dc = val / place; 300 | stringbuilder.Append((char)(dc + 48)); 301 | } 302 | else 303 | { 304 | if (addzeros) { stringbuilder.Append('0'); } 305 | } 306 | place = (int)(place * .1); 307 | } 308 | return this; 309 | } 310 | public MgStringBuilder Append(long value) 311 | { 312 | // basics 313 | if (value < 0) 314 | { 315 | // Negative. 316 | stringbuilder.Append(minus); 317 | value = -value; 318 | } 319 | if (value == 0) 320 | { 321 | stringbuilder.Append('0'); 322 | return this; 323 | } 324 | 325 | long place = 10000000000000000L; 326 | if (value >= place * 10) 327 | { 328 | // just append it, 329 | stringbuilder.Append(value); 330 | return this; 331 | } 332 | // part 1 pull integer digits 333 | long n = (long)(value); 334 | bool addzeros = false; 335 | while (place > 0) 336 | { 337 | if (n >= place) 338 | { 339 | addzeros = true; 340 | long modulator = place * 10L; 341 | long val = n % modulator; 342 | long dc = val / place; 343 | stringbuilder.Append((char)(dc + 48)); 344 | } 345 | else 346 | { 347 | if (addzeros) { stringbuilder.Append('0'); } 348 | } 349 | place = (long)(place * .1); 350 | } 351 | return this; 352 | } 353 | 354 | public MgStringBuilder Append(float value) 355 | { 356 | // basics 357 | bool addZeros = false; 358 | int n = (int)(value); 359 | int place = 100000000; 360 | if (value < 0) 361 | { 362 | // Negative. 363 | stringbuilder.Append(minus); 364 | value = -value; 365 | n = -n; 366 | } 367 | if (value == 0) 368 | { 369 | stringbuilder.Append('0'); 370 | return this; 371 | } 372 | // fix march 18-17 373 | // values not zero value is at least a integer 374 | if (value <= -1f || value >= 1f) 375 | { 376 | 377 | place = 100000000; 378 | if (value >= place * 10) 379 | { 380 | // just append it, if its this big its a edge case. 381 | stringbuilder.Append(value); 382 | return this; 383 | } 384 | // part 1 pull integer digits 385 | // int n = // moved 386 | addZeros = false; 387 | while (place > 0) 388 | { 389 | if (n >= place) 390 | { 391 | addZeros = true; 392 | int modulator = place * 10; 393 | int val = n % modulator; 394 | int dc = val / place; 395 | stringbuilder.Append((char)(dc + 48)); 396 | } 397 | else 398 | { 399 | if (addZeros) { stringbuilder.Append('0'); } 400 | } 401 | place = (int)(place * .1); 402 | } 403 | } 404 | else 405 | stringbuilder.Append('0'); 406 | 407 | stringbuilder.Append(decimalseperator); 408 | 409 | // part 2 410 | 411 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 412 | place = 1000000; 413 | // pull decimal to integer digits, based on the number of place digits 414 | int dn = (int)((value - (float)(n)) * place * 10); 415 | // ... march 17 testing... cut out extra zeros case 1 416 | if (dn == 0) 417 | { 418 | stringbuilder.Append('0'); 419 | return this; 420 | } 421 | addZeros = true; 422 | while (place > 0) 423 | { 424 | if (dn >= place) 425 | { 426 | //addzeros = true; 427 | int modulator = place * 10; 428 | int val = dn % modulator; 429 | int dc = val / place; 430 | stringbuilder.Append((char)(dc + 48)); 431 | if (val - dc * place == 0) // && trimEndZeros this would be a acstetic 432 | { 433 | return this; 434 | } 435 | } 436 | else 437 | { 438 | if (addZeros) { stringbuilder.Append('0'); } 439 | } 440 | place = (int)(place * .1); 441 | } 442 | return this; 443 | } 444 | public MgStringBuilder Append(double value) 445 | { 446 | // basics 447 | bool addZeros = false; 448 | long n = (long)(value); 449 | long place = 10000000000000000L; 450 | if (value < 0) // is Negative. 451 | { 452 | stringbuilder.Append(minus); 453 | value = -value; 454 | n = -n; 455 | } 456 | if (value == 0) // is Zero 457 | { 458 | stringbuilder.Append('0'); 459 | return this; 460 | } 461 | if (value <= -1d || value >= 1d) // is a Integer 462 | { 463 | if (value >= place * 10) 464 | { 465 | stringbuilder.Append(value); // is big, just append its a edge case. 466 | return this; 467 | } 468 | // part 1 pull integer digits 469 | addZeros = false; 470 | while (place > 0) 471 | { 472 | if (n >= place) 473 | { 474 | addZeros = true; 475 | long modulator = place * 10; 476 | long val = n % modulator; 477 | long dc = val / place; 478 | stringbuilder.Append((char)(dc + 48)); 479 | } 480 | else 481 | if (addZeros) { stringbuilder.Append('0'); } 482 | 483 | place = (long)(place * .1d); 484 | } 485 | } 486 | else 487 | stringbuilder.Append('0'); 488 | 489 | stringbuilder.Append(decimalseperator); 490 | 491 | // part 2 492 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 493 | place = 1000000000000000L; 494 | // pull decimal to integer digits, based on the number of place digits 495 | long dn = (long)((value - (double)(n)) * place * 10); 496 | if (dn == 0) 497 | { 498 | stringbuilder.Append('0'); 499 | return this; 500 | } 501 | addZeros = true; 502 | while (place > 0) 503 | { 504 | if (dn >= place) 505 | { 506 | long modulator = place * 10; 507 | long val = dn % modulator; 508 | long dc = val / place; 509 | stringbuilder.Append((char)(dc + 48)); 510 | if (val - dc * place == 0) // && trimEndZeros aectetic 511 | { 512 | return this; 513 | } 514 | } 515 | else 516 | if (addZeros) { stringbuilder.Append('0'); } 517 | 518 | place = (long)(place * .1); 519 | } 520 | return this; 521 | } 522 | 523 | public MgStringBuilder Append(Point value) 524 | { 525 | Append("("); 526 | Append(value.X); 527 | Append(", "); 528 | Append(value.Y); 529 | Append(")"); 530 | return this; 531 | } 532 | public MgStringBuilder Append(Vector2 value) 533 | { 534 | Append("("); 535 | Append(value.X); 536 | Append(", "); 537 | Append(value.Y); 538 | Append(")"); 539 | return this; 540 | } 541 | public MgStringBuilder Append(Vector3 value) 542 | { 543 | Append("("); 544 | Append(value.X); 545 | Append(", "); 546 | Append(value.Y); 547 | Append(", "); 548 | Append(value.Z); 549 | Append(")"); 550 | return this; 551 | } 552 | public MgStringBuilder Append(Vector4 value) 553 | { 554 | Append("("); 555 | Append(value.X); 556 | Append(", "); 557 | Append(value.Y); 558 | Append(", "); 559 | Append(value.Z); 560 | Append(", "); 561 | Append(value.W); 562 | Append(")"); 563 | return this; 564 | } 565 | public MgStringBuilder Append(Color value) 566 | { 567 | Append("("); 568 | Append(value.R); 569 | Append(", "); 570 | Append(value.G); 571 | Append(", "); 572 | Append(value.B); 573 | Append(", "); 574 | Append(value.A); 575 | Append(")"); 576 | return this; 577 | } 578 | 579 | public MgStringBuilder AppendTrim(float value) 580 | { 581 | // basics 582 | bool addZeros = false; 583 | int n = (int)(value); 584 | int place = 100000000; 585 | if (value < 0) 586 | { 587 | // Negative. 588 | stringbuilder.Append(minus); 589 | value = -value; 590 | n = -n; 591 | } 592 | if (value == 0) 593 | { 594 | stringbuilder.Append('0'); 595 | return this; 596 | } 597 | // fix march 18-17 598 | // values not zero value is at least a integer 599 | if (value <= -1f || value >= 1f) 600 | { 601 | 602 | place = 100000000; 603 | if (value >= place * 10) 604 | { 605 | // just append it, if its this big its a edge case. 606 | stringbuilder.Append(value); 607 | return this; 608 | } 609 | // part 1 pull integer digits 610 | // int n = // moved 611 | addZeros = false; 612 | while (place > 0) 613 | { 614 | if (n >= place) 615 | { 616 | addZeros = true; 617 | int modulator = place * 10; 618 | int val = n % modulator; 619 | int dc = val / place; 620 | stringbuilder.Append((char)(dc + 48)); 621 | } 622 | else 623 | { 624 | if (addZeros) { stringbuilder.Append('0'); } 625 | } 626 | place = (int)(place * .1); 627 | } 628 | } 629 | else 630 | stringbuilder.Append('0'); 631 | 632 | stringbuilder.Append(decimalseperator); 633 | 634 | // part 2 635 | 636 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 637 | place = 100; 638 | // pull decimal to integer digits, based on the number of place digits 639 | int dn = (int)((value - (float)(n)) * place * 10); 640 | // ... march 17 testing... cut out extra zeros case 1 641 | if (dn == 0) 642 | { 643 | stringbuilder.Append('0'); 644 | return this; 645 | } 646 | addZeros = true; 647 | while (place > 0) 648 | { 649 | if (dn >= place) 650 | { 651 | //addzeros = true; 652 | int modulator = place * 10; 653 | int val = dn % modulator; 654 | int dc = val / place; 655 | stringbuilder.Append((char)(dc + 48)); 656 | if (val - dc * place == 0) // && trimEndZeros this would be a acstetic 657 | { 658 | return this; 659 | } 660 | } 661 | else 662 | { 663 | if (addZeros) { stringbuilder.Append('0'); } 664 | } 665 | place = (int)(place * .1); 666 | } 667 | return this; 668 | } 669 | public MgStringBuilder AppendTrim(double value) 670 | { 671 | // basics 672 | bool addZeros = false; 673 | long n = (long)(value); 674 | long place = 10000000000000000L; 675 | if (value < 0) // is Negative. 676 | { 677 | stringbuilder.Append(minus); 678 | value = -value; 679 | n = -n; 680 | } 681 | if (value == 0) // is Zero 682 | { 683 | stringbuilder.Append('0'); 684 | return this; 685 | } 686 | if (value <= -1d || value >= 1d) // is a Integer 687 | { 688 | if (value >= place * 10) 689 | { 690 | stringbuilder.Append(value); // is big, just append its a edge case. 691 | return this; 692 | } 693 | // part 1 pull integer digits 694 | addZeros = false; 695 | while (place > 0) 696 | { 697 | if (n >= place) 698 | { 699 | addZeros = true; 700 | long modulator = place * 10; 701 | long val = n % modulator; 702 | long dc = val / place; 703 | stringbuilder.Append((char)(dc + 48)); 704 | } 705 | else 706 | if (addZeros) { stringbuilder.Append('0'); } 707 | 708 | place = (long)(place * .1); 709 | } 710 | } 711 | else 712 | stringbuilder.Append('0'); 713 | 714 | stringbuilder.Append(decimalseperator); 715 | 716 | // part 2 717 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 718 | place = 100L; 719 | // pull decimal to integer digits, based on the number of place digits 720 | long dn = (long)((value - (double)(n)) * place * 10); 721 | if (dn == 0) 722 | { 723 | stringbuilder.Append('0'); 724 | return this; 725 | } 726 | addZeros = true; 727 | while (place > 0) 728 | { 729 | if (dn >= place) 730 | { 731 | long modulator = place * 10; 732 | long val = dn % modulator; 733 | long dc = val / place; 734 | stringbuilder.Append((char)(dc + 48)); 735 | if (val - dc * place == 0) // && trimEndZeros aectetic 736 | { 737 | return this; 738 | } 739 | } 740 | else 741 | if (addZeros) { stringbuilder.Append('0'); } 742 | 743 | place = (long)(place * .1); 744 | } 745 | return this; 746 | } 747 | 748 | public MgStringBuilder AppendTrim(float value, int digits) 749 | { 750 | // basics 751 | bool addZeros = false; 752 | int n = (int)(value); 753 | int place = 100000000; 754 | if (value < 0) 755 | { 756 | // Negative. 757 | stringbuilder.Append(minus); 758 | value = -value; 759 | n = -n; 760 | } 761 | if (value == 0) 762 | { 763 | stringbuilder.Append('0'); 764 | return this; 765 | } 766 | // fix march 18-17 767 | // values not zero value is at least a integer 768 | if (value <= -1f || value >= 1f) 769 | { 770 | 771 | place = 100000000; 772 | if (value >= place * 10) 773 | { 774 | // just append it, if its this big its a edge case. 775 | stringbuilder.Append(value); 776 | return this; 777 | } 778 | // part 1 pull integer digits 779 | // int n = // moved 780 | addZeros = false; 781 | while (place > 0) 782 | { 783 | if (n >= place) 784 | { 785 | addZeros = true; 786 | int modulator = place * 10; 787 | int val = n % modulator; 788 | int dc = val / place; 789 | stringbuilder.Append((char)(dc + 48)); 790 | } 791 | else 792 | { 793 | if (addZeros) { stringbuilder.Append('0'); } 794 | } 795 | place = (int)(place * .1); 796 | } 797 | } 798 | else 799 | stringbuilder.Append('0'); 800 | 801 | stringbuilder.Append(decimalseperator); 802 | 803 | // part 2 804 | 805 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 806 | 807 | // nov 20 2018 added the amount of digits to trim. 808 | //place = 100; 809 | if (digits < 1) 810 | place = 0; 811 | else 812 | { 813 | place = 1; 814 | for (int i = 0; i < digits - 1; i++) 815 | place *= 10; 816 | } 817 | 818 | // pull decimal to integer digits, based on the number of place digits 819 | int dn = (int)((value - (float)(n)) * place * 10); 820 | // ... march 17 testing... cut out extra zeros case 1 821 | if (dn == 0) 822 | { 823 | stringbuilder.Append('0'); 824 | return this; 825 | } 826 | addZeros = true; 827 | while (place > 0) 828 | { 829 | if (dn >= place) 830 | { 831 | //addzeros = true; 832 | int modulator = place * 10; 833 | int val = dn % modulator; 834 | int dc = val / place; 835 | stringbuilder.Append((char)(dc + 48)); 836 | if (val - dc * place == 0) // && trimEndZeros this would be a acstetic 837 | { 838 | return this; 839 | } 840 | } 841 | else 842 | { 843 | if (addZeros) { stringbuilder.Append('0'); } 844 | } 845 | place = (int)(place * .1); 846 | } 847 | return this; 848 | } 849 | public MgStringBuilder AppendTrim(double value, int digits) 850 | { 851 | // basics 852 | bool addZeros = false; 853 | long n = (long)(value); 854 | long place = 10000000000000000L; 855 | if (value < 0) // is Negative. 856 | { 857 | stringbuilder.Append(minus); 858 | value = -value; 859 | n = -n; 860 | } 861 | if (value == 0) // is Zero 862 | { 863 | stringbuilder.Append('0'); 864 | return this; 865 | } 866 | if (value <= -1d || value >= 1d) // is a Integer 867 | { 868 | if (value >= place * 10) 869 | { 870 | stringbuilder.Append(value); // is big, just append its a edge case. 871 | return this; 872 | } 873 | // part 1 pull integer digits 874 | addZeros = false; 875 | while (place > 0) 876 | { 877 | if (n >= place) 878 | { 879 | addZeros = true; 880 | long modulator = place * 10; 881 | long val = n % modulator; 882 | long dc = val / place; 883 | stringbuilder.Append((char)(dc + 48)); 884 | } 885 | else 886 | if (addZeros) { stringbuilder.Append('0'); } 887 | 888 | place = (long)(place * .1); 889 | } 890 | } 891 | else 892 | stringbuilder.Append('0'); 893 | 894 | stringbuilder.Append(decimalseperator); 895 | 896 | // part 2 897 | // floating point part now it can have about 28 digits but uh ya.. nooo lol 898 | // nov 20 2018 added the amount of digits to trim. 899 | //place = 100L; 900 | if (digits < 1) 901 | place = 0; 902 | else 903 | { 904 | place = 1; 905 | for (int i = 0; i < digits - 1; i++) 906 | place *= 10; 907 | } 908 | 909 | // pull decimal to integer digits, based on the number of place digits 910 | long dn = (long)((value - (double)(n)) * place * 10); 911 | if (dn == 0) 912 | { 913 | stringbuilder.Append('0'); 914 | return this; 915 | } 916 | addZeros = true; 917 | while (place > 0) 918 | { 919 | if (dn >= place) 920 | { 921 | long modulator = place * 10; 922 | long val = dn % modulator; 923 | long dc = val / place; 924 | stringbuilder.Append((char)(dc + 48)); 925 | if (val - dc * place == 0) // && trimEndZeros aectetic 926 | { 927 | return this; 928 | } 929 | } 930 | else 931 | if (addZeros) { stringbuilder.Append('0'); } 932 | 933 | place = (long)(place * .1); 934 | } 935 | return this; 936 | } 937 | 938 | public MgStringBuilder AppendTrim(Vector2 value) 939 | { 940 | Append("("); 941 | AppendTrim(value.X); 942 | Append(", "); 943 | AppendTrim(value.Y); 944 | Append(")"); 945 | return this; 946 | } 947 | public MgStringBuilder AppendTrim(Vector3 value) 948 | { 949 | Append("("); 950 | AppendTrim(value.X); 951 | Append(", "); 952 | AppendTrim(value.Y); 953 | Append(", "); 954 | AppendTrim(value.Z); 955 | Append(")"); 956 | return this; 957 | } 958 | public MgStringBuilder AppendTrim(Vector4 value) 959 | { 960 | Append("("); 961 | AppendTrim(value.X); 962 | Append(", "); 963 | AppendTrim(value.Y); 964 | Append(", "); 965 | AppendTrim(value.Z); 966 | Append(", "); 967 | AppendTrim(value.W); 968 | Append(")"); 969 | return this; 970 | } 971 | public MgStringBuilder Append(Rectangle value) 972 | { 973 | Append("("); 974 | AppendTrim(value.X); 975 | Append(", "); 976 | AppendTrim(value.Y); 977 | Append(", "); 978 | AppendTrim(value.Width); 979 | Append(", "); 980 | AppendTrim(value.Height); 981 | Append(")"); 982 | return this; 983 | } 984 | 985 | public MgStringBuilder AppendLine(StringBuilder s) 986 | { 987 | Append(s); 988 | stringbuilder.AppendLine(); 989 | return this; 990 | } 991 | public MgStringBuilder AppendLine(string s) 992 | { 993 | stringbuilder.Append(s); 994 | stringbuilder.AppendLine(); 995 | return this; 996 | } 997 | public MgStringBuilder AppendLine() 998 | { 999 | stringbuilder.AppendLine(); 1000 | return this; 1001 | } 1002 | 1003 | /// 1004 | /// Functions just like a indexer. 1005 | /// 1006 | public void OverWriteAt(int index, Char s) 1007 | { 1008 | CheckOverWriteCapacityAndLength(index, 1); 1009 | this.StringBuilder[index] = (char)(s); 1010 | } 1011 | /// 1012 | /// Functions to overwrite data at the index on 1013 | /// 1014 | public void OverWriteAt(int index, StringBuilder s) 1015 | { 1016 | CheckOverWriteCapacityAndLength(index, s.Length); 1017 | for (int i = 0; i < s.Length; i++) 1018 | this.StringBuilder[i + index] = (char)(s[i]); 1019 | } 1020 | /// 1021 | /// Functions to overwrite data at the index on 1022 | /// 1023 | public void OverWriteAt(int index, String s) 1024 | { 1025 | CheckAppendCapacityAndLength(index, s.Length); 1026 | for (int i = 0; i < s.Length; i++) 1027 | this.StringBuilder[i + index] = (char)(s[i]); 1028 | } 1029 | 1030 | /// 1031 | /// This uses AppendAt to get around problems with garbage collections. 1032 | /// 1033 | public MgStringBuilder Insert(int index, char c) 1034 | { 1035 | AppendAt(index, c); 1036 | return this; 1037 | } 1038 | /// 1039 | /// This uses AppendAt to get around problems with garbage collections. 1040 | /// 1041 | public MgStringBuilder Insert(int index, StringBuilder s) 1042 | { 1043 | AppendAt(index, s); 1044 | return this; 1045 | } 1046 | /// 1047 | /// This uses AppendAt to get around problems with garbage collections. 1048 | /// 1049 | public MgStringBuilder Insert(int index, string s) 1050 | { 1051 | AppendAt(index, s); 1052 | return this; 1053 | } 1054 | 1055 | /// 1056 | /// Functions as a insert, existing text will be moved over. 1057 | /// 1058 | public void AppendAt(int index, Char s) 1059 | { 1060 | CheckAppendCapacityAndLength(index, 1); 1061 | for (int j = StringBuilder.Length - 1; j >= index + 1; j--) 1062 | stringbuilder[j] = stringbuilder[j - 1]; 1063 | for (int i = 0; i < 1; i++) 1064 | stringbuilder[i + index] = (char)(s); 1065 | } 1066 | /// 1067 | /// Functions as a insert, existing text will be moved over. 1068 | /// 1069 | public void AppendAt(int index, StringBuilder s) 1070 | { 1071 | CheckAppendCapacityAndLength(index, s.Length); 1072 | int insertedsbLength = s.Length; 1073 | for (int j = stringbuilder.Length - 1; j >= index + insertedsbLength; j--) 1074 | stringbuilder[j] = stringbuilder[j - insertedsbLength]; 1075 | for (int i = 0; i < insertedsbLength; i++) 1076 | { 1077 | stringbuilder[index + i] = s[i]; 1078 | } 1079 | } 1080 | /// 1081 | /// Functions as a insert, existing text will be moved over. Notes are left in this method overload. 1082 | /// 1083 | public void AppendAt(int index, String s) 1084 | { 1085 | CheckAppendCapacityAndLength(index, s.Length); 1086 | // Now we will wind from back to front the current characters in the stringbuilder to make room for this append. 1087 | // Yes this will be a expensive operation however this must be done if we want a proper AppendAt. 1088 | // Chunks or no chunks stringbuilders insert is piss poor worse it creates garbage. 1089 | int insertedsbLength = s.Length; 1090 | for (int j = stringbuilder.Length - 1; j >= index + insertedsbLength; j--) 1091 | stringbuilder[j] = stringbuilder[j - insertedsbLength]; 1092 | // perform the append 1093 | for (int i = 0; i < insertedsbLength; i++) 1094 | { 1095 | stringbuilder[index + i] = s[i]; 1096 | } 1097 | } 1098 | 1099 | public MgStringBuilder Remove(int index, int length) 1100 | { 1101 | stringbuilder.Remove(index, length); 1102 | //Delete(index, length); 1103 | return this; 1104 | } 1105 | 1106 | public MgStringBuilder Delete(int index, int length) 1107 | { 1108 | int len = stringbuilder.Length - length; 1109 | int j = length; 1110 | for (int i = index; i < len; i++) 1111 | { 1112 | if (i + j < len) 1113 | stringbuilder[i] = stringbuilder[i + j]; 1114 | else 1115 | stringbuilder[i] = ' '; 1116 | } 1117 | stringbuilder.Length = len; 1118 | return this; 1119 | } 1120 | 1121 | private void CheckAppendCapacityAndLength(int index, int lengthOfAddition) 1122 | { 1123 | int newLength = lengthOfAddition + stringbuilder.Length; 1124 | int reqcapacity = (newLength + 1) - (stringbuilder.Capacity); 1125 | if (reqcapacity >= 0) 1126 | stringbuilder.Capacity = (stringbuilder.Capacity + reqcapacity + 64); 1127 | stringbuilder.Length = newLength; 1128 | } 1129 | private void CheckOverWriteCapacityAndLength(int index, int lengthOfOverWrite) 1130 | { 1131 | int dist = (index + lengthOfOverWrite); 1132 | if (dist > stringbuilder.Length) 1133 | { 1134 | int newLength = index + lengthOfOverWrite; 1135 | int reqcapacity = (newLength + 1) - (stringbuilder.Capacity); 1136 | if (reqcapacity >= 0) 1137 | stringbuilder.Capacity = (stringbuilder.Capacity + reqcapacity + 64); 1138 | stringbuilder.Length = newLength; 1139 | } 1140 | } 1141 | 1142 | /// 1143 | /// Use with caution this solves a rare edge case. 1144 | /// Be careful using this you should understand c# references before doing so. 1145 | /// This creates a direct secondary reference to the internal stringbuilder via out. 1146 | /// Declare a StringBuilder reference such as StringBuilder sb; don't call new on it, then pass sb to this function. 1147 | /// When you are done with it unlink it by declaring new on it like so, sb = new StringBuilder(); 1148 | /// This allows a way to link a reference to the internal stringbuilder without creating deallocation garbage. 1149 | /// 1150 | public void LinkReferenceToTheInnerStringBuilder(out StringBuilder rsb) 1151 | { 1152 | rsb = stringbuilder; 1153 | } 1154 | 1155 | public char[] ToCharArray() 1156 | { 1157 | char[] a = new char[stringbuilder.Length]; 1158 | stringbuilder.CopyTo(0, a, 0, stringbuilder.Length); 1159 | return a; 1160 | } 1161 | 1162 | public override string ToString() 1163 | { 1164 | return stringbuilder.ToString(); 1165 | } 1166 | } 1167 | --------------------------------------------------------------------------------