using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace SpaceTransformationExample { class WorldObject { public Texture2D Texture; public Vector2 TextureOrigin; public Vector2 PositionInWorldSpace; public Color color; public Vector2 velocity; public Color[] data; public float RotationInWorldSpace; static Vector2 rotate(Vector2 v, float theta) { return new Vector2((float)(v.X*Math.Cos(theta) - v.Y*Math.Sin(theta)),(float) (v.X*Math.Sin(theta) + v.Y*Math.Cos(theta))); } public static float min4(float a, float b, float c, float d) { return Math.Min(Math.Min(a,b), Math.Min(c,d)); } public static float max4(float a, float b, float c, float d) { return Math.Max(Math.Max(a,b), Math.Max(c,d)); } /// /// Given a rectangle defining an AABB (in world space), and a vector from the /// upper-left corner of the AABB to the rotation point, rotate this AABB around /// that point, and return the smallest possible AABB that encloses this AABB. /// /// The AABB to rotate /// Vector from the upper-left corner of the AABB to the point of rotation /// Amount to rotate (in radians) /// New AABB that contains the rotated AABB public static Rectangle rotateAABB(Rectangle source, Vector2 rotationPoint, float angle) { // First, extract the 4 extreme points in the AABB Vector2 TopLeft = - rotationPoint; Vector2 TopRight = new Vector2(source.Width-rotationPoint.X, -rotationPoint.Y); Vector2 BottomLeft = new Vector2(-rotationPoint.X, source.Height -rotationPoint.Y); Vector2 BottomRight = new Vector2(source.Width - rotationPoint.X, source.Height-rotationPoint.Y); // Rotate these 4 points TopLeft = WorldObject.rotate(TopLeft, angle); TopRight = WorldObject.rotate(TopRight, angle); BottomLeft = WorldObject.rotate(BottomLeft, angle); BottomRight = WorldObject.rotate(BottomRight, angle); // Find the smallest rectangle that contains these 4 points int left = (int) WorldObject.min4(TopLeft.X, TopRight.X, BottomLeft.X, BottomRight.X); int right = (int) WorldObject.max4(TopLeft.X, TopRight.X, BottomLeft.X, BottomRight.X); int top = (int) WorldObject.min4(TopLeft.Y, TopRight.Y, BottomLeft.Y, BottomRight.Y); int bottom = (int) WorldObject.max4(TopLeft.Y, TopRight.Y, BottomLeft.Y, BottomRight.Y); return new Rectangle(left, top, right-left, bottom-top); } /// /// Given an x,y position in Texture space, return true if that location /// is a valid location in the texture and contains a non-transparent pixel /// /// X position in texture space /// Y position in texture space /// True if there is a non-trasparent pixel at this location public bool nonTransparentPixel(int x, int y) { if (x < 0 || x >= Texture.Width || y < 0 || y >= Texture.Height) return false; return data[x + y * Texture.Width].A > 0; } /// /// Given an position in Texture space, return true if that location /// is a valid location in the texture and contains a non-transparent pixel /// /// Point in texture space to check /// True if there is a non-transparent pixel at this location public bool nonTransparentPixel(Vector2 pointInTexureSpace) { return nonTransparentPixel((int) pointInTexureSpace.X, (int) pointInTexureSpace.Y); } /// /// Returns the smallest possible Axis Aligned Bounding Box (in world space) that /// completely contains the sprite /// /// public Rectangle AABB() { Rectangle myAABB = new Rectangle((int)(PositionInWorldSpace.X - TextureOrigin.X), (int)(PositionInWorldSpace.Y - TextureOrigin.Y), Texture.Width, Texture.Height); if (RotationInWorldSpace == 0) { return myAABB; } else { return WorldObject.rotateAABB(myAABB, TextureOrigin, RotationInWorldSpace); } } /// /// Converrt a point from the object space of this object to the texture space /// of this object /// /// Point to convert from object space to texture space /// The point in texture space public Vector2 objectSpaceToTextureSpace(Vector2 pointInObjectSpace) { return pointInObjectSpace + TextureOrigin; } /// /// Converrt a point from the texture space of this object to the object space /// of this object /// /// /// The point in object space public Vector2 TextureSpaceToObjectSpace(Vector2 pointInObjectSpace) { return pointInObjectSpace - TextureOrigin; } /// /// Converrt a point from world space to the object space /// of this object /// /// Point to convert from world space /// Point in object space public Vector2 WorldSpaceToObjectSpace(Vector2 pointInWorldSpace) { return WorldObject.rotate(pointInWorldSpace - PositionInWorldSpace, -RotationInWorldSpace); } /// /// Converrt a point from the object space of this object to /// world space /// /// Point to convert from object space to world space /// Point in world space public Vector2 ObjectSpaceToWorldSpace(Vector2 pointInObjectSpace) { return PositionInWorldSpace + WorldObject.rotate(pointInObjectSpace, RotationInWorldSpace); } /// /// Convert a point from the texture space of this object to world space /// /// The point to convert to world space /// Point in world space public Vector2 TextureSpaceToWorldSpace(Vector2 pointInTextureSpace) { return ObjectSpaceToWorldSpace(TextureSpaceToObjectSpace(pointInTextureSpace)); } /// /// Convert a point from world space to the texture space of this object /// /// The point to conver from world space to texture space /// Point in texture space public Vector2 WorldSpaceToTextureSpace(Vector2 pointInWorldSpace) { return objectSpaceToTextureSpace(WorldSpaceToObjectSpace(pointInWorldSpace)); } public bool Collides(WorldObject other) { Rectangle myAABB = AABB(); Rectangle otherAABB = other.AABB(); if (myAABB.Intersects(otherAABB)) { // No rotation case if (RotationInWorldSpace == 0 && other.RotationInWorldSpace == 0) { // Find the top, left, bottom, and right of the rectangle defined by the // interesction of the two AABBs int left = Math.Max(myAABB.Left, otherAABB.Left); int top = Math.Max(myAABB.Top, otherAABB.Top); int bottom = Math.Min(myAABB.Bottom, otherAABB.Bottom); int right = Math.Min(myAABB.Right, otherAABB.Right); // Go through every point in this intersection rectangle, and determine if the // corresponding points in the other two textures are both non-transparent for (int i = left; i < right; i++) { for (int j = top; j < bottom; j++) { int index1 = i - myAABB.Left + (j - myAABB.Top) * Texture.Width; int index2 = i - otherAABB.Left + (j - otherAABB.Top) * other.Texture.Width; if (data[index1].A > 0 && other.data[index2].A > 0) { return true; } } } return false; } // Rotation case else { // First, find the 4 corners of the other object in my texture space Vector2 upperLeft = WorldSpaceToTextureSpace(other.TextureSpaceToWorldSpace(new Vector2(0, 0))); Vector2 upperRight = WorldSpaceToTextureSpace(other.TextureSpaceToWorldSpace(new Vector2(other.Texture.Width, 0))); Vector2 lowerRight = WorldSpaceToTextureSpace(other.TextureSpaceToWorldSpace(new Vector2(other.Texture.Width, other.Texture.Height))); Vector2 lowerLeft = WorldSpaceToTextureSpace(other.TextureSpaceToWorldSpace(new Vector2(0, other.Texture.Height))); // Next, find the AABB that contains these points, in my texture space int top = (int)min4(upperLeft.Y, upperRight.Y, lowerLeft.Y, lowerRight.Y); int bottom = (int)max4(upperLeft.Y, upperRight.Y, lowerLeft.Y, lowerRight.Y); int left = (int)min4(upperLeft.X, upperRight.X, lowerLeft.X, lowerRight.X); int right = (int)max4(upperLeft.X, upperRight.X, lowerLeft.X, lowerRight.X); // Next, find the intersection of this AABB and my texture in my texture space int top2 = Math.Max(top, 0); int bottom2 = Math.Min(bottom, Texture.Height); int left2 = Math.Max(0, left); int right2 = Math.Min(right, Texture.Width); // Go though each point in this intersection AABB (which has been defined in *my* texture space), // and find the corresponding point in the other texture. Check if both points are non-transparent. for (int i = left2; i < right2; i++) { for (int j = top2; j < bottom2; j++) { Vector2 pointInMyTextureSpace = new Vector2(i, j); Vector2 pointInOtherTextureSpace = other.WorldSpaceToTextureSpace(TextureSpaceToWorldSpace(pointInMyTextureSpace)); if (nonTransparentPixel(pointInMyTextureSpace) && other.nonTransparentPixel(pointInOtherTextureSpace)) { return true; } } } return false; } } return false; } public WorldObject(Texture2D texture) { color = Color.White; Texture = texture; TextureOrigin = new Vector2(texture.Width, texture.Height) / 2; data = new Color[texture.Width * texture.Height]; Texture.GetData(data); RotationInWorldSpace = 0.0f; } public WorldObject(Texture2D texture, Vector2 initialPosition) : this(texture) { PositionInWorldSpace = initialPosition; } public void Update(GameTime gameTime) { PositionInWorldSpace += velocity * (float) gameTime.ElapsedGameTime.TotalSeconds; if (PositionInWorldSpace.X > Game1.SCREEN_WIDTH) { PositionInWorldSpace.X = Game1.SCREEN_WIDTH; velocity.X = -velocity.X; } if (PositionInWorldSpace.X < 0) { PositionInWorldSpace.X = 0; velocity.X = -velocity.X; } if (PositionInWorldSpace.Y > Game1.SCREEN_HEIGHT) { PositionInWorldSpace.Y = Game1.SCREEN_HEIGHT; velocity.Y = -velocity.Y; } if (PositionInWorldSpace.Y < 0) { PositionInWorldSpace.Y = 0; velocity.Y = -velocity.Y; } RotationInWorldSpace = RotationInWorldSpace + (velocity.X - 15) * (float)gameTime.ElapsedGameTime.TotalSeconds / 10.0f; if (RotationInWorldSpace > 2 * Math.PI) { RotationInWorldSpace -= 2 * (float)Math.PI; } } } }