diff --git a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs index e950db8d9..e8b8cbea2 100644 --- a/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs +++ b/Libraries/Farseer Physics Engine 3.5/Dynamics/World.cs @@ -999,7 +999,7 @@ namespace FarseerPhysics.Dynamics if (body == null) throw new ArgumentNullException("body"); if (body.World != this) - throw new ArgumentException("You are removing a body that is not in the simulation.", "body"); + throw new ArgumentException($"You are removing a body that is not in the simulation (userdata: {body.UserData?.ToString() ?? "null"}).", "body"); #if USE_AWAKE_BODY_SET Debug.Assert(!AwakeBodySet.Contains(body)); @@ -1034,6 +1034,7 @@ namespace FarseerPhysics.Dynamics body.DestroyProxies(); for (int i = 0; i < body.FixtureList.Count; i++) { + body.FixtureList[i].UserData = null; if (FixtureRemoved != null) FixtureRemoved(this, body, body.FixtureList[i]); } diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs index a2cc3d4e3..d6560df72 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatch.cs @@ -3,6 +3,9 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text; namespace Microsoft.Xna.Framework.Graphics @@ -25,15 +28,58 @@ namespace Microsoft.Xna.Framework.Graphics /// public class SpriteBatch : GraphicsResource, ISpriteBatch { + public struct EffectWithParams + { + private static readonly Dictionary parameterSetters; + private static readonly object[] setterParams = new object[1]; + + static EffectWithParams() + { + parameterSetters = new Dictionary(); + foreach (var method in typeof(EffectParameter).GetMethods()) + { + if (method.Name.Equals("SetValue", StringComparison.InvariantCulture) + && method.GetParameters() is { Length: 1 } parameters) + { + var type = parameters[0].ParameterType; + parameterSetters[type] = method; + foreach (var derived in Assembly.GetAssembly(type).GetTypes().Where(t => t.IsSubclassOf(type))) + { + parameterSetters[derived] = method; + } + } + } + } + + public Effect Effect; + public Dictionary Params; + + public EffectWithParams(Effect effect, Dictionary parameters = null) + { + Effect = effect; + Params = parameters; + } + + internal void Apply() + { + foreach (var (paramName, paramValue) in Params) + { + setterParams[0] = paramValue; + parameterSetters[paramValue.GetType()].Invoke(Effect.Parameters[paramName], setterParams); + } + Effect.CurrentTechnique.Passes[0].Apply(); + } + } + #region Private Fields readonly SpriteBatcher _batcher; SpriteSortMode _sortMode; BlendState _blendState; SamplerState _samplerState; - DepthStencilState _depthStencilState; - RasterizerState _rasterizerState; - Effect _effect; + DepthStencilState _depthStencilState; + RasterizerState _rasterizerState; + EffectWithParams _effect; bool _beginCalled; Effect _spriteEffect; @@ -60,7 +106,7 @@ namespace Microsoft.Xna.Framework.Graphics if (graphicsDevice == null) { throw new ArgumentNullException ("graphicsDevice", FrameworkResources.ResourceCreationWhenDeviceIsNull); - } + } this.GraphicsDevice = graphicsDevice; @@ -107,7 +153,7 @@ namespace Microsoft.Xna.Framework.Graphics _samplerState = samplerState ?? SamplerState.LinearClamp; _depthStencilState = depthStencilState ?? DepthStencilState.None; _rasterizerState = rasterizerState ?? RasterizerState.CullCounterClockwise; - _effect = effect; + _effect = new EffectWithParams(effect); _matrix = transformMatrix; // Setup things now so a user can change them. @@ -119,6 +165,11 @@ namespace Microsoft.Xna.Framework.Graphics _beginCalled = true; } + /// + /// Returns the current effect. + /// + public Effect GetCurrentEffect() => _effect.Effect; + /// /// Flushes all batched text and sprites to the screen. /// @@ -132,18 +183,31 @@ namespace Microsoft.Xna.Framework.Graphics if (_sortMode != SpriteSortMode.Immediate) Setup(); - - _batcher.DrawBatch(_sortMode, _effect); + + _batcher.DrawBatch(_sortMode, _spritePass); } - - void Setup() + + /// + /// Swaps the current effect. + /// + public void SwapEffect(Effect effect = null, Dictionary parameters = null) + { + _effect = new EffectWithParams(effect, parameters); + } + + public void SwapEffect(EffectWithParams effectWithParams) + { + _effect = effectWithParams; + } + + void Setup() { var gd = GraphicsDevice; gd.BlendState = _blendState; gd.DepthStencilState = _depthStencilState; gd.RasterizerState = _rasterizerState; gd.SamplerStates[0] = _samplerState; - + var vp = gd.Viewport; if ((vp.Width != _lastViewport.Width) || (vp.Height != _lastViewport.Height)) { @@ -169,7 +233,7 @@ namespace Microsoft.Xna.Framework.Graphics _spritePass.Apply(); } - + void CheckValid(Texture2D texture) { if (texture == null) @@ -279,6 +343,7 @@ namespace Microsoft.Xna.Framework.Graphics { var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; item.SortKey = sortKey; @@ -315,6 +380,7 @@ namespace Microsoft.Xna.Framework.Graphics var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -332,9 +398,9 @@ namespace Microsoft.Xna.Framework.Graphics item.SortKey = -layerDepth; break; } - + origin = origin * scale; - + float w, h; if (sourceRectangle.HasValue) { @@ -353,7 +419,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -366,7 +432,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR.X = _texCoordTL.X; _texCoordTL.X = temp; } - + if (rotation == 0f) { item.Set(position.X - origin.X, @@ -393,7 +459,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + FlushIfNeeded(); } @@ -444,9 +510,10 @@ namespace Microsoft.Xna.Framework.Graphics float layerDepth) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; + item.Effect = _effect; // set SortKey based on SpriteSortMode. switch ( _sortMode ) @@ -478,7 +545,7 @@ namespace Microsoft.Xna.Framework.Graphics else origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; if(srcRect.Height != 0) - origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; + origin.Y = origin.Y * (float)destinationRectangle.Height / (float)srcRect.Height; else origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } @@ -486,11 +553,11 @@ namespace Microsoft.Xna.Framework.Graphics { _texCoordTL = Vector2.Zero; _texCoordBR = Vector2.One; - + origin.X = origin.X * (float)destinationRectangle.Width * texture.TexelWidth; origin.Y = origin.Y * (float)destinationRectangle.Height * texture.TexelHeight; } - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -539,7 +606,7 @@ namespace Microsoft.Xna.Framework.Graphics { if (_sortMode == SpriteSortMode.Immediate) { - _batcher.DrawBatch(_sortMode, _effect); + _batcher.DrawBatch(_sortMode, _spritePass); } } @@ -553,10 +620,11 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; @@ -600,13 +668,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + if (sourceRectangle.HasValue) { var srcRect = sourceRectangle.GetValueOrDefault(); @@ -629,7 +698,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + FlushIfNeeded(); } @@ -642,13 +711,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw (Texture2D texture, Vector2 position, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(position.X, position.Y, texture.Width, @@ -670,13 +740,14 @@ namespace Microsoft.Xna.Framework.Graphics public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color) { CheckValid(texture); - + var item = _batcher.CreateBatchItem(); item.Texture = texture; - + item.Effect = _effect; + // set SortKey based on SpriteSortMode. item.SortKey = _sortMode == SpriteSortMode.Texture ? texture.SortingKey : 0; - + item.Set(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, @@ -685,7 +756,7 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Zero, Vector2.One, 0); - + FlushIfNeeded(); } @@ -699,7 +770,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, string text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -720,7 +791,7 @@ namespace Microsoft.Xna.Framework.Graphics firstGlyphOfLine = true; continue; } - + var currentGlyphIndex = spriteFont.GetGlyphIndexOrDefault(c); var pCurrentGlyph = pGlyphs + currentGlyphIndex; @@ -737,15 +808,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -759,7 +831,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordTL, _texCoordBR, 0); - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -804,7 +876,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -831,7 +903,7 @@ namespace Microsoft.Xna.Framework.Graphics if (flippedVert || flippedHorz) { Vector2 size; - + var source = new SpriteFont.CharacterSource(text); spriteFont.MeasureString(ref source, out size); @@ -847,7 +919,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -866,7 +938,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -916,15 +988,16 @@ namespace Microsoft.Xna.Framework.Graphics Vector2.Transform(ref p, ref transformation, out p); - var item = _batcher.CreateBatchItem(); + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; @@ -964,7 +1037,7 @@ namespace Microsoft.Xna.Framework.Graphics _texCoordBR, layerDepth); } - + offset.X += pCurrentGlyph->Width + pCurrentGlyph->RightSideBearing; } @@ -982,7 +1055,7 @@ namespace Microsoft.Xna.Framework.Graphics public unsafe void DrawString (SpriteFont spriteFont, StringBuilder text, Vector2 position, Color color) { CheckValid(spriteFont, text); - + float sortKey = (_sortMode == SpriteSortMode.Texture) ? spriteFont.Texture.SortingKey : 0; var offset = Vector2.Zero; @@ -1020,15 +1093,16 @@ namespace Microsoft.Xna.Framework.Graphics offset.X += spriteFont.Spacing + pCurrentGlyph->LeftSideBearing; } - var p = offset; + var p = offset; p.X += pCurrentGlyph->Cropping.X; p.Y += pCurrentGlyph->Cropping.Y; p += position; - + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * spriteFont.Texture.TexelWidth; @@ -1087,7 +1161,7 @@ namespace Microsoft.Xna.Framework.Graphics float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) { CheckValid(spriteFont, text); - + float sortKey = 0; // set SortKey based on SpriteSortMode. switch (_sortMode) @@ -1129,7 +1203,7 @@ namespace Microsoft.Xna.Framework.Graphics flipAdjustment.Y = spriteFont.LineSpacing - size.Y; } } - + Matrix transformation = Matrix.Identity; float cos = 0, sin = 0; if (rotation == 0) @@ -1148,7 +1222,7 @@ namespace Microsoft.Xna.Framework.Graphics transformation.M21 = (flippedVert ? -scale.Y : scale.Y) * (-sin); transformation.M22 = (flippedVert ? -scale.Y : scale.Y) * cos; transformation.M41 = (((flipAdjustment.X - origin.X) * transformation.M11) + (flipAdjustment.Y - origin.Y) * transformation.M21) + position.X; - transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; + transformation.M42 = (((flipAdjustment.X - origin.X) * transformation.M12) + (flipAdjustment.Y - origin.Y) * transformation.M22) + position.Y; } var offset = Vector2.Zero; @@ -1197,16 +1271,17 @@ namespace Microsoft.Xna.Framework.Graphics p.Y += pCurrentGlyph->Cropping.Y; Vector2.Transform(ref p, ref transformation, out p); - - var item = _batcher.CreateBatchItem(); + + var item = _batcher.CreateBatchItem(); item.Texture = spriteFont.Texture; + item.Effect = _effect; item.SortKey = sortKey; - + _texCoordTL.X = pCurrentGlyph->BoundsInTexture.X * (float)spriteFont.Texture.TexelWidth; _texCoordTL.Y = pCurrentGlyph->BoundsInTexture.Y * (float)spriteFont.Texture.TexelHeight; _texCoordBR.X = (pCurrentGlyph->BoundsInTexture.X + pCurrentGlyph->BoundsInTexture.Width) * (float)spriteFont.Texture.TexelWidth; _texCoordBR.Y = (pCurrentGlyph->BoundsInTexture.Y + pCurrentGlyph->BoundsInTexture.Height) * (float)spriteFont.Texture.TexelHeight; - + if ((effects & SpriteEffects.FlipVertically) != 0) { var temp = _texCoordBR.Y; diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs index d0ebb45e5..484e521d4 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatchItem.cs @@ -9,6 +9,7 @@ namespace Microsoft.Xna.Framework.Graphics internal class SpriteBatchItem : IComparable { public Texture2D Texture; + public SpriteBatch.EffectWithParams Effect; public float SortKey; public VertexPositionColorTexture vertexTL; @@ -20,7 +21,7 @@ namespace Microsoft.Xna.Framework.Graphics vertexTL = new VertexPositionColorTexture(); vertexTR = new VertexPositionColorTexture(); vertexBL = new VertexPositionColorTexture(); - vertexBR = new VertexPositionColorTexture(); + vertexBR = new VertexPositionColorTexture(); } public void Set ( float x, float y, float dx, float dy, float w, float h, float sin, float cos, Color color, Vector2 texCoordTL, Vector2 texCoordBR, float depth ) diff --git a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs index 036fd783c..948d0078c 100644 --- a/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs +++ b/Libraries/MonoGame.Framework/Src/MonoGame.Framework/Graphics/SpriteBatcher.cs @@ -11,7 +11,7 @@ namespace Microsoft.Xna.Framework.Graphics /// This class handles the queueing of batch items into the GPU by creating the triangle tesselations /// that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be /// batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices - /// sent to the GPU). + /// sent to the GPU). /// internal class SpriteBatcher { @@ -68,7 +68,7 @@ namespace Microsoft.Xna.Framework.Graphics } /// - /// Reuse a previously allocated SpriteBatchItem from the item pool. + /// Reuse a previously allocated SpriteBatchItem from the item pool. /// if there is none available grow the pool and initialize new items. /// /// @@ -143,12 +143,8 @@ namespace Microsoft.Xna.Framework.Graphics /// overflow the 16 bit array indices for vertices. /// /// The type of depth sorting desired for the rendering. - /// The custom effect to apply to the drawn geometry - public unsafe void DrawBatch(SpriteSortMode sortMode, Effect effect) + public unsafe void DrawBatch(SpriteSortMode sortMode, EffectPass defaultSpritePass) { - if (effect != null && effect.IsDisposed) - throw new ObjectDisposedException("effect"); - // nothing to do if (_batchItemCount == 0) return; @@ -180,6 +176,7 @@ namespace Microsoft.Xna.Framework.Graphics var startIndex = 0; var index = 0; Texture2D tex = null; + SpriteBatch.EffectWithParams effect = default; int numBatchesToProcess = batchCount; if (numBatchesToProcess > MaxBatchSize) @@ -196,12 +193,24 @@ namespace Microsoft.Xna.Framework.Graphics { SpriteBatchItem item = _batchItemList[batchIndex]; // if the texture changed, we need to flush and bind the new texture - var shouldFlush = !ReferenceEquals(item.Texture, tex); + var shouldFlush = + !ReferenceEquals(item.Texture, tex) + || !ReferenceEquals(item.Effect.Effect, effect.Effect) + || !ReferenceEquals(item.Effect.Params, effect.Params); if (shouldFlush) { - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); tex = item.Texture; + effect = item.Effect; + if (effect.Effect is null || effect.Params is null) + { + defaultSpritePass.Apply(); + } + else + { + effect.Apply(); + } startIndex = index = 0; vertexArrayPtr = vertexArrayFixedPtr; _device.Textures[0] = tex; @@ -215,15 +224,16 @@ namespace Microsoft.Xna.Framework.Graphics // Release the texture. item.Texture = null; + item.Effect = default; } } // flush the remaining vertexArray data - FlushVertexArray(startIndex, index, effect, tex); + FlushVertexArray(startIndex, index, effect.Effect, tex); // Update our batch count to continue the process of culling down // large batches batchCount -= numBatchesToProcess; } - // return items to the pool. + // return items to the pool. _batchItemCount = 0; } diff --git a/Libraries/XNATypes/RectangleF.cs b/Libraries/XNATypes/RectangleF.cs new file mode 100644 index 000000000..1459e1e90 --- /dev/null +++ b/Libraries/XNATypes/RectangleF.cs @@ -0,0 +1,551 @@ +// MIT License - Copyright (C) The Mono.Xna Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +using System; + +namespace Microsoft.Xna.Framework +{ + public struct RectangleF : IEquatable + { + #region Private Fields + + private static RectangleF emptyRectangle = new RectangleF(); + + #endregion + + #region Public Fields + + /// + /// The x coordinate of the top-left corner of this . + /// + + public float X; + + /// + /// The y coordinate of the top-left corner of this . + /// + + public float Y; + + /// + /// The width of this . + /// + + public float Width; + + /// + /// The height of this . + /// + + public float Height; + + #endregion + + #region Public Properties + + /// + /// Returns a with X=0, Y=0, Width=0, Height=0. + /// + public static RectangleF Empty + { + get { return emptyRectangle; } + } + + /// + /// Returns the x coordinate of the left edge of this . + /// + public float Left + { + get { return this.X; } + } + + /// + /// Returns the x coordinate of the right edge of this . + /// + public float Right + { + get { return (this.X + this.Width); } + } + + /// + /// Returns the y coordinate of the top edge of this . + /// + public float Top + { + get { return this.Y; } + } + + /// + /// Returns the y coordinate of the bottom edge of this . + /// + public float Bottom + { + get { return (this.Y + this.Height); } + } + + /// + /// Whether or not this has a and + /// of 0, and a of (0, 0). + /// + public bool IsEmpty + { + get + { + return ((((this.Width == 0) && (this.Height == 0)) && (this.X == 0)) && (this.Y == 0)); + } + } + + /// + /// The top-left coordinates of this . + /// + public Vector2 Location + { + get + { + return new Vector2(this.X, this.Y); + } + set + { + X = value.X; + Y = value.Y; + } + } + + /// + /// The width-height coordinates of this . + /// + public Vector2 Size + { + get + { + return new Vector2(this.Width, this.Height); + } + set + { + Width = value.X; + Height = value.Y; + } + } + + /// + /// A located in the center of this . + /// + /// + /// If or is an odd number, + /// the center point will be rounded down. + /// + public Vector2 Center + { + get + { + return new Vector2(this.X + (this.Width / 2f), this.Y + (this.Height / 2f)); + } + } + + #endregion + + #region Internal Properties + + internal string DebugDisplayString + { + get + { + return string.Concat( + this.X, " ", + this.Y, " ", + this.Width, " ", + this.Height + ); + } + } + + #endregion + + #region Constructors + + /// + /// Creates a new instance of struct, with the specified + /// position, width, and height. + /// + /// The x coordinate of the top-left corner of the created . + /// The y coordinate of the top-left corner of the created . + /// The width of the created . + /// The height of the created . + public RectangleF(float x, float y, float width, float height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + /// + /// Creates a new instance of struct, with the specified + /// location and size. + /// + /// The x and y coordinates of the top-left corner of the created . + /// The width and height of the created . + public RectangleF(Vector2 location, Vector2 size) + { + this.X = location.X; + this.Y = location.Y; + this.Width = size.X; + this.Height = size.Y; + } + + #endregion + + #region Operators + + /// + /// Compares whether two instances are equal. + /// + /// instance on the left of the equal sign. + /// instance on the right of the equal sign. + /// true if the instances are equal; false otherwise. + public static bool operator ==(RectangleF a, RectangleF b) + { + return ((a.X == b.X) && (a.Y == b.Y) && (a.Width == b.Width) && (a.Height == b.Height)); + } + + /// + /// Compares whether two instances are not equal. + /// + /// instance on the left of the not equal sign. + /// instance on the right of the not equal sign. + /// true if the instances are not equal; false otherwise. + public static bool operator !=(RectangleF a, RectangleF b) + { + return !(a == b); + } + + public static implicit operator RectangleF(Rectangle r) => new RectangleF(r.X, r.Y, r.Width, r.Height); + + #endregion + + #region Public Methods + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(int x, int y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided coordinates lie within the bounds of this . + /// + /// The x coordinate of the point to check for containment. + /// The y coordinate of the point to check for containment. + /// true if the provided coordinates lie inside this ; false otherwise. + public bool Contains(float x, float y) + { + return ((((this.X <= x) && (x < (this.X + this.Width))) && (this.Y <= y)) && (y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Point value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Point value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. + public bool Contains(Vector2 value) + { + return ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The coordinates to check for inclusion in this . + /// true if the provided lies inside this ; false otherwise. As an output parameter. + public void Contains(ref Vector2 value, out bool result) + { + result = ((((this.X <= value.X) && (value.X < (this.X + this.Width))) && (this.Y <= value.Y)) && (value.Y < (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. + public bool Contains(RectangleF value) + { + return ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Gets whether or not the provided lies within the bounds of this . + /// + /// The to check for inclusion in this . + /// true if the provided 's bounds lie entirely inside this ; false otherwise. As an output parameter. + public void Contains(ref RectangleF value, out bool result) + { + result = ((((this.X <= value.X) && ((value.X + value.Width) <= (this.X + this.Width))) && (this.Y <= value.Y)) && ((value.Y + value.Height) <= (this.Y + this.Height))); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public override bool Equals(object obj) + { + return (obj is RectangleF) && this == ((RectangleF)obj); + } + + /// + /// Compares whether current instance is equal to specified . + /// + /// The to compare. + /// true if the instances are equal; false otherwise. + public bool Equals(RectangleF other) + { + return this == other; + } + + /// + /// Gets the hash code of this . + /// + /// Hash code of this . + public override int GetHashCode() + { + unchecked + { + var hash = 17; + hash = hash * 23 + X.GetHashCode(); + hash = hash * 23 + Y.GetHashCode(); + hash = hash * 23 + Width.GetHashCode(); + hash = hash * 23 + Height.GetHashCode(); + return hash; + } + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(int horizontalAmount, int verticalAmount) + { + X -= horizontalAmount; + Y -= verticalAmount; + Width += horizontalAmount * 2; + Height += verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the left and right edges. + /// Value to adjust the top and bottom edges. + public void Inflate(float horizontalAmount, float verticalAmount) + { + X -= (float)horizontalAmount; + Y -= (float)verticalAmount; + Width += (float)horizontalAmount * 2; + Height += (float)verticalAmount * 2; + } + + /// + /// Adjusts the edges of this by specified horizontal and vertical amounts. + /// + /// Value to adjust the edges. + public void Inflate(Vector2 amount) + { + Inflate(amount.X, amount.Y); + } + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. + public bool Intersects(RectangleF value) + { + return value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + + /// + /// Gets whether or not the other intersects with this rectangle. + /// + /// The other rectangle for testing. + /// true if other intersects with this rectangle; false otherwise. As an output parameter. + public void Intersects(ref RectangleF value, out bool result) + { + result = value.Left < Right && + Left < value.Right && + value.Top < Bottom && + Top < value.Bottom; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles. + public static RectangleF Intersect(RectangleF value1, RectangleF value2) + { + RectangleF rectangle; + Intersect(ref value1, ref value2, out rectangle); + return rectangle; + } + + /// + /// Creates a new that contains overlapping region of two other rectangles. + /// + /// The first . + /// The second . + /// Overlapping region of the two rectangles as an output parameter. + public static void Intersect(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + if (value1.Intersects(value2)) + { + float right_side = MathF.Min(value1.X + value1.Width, value2.X + value2.Width); + float left_side = MathF.Max(value1.X, value2.X); + float top_side = MathF.Max(value1.Y, value2.Y); + float bottom_side = MathF.Min(value1.Y + value1.Height, value2.Y + value2.Height); + result = new RectangleF(left_side, top_side, right_side - left_side, bottom_side - top_side); + } + else + { + result = new RectangleF(0, 0, 0, 0); + } + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(int offsetX, int offsetY) + { + X += offsetX; + Y += offsetY; + } + + /// + /// Changes the of this . + /// + /// The x coordinate to add to this . + /// The y coordinate to add to this . + public void Offset(float offsetX, float offsetY) + { + X += (float)offsetX; + Y += (float)offsetY; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Point amount) + { + X += amount.X; + Y += amount.Y; + } + + /// + /// Changes the of this . + /// + /// The x and y components to add to this . + public void Offset(Vector2 amount) + { + X += (float)amount.X; + Y += (float)amount.Y; + } + + /// + /// Returns a representation of this in the format: + /// {X:[] Y:[] Width:[] Height:[]} + /// + /// representation of this . + public override string ToString() + { + return "{X:" + X + " Y:" + Y + " Width:" + Width + " Height:" + Height + "}"; + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles. + public static RectangleF Union(RectangleF value1, RectangleF value2) + { + float x = MathF.Min(value1.X, value2.X); + float y = MathF.Min(value1.Y, value2.Y); + return new RectangleF(x, y, + Math.Max(value1.Right, value2.Right) - x, + Math.Max(value1.Bottom, value2.Bottom) - y); + } + + /// + /// Creates a new that completely contains two other rectangles. + /// + /// The first . + /// The second . + /// The union of the two rectangles as an output parameter. + public static void Union(ref RectangleF value1, ref RectangleF value2, out RectangleF result) + { + result.X = Math.Min(value1.X, value2.X); + result.Y = Math.Min(value1.Y, value2.Y); + result.Width = Math.Max(value1.Right, value2.Right) - result.X; + result.Height = Math.Max(value1.Bottom, value2.Bottom) - result.Y; + } + + public void AddPoint(Point point) + { + if (point.X < X) + { + Width += X - point.X; + X = point.X; + } + else if (point.X > Right) + { + Width += point.X - Right; + } + + if (point.Y < Y) + { + Height += Y - point.Y; + Y = point.Y; + } + else if (point.Y > Bottom) + { + Height += point.Y - Bottom; + } + } + + #endregion + } +}