├── 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 |
--------------------------------------------------------------------------------