#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";
}
}