#region Copyright /* -------------------------------------------------------------------------------- This source file is part of Xenocide by Project Xenocide Team For the latest info on Xenocide, see http://www.projectxenocide.com/ This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. -------------------------------------------------------------------------------- */ /* * @file Util.cs * @date Created: 2007/03/12 * @author File creator: David Teviotdale * @author Credits: none */ #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Xml; using System.Xml.XPath; using System.Threading; using System.IO; using System.Globalization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Graphics; using ProjectXenocide.Model; using ProjectXenocide.Model.Geoscape; using ProjectXenocide.UI.Screens; using ProjectXenocide.UI.Dialogs; using CeGui; using Vector3 = Microsoft.Xna.Framework.Vector3; using Xenocide.Resources; #endregion namespace ProjectXenocide.Utils { /// /// Used to pick an element at random from a set, where /// the different elements may have different selection probabilities /// public interface IOdds { /// /// Relative odds that this element is selected /// int Odds { get; } } /// Simple general purpose container public class Pair { /// Ctor /// /// public Pair(T1 first, T2 second) { this.first = first; this.second = second; } #region fields /// public T1 First { get { return first; } set { first = value; } } /// public T2 Second { get { return second; } set { second = value; } } private T1 first; private T2 second; #endregion fields } /// /// Assorted utility functions /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "we're not going to be using System.Web.Util")] public static class Util { /// /// Slightly enhanced Debug.WriteLine(), will timestamp line with game's GeoTime /// /// formatting string /// values to inject into formatting string [Conditional("DEBUG")] public static void GeoTimeDebugWriteLine(string format, params Object[] args) { Debug.WriteLine( Xenocide.GameState.GeoData.GeoTime.ToString() + " " + StringFormat(format, args) ); } /// /// Utility function, to stop Code Analysis complaining about ToString lacking IFormatProvider /// /// integer to obtain string representation of /// the string [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if obj == null")] public static string ToString(int i) { return i.ToString(CultureInfo.InvariantCulture); } /// /// Format currency value to display to user /// /// value /// string to display to user public static string FormatCurrency(int dollars) { return Util.StringFormat(Strings.FORMAT_CURRENCY, dollars); } /// /// Utility function, to stop Code Analysis complaining about String.Format lacking IFormatProvider /// /// formatting string /// values to inject into formatting string /// expanded string public static string StringFormat(string format, params Object[] args) { return String.Format(Thread.CurrentThread.CurrentCulture, format, args); } /// /// Load a string from the resources, with error checking /// /// name of string to load /// the loaded string [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if resourceName == null")] public static string LoadString(string resourceName) { // need to replace any hyphens in the name with underscores resourceName = resourceName.Replace('-', '_'); string temp = XenocideResourceManager.Get(resourceName); Debug.Assert(!String.IsNullOrEmpty(temp)); return temp; } /// /// Version of LoadString that will return the resourceName if can't find a resource with the name /// /// name of string to load /// the loaded string, or resourceName if not able to load [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if resourceName == null")] public static string SafeLoadString(string resourceName) { // need to replace any hyphens in the name with underscores resourceName = resourceName.Replace('-', '_'); string temp = XenocideResourceManager.Get(resourceName); if (String.IsNullOrEmpty(temp)) { temp = resourceName; } return temp; } /// /// Check if this XML element contains a specified attribute /// /// Navigator pointing at element to get attribute from /// name of attribute to check for /// true if element found [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static bool AttributePresent(XPathNavigator element, string attributeName) { bool found = element.MoveToAttribute(attributeName, String.Empty); // Need to reset the navigator after calling MoveToAttribute if (found) { element.MoveToParent(); } return found; } /// /// Advance XPathNavigator to specified attribute throwing exception if attribute not there /// /// Navigator pointing at element to get attribute from /// name of attribute to advance to private static void MoveToAttribute(XPathNavigator element, string attributeName) { if (!element.MoveToAttribute(attributeName, String.Empty)) { throw new XmlException(StringFormat(Strings.EXCEPTION_XML_ATTRIBUTE_NOT_FOUND, attributeName)); } } /// /// Retrive an integer valued attribute from an XML element /// /// XML element holding the attribute /// attribute of the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static int GetIntAttribute(XPathNavigator element, string attributeName) { return XmlConvert.ToInt32(GetStringAttribute(element, attributeName)); } /// /// Retrive a string valued attribute from an XML element /// /// XML element holding the attribute /// attribute of the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static string GetStringAttribute(XPathNavigator element, string attributeName) { MoveToAttribute(element, attributeName); string value = element.Value; // Need to reset the navigator after calling MoveToAttribute element.MoveToParent(); return value; } /// /// Retrive a boolean valued attribute from an XML element /// /// XML element holding the attribute /// attribute of the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static bool GetBoolAttribute(XPathNavigator element, string attributeName) { return XmlConvert.ToBoolean(GetStringAttribute(element, attributeName)); } /// /// Retrive a floating point double valued attribute from an XML element /// /// XML element holding the attribute /// attribute of the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static double GetDoubleAttribute(XPathNavigator element, string attributeName) { return XmlConvert.ToDouble(GetStringAttribute(element, attributeName)); } /// /// Retrive a float valued attribute from an XML element /// /// XML element holding the attribute /// attribute of the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static float GetFloatAttribute(XPathNavigator element, string attributeName) { return (float)GetDoubleAttribute(element, attributeName); } /// /// Retrive an attribute holding a degrees value /// /// XML element holding the attribute /// attribute holding the degrees /// degress, converted to radians [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static float GetDegreesAttribute(XPathNavigator element, string attributeName) { return MathHelper.ToRadians(Util.GetFloatAttribute(element, attributeName)); } /// /// Retrive a color keyy element from an XML element /// /// XML element holding the ColorKey child element /// Namespace used in planet.xml /// the color, as a packed ARGB [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if element == null")] public static uint GetColorKey(XPathNavigator element, XmlNamespaceManager manager) { XPathNavigator colorKeyAttrib = element.SelectSingleNode("p:colorKey", manager); uint pixel = 0xFF000000; pixel += ((uint)GetIntAttribute(colorKeyAttrib, "R") << 16); pixel += ((uint)GetIntAttribute(colorKeyAttrib, "G") << 8); pixel += (uint)GetIntAttribute(colorKeyAttrib, "B"); return pixel; } /// /// Add a column to the supplied MultiColumnList /// public static ListboxTextItem CreateListboxItem(String text) { ListboxTextItem item = new ListboxTextItem(text); item.SetSelectionBrushImage("TaharezLook", "MultiListSelectionBrush"); return item; } /// /// Get error message to show player coresponding to the XenoError enumeration /// /// Error code /// Textual represention of error public static String GetErrorMessage(XenoError xenoError) { switch (xenoError) { case XenoError.None: return Strings.XENOERROR_NONE; case XenoError.PositionNotInBase: return Strings.XENOERROR_POSITION_NOT_IN_BASE; case XenoError.PositionAlreadyOccupied: return Strings.XENOERROR_POSITION_ALREADY_OCCUPIED; case XenoError.CellHasNoNeighbours: return Strings.XENOERROR_CELL_HAS_NO_NEIGHBOURS; case XenoError.FacilityIsInUse: return Strings.XENOERROR_FACILITY_IS_IN_USE; case XenoError.DeleteWillSplitBase: return Strings.XENOERROR_DELETE_WILL_SPLIT_BASE; default: Debug.Assert(false); return ""; } } /// /// Format a message and put up as a message box /// /// The text to show in the message box /// Arguments to put into the string public static void ShowMessageBox(string format, params Object[] args) { Xenocide.ScreenManager.ShowDialog(new MessageBoxDialog(Util.StringFormat(format, args))); } /// /// Construct a validating XPathNavigator for the given XML file /// /// full filename of XML file /// the namespace used in the XML file /// the XPathNavigator public static XPathNavigator MakeValidatingXPathNavigator(string filename, string xmlns) { XmlReaderSettings settings = new XmlReaderSettings(); settings.Schemas.Add(xmlns, Path.ChangeExtension(filename, ".xsd")); settings.ValidationType = ValidationType.Schema; return (new XPathDocument(XmlReader.Create(filename, settings))).CreateNavigator(); } /// /// Return an interator that returns a filtered subset of a collection /// /// the collection to filter /// predicate that defines the filtering critera /// the iterator [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "FxCop bug, reporting false positive")] public static IEnumerable FilterColection(IEnumerable collection, Predicate filter) { foreach (T t in collection) { if (filter(t)) { yield return t; } } } /// /// Find number of items in a sequence /// /// Sequence is consumed by operation /// anything /// the sequence /// number of items [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "FxCop bug, reporting false positive")] public static int SequenceLength(IEnumerable sequence) { int count = 0; IEnumerator itr = sequence.GetEnumerator(); while (itr.MoveNext()) { ++count; } return count; } /// /// Add a cell holding a number to a MultiColumnList /// /// the multicolumn list /// to put the element in (zero based) /// to put the element in (zero based) /// integer to show in the element public static void AddNumericElementToGrid(CeGui.Widgets.MultiColumnList grid, int column, int row, T value) { AddStringElementToGrid(grid, column, row, StringFormat("{0}", value)); } /// /// Add a cell holding a string to a MultiColumnList /// /// the multicolumn list /// to put the element in (zero based) /// to put the element in (zero based) /// text show in the element [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "will throw if grid == null")] public static void AddStringElementToGrid(CeGui.Widgets.MultiColumnList grid, int column, int row, string text) { grid.SetGridItem(column, row, CreateListboxItem(text)); } /// /// Return value rounded to nearest 1000 /// /// value to round /// rounded value public static int RoundTo1000(double v) { return (int)(Math.Round(v / 1000)) * 1000; } /// /// Return the ratio of the two values as a percent /// /// the value /// value that indicates 100% /// public static int ToPercent(double numerator, double denominator) { if (numerator < 0) { return 0; } if (denominator < numerator) { return 100; } return (int)Math.Round(numerator * 100.0 / denominator); } /// /// Round a float to nearest integer /// /// float to round /// rounded value public static int Round(float f) { Debug.Assert(0.0f <= f); return (int)Math.Truncate(f + 0.5f); } /// /// Convert a string representation of an enumeration into it's enumerated value /// /// The enumeration type /// string representation of the value /// enumerated value [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "FxCop bug, reporting false positive")] public static T ParseEnum(string s) { return (T)Enum.Parse(typeof(T), s, true); } /// /// Used to pick an element at random from a set, where /// the different elements may have different selection probabilities /// public static T SelectRandom(ICollection elements) where T : IOdds { // first, find total options. int totalOdds = 0; foreach (T element in elements) { totalOdds += element.Odds; } Debug.Assert(0 < totalOdds); // now pick at random int choice = Xenocide.Rng.Next(totalOdds) + 1; foreach (T element in elements) { choice -= element.Odds; if (choice <= 0) { return element; } } // If get here, something went wrong Debug.Assert(false); return default(T); } /// /// Is the right mouse button down? /// /// true if button is down public static bool IsRightMouseButtonDown() { return (Mouse.GetState().RightButton == ButtonState.Pressed); } /// /// Compute bounding sphere for a model /// /// to compute bounding sphere for /// the sphere public static BoundingSphere CalcBoundingSphere(Microsoft.Xna.Framework.Graphics.Model model) { // Copy any parent transforms Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); // compute bounding sphere that holds all the meshes in the model BoundingSphere sphere = new BoundingSphere(); foreach (ModelMesh mesh in model.Meshes) { // appply mesh's bone transforms to bounding sphere float radius = mesh.BoundingSphere.Radius; Vector3 scale = new Vector3(radius, radius, radius); scale = Vector3.TransformNormal(scale, transforms[mesh.ParentBone.Index]); BoundingSphere subsphere = new BoundingSphere( Vector3.Transform(mesh.BoundingSphere.Center, transforms[mesh.ParentBone.Index]), Math.Max(Math.Abs(scale.X), Math.Max(Math.Abs(scale.Y), Math.Abs(scale.Z)))); // now combine each mesh's bounding sphere if (0.0f == sphere.Radius) { // first mesh sphere = subsphere; } else { sphere = BoundingSphere.CreateMerged(sphere, subsphere); } } return sphere; } /// Debugging test, assert two float values are same value /// value to compare /// other value to compare it to [Conditional("DEBUG")] public static void DebugTestFloatValuesSame(double v1, double v2) { Debug.Assert(MathHelper.Distance((float)v1, (float)v2) < 0.000001f); } /// Compute Axis Aligned Bounding Box for a model /// to compute AABB for /// the AABB public static BoundingBox CalcBoundingBox(Microsoft.Xna.Framework.Graphics.Model model) { // Copy any parent transforms Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); // compute bounding box that holds all the meshes in the model BoundingBox box = new BoundingBox(); bool firstMesh = true; foreach (ModelMesh mesh in model.Meshes) { // get limits of this mesh Vector3[] extents = CalcMeshExtents(mesh); // scale by bone transform Vector3.Transform(extents, ref transforms[mesh.ParentBone.Index], extents); // and merge with the boxes so far BoundingBox meshBox = new BoundingBox(extents[0], extents[1]); if (firstMesh) { box = meshBox; firstMesh = false; } else { box = BoundingBox.CreateMerged(box, meshBox); } } return box; } /// Compute Axis Aligned Bounding Box for a model mesh /// to compute AABB for /// min [0] and max [1] extents of the mesh public static Vector3[] CalcMeshExtents(ModelMesh mesh) { // get offset to position element in each vertex // assumes all mesh parts in mesh use same vertex layout int positionOffset = -1; foreach (VertexElement element in mesh.MeshParts[0].VertexDeclaration.GetVertexElements()) { if (element.VertexElementUsage == VertexElementUsage.Position) { positionOffset = element.Offset; break; } } Debug.Assert(0 <= positionOffset); int stride = mesh.MeshParts[0].VertexStride; // calc number of vertices in mesh int vertices = mesh.VertexBuffer.SizeInBytes / stride; Debug.Assert((vertices * stride) == mesh.VertexBuffer.SizeInBytes); // load vertex info where it can be read. byte[] buffer = new byte[mesh.VertexBuffer.SizeInBytes]; mesh.VertexBuffer.GetData(buffer); // scan the vertices, recording the maximum and minimum extents Vector3[] extents = { new Vector3( float.MaxValue, float.MaxValue, float.MaxValue), new Vector3(-float.MaxValue, -float.MaxValue, -float.MaxValue), }; for (int i = 0; i < vertices; ++i) { float x = BitConverter.ToSingle(buffer, (i * stride) + positionOffset); extents[0].X = Math.Min(x, extents[0].X); extents[1].X = Math.Max(x, extents[1].X); float y = BitConverter.ToSingle(buffer, (i * stride) + positionOffset + sizeof(float)); extents[0].Y = Math.Min(y, extents[0].Y); extents[1].Y = Math.Max(y, extents[1].Y); float z = BitConverter.ToSingle(buffer, (i * stride) + positionOffset + (2 * sizeof(float))); extents[0].Z = Math.Min(z, extents[0].Z); extents[1].Z = Math.Max(z, extents[1].Z); } return extents; } /// /// What shader version is supported? /// /// capabilities of device to check /// 0 if can't tell, 1 if 1.x, 2 if 2.x, 3 if 3.0 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Will throw if device is null")] public static int GetShaderVersion(GraphicsDeviceCapabilities caps) { ShaderProfile pixel = caps.MaxPixelShaderProfile; ShaderProfile vertex = caps.MaxVertexShaderProfile; if (pixel == ShaderProfile.Unknown) { return 0; } if ((pixel < ShaderProfile.PS_2_0) || (vertex < ShaderProfile.VS_2_0)) { return 1; } if ((pixel < ShaderProfile.PS_3_0) || (vertex < ShaderProfile.VS_3_0)) { return 2; } else return 3; } /// /// Linefeed sequence /// public const String Linefeed = "\r\n"; } }