From 593f33b541c8af0dc8b8f5005f2eacd56b461717 Mon Sep 17 00:00:00 2001 From: Glatzemann Date: Wed, 2 Nov 2011 16:01:05 +0000 Subject: [PATCH] SpriteBatch: fixed some issues with sizing when the sourceRectangle parameter was set SpriteBatch/SpriteFonts: rendering of SpriteFonts is finished now Added test cases for Vector2 static Transform Added test cases for Matrix: CreateRotationX, CreateRotationY, CreateRotationZ, Multiply and CreateTranslation Matrix: Fixed a bug in CreateRotationY (wrong result) --- .../Strukturen/MatrixTest.cs | 91 +++++++++++ .../Strukturen/Vector2Test.cs | 62 +++++++- ANX.Framework/Graphics/SpriteBatch.cs | 11 +- ANX.Framework/Graphics/SpriteFont.cs | 143 ++++++++++++++++-- ANX.Framework/Matrix.cs | 3 +- .../SampleContent/SampleContent.contentproj | 7 + .../SampleContent/Textures/Test_100x100.png | Bin 0 -> 2707 bytes Samples/TextRendering/Game1.cs | 7 +- 8 files changed, 301 insertions(+), 23 deletions(-) create mode 100644 Samples/SampleContent/Textures/Test_100x100.png diff --git a/ANX.Framework.TestCenter/Strukturen/MatrixTest.cs b/ANX.Framework.TestCenter/Strukturen/MatrixTest.cs index f5b5da23..3a7ea61e 100644 --- a/ANX.Framework.TestCenter/Strukturen/MatrixTest.cs +++ b/ANX.Framework.TestCenter/Strukturen/MatrixTest.cs @@ -165,6 +165,60 @@ namespace ANX.Framework.TestCenter.Strukturen ConvertEquals(xnaM1 * xnaM2, anxM1 * anxM2, "MultiplyOperator"); } + [Test, TestCaseSource("sixteenfloats")] + public void Multiply(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaM1 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + XNAMatrix xnaM2 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + + ANXMatrix anxM1 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + ANXMatrix anxM2 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + + ConvertEquals(XNAMatrix.Multiply(xnaM1, xnaM2), ANXMatrix.Multiply(anxM1, anxM2), "Multiply"); + } + + [Test, TestCaseSource("sixteenfloats")] + public void Multiply2(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaM1 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + XNAMatrix xnaM2 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + XNAMatrix xnaResult; + + ANXMatrix anxM1 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + ANXMatrix anxM2 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + ANXMatrix anxResult; + + XNAMatrix.Multiply(ref xnaM1, ref xnaM2, out xnaResult); + ANXMatrix.Multiply(ref anxM1, ref anxM2, out anxResult); + + ConvertEquals(xnaResult, anxResult, "Multiply2"); + } + + [Test, TestCaseSource("sixteenfloats")] + public void Multiply3(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaM1 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + + ANXMatrix anxM1 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + + ConvertEquals(XNAMatrix.Multiply(xnaM1, m11), ANXMatrix.Multiply(anxM1, m11), "Multiply3"); + } + + [Test, TestCaseSource("sixteenfloats")] + public void Multiply4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaM1 = new XNAMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + XNAMatrix xnaResult; + + ANXMatrix anxM1 = new ANXMatrix(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); + ANXMatrix anxResult; + + XNAMatrix.Multiply(ref xnaM1, m11, out xnaResult); + ANXMatrix.Multiply(ref anxM1, m11, out anxResult); + + ConvertEquals(xnaResult, anxResult, "Multiply4"); + } + [Test, TestCaseSource("sixteenfloats")] public void AddOperator(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) { @@ -201,5 +255,42 @@ namespace ANX.Framework.TestCenter.Strukturen ConvertEquals(xnaM1 / xnaM2, anxM1 / anxM2, "DivideOperator"); } + [Test, TestCaseSource("sixteenfloats")] + public void CreateRotationX(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaMatrix = XNAMatrix.CreateRotationX(m11); + ANXMatrix anxMatrix = ANXMatrix.CreateRotationX(m11); + + ConvertEquals(xnaMatrix, anxMatrix, "CreateRotationX"); + } + + [Test, TestCaseSource("sixteenfloats")] + public void CreateRotationY(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaMatrix = XNAMatrix.CreateRotationY(m11); + ANXMatrix anxMatrix = ANXMatrix.CreateRotationY(m11); + + ConvertEquals(xnaMatrix, anxMatrix, "CreateRotationY"); + } + + + [Test, TestCaseSource("sixteenfloats")] + public void CreateRotationZ(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaMatrix = XNAMatrix.CreateRotationZ(m11); + ANXMatrix anxMatrix = ANXMatrix.CreateRotationZ(m11); + + ConvertEquals(xnaMatrix, anxMatrix, "CreateRotationZ"); + } + + [Test, TestCaseSource("sixteenfloats")] + public void CreateTranslation(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + XNAMatrix xnaMatrix = XNAMatrix.CreateTranslation(m11, m12, m13); + ANXMatrix anxMatrix = ANXMatrix.CreateTranslation(m11, m12, m13); + + ConvertEquals(xnaMatrix, anxMatrix, "CreateTranslation"); + } + } } diff --git a/ANX.Framework.TestCenter/Strukturen/Vector2Test.cs b/ANX.Framework.TestCenter/Strukturen/Vector2Test.cs index 6c40bd1f..35c5ce7a 100644 --- a/ANX.Framework.TestCenter/Strukturen/Vector2Test.cs +++ b/ANX.Framework.TestCenter/Strukturen/Vector2Test.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using NUnit.Framework; using XNAVector2 = Microsoft.Xna.Framework.Vector2; using ANXVector2 = ANX.Framework.Vector2; -using NUnit.Framework; +using XNAMatrix = Microsoft.Xna.Framework.Matrix; +using ANXMatrix = ANX.Framework.Matrix; #endregion // Using Statements @@ -78,6 +80,19 @@ namespace ANX.Framework.TestCenter.Strukturen } } + public void ConvertEquals(ANXVector2 xna, ANXVector2 anx, String test) + { + //comparing string to catch "not defined" and "infinity" (which seems not to be equal) + if (anx.X.ToString().Equals(anx.X.ToString()) && anx.Y.ToString().Equals(anx.Y.ToString())) + { + Assert.Pass(test + " passed"); + } + else + { + Assert.Fail(test + " failed: anx({" + xna.X + "}{" + xna.Y + "}) compared to anx({" + anx.X + "}{" + anx.Y + "})"); + } + } + #endregion #region Testdata @@ -526,6 +541,51 @@ namespace ANX.Framework.TestCenter.Strukturen Assert.AreEqual(xnaR, anxR); } + [Test, TestCaseSource("ninefloats")] + public void StaticTransform(float x1, float y1, float x2, float y2, float nop1, float nop2, float nop3, float nop4, float nop5) + { + XNAVector2 xna1 = new XNAVector2(x1, y1); + XNAVector2 xnaResult; + XNAMatrix xnaMatrix = XNAMatrix.CreateRotationX(nop1) * XNAMatrix.CreateRotationY(nop2) * XNAMatrix.CreateRotationZ(nop3) * XNAMatrix.CreateTranslation(nop4, nop5, nop1); + + ANXVector2 anx1 = new ANXVector2(x1, y1); + ANXVector2 anxResult; + ANXMatrix anxMatrix = ANXMatrix.CreateRotationX(nop1) * ANXMatrix.CreateRotationY(nop2) * ANXMatrix.CreateRotationZ(nop3) * ANXMatrix.CreateTranslation(nop4, nop5, nop1); + + XNAVector2.Transform(ref xna1, ref xnaMatrix, out xnaResult); + ANXVector2.Transform(ref anx1, ref anxMatrix, out anxResult); + + ConvertEquals(xnaResult, anxResult, "StaticTransform"); + } + + [Test, TestCaseSource("ninefloats")] + public void StaticTransform2(float x1, float y1, float x2, float y2, float nop1, float nop2, float nop3, float nop4, float nop5) + { + XNAVector2 xna1 = new XNAVector2(x1, y1); + XNAMatrix xnaMatrix = XNAMatrix.CreateRotationX(nop1) * XNAMatrix.CreateRotationY(nop2) * XNAMatrix.CreateRotationZ(nop3) * XNAMatrix.CreateTranslation(nop4, nop5, nop1); + XNAVector2 xnaResult = XNAVector2.Transform(xna1, xnaMatrix); + + ANXVector2 anx1 = new ANXVector2(x1, y1); + ANXMatrix anxMatrix = ANXMatrix.CreateRotationX(nop1) * ANXMatrix.CreateRotationY(nop2) * ANXMatrix.CreateRotationZ(nop3) * ANXMatrix.CreateTranslation(nop4, nop5, nop1); + ANXVector2 anxResult = ANXVector2.Transform(anx1, anxMatrix); + + ConvertEquals(xnaResult, anxResult, "StaticTransform2"); + } + + [Test, TestCaseSource("ninefloats")] + public void StaticTransform3_ANXonly(float x1, float y1, float x2, float y2, float nop1, float nop2, float nop3, float nop4, float nop5) + { + ANXVector2 anx1 = new ANXVector2(x1, y1); + ANXMatrix anxMatrix = ANXMatrix.CreateRotationX(nop1) * ANXMatrix.CreateRotationY(nop2) * ANXMatrix.CreateRotationZ(nop3) * ANXMatrix.CreateTranslation(nop4, nop5, nop1); + + ANXVector2 anxResult1 = ANXVector2.Transform(anx1, anxMatrix); + ANXVector2 anxResult2; + + Vector2.Transform(ref anx1, ref anxMatrix, out anxResult2); + + ConvertEquals(anxResult1, anxResult2, "StaticTransform3_ANXonly"); + } + /* public static Vector2 Transform(Vector2 position, Matrix matrix) { diff --git a/ANX.Framework/Graphics/SpriteBatch.cs b/ANX.Framework/Graphics/SpriteBatch.cs index e5ad5539..a4c254b7 100644 --- a/ANX.Framework/Graphics/SpriteBatch.cs +++ b/ANX.Framework/Graphics/SpriteBatch.cs @@ -163,7 +163,7 @@ namespace ANX.Framework.Graphics public void Draw(Texture2D texture, Rectangle destinationRectangle, Nullable sourceRectangle, Color color, Single rotation, Vector2 origin, SpriteEffects effects, Single layerDepth) { - Draw(texture, new Vector2(destinationRectangle.X, destinationRectangle.Y), new Vector2(destinationRectangle.Width, destinationRectangle.Height), sourceRectangle, color, origin, layerDepth, 0.0f, Vector2.One, effects); + Draw(texture, new Vector2(destinationRectangle.X, destinationRectangle.Y), new Vector2(destinationRectangle.Width, destinationRectangle.Height), sourceRectangle, color, origin, layerDepth, rotation, Vector2.One, effects); } public void Draw(Texture2D texture, Vector2 position, Color color) @@ -173,17 +173,20 @@ namespace ANX.Framework.Graphics public void Draw(Texture2D texture, Vector2 position, Nullable sourceRectangle, Color color) { - Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceRectangle, color, Vector2.Zero, 0.0f, 0.0f, Vector2.One, SpriteEffects.None); + Vector2 size = sourceRectangle.HasValue ? new Vector2(sourceRectangle.Value.Width, sourceRectangle.Value.Height) : new Vector2(texture.Width, texture.Height); + Draw(texture, position, size, sourceRectangle, color, Vector2.Zero, 0.0f, 0.0f, Vector2.One, SpriteEffects.None); } public void Draw(Texture2D texture, Vector2 position, Nullable sourceRectangle, Color color, Single rotation, Vector2 origin, Single scale, SpriteEffects effects, Single layerDepth) { - Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceRectangle, color, origin, layerDepth, rotation, new Vector2(scale), effects); + Vector2 size = sourceRectangle.HasValue ? new Vector2(sourceRectangle.Value.Width, sourceRectangle.Value.Height) : new Vector2(texture.Width, texture.Height); + Draw(texture, position, size, sourceRectangle, color, origin, layerDepth, rotation, new Vector2(scale), effects); } public void Draw(Texture2D texture, Vector2 position, Nullable sourceRectangle, Color color, Single rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, Single layerDepth) { - Draw(texture, position, new Vector2(texture.Width, texture.Height), sourceRectangle, color, origin, layerDepth, rotation, scale, effects); + Vector2 size = sourceRectangle.HasValue ? new Vector2(sourceRectangle.Value.Width, sourceRectangle.Value.Height) : new Vector2(texture.Width, texture.Height); + Draw(texture, position, size, sourceRectangle, color, origin, layerDepth, rotation, scale, effects); } #endregion // Draw-Method diff --git a/ANX.Framework/Graphics/SpriteFont.cs b/ANX.Framework/Graphics/SpriteFont.cs index 20cc8c35..acd28c97 100644 --- a/ANX.Framework/Graphics/SpriteFont.cs +++ b/ANX.Framework/Graphics/SpriteFont.cs @@ -56,6 +56,7 @@ namespace ANX.Framework.Graphics { public sealed class SpriteFont { + #region Private Members private Texture2D texture; private List glyphs; private List cropping; @@ -64,8 +65,10 @@ namespace ANX.Framework.Graphics private float horizontalSpacing; private List kerning; private char? defaultCharacter; - private ReadOnlyCollection characters; + + #endregion // Private Members + public ReadOnlyCollection Characters { get { return characters; } @@ -98,7 +101,7 @@ namespace ANX.Framework.Graphics internal SpriteFont( - Texture2D texture, List glyphs, List cropping, List charMap, + Texture2D texture, List glyphs, List cropping, List charMap, int lineSpacing, float horizontalSpacing, List kerning, char? defaultCharacter) { this.texture = texture; @@ -120,7 +123,7 @@ namespace ANX.Framework.Graphics throw new ArgumentNullException("text"); } - throw new NotImplementedException(); + return InternalMeasure(ref text); } public Vector2 MeasureString(StringBuilder text) @@ -130,31 +133,141 @@ namespace ANX.Framework.Graphics throw new ArgumentNullException("text"); } - throw new NotImplementedException(); + String cachedText = text.ToString(); + return InternalMeasure(ref cachedText); } - internal void DrawString(ref String text, SpriteBatch spriteBatch, Vector2 position, Color color, Vector2 scale, Vector2 origin, float rotation, float layerDepth, SpriteEffects effects) + internal void DrawString(ref String text, SpriteBatch spriteBatch, Vector2 textPos, Color color, Vector2 scale, Vector2 origin, float rotation, float layerDepth, SpriteEffects effects) { - int posX = (int)position.X; - int posY = (int)position.Y; + Matrix transformation = Matrix.CreateRotationZ(rotation) * Matrix.CreateTranslation(-origin.X * scale.X, -origin.Y * scale.Y, 0f); + int horizontalFlipModifier = 1; + float width = 0f; + Vector2 topLeft = new Vector2(); + bool firstCharacterInLine = true; + + if ((effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) + { + topLeft.X = width = this.InternalMeasure(ref text).X * scale.X; + horizontalFlipModifier = -1; + } + + if ((effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) + { + topLeft.Y = (this.InternalMeasure(ref text).Y - this.lineSpacing) * scale.Y; + } for (int i = 0; i < text.Length; i++) { char currentCharacter = text[i]; - int characterIndex = GetIndexForCharacter(currentCharacter); - Vector3 kerning = this.kerning[characterIndex]; - Rectangle glyphRect = this.glyphs[characterIndex]; - Rectangle croppingRect = this.cropping[characterIndex]; + switch (currentCharacter) + { + case '\r': + break; - posX += (int)(croppingRect.X); - posY = (int)(position.Y + croppingRect.Y); + case '\n': + firstCharacterInLine = true; + topLeft.X = width; + if ((effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) + { + topLeft.Y -= this.lineSpacing * scale.Y; + } + else + { + topLeft.Y += this.lineSpacing * scale.Y; + } + break; - spriteBatch.Draw(this.texture, new Rectangle(posX, posY, glyphRect.Width, glyphRect.Height), glyphRect, color, 0.0f, origin, SpriteEffects.None, layerDepth); + default: + { + int characterIndex = GetIndexForCharacter(currentCharacter); + Vector3 kerning = this.kerning[characterIndex]; + Rectangle glyphRect = this.glyphs[characterIndex]; + Rectangle croppingRect = this.cropping[characterIndex]; - posX += (int)(glyphRect.Width + this.Spacing + kerning.X); + if (firstCharacterInLine) + { + kerning.X = Math.Max(kerning.X, 0f); + } + else + { + topLeft.X += (this.Spacing * scale.X) * horizontalFlipModifier; + } + topLeft.X += (kerning.X * scale.X) * horizontalFlipModifier; + + if ((effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) + { + croppingRect.Y = (this.lineSpacing - glyphRect.Height) - croppingRect.Y; + } + if ((effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) + { + croppingRect.X -= croppingRect.Width; + } + Vector2 position = Vector2.Transform(topLeft + (new Vector2(croppingRect.X, croppingRect.Y) * scale), transformation); + spriteBatch.Draw(this.texture, position + textPos, glyphRect, color, rotation, Vector2.Zero, scale, effects, layerDepth); + firstCharacterInLine = false; + topLeft.X += ((kerning.Y + kerning.Z) * scale.X) * horizontalFlipModifier; + break; + } + } } } + private Vector2 InternalMeasure(ref String text) + { + if (text.Length < 1) + { + return Vector2.Zero; + } + + Vector2 size = Vector2.Zero; + size.Y = this.lineSpacing; + float maxWidth = 0f; + int currentCharacter = 0; + float z = 0f; + bool firstCharacterInLine = true; + + for (int i = 0; i < text.Length; i++) + { + char currentChar = text[i]; + + if (currentChar == '\r') + { + continue; + } + + if (currentChar == '\n') + { + size.X += Math.Max(z, 0f); + z = 0f; + maxWidth = Math.Max(size.X, maxWidth); + size = Vector2.Zero; + size.Y = this.lineSpacing; + firstCharacterInLine = true; + currentCharacter++; + } + else + { + int currentCharIndex = this.GetIndexForCharacter(currentChar); + Vector3 kerning = this.kerning[currentCharIndex]; + if (firstCharacterInLine) + { + kerning.X = Math.Max(kerning.X, 0f); + } + else + { + size.X += this.Spacing + z; + } + size.X += kerning.X + kerning.Y; + z = kerning.Z; + size.Y = Math.Max(size.Y, (float)this.cropping[currentCharIndex].Height); + firstCharacterInLine = false; + } + } + size.Y += currentCharacter * this.lineSpacing; + size.X = Math.Max(Math.Max(z, 0) + size.X, maxWidth); + return size; + } + private int GetIndexForCharacter(char character) { int currentIndex = 0; diff --git a/ANX.Framework/Matrix.cs b/ANX.Framework/Matrix.cs index d80196ae..7c8aee5d 100644 --- a/ANX.Framework/Matrix.cs +++ b/ANX.Framework/Matrix.cs @@ -936,7 +936,8 @@ namespace ANX.Framework { result = Matrix.Identity; result.M11 = (float)Math.Cos(radians); - result.M13 = (float)Math.Sin(radians); + result.M13 = (float)-Math.Sin(radians); + result.M22 = 1f; result.M31 = -result.M13; result.M33 = result.M11; } diff --git a/Samples/SampleContent/SampleContent.contentproj b/Samples/SampleContent/SampleContent.contentproj index 819558a7..0e97b333 100644 --- a/Samples/SampleContent/SampleContent.contentproj +++ b/Samples/SampleContent/SampleContent.contentproj @@ -77,6 +77,13 @@ FontDescriptionProcessor + + + Test_100x100 + TextureImporter + TextureProcessor + +