├── .DS_Store ├── LICENSE ├── Math2 ├── AxisAlignedLine2.cs ├── Circle2.cs ├── Line2.cs ├── Math2.cs ├── Polygon2.cs ├── Rect2.cs ├── RelativeRectangle2.cs ├── Rotation2.cs ├── Shape2.cs ├── ShapeUtils.cs └── Triangle2.cs ├── Microsoft └── Xna │ └── Framework │ ├── Point.cs │ └── Vector2.cs ├── README.md ├── SharpMath2.projitems ├── SharpMath2.shproj └── imgs └── banner.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tjstretchalot/SharpMath2/954a9a3fef542dec85e32fb5f49fff70b328adea/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Timothy Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Math2/AxisAlignedLine2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | /// 11 | /// Describes a line that's projected onto a specified axis. This is a useful 12 | /// mathematical concept. Axis aligned lines *do* have position because they 13 | /// are only used as an interim calculation, where position won't change. 14 | /// 15 | public class AxisAlignedLine2 16 | { 17 | /// 18 | /// The axis that this projected line is on. Optional. 19 | /// 20 | public readonly Vector2 Axis; 21 | 22 | /// 23 | /// The minimum of this line 24 | /// 25 | public readonly float Min; 26 | 27 | /// 28 | /// The maximum of this line 29 | /// 30 | public readonly float Max; 31 | 32 | /// 33 | /// Initializes an an axis aligned line. Will autocorrect if min > max 34 | /// 35 | /// The axis 36 | /// The min 37 | /// The max 38 | public AxisAlignedLine2(Vector2 axis, float min, float max) 39 | { 40 | Axis = axis; 41 | 42 | Min = Math.Min(min, max); 43 | Max = Math.Max(min, max); 44 | } 45 | 46 | /// 47 | /// Determines if line1 intersects line2. 48 | /// 49 | /// Line 1 50 | /// Line 2 51 | /// If overlap is required for intersection 52 | /// If line1 and line2 intersect 53 | /// if line1.Axis != line2.Axis 54 | public static bool Intersects(AxisAlignedLine2 line1, AxisAlignedLine2 line2, bool strict) 55 | { 56 | if (line1.Axis != line2.Axis) 57 | throw new ArgumentException($"Lines {line1} and {line2} are not aligned - you will need to convert to Line2 to check intersection."); 58 | 59 | return Intersects(line1.Min, line1.Max, line2.Min, line2.Max, strict, false); 60 | } 61 | 62 | /// 63 | /// Determines the best way for line1 to move to prevent intersection with line2 64 | /// 65 | /// Line1 66 | /// Line2 67 | /// MTV for line1 68 | public static float? IntersectMTV(AxisAlignedLine2 line1, AxisAlignedLine2 line2) 69 | { 70 | if (line1.Axis != line2.Axis) 71 | throw new ArgumentException($"Lines {line1} and {line2} are not aligned - you will need to convert to Line2 to check intersection."); 72 | 73 | return IntersectMTV(line1.Min, line1.Max, line2.Min, line2.Max, false); 74 | } 75 | 76 | /// 77 | /// Determines if the specified line contains the specified point. 78 | /// 79 | /// The line 80 | /// The point 81 | /// If the edges of the line are excluded 82 | /// if line contains point 83 | public static bool Contains(AxisAlignedLine2 line, float point, bool strict) 84 | { 85 | return Contains(line.Min, line.Max, point, strict, false); 86 | } 87 | 88 | /// 89 | /// Determines if axis aligned line (min1, max1) intersects (min2, max2) 90 | /// 91 | /// Min 1 92 | /// Max 1 93 | /// Min 2 94 | /// Max 2 95 | /// If overlap is required for intersection 96 | /// If true (default true) mins and maxes will be swapped if in the wrong order 97 | /// If (min1, max1) intersects (min2, max2) 98 | public static bool Intersects(float min1, float max1, float min2, float max2, bool strict, bool correctMinMax = true) 99 | { 100 | if (correctMinMax) 101 | { 102 | float tmp1 = min1, tmp2 = max1; 103 | min1 = Math.Min(tmp1, tmp2); 104 | max1 = Math.Max(tmp1, tmp2); 105 | 106 | tmp1 = min2; 107 | tmp2 = max2; 108 | min2 = Math.Min(tmp1, tmp2); 109 | max2 = Math.Max(tmp1, tmp2); 110 | 111 | } 112 | 113 | if (strict) 114 | return (min1 <= min2 && max1 > min2 + Math2.DEFAULT_EPSILON) || (min2 <= min1 && max2 > min1 + Math2.DEFAULT_EPSILON); 115 | else 116 | return (min1 <= min2 && max1 > min2 - Math2.DEFAULT_EPSILON) || (min2 <= min1 && max2 > min1 - Math2.DEFAULT_EPSILON); 117 | } 118 | 119 | /// 120 | /// Determines the translation to move line 1 to have line 1 not intersect line 2. Returns 121 | /// null if line1 does not intersect line1. 122 | /// 123 | /// Line 1 min 124 | /// Line 1 max 125 | /// Line 2 min 126 | /// Line 2 max 127 | /// If mins and maxs might be reversed 128 | /// a number to move along the projected axis (positive or negative) or null if no intersection 129 | public static float? IntersectMTV(float min1, float max1, float min2, float max2, bool correctMinMax = true) 130 | { 131 | if (correctMinMax) 132 | { 133 | float tmp1 = min1, tmp2 = max1; 134 | min1 = Math.Min(tmp1, tmp2); 135 | max1 = Math.Max(tmp1, tmp2); 136 | 137 | tmp1 = min2; 138 | tmp2 = max2; 139 | min2 = Math.Min(tmp1, tmp2); 140 | max2 = Math.Max(tmp1, tmp2); 141 | } 142 | 143 | if (min1 <= min2 && max1 > min2) 144 | return min2 - max1; 145 | else if (min2 <= min1 && max2 > min1) 146 | return max2 - min1; 147 | return null; 148 | } 149 | 150 | /// 151 | /// Determines if the line from (min, max) contains point 152 | /// 153 | /// Min of line 154 | /// Max of line 155 | /// Point to check 156 | /// If edges are excluded 157 | /// if true (default true) min and max will be swapped if in the wrong order 158 | /// if line (min, max) contains point 159 | public static bool Contains(float min, float max, float point, bool strict, bool correctMinMax = true) 160 | { 161 | if (correctMinMax) 162 | { 163 | float tmp1 = min, tmp2 = max; 164 | min = Math.Min(tmp1, tmp2); 165 | max = Math.Max(tmp1, tmp2); 166 | } 167 | 168 | if (strict) 169 | return min < point && max > point; 170 | else 171 | return min <= point && max >= point; 172 | } 173 | 174 | /// 175 | /// Detrmines the shortest distance from the line to get to point. Returns 176 | /// null if the point is on the line (not strict). Always returns a positive value. 177 | /// 178 | /// The distance. 179 | /// Line. 180 | /// Point. 181 | public static float? MinDistance(AxisAlignedLine2 line, float point) 182 | { 183 | return MinDistance(line.Min, line.Max, point, false); 184 | } 185 | 186 | /// 187 | /// Determines the shortest distance for line1 to go to touch line2. Returns 188 | /// null if line1 and line 2 intersect (not strictly) 189 | /// 190 | /// The distance. 191 | /// Line1. 192 | /// Line2. 193 | public static float? MinDistance(AxisAlignedLine2 line1, AxisAlignedLine2 line2) 194 | { 195 | return MinDistance(line1.Min, line1.Max, line2.Min, line2.Max, false); 196 | } 197 | 198 | /// 199 | /// Determines the shortest distance from the line (min, max) to the point. Returns 200 | /// null if the point is on the line (not strict). Always returns a positive value. 201 | /// 202 | /// The distance. 203 | /// Minimum of line. 204 | /// Maximum of line. 205 | /// Point to check. 206 | /// If set to true will correct minimum max being reversed if they are 207 | public static float? MinDistance(float min, float max, float point, bool correctMinMax = true) 208 | { 209 | if (correctMinMax) 210 | { 211 | float tmp1 = min, tmp2 = max; 212 | min = Math.Min(tmp1, tmp2); 213 | max = Math.Max(tmp1, tmp2); 214 | } 215 | 216 | if (point < min) 217 | return min - point; 218 | else if (point > max) 219 | return point - max; 220 | else 221 | return null; 222 | } 223 | 224 | /// 225 | /// Calculates the shortest distance for line1 (min1, max1) to get to line2 (min2, max2). 226 | /// Returns null if line1 and line2 intersect (not strictly) 227 | /// 228 | /// The distance along the mutual axis or null. 229 | /// Min1. 230 | /// Max1. 231 | /// Min2. 232 | /// Max2. 233 | /// If set to true correct minimum max being potentially reversed. 234 | public static float? MinDistance(float min1, float max1, float min2, float max2, bool correctMinMax = true) 235 | { 236 | if (correctMinMax) 237 | { 238 | float tmp1 = min1, tmp2 = max1; 239 | min1 = Math.Min(tmp1, tmp2); 240 | max1 = Math.Max(tmp1, tmp2); 241 | 242 | tmp1 = min2; 243 | tmp2 = max2; 244 | min2 = Math.Min(tmp1, tmp2); 245 | max2 = Math.Max(tmp1, tmp2); 246 | } 247 | 248 | if (min1 < min2) 249 | { 250 | if (max1 < min2) 251 | return min2 - max1; 252 | else 253 | return null; 254 | } 255 | else if (min2 < min1) 256 | { 257 | if (max2 < min1) 258 | return min1 - max2; 259 | else 260 | return null; 261 | } 262 | 263 | return null; 264 | } 265 | 266 | /// 267 | /// Creates a human-readable representation of this line 268 | /// 269 | /// string representation of this vector 270 | public override string ToString() 271 | { 272 | return $"[{Min} -> {Max} along {Axis}]"; 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /Math2/Circle2.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SharpMath2 7 | { 8 | /// 9 | /// Describes a circle in the x-y plane. 10 | /// 11 | public struct Circle2 12 | { 13 | /// 14 | /// The radius of the circle 15 | /// 16 | public readonly float Radius; 17 | 18 | /// 19 | /// Constructs a circle with the specified radius 20 | /// 21 | /// Radius of the circle 22 | public Circle2(float radius) 23 | { 24 | Radius = radius; 25 | } 26 | 27 | /// 28 | /// Determines if the first circle is equal to the second circle 29 | /// 30 | /// The first circle 31 | /// The second circle 32 | /// If c1 is equal to c2 33 | public static bool operator ==(Circle2 c1, Circle2 c2) 34 | { 35 | if (ReferenceEquals(c1, null) || ReferenceEquals(c2, null)) 36 | return ReferenceEquals(c1, c2); 37 | 38 | return c1.Radius == c2.Radius; 39 | } 40 | 41 | /// 42 | /// Determines if the first circle is not equal to the second circle 43 | /// 44 | /// The first circle 45 | /// The second circle 46 | /// If c1 is not equal to c2 47 | public static bool operator !=(Circle2 c1, Circle2 c2) 48 | { 49 | if (ReferenceEquals(c1, null) || ReferenceEquals(c2, null)) 50 | return !ReferenceEquals(c1, c2); 51 | 52 | return c1.Radius != c2.Radius; 53 | } 54 | 55 | /// 56 | /// Determines if this circle is logically the same as the 57 | /// specified object. 58 | /// 59 | /// The object to compare against 60 | /// if it is a circle with the same radius 61 | public override bool Equals(object obj) 62 | { 63 | if (obj.GetType() != typeof(Circle2)) 64 | return false; 65 | 66 | var other = (Circle2)obj; 67 | return this == other; 68 | } 69 | 70 | /// 71 | /// Calculate a hashcode based solely on the radius of this circle. 72 | /// 73 | /// hashcode 74 | public override int GetHashCode() 75 | { 76 | return Radius.GetHashCode(); 77 | } 78 | 79 | /// 80 | /// Determines if the circle at the specified position contains the point 81 | /// 82 | /// The circle 83 | /// The top-left of the circles bounding box 84 | /// The point to check if is in the circle at pos 85 | /// If the edges do not count 86 | /// If the circle at pos contains point 87 | public static bool Contains(Circle2 circle, Vector2 pos, Vector2 point, bool strict) 88 | { 89 | var distSq = (point - new Vector2(pos.X + circle.Radius, pos.Y + circle.Radius)).LengthSquared(); 90 | 91 | if (strict) 92 | return distSq < circle.Radius * circle.Radius; 93 | else 94 | return distSq <= circle.Radius * circle.Radius; 95 | } 96 | 97 | /// 98 | /// Determines if the first circle at the specified position intersects the second circle 99 | /// at the specified position. 100 | /// 101 | /// First circle 102 | /// Second circle 103 | /// Top-left of the bounding box of the first circle 104 | /// Top-left of the bounding box of the second circle 105 | /// If overlap is required for intersection 106 | /// If circle1 at pos1 intersects circle2 at pos2 107 | public static bool Intersects(Circle2 circle1, Circle2 circle2, Vector2 pos1, Vector2 pos2, bool strict) 108 | { 109 | return Intersects(circle1.Radius, circle2.Radius, pos1, pos2, strict); 110 | } 111 | 112 | /// 113 | /// Determines if the first circle of specified radius and (bounding box top left) intersects 114 | /// the second circle of specified radius and (bounding box top left) 115 | /// 116 | /// Radius of the first circle 117 | /// Radius of the second circle 118 | /// Top-left of the bounding box of the first circle 119 | /// Top-left of the bounding box of the second circle 120 | /// If overlap is required for intersection 121 | /// If circle1 of radius=radius1, topleft=pos1 intersects circle2 of radius=radius2, topleft=pos2 122 | public static bool Intersects(float radius1, float radius2, Vector2 pos1, Vector2 pos2, bool strict) 123 | { 124 | var vecCenterToCenter = pos1 - pos2; 125 | vecCenterToCenter.X += radius1 - radius2; 126 | vecCenterToCenter.Y += radius1 - radius2; 127 | var distSq = vecCenterToCenter.LengthSquared(); 128 | return distSq < (radius1 + radius2) * (radius1 + radius2); 129 | } 130 | 131 | /// 132 | /// Determines the shortest axis and overlap for which the first circle at the specified position 133 | /// overlaps the second circle at the specified position. If the circles do not overlap, returns null. 134 | /// 135 | /// First circle 136 | /// Second circle 137 | /// Top-left of the first circles bounding box 138 | /// Top-left of the second circles bounding box 139 | /// 140 | public static Tuple IntersectMTV(Circle2 circle1, Circle2 circle2, Vector2 pos1, Vector2 pos2) 141 | { 142 | return IntersectMTV(circle1.Radius, circle2.Radius, pos1, pos2); 143 | } 144 | 145 | /// 146 | /// Determines the shortest axis and overlap for which the first circle, specified by its radius and its bounding 147 | /// box's top-left, intersects the second circle specified by its radius and bounding box top-left. Returns null if 148 | /// the circles do not overlap. 149 | /// 150 | /// Radius of the first circle 151 | /// 152 | /// 153 | /// 154 | /// The direction and magnitude to move pos1 to prevent intersection 155 | public static Tuple IntersectMTV(float radius1, float radius2, Vector2 pos1, Vector2 pos2) 156 | { 157 | var betweenVec = pos1 - pos2; 158 | betweenVec.X += (radius1 - radius2); 159 | betweenVec.Y += (radius1 - radius2); 160 | 161 | var lengthSq = betweenVec.LengthSquared(); 162 | if(lengthSq < (radius1 + radius2) * (radius1 + radius2)) 163 | { 164 | var len = Math.Sqrt(lengthSq); 165 | betweenVec *= (float)(1 / len); 166 | 167 | return Tuple.Create(betweenVec, radius1 + radius2 - (float)len); 168 | } 169 | return null; 170 | } 171 | 172 | /// 173 | /// Projects the specified circle with the upper-left at the specified position onto 174 | /// the specified axis. 175 | /// 176 | /// The circle 177 | /// The position of the circle 178 | /// the axis to project along 179 | /// Projects circle at pos along axis 180 | public static AxisAlignedLine2 ProjectAlongAxis(Circle2 circle, Vector2 pos, Vector2 axis) 181 | { 182 | return ProjectAlongAxis(circle.Radius, pos, axis); 183 | } 184 | 185 | /// 186 | /// Projects a circle defined by its radius and the top-left of its bounding box along 187 | /// the specified axis. 188 | /// 189 | /// Radius of the circle to project 190 | /// Position of the circle 191 | /// Axis to project on 192 | /// 193 | public static AxisAlignedLine2 ProjectAlongAxis(float radius, Vector2 pos, Vector2 axis) 194 | { 195 | var centerProj = Vector2.Dot(new Vector2(pos.X + radius, pos.Y + radius), axis); 196 | 197 | return new AxisAlignedLine2(axis, centerProj - radius, centerProj + radius); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /Math2/Line2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | public enum LineInterType 11 | { 12 | /// 13 | /// Two segments with different slopes which do not intersect 14 | /// 15 | NonParallelNone, 16 | /// 17 | /// Two segments with different slopes which intersect at a 18 | /// single point. 19 | /// 20 | NonParallelPoint, 21 | /// 22 | /// Two parallel but not coincident segments. These never intersect 23 | /// 24 | ParallelNone, 25 | /// 26 | /// Two coincident segments which do not intersect 27 | /// 28 | CoincidentNone, 29 | /// 30 | /// Two coincident segments which intersect at a point 31 | /// 32 | CoincidentPoint, 33 | /// 34 | /// Two coincident segments which intersect on infinitely many points 35 | /// 36 | CoincidentLine 37 | } 38 | 39 | /// 40 | /// Describes a line. Does not have position and is meant to be reused. 41 | /// 42 | public class Line2 43 | { 44 | /// 45 | /// Where the line begins 46 | /// 47 | public readonly Vector2 Start; 48 | 49 | /// 50 | /// Where the line ends 51 | /// 52 | public readonly Vector2 End; 53 | 54 | /// 55 | /// End - Start 56 | /// 57 | public readonly Vector2 Delta; 58 | 59 | /// 60 | /// Normalized Delta 61 | /// 62 | public readonly Vector2 Axis; 63 | 64 | /// 65 | /// The normalized normal of axis. 66 | /// 67 | public readonly Vector2 Normal; 68 | 69 | /// 70 | /// Square of the magnitude of this line 71 | /// 72 | public readonly float MagnitudeSquared; 73 | 74 | /// 75 | /// Magnitude of this line 76 | /// 77 | public readonly float Magnitude; 78 | 79 | /// 80 | /// Min x 81 | /// 82 | public readonly float MinX; 83 | /// 84 | /// Min y 85 | /// 86 | public readonly float MinY; 87 | 88 | /// 89 | /// Max x 90 | /// 91 | public readonly float MaxX; 92 | 93 | /// 94 | /// Max y 95 | /// 96 | public readonly float MaxY; 97 | 98 | /// 99 | /// Slope of this line 100 | /// 101 | public readonly float Slope; 102 | 103 | /// 104 | /// Where this line would hit the y intercept. NaN if vertical line. 105 | /// 106 | public readonly float YIntercept; 107 | 108 | /// 109 | /// If this line is horizontal 110 | /// 111 | public readonly bool Horizontal; 112 | 113 | /// 114 | /// If this line is vertical 115 | /// 116 | public readonly bool Vertical; 117 | 118 | /// 119 | /// Creates a line from start to end 120 | /// 121 | /// Start 122 | /// End 123 | public Line2(Vector2 start, Vector2 end) 124 | { 125 | if (Math2.Approximately(start, end)) 126 | throw new ArgumentException($"start is approximately end - that's a point, not a line. start={start}, end={end}"); 127 | 128 | Start = start; 129 | End = end; 130 | 131 | 132 | Delta = End - Start; 133 | Axis = Vector2.Normalize(Delta); 134 | Normal = Vector2.Normalize(Math2.Perpendicular(Delta)); 135 | MagnitudeSquared = Delta.LengthSquared(); 136 | Magnitude = (float)Math.Sqrt(MagnitudeSquared); 137 | 138 | MinX = Math.Min(Start.X, End.X); 139 | MinY = Math.Min(Start.Y, End.Y); 140 | MaxX = Math.Max(Start.X, End.X); 141 | MaxY = Math.Max(Start.Y, End.Y); 142 | Horizontal = Math.Abs(End.Y - Start.Y) <= Math2.DEFAULT_EPSILON; 143 | Vertical = Math.Abs(End.X - Start.X) <= Math2.DEFAULT_EPSILON; 144 | 145 | if (Vertical) 146 | Slope = float.PositiveInfinity; 147 | else 148 | Slope = (End.Y - Start.Y) / (End.X - Start.X); 149 | 150 | if (Vertical) 151 | YIntercept = float.NaN; 152 | else 153 | { 154 | // y = mx + b 155 | // Start.Y = Slope * Start.X + b 156 | // b = Start.Y - Slope * Start.X 157 | YIntercept = Start.Y - Slope * Start.X; 158 | } 159 | } 160 | 161 | /// 162 | /// Determines if the two lines are parallel. Shifting lines will not 163 | /// effect the result. 164 | /// 165 | /// The first line 166 | /// The second line 167 | /// True if the lines are parallel, false otherwise 168 | public static bool Parallel(Line2 line1, Line2 line2) 169 | { 170 | return ( 171 | Math2.Approximately(line1.Axis, line2.Axis) 172 | || Math2.Approximately(line1.Axis, -line2.Axis) 173 | ); 174 | } 175 | 176 | /// 177 | /// Determines if the given point is along the infinite line described 178 | /// by the given line shifted the given amount. 179 | /// 180 | /// The line 181 | /// The shift for the line 182 | /// The point 183 | /// True if pt is on the infinite line extension of the segment 184 | public static bool AlongInfiniteLine(Line2 line, Vector2 pos, Vector2 pt) 185 | { 186 | float normalPart = Vector2.Dot(pt - pos - line.Start, line.Normal); 187 | return Math2.Approximately(normalPart, 0); 188 | } 189 | 190 | /// 191 | /// Determines if the given line contains the given point. 192 | /// 193 | /// The line to check 194 | /// The offset for the line 195 | /// The point to check 196 | /// True if pt is on the line, false otherwise 197 | public static bool Contains(Line2 line, Vector2 pos, Vector2 pt) 198 | { 199 | // The horizontal/vertical checks are not required but are 200 | // very fast to calculate and short-circuit the common case 201 | // (false) very quickly 202 | if(line.Horizontal) 203 | { 204 | return Math2.Approximately(line.Start.Y + pos.Y, pt.Y) 205 | && AxisAlignedLine2.Contains(line.MinX, line.MaxX, pt.X - pos.X, false, false); 206 | } 207 | if(line.Vertical) 208 | { 209 | return Math2.Approximately(line.Start.X + pos.X, pt.X) 210 | && AxisAlignedLine2.Contains(line.MinY, line.MaxY, pt.Y - pos.Y, false, false); 211 | } 212 | 213 | // Our line is not necessarily a linear space, but if we shift 214 | // our line to the origin and adjust the point correspondingly 215 | // then we have a linear space and the problem remains the same. 216 | 217 | // Our line at the origin is just the infinite line with slope 218 | // Axis. We can form an orthonormal basis of R2 as (Axis, Normal). 219 | // Hence we can write pt = line_part * Axis + normal_part * Normal. 220 | // where line_part and normal_part are floats. If the normal_part 221 | // is 0, then pt = line_part * Axis, hence the point is on the 222 | // infinite line. 223 | 224 | // Since we are working with an orthonormal basis, we can find 225 | // components with dot products. 226 | 227 | // To check the finite line, we consider the start of the line 228 | // the origin. Then the end of the line is line.Magnitude * line.Axis. 229 | 230 | Vector2 lineStart = pos + line.Start; 231 | 232 | float normalPart = Math2.Dot(pt - lineStart, line.Normal); 233 | if (!Math2.Approximately(normalPart, 0)) 234 | return false; 235 | 236 | float axisPart = Math2.Dot(pt - lineStart, line.Axis); 237 | return axisPart > -Math2.DEFAULT_EPSILON 238 | && axisPart < line.Magnitude + Math2.DEFAULT_EPSILON; 239 | } 240 | 241 | private static unsafe void FindSortedOverlap(float* projs, bool* isFromLine1) 242 | { 243 | // ascending insertion sort while simultaneously updating 244 | // isFromLine1 245 | for (int i = 0; i < 3; i++) 246 | { 247 | int best = i; 248 | for (int j = i + 1; j < 4; j++) 249 | { 250 | if (projs[j] < projs[best]) 251 | { 252 | best = j; 253 | } 254 | } 255 | if (best != i) 256 | { 257 | float projTmp = projs[i]; 258 | projs[i] = projs[best]; 259 | projs[best] = projTmp; 260 | bool isFromLine1Tmp = isFromLine1[i]; 261 | isFromLine1[i] = isFromLine1[best]; 262 | isFromLine1[best] = isFromLine1Tmp; 263 | } 264 | } 265 | } 266 | 267 | /// 268 | /// Checks the type of intersection between the two coincident lines. 269 | /// 270 | /// The first line 271 | /// The second line 272 | /// The offset for the first line 273 | /// The offset for the second line 274 | /// The type of intersection 275 | public static unsafe LineInterType CheckCoincidentIntersectionType(Line2 a, Line2 b, Vector2 pos1, Vector2 pos2) 276 | { 277 | Vector2 relOrigin = a.Start + pos1; 278 | 279 | float* projs = stackalloc float[4] { 280 | 0, 281 | a.Magnitude, 282 | Math2.Dot((b.Start + pos2) - relOrigin, a.Axis), 283 | Math2.Dot((b.End + pos2) - relOrigin, a.Axis) 284 | }; 285 | 286 | bool* isFromLine1 = stackalloc bool[4] { 287 | true, 288 | true, 289 | false, 290 | false 291 | }; 292 | 293 | FindSortedOverlap(projs, isFromLine1); 294 | 295 | if (Math2.Approximately(projs[1], projs[2])) 296 | return LineInterType.CoincidentPoint; 297 | if (isFromLine1[0] == isFromLine1[1]) 298 | return LineInterType.CoincidentNone; 299 | return LineInterType.CoincidentLine; 300 | } 301 | 302 | /// 303 | /// Determines if line1 intersects line2, when line1 is offset by pos1 and line2 304 | /// is offset by pos2. 305 | /// 306 | /// Line 1 307 | /// Line 2 308 | /// Origin of line 1 309 | /// Origin of line 2 310 | /// If overlap is required for intersection 311 | /// If line1 intersects line2 312 | public static unsafe bool Intersects(Line2 line1, Line2 line2, Vector2 pos1, Vector2 pos2, bool strict) 313 | { 314 | if (Parallel(line1, line2)) 315 | { 316 | if (!AlongInfiniteLine(line1, pos1, line2.Start + pos2)) 317 | return false; 318 | LineInterType iType = CheckCoincidentIntersectionType(line1, line2, pos1, pos2); 319 | if (iType == LineInterType.CoincidentNone) 320 | return false; 321 | if (iType == LineInterType.CoincidentPoint) 322 | return !strict; 323 | return true; 324 | } 325 | 326 | return GetIntersection(line1, line2, pos1, pos2, strict, out Vector2 pt); 327 | } 328 | 329 | /// 330 | /// Finds the intersection of two non-parallel lines a and b. Returns 331 | /// true if the point of intersection is on both line segments, returns 332 | /// false if the point of intersection is not on at least one line 333 | /// segment. In either case, pt is set to where the intersection is 334 | /// on the infinite lines. 335 | /// 336 | /// First line 337 | /// Second line 338 | /// The shift of the first line 339 | /// The shift of the second line 340 | /// True if we should return true if pt is on an edge of a line as well 341 | /// as in the middle of the line. False to return true only if pt is really within the lines 342 | /// True if both segments contain the pt, false otherwise 343 | public static unsafe bool GetIntersection(Line2 line1, Line2 line2, Vector2 pos1, Vector2 pos2, bool strict, out Vector2 pt) 344 | { 345 | // The infinite lines intersect at exactly one point. The segments intersect 346 | // if they both contain that point. We will treat the lines as first-degree 347 | // Bezier lines to skip the vertical case 348 | // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection 349 | 350 | float x1 = line1.Start.X + pos1.X; 351 | float x2 = line1.End.X + pos1.X; 352 | float x3 = line2.Start.X + pos2.X; 353 | float x4 = line2.End.X + pos2.X; 354 | float y1 = line1.Start.Y + pos1.Y; 355 | float y2 = line1.End.Y + pos1.Y; 356 | float y3 = line2.Start.Y + pos2.Y; 357 | float y4 = line2.End.Y + pos2.Y; 358 | 359 | float det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); 360 | // we assume det != 0 (lines not parallel) 361 | 362 | var t = ( 363 | ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / det 364 | ); 365 | 366 | pt = new Vector2(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t); 367 | 368 | float min = strict ? Math2.DEFAULT_EPSILON : -Math2.DEFAULT_EPSILON; 369 | float max = 1 - min; 370 | 371 | if (t < min || t > max) 372 | return false; 373 | 374 | float u = -( 375 | ((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / det 376 | ); 377 | return u >= min && u <= max; 378 | } 379 | 380 | /// 381 | /// Finds the line of overlap between the the two lines if there is 382 | /// one. If the two lines are not coincident (i.e., if the infinite 383 | /// lines are not the same) then they don't share a line of points. 384 | /// If they are coincident, they may still share no points (two 385 | /// seperate but coincident line segments), one point (they share 386 | /// an edge), or infinitely many points (the share a coincident 387 | /// line segment). In all but the last case, this returns false 388 | /// and overlap is set to null. In the last case this returns true 389 | /// and overlap is set to the line of overlap. 390 | /// 391 | /// The first line 392 | /// The second line 393 | /// The position of the first line 394 | /// the position of the second line 395 | /// Set to null or the line of overlap 396 | /// True if a and b overlap at infinitely many points, 397 | /// false otherwise 398 | public static unsafe bool LineOverlap(Line2 a, Line2 b, Vector2 pos1, Vector2 pos2, out Line2 overlap) 399 | { 400 | if (!Parallel(a, b)) 401 | { 402 | overlap = null; 403 | return false; 404 | } 405 | if (!AlongInfiniteLine(a, pos1, b.Start + pos2)) 406 | { 407 | overlap = null; 408 | return false; 409 | } 410 | 411 | Vector2 relOrigin = a.Start + pos1; 412 | 413 | float* projs = stackalloc float[4] { 414 | 0, 415 | a.Magnitude, 416 | Math2.Dot((b.Start + pos2) - relOrigin, a.Axis), 417 | Math2.Dot((b.End + pos2) - relOrigin, a.Axis) 418 | }; 419 | 420 | bool* isFromLine1 = stackalloc bool[4] { 421 | true, 422 | true, 423 | false, 424 | false 425 | }; 426 | 427 | FindSortedOverlap(projs, isFromLine1); 428 | 429 | if (isFromLine1[0] == isFromLine1[1]) 430 | { 431 | // at best we overlap at one point, most likely no overlap 432 | overlap = null; 433 | return false; 434 | } 435 | 436 | if (Math2.Approximately(projs[1], projs[2])) 437 | { 438 | // Overlap at one point 439 | overlap = null; 440 | return false; 441 | } 442 | 443 | overlap = new Line2( 444 | relOrigin + projs[1] * a.Axis, 445 | relOrigin + projs[2] * a.Axis 446 | ); 447 | return true; 448 | } 449 | 450 | /// 451 | /// Calculates the distance that the given point is from this line. 452 | /// Will be nearly 0 if the point is on the line. 453 | /// 454 | /// The line 455 | /// The shift for the line 456 | /// The point that you want the distance from the line 457 | /// The distance the point is from the line 458 | public static float Distance(Line2 line, Vector2 pos, Vector2 pt) 459 | { 460 | // As is typical for this type of question, we will solve along 461 | // the line treated as a linear space (which requires shifting 462 | // so the line goes through the origin). We will use that to find 463 | // the nearest point on the line to the given pt, then just 464 | // calculate the distance normally. 465 | 466 | Vector2 relPt = pt - line.Start - pos; 467 | 468 | float axisPart = Math2.Dot(relPt, line.Axis); 469 | float nearestAxisPart; 470 | if (axisPart < 0) 471 | nearestAxisPart = 0; 472 | else if (axisPart > line.Magnitude) 473 | nearestAxisPart = line.Magnitude; 474 | else 475 | nearestAxisPart = axisPart; 476 | 477 | Vector2 nearestOnLine = line.Start + pos + nearestAxisPart * line.Axis; 478 | return (pt - nearestOnLine).Length(); 479 | } 480 | 481 | /// 482 | /// Create a human-readable representation of this line 483 | /// 484 | /// human-readable string 485 | public override string ToString() 486 | { 487 | return $"[{Start} to {End}]"; 488 | } 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /Math2/Math2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | /// 11 | /// Contains utility functions for doing math in two-dimensions that 12 | /// don't fit elsewhere. Also contains any necessary constants. 13 | /// 14 | public class Math2 15 | { 16 | /// 17 | /// Default epsilon 18 | /// 19 | public const float DEFAULT_EPSILON = 0.001f; 20 | 21 | /// 22 | /// Determines if v1, v2, and v3 are collinear 23 | /// 24 | /// Vector 1 25 | /// Vector 2 26 | /// Vector 3 27 | /// How close is close enough 28 | /// If v1, v2, v3 is collinear 29 | public static bool IsOnLine(Vector2 v1, Vector2 v2, Vector2 v3, float epsilon = DEFAULT_EPSILON) 30 | { 31 | var fromV1ToV2 = v2 - v1; 32 | var axis = Vector2.Normalize(fromV1ToV2); 33 | var normal = Perpendicular(axis); 34 | 35 | var fromV1ToV3 = v3 - v1; 36 | var normalPortion = Dot(fromV1ToV3, normal); 37 | 38 | return Approximately(normalPortion, 0, epsilon); 39 | } 40 | 41 | /// 42 | /// Determines if the given pt is between the line between v1 and v2. 43 | /// 44 | /// The first edge of the line 45 | /// The second edge of the line 46 | /// The point to test 47 | /// How close is close enough (not exactly distance) 48 | /// True if pt is on the line between v1 and v2, false otherwise 49 | public static bool IsBetweenLine(Vector2 v1, Vector2 v2, Vector2 pt, float epsilon = DEFAULT_EPSILON) 50 | { 51 | var fromV1ToV2 = v2 - v1; 52 | var axis = Vector2.Normalize(fromV1ToV2); 53 | var normal = Perpendicular(axis); 54 | 55 | var fromV1ToPt = pt - v1; 56 | var normalPortion = Dot(fromV1ToPt, normal); 57 | 58 | if (!Approximately(normalPortion, 0, epsilon)) 59 | return false; // not on the infinite line 60 | 61 | var axisPortion = Dot(fromV1ToPt, axis); 62 | 63 | if (axisPortion < -epsilon) 64 | return false; // left of the first point 65 | 66 | if (axisPortion > fromV1ToV2.Length() + epsilon) 67 | return false; // right of second point 68 | 69 | return true; 70 | } 71 | 72 | /// 73 | /// Computes the triple cross product (A X B) X A 74 | /// 75 | /// First vector 76 | /// Second vector 77 | /// 78 | /// Result of projecting to 3 dimensions, performing the 79 | /// triple cross product, and then projecting back down to 2 dimensions. 80 | /// 81 | public static Vector2 TripleCross(Vector2 a, Vector2 b) 82 | { 83 | return new Vector2( 84 | -a.X * a.Y * b.Y + a.Y * a.Y * b.X, 85 | a.X * a.X * b.Y - a.X * a.Y * b.X 86 | ); 87 | } 88 | 89 | /// 90 | /// Calculates the square of the area of the triangle made up of the specified points. 91 | /// 92 | /// First point 93 | /// Second point 94 | /// Third point 95 | /// Area of the triangle made up of the given 3 points 96 | public static float AreaOfTriangle(Vector2 v1, Vector2 v2, Vector2 v3) 97 | { 98 | return 0.5f * Math.Abs((v2.X - v1.X) * (v3.Y - v1.Y) - (v3.X - v1.X) * (v2.Y - v1.Y)); 99 | } 100 | 101 | /// 102 | /// Finds a vector that is perpendicular to the specified vector. 103 | /// 104 | /// A vector perpendicular to v 105 | /// Vector 106 | public static Vector2 Perpendicular(Vector2 v) 107 | { 108 | return new Vector2(-v.Y, v.X); 109 | } 110 | 111 | /// 112 | /// Finds the dot product of (x1, y1) and (x2, y2) 113 | /// 114 | /// The dot. 115 | /// The first x value. 116 | /// The first y value. 117 | /// The second x value. 118 | /// The second y value. 119 | public static float Dot(float x1, float y1, float x2, float y2) 120 | { 121 | return x1 * x2 + y1 * y2; 122 | } 123 | 124 | /// 125 | /// Finds the dot product of the two vectors 126 | /// 127 | /// First vector 128 | /// Second vector 129 | /// The dot product between v1 and v2 130 | public static float Dot(Vector2 v1, Vector2 v2) 131 | { 132 | return Dot(v1.X, v1.Y, v2.X, v2.Y); 133 | } 134 | 135 | /// 136 | /// Finds the dot product of two vectors, where one is specified 137 | /// by its components 138 | /// 139 | /// The first vector 140 | /// The x-value of the second vector 141 | /// The y-value of the second vector 142 | /// The dot product of v and (x2, y2) 143 | public static float Dot(Vector2 v, float x2, float y2) 144 | { 145 | return Dot(v.X, v.Y, x2, y2); 146 | } 147 | 148 | /// 149 | /// Determines if f1 and f2 are approximately the same. 150 | /// 151 | /// The approximately. 152 | /// F1. 153 | /// F2. 154 | /// Epsilon. 155 | public static bool Approximately(float f1, float f2, float epsilon = DEFAULT_EPSILON) 156 | { 157 | return Math.Abs(f1 - f2) <= epsilon; 158 | } 159 | 160 | /// 161 | /// Determines if vectors v1 and v2 are approximately equal, such that 162 | /// both coordinates are within epsilon. 163 | /// 164 | /// If v1 and v2 are approximately equal. 165 | /// V1. 166 | /// V2. 167 | /// Epsilon. 168 | public static bool Approximately(Vector2 v1, Vector2 v2, float epsilon = DEFAULT_EPSILON) 169 | { 170 | return Approximately(v1.X, v2.X, epsilon) && Approximately(v1.Y, v2.Y, epsilon); 171 | } 172 | 173 | /// 174 | /// Rotates the specified vector about the specified vector a rotation of the 175 | /// specified amount. 176 | /// 177 | /// The vector to rotate 178 | /// The point to rotate vec around 179 | /// The rotation 180 | /// The vector vec rotated about about rotation.Theta radians. 181 | public static Vector2 Rotate(Vector2 vec, Vector2 about, Rotation2 rotation) 182 | { 183 | if (rotation.Theta == 0) 184 | return vec; 185 | var tmp = vec - about; 186 | return new Vector2(tmp.X * rotation.CosTheta - tmp.Y * rotation.SinTheta + about.X, 187 | tmp.X * rotation.SinTheta + tmp.Y * rotation.CosTheta + about.Y); 188 | } 189 | 190 | /// 191 | /// Returns either the vector or -vector such that MakeStandardNormal(vec) == MakeStandardNormal(-vec) 192 | /// 193 | /// The vector 194 | /// Normal such that vec.X is positive (unless vec.X is 0, in which such that vec.Y is positive) 195 | public static Vector2 MakeStandardNormal(Vector2 vec) 196 | { 197 | if (vec.X < -DEFAULT_EPSILON) 198 | return -vec; 199 | 200 | if (Approximately(vec.X, 0) && vec.Y < 0) 201 | return -vec; 202 | 203 | return vec; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Math2/Polygon2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | /// 11 | /// Describes a simple polygon based on it's vertices. Does not 12 | /// have position - most functions require specifying the origin of the 13 | /// polygon. Polygons are meant to be reused. 14 | /// 15 | public class Polygon2 : Shape2 16 | { 17 | /// 18 | /// The vertices of this polygon, such that any two adjacent vertices 19 | /// create a line of the polygon 20 | /// 21 | public readonly Vector2[] Vertices; 22 | 23 | /// 24 | /// The lines of this polygon, such that any two adjacent (wrapping) 25 | /// lines share a vertex 26 | /// 27 | public readonly Line2[] Lines; 28 | 29 | /// 30 | /// The center of this polyogn 31 | /// 32 | public readonly Vector2 Center; 33 | 34 | /// 35 | /// This convex polygon partitioned into triangles, sorted by the area 36 | /// of the triangles in descending order 37 | /// 38 | public readonly Triangle2[] TrianglePartition; 39 | 40 | /// 41 | /// The three normal vectors of this polygon, normalized 42 | /// 43 | public readonly List Normals; 44 | 45 | /// 46 | /// The bounding box. 47 | /// 48 | public readonly Rect2 AABB; 49 | 50 | private float _LongestAxisLength; 51 | 52 | /// 53 | /// The longest line that can be created inside this polygon. 54 | /// 55 | /// var poly = ShapeUtils.CreateRectangle(2, 3); 56 | /// 57 | /// Console.WriteLine($"corner-to-corner = longest axis = Math.Sqrt(2 * 2 + 3 * 3) = {Math.Sqrt(2 * 2 + 3 * 3)} = {poly.LongestAxisLength}"); 58 | /// 59 | /// 60 | public float LongestAxisLength 61 | { 62 | get 63 | { 64 | if(_LongestAxisLength < 0) 65 | { 66 | Vector2[] verts = Vertices; 67 | float longestAxisLenSq = -1; 68 | for (int i = 1, len = verts.Length; i < len; i++) 69 | { 70 | for (int j = 0; j < i; j++) 71 | { 72 | var vec = verts[i] - verts[j]; 73 | var vecLenSq = vec.LengthSquared(); 74 | if (vecLenSq > longestAxisLenSq) 75 | longestAxisLenSq = vecLenSq; 76 | } 77 | } 78 | _LongestAxisLength = (float)Math.Sqrt(longestAxisLenSq); 79 | } 80 | 81 | return _LongestAxisLength; 82 | } 83 | } 84 | 85 | /// 86 | /// The area of this polygon 87 | /// 88 | public readonly float Area; 89 | 90 | /// 91 | /// If this polygon is defined clockwise 92 | /// 93 | public readonly bool Clockwise; 94 | 95 | /// 96 | /// Initializes a polygon with the specified vertices 97 | /// 98 | /// Vertices 99 | /// If vertices is null 100 | public Polygon2(Vector2[] vertices) 101 | { 102 | if (vertices == null) 103 | throw new ArgumentNullException(nameof(vertices)); 104 | 105 | Vertices = vertices; 106 | 107 | Normals = new List(); 108 | Vector2 tmp; 109 | for (int i = 1; i < vertices.Length; i++) 110 | { 111 | tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[i] - vertices[i - 1]))); 112 | if (!Normals.Contains(tmp)) 113 | Normals.Add(tmp); 114 | } 115 | 116 | tmp = Math2.MakeStandardNormal(Vector2.Normalize(Math2.Perpendicular(vertices[0] - vertices[vertices.Length - 1]))); 117 | if (!Normals.Contains(tmp)) 118 | Normals.Add(tmp); 119 | 120 | var min = new Vector2(vertices[0].X, vertices[0].Y); 121 | var max = new Vector2(min.X, min.Y); 122 | for (int i = 1; i < vertices.Length; i++) 123 | { 124 | min.X = Math.Min(min.X, vertices[i].X); 125 | min.Y = Math.Min(min.Y, vertices[i].Y); 126 | max.X = Math.Max(max.X, vertices[i].X); 127 | max.Y = Math.Max(max.Y, vertices[i].Y); 128 | } 129 | AABB = new Rect2(min, max); 130 | 131 | _LongestAxisLength = -1; 132 | 133 | // Center, area, and lines 134 | TrianglePartition = new Triangle2[Vertices.Length - 2]; 135 | float[] triangleSortKeys = new float[TrianglePartition.Length]; 136 | float area = 0; 137 | Lines = new Line2[Vertices.Length]; 138 | Lines[0] = new Line2(Vertices[Vertices.Length - 1], Vertices[0]); 139 | var last = Vertices[0]; 140 | Center = new Vector2(0, 0); 141 | for (int i = 1; i < Vertices.Length - 1; i++) 142 | { 143 | var next = Vertices[i]; 144 | var next2 = Vertices[i + 1]; 145 | Lines[i] = new Line2(last, next); 146 | var tri = new Triangle2(new Vector2[] { Vertices[0], next, next2 }); 147 | TrianglePartition[i - 1] = tri; 148 | triangleSortKeys[i - 1] = -tri.Area; 149 | area += tri.Area; 150 | Center += tri.Center * tri.Area; 151 | last = next; 152 | } 153 | Lines[Vertices.Length - 1] = new Line2(Vertices[Vertices.Length - 2], Vertices[Vertices.Length - 1]); 154 | 155 | Array.Sort(triangleSortKeys, TrianglePartition); 156 | 157 | Area = area; 158 | Center /= area; 159 | 160 | last = Vertices[Vertices.Length - 1]; 161 | var centToLast = (last - Center); 162 | var angLast = Rotation2.Standardize((float)Math.Atan2(centToLast.Y, centToLast.X)); 163 | var cwCounter = 0; 164 | var ccwCounter = 0; 165 | var foundDefinitiveResult = false; 166 | for (int i = 0; i < Vertices.Length; i++) 167 | { 168 | var curr = Vertices[i]; 169 | var centToCurr = (curr - Center); 170 | var angCurr = Rotation2.Standardize((float)Math.Atan2(centToCurr.Y, centToCurr.X)); 171 | 172 | 173 | var clockwise = (angCurr < angLast && (angCurr - angLast) < Math.PI) || (angCurr - angLast) > Math.PI; 174 | if (clockwise) 175 | cwCounter++; 176 | else 177 | ccwCounter++; 178 | 179 | Clockwise = clockwise; 180 | if (Math.Abs(angLast - angCurr) > Math2.DEFAULT_EPSILON) 181 | { 182 | foundDefinitiveResult = true; 183 | break; 184 | } 185 | 186 | last = curr; 187 | centToLast = centToCurr; 188 | angLast = angCurr; 189 | } 190 | if (!foundDefinitiveResult) 191 | Clockwise = cwCounter > ccwCounter; 192 | } 193 | 194 | /// 195 | /// Determines the actual location of the vertices of the given polygon 196 | /// when at the given offset and rotation. 197 | /// 198 | /// The polygon 199 | /// The polygons offset 200 | /// The polygons rotation 201 | /// The actualized polygon 202 | public static Vector2[] ActualizePolygon(Polygon2 polygon, Vector2 offset, Rotation2 rotation) 203 | { 204 | int len = polygon.Vertices.Length; 205 | Vector2[] result = new Vector2[len]; 206 | 207 | if (rotation != Rotation2.Zero) 208 | { 209 | for (int i = 0; i < len; i++) 210 | { 211 | result[i] = Math2.Rotate(polygon.Vertices[i], polygon.Center, rotation) + offset; 212 | } 213 | } else 214 | { 215 | // performance sensitive section 216 | int i = 0; 217 | for (; i + 3 < len; i += 4) 218 | { 219 | result[i] = new Vector2( 220 | polygon.Vertices[i].X + offset.X, 221 | polygon.Vertices[i].Y + offset.Y 222 | ); 223 | result[i + 1] = new Vector2( 224 | polygon.Vertices[i + 1].X + offset.X, 225 | polygon.Vertices[i + 1].Y + offset.Y 226 | ); 227 | result[i + 2] = new Vector2( 228 | polygon.Vertices[i + 2].X + offset.X, 229 | polygon.Vertices[i + 2].Y + offset.Y 230 | ); 231 | result[i + 3] = new Vector2( 232 | polygon.Vertices[i + 3].X + offset.X, 233 | polygon.Vertices[i + 3].Y + offset.Y 234 | ); 235 | } 236 | 237 | for (; i < len; i++) 238 | { 239 | result[i] = new Vector2( 240 | polygon.Vertices[i].X + offset.X, 241 | polygon.Vertices[i].Y + offset.Y 242 | ); 243 | } 244 | } 245 | 246 | return result; 247 | } 248 | 249 | /// 250 | /// Determines if the specified polygon at the specified position and rotation contains the specified point 251 | /// 252 | /// The polygon 253 | /// Origin of the polygon 254 | /// Rotation of the polygon 255 | /// Point to check 256 | /// True if the edges do not count as inside 257 | /// If the polygon at pos with rotation rot about its center contains point 258 | public static bool Contains(Polygon2 poly, Vector2 pos, Rotation2 rot, Vector2 point, bool strict) 259 | { 260 | // The point is contained in the polygon iff it is contained in one of the triangles 261 | // which partition this polygon. Due to how we constructed the triangles, it will 262 | // be on the edge of the polygon if its on the first 2 edges of the triangle. 263 | 264 | for (int i = 0, len = poly.TrianglePartition.Length; i < len; i++) 265 | { 266 | var tri = poly.TrianglePartition[i]; 267 | 268 | if (Triangle2.Contains(tri, pos, point)) 269 | { 270 | if (strict && (Line2.Contains(tri.Edges[0], pos, point) || Line2.Contains(tri.Edges[1], pos, point))) 271 | return false; 272 | return true; 273 | } 274 | } 275 | return false; 276 | } 277 | 278 | /// 279 | /// Determines if the first polygon intersects the second polygon when they are at 280 | /// the respective positions and rotations. 281 | /// 282 | /// First polygon 283 | /// Second polygon 284 | /// Position of the first polygon 285 | /// Position of the second polygon 286 | /// Rotation of the first polygon 287 | /// Rotation fo the second polyogn 288 | /// If overlapping is required for intersection 289 | /// If poly1 at pos1 with rotation rot1 intersects poly2 at pos2with rotation rot2 290 | public static bool Intersects(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict) 291 | { 292 | return IntersectsSAT(poly1, poly2, pos1, pos2, rot1, rot2, strict); 293 | } 294 | 295 | /// 296 | /// Determines if the two polygons intersect using the Separating Axis Theorem. 297 | /// The performance of this function depends on the number of unique normals 298 | /// between the two polygons. 299 | /// 300 | /// First polygon 301 | /// Second polygon 302 | /// Offset for the vertices of the first polygon 303 | /// Offset for the vertices of the second polygon 304 | /// Rotation of the first polygon 305 | /// Rotation of the second polygon 306 | /// 307 | /// True if the two polygons must overlap a non-zero area for intersection, 308 | /// false if they must overlap on at least one point for intersection. 309 | /// 310 | /// True if the polygons overlap, false if they do not 311 | public static bool IntersectsSAT(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict) 312 | { 313 | if (rot1 == Rotation2.Zero && rot2 == Rotation2.Zero) 314 | { 315 | // This was a serious performance bottleneck so we speed up the fast case 316 | HashSet seen = new HashSet(); 317 | Vector2[] poly1Verts = poly1.Vertices; 318 | Vector2[] poly2Verts = poly2.Vertices; 319 | for (int i = 0, len = poly1.Normals.Count; i < len; i++) 320 | { 321 | var axis = poly1.Normals[i]; 322 | var proj1 = ProjectAlongAxis(axis, pos1, poly1Verts); 323 | var proj2 = ProjectAlongAxis(axis, pos2, poly2Verts); 324 | if (!AxisAlignedLine2.Intersects(proj1, proj2, strict)) 325 | return false; 326 | seen.Add(axis); 327 | } 328 | for (int i = 0, len = poly2.Normals.Count; i < len; i++) 329 | { 330 | var axis = poly2.Normals[i]; 331 | if (seen.Contains(axis)) 332 | continue; 333 | 334 | var proj1 = ProjectAlongAxis(axis, pos1, poly1Verts); 335 | var proj2 = ProjectAlongAxis(axis, pos2, poly2Verts); 336 | if (!AxisAlignedLine2.Intersects(proj1, proj2, strict)) 337 | return false; 338 | } 339 | return true; 340 | } 341 | 342 | foreach (var norm in poly1.Normals.Select((v) => Tuple.Create(v, rot1)).Union(poly2.Normals.Select((v) => Tuple.Create(v, rot2)))) 343 | { 344 | var axis = Math2.Rotate(norm.Item1, Vector2.Zero, norm.Item2); 345 | if (!IntersectsAlongAxis(poly1, poly2, pos1, pos2, rot1, rot2, strict, axis)) 346 | return false; 347 | } 348 | 349 | return true; 350 | } 351 | 352 | /// 353 | /// Determines if the two polygons intersect, inspired by the GJK algorithm. The 354 | /// performance of this algorithm generally depends on how separated the 355 | /// two polygons are. 356 | /// 357 | /// This essentially acts as a directed search of the triangles in the 358 | /// minkowski difference to check if any of them contain the origin. 359 | /// 360 | /// The minkowski difference polygon has up to M*N possible vertices, where M is the 361 | /// number of vertices in the first polygon and N is the number of vertices 362 | /// in the second polygon. 363 | /// 364 | /// First polygon 365 | /// Second polygon 366 | /// Offset for the vertices of the first polygon 367 | /// Offset for the vertices of the second polygon 368 | /// Rotation of the first polygon 369 | /// Rotation of the second polygon 370 | /// 371 | /// True if the two polygons must overlap a non-zero area for intersection, 372 | /// false if they must overlap on at least one point for intersection. 373 | /// 374 | /// True if the polygons overlap, false if they do not 375 | public static unsafe bool IntersectsGJK(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict) 376 | { 377 | Vector2[] verts1 = ActualizePolygon(poly1, pos1, rot1); 378 | Vector2[] verts2 = ActualizePolygon(poly2, pos2, rot2); 379 | 380 | Vector2 desiredAxis = new Vector2( 381 | poly1.Center.X + pos1.X - poly2.Center.X - pos2.X, 382 | poly2.Center.Y + pos1.Y - poly2.Center.Y - pos2.Y 383 | ); 384 | 385 | if (Math2.Approximately(desiredAxis, Vector2.Zero)) 386 | desiredAxis = Vector2.UnitX; 387 | else 388 | desiredAxis.Normalize(); // cleanup rounding issues 389 | 390 | var simplex = stackalloc Vector2[3]; 391 | int simplexIndex = -1; 392 | bool simplexProper = true; 393 | 394 | while (true) 395 | { 396 | if (simplexIndex < 2) { 397 | simplex[++simplexIndex] = CalculateSupport(verts1, verts2, desiredAxis); 398 | 399 | float progressFromOriginTowardDesiredAxis = Math2.Dot(simplex[simplexIndex], desiredAxis); 400 | if (progressFromOriginTowardDesiredAxis < -Math2.DEFAULT_EPSILON) 401 | { 402 | return false; // no hope 403 | } 404 | 405 | if (progressFromOriginTowardDesiredAxis < Math2.DEFAULT_EPSILON) 406 | { 407 | if (Math2.Approximately(simplex[simplexIndex], Vector2.Zero)) 408 | { 409 | // We've determined that the origin is a point on the 410 | // edge of the minkowski difference. In fact, it's even 411 | // a vertex. This means that the two polygons are just 412 | // touching. 413 | return !strict; 414 | } 415 | // When we go to check the simplex, we can't assume that 416 | // we know the origin will be in either AC or AB, as that 417 | // assumption relies on this progress being strictly positive. 418 | simplexProper = false; 419 | } 420 | 421 | if (simplexIndex == 0) 422 | { 423 | desiredAxis = -simplex[0]; 424 | desiredAxis.Normalize(); // resolve rounding issues 425 | continue; 426 | } 427 | 428 | if (simplexIndex == 1) 429 | { 430 | // We only have 2 points; we need to select the third. 431 | desiredAxis = Math2.TripleCross(simplex[1] - simplex[0], -simplex[1]); 432 | 433 | if (Math2.Approximately(desiredAxis, Vector2.Zero)) 434 | { 435 | // This means that the origin lies along the infinite 436 | // line which goes through simplex[0] and simplex[1]. 437 | // We will choose a point perpendicular for now, but we 438 | // will have to do extra work later to handle the fact that 439 | // the origin won't be in regions AB or AC. 440 | simplexProper = false; 441 | desiredAxis = Math2.Perpendicular(simplex[1] - simplex[0]); 442 | } 443 | 444 | desiredAxis.Normalize(); // resolve rounding issues 445 | continue; 446 | } 447 | } 448 | 449 | Vector2 ac = simplex[0] - simplex[2]; 450 | Vector2 ab = simplex[1] - simplex[2]; 451 | Vector2 ao = -simplex[2]; 452 | 453 | Vector2 acPerp = Math2.TripleCross(ac, ab); 454 | acPerp.Normalize(); // resolve rounding issues 455 | float amountTowardsOriginAC = Math2.Dot(acPerp, ao); 456 | 457 | if (amountTowardsOriginAC < -Math2.DEFAULT_EPSILON) 458 | { 459 | // We detected that the origin is in the AC region 460 | desiredAxis = -acPerp; 461 | simplexProper = true; 462 | } 463 | else 464 | { 465 | if (amountTowardsOriginAC < Math2.DEFAULT_EPSILON) 466 | { 467 | simplexProper = false; 468 | } 469 | 470 | // Could still be within the triangle. 471 | Vector2 abPerp = Math2.TripleCross(ab, ac); 472 | abPerp.Normalize(); // resolve rounding issues 473 | 474 | float amountTowardsOriginAB = Math2.Dot(abPerp, ao); 475 | if (amountTowardsOriginAB < -Math2.DEFAULT_EPSILON) 476 | { 477 | // We detected that the origin is in the AB region 478 | simplex[0] = simplex[1]; 479 | desiredAxis = -abPerp; 480 | simplexProper = true; 481 | } 482 | else 483 | { 484 | if (amountTowardsOriginAB < Math2.DEFAULT_EPSILON) 485 | { 486 | simplexProper = false; 487 | } 488 | 489 | if (simplexProper) 490 | { 491 | return true; 492 | } 493 | 494 | // We've eliminated the standard cases for the simplex, i.e., 495 | // regions AB and AC. If the previous steps succeeded, this 496 | // means we've definitively shown that the origin is within 497 | // the triangle. However, if the simplex is improper, then 498 | // we need to check the edges before we can be confident. 499 | 500 | // We'll check edges first. 501 | bool isOnABEdge = false; 502 | 503 | if (Math2.IsBetweenLine(simplex[0], simplex[2], Vector2.Zero)) 504 | { 505 | // we've determined the origin is on the edge AC. 506 | // we'll swap B and C so that we're now on the edge 507 | // AB, and handle like that case. abPerp and acPerp also swap, 508 | // but we don't care about acPerp anymore 509 | Vector2 tmp = simplex[0]; 510 | simplex[0] = simplex[1]; 511 | simplex[1] = tmp; 512 | abPerp = acPerp; 513 | isOnABEdge = true; 514 | } 515 | else if (Math2.IsBetweenLine(simplex[0], simplex[1], Vector2.Zero)) 516 | { 517 | // we've determined the origin is on edge BC. 518 | // we'll swap A and C so that we're now on the 519 | // edge AB, and handle like that case. we'll need to 520 | // recalculate abPerp 521 | Vector2 tmp = simplex[2]; 522 | simplex[2] = simplex[0]; 523 | simplex[0] = tmp; 524 | ab = simplex[1] - simplex[2]; 525 | ac = simplex[0] - simplex[2]; 526 | abPerp = Math2.TripleCross(ab, ac); 527 | abPerp.Normalize(); 528 | isOnABEdge = true; 529 | } 530 | 531 | if (isOnABEdge || Math2.IsBetweenLine(simplex[1], simplex[2], Vector2.Zero)) 532 | { 533 | // The origin is along the line AB. This means we'll either 534 | // have another choice for A that wouldn't have done this, 535 | // or the line AB is actually on the edge of the minkowski 536 | // difference, and hence we are just touching. 537 | 538 | // There is a case where this trick isn't going to work, in 539 | // particular, if when you triangularize the polygon, the 540 | // origin falls on an inner edge. 541 | 542 | // In our case, at this point, we are going to have 4 points, 543 | // which form a quadrilateral which contains the origin, but 544 | // for which there is no way to draw a triangle out of the 545 | // vertices that does not have the origin on the edge. 546 | 547 | // I think though that the only way this happens would imply 548 | // the origin is on simplex[1] <-> ogSimplex2 (we know this 549 | // as that is what this if statement is for) and on 550 | // simplex[0], (new) simplex[2], and I think it guarrantees 551 | // we're in that case. 552 | 553 | 554 | desiredAxis = -abPerp; 555 | Vector2 ogSimplex2 = simplex[2]; 556 | 557 | simplex[2] = CalculateSupport(verts1, verts2, desiredAxis); 558 | 559 | if ( 560 | Math2.Approximately(simplex[1], simplex[2]) || 561 | Math2.Approximately(ogSimplex2, simplex[2]) || 562 | Math2.Approximately(simplex[2], Vector2.Zero) 563 | ) 564 | { 565 | // we've shown that this is a true edge 566 | return !strict; 567 | } 568 | 569 | if (Math2.Dot(simplex[2], desiredAxis) <= 0) 570 | { 571 | // we didn't find a useful point! 572 | return !strict; 573 | } 574 | 575 | if (Math2.IsBetweenLine(simplex[0], simplex[2], Vector2.Zero)) 576 | { 577 | // We've proven that we're contained in a quadrilateral 578 | // Example of how we get here: C B A ogSimplex2 579 | // (-1, -1), (-1, 0), (5, 5), (5, 0) 580 | return true; 581 | } 582 | 583 | if (Math2.IsBetweenLine(simplex[1], simplex[2], Vector2.Zero)) 584 | { 585 | // We've shown that we on the edge 586 | // Example of how we get here: C B A ogSimplex2 587 | // (-32.66077,4.318787), (1.25, 0), (-25.41077, -0.006134033), (-32.66077, -0.006134033 588 | return !strict; 589 | } 590 | 591 | simplexProper = true; 592 | continue; 593 | } 594 | 595 | // we can trust our results now as we know the point is 596 | // not on an edge. we'll need to be confident in our 597 | // progress check as well, so we'll skip the top of the 598 | // loop 599 | 600 | if (amountTowardsOriginAB < 0) 601 | { 602 | // in the AB region 603 | simplex[0] = simplex[1]; 604 | desiredAxis = -abPerp; 605 | } 606 | else if (amountTowardsOriginAC < 0) 607 | { 608 | // in the AC region 609 | desiredAxis = -acPerp; 610 | } 611 | else 612 | { 613 | // now we're sure the point is in the triangle 614 | return true; 615 | } 616 | 617 | simplex[1] = simplex[2]; 618 | simplex[2] = CalculateSupport(verts1, verts2, desiredAxis); 619 | if (Math2.Dot(simplex[simplexIndex], desiredAxis) < 0) 620 | { 621 | return false; 622 | } 623 | 624 | simplexProper = true; 625 | continue; 626 | } 627 | } 628 | 629 | simplex[1] = simplex[2]; 630 | simplexIndex--; 631 | } 632 | } 633 | 634 | 635 | /// 636 | /// Calculates the support vector along +axis+ for the two polygons. This 637 | /// is the point furthest in the direction of +axis+ within the minkowski 638 | /// difference of poly1 (-) poly2. 639 | /// 640 | /// First polygon vertices 641 | /// Second polygon vertices 642 | /// The axis for the support 643 | /// Support along axis for the minkowski difference 644 | public static Vector2 CalculateSupport(Vector2[] verts1, Vector2[] verts2, Vector2 axis) 645 | { 646 | // We calculate the two supports individually, and the difference will 647 | // still satisfy the necessary property. 648 | int index1 = IndexOfFurthestPoint(verts1, axis); 649 | int index2 = IndexOfFurthestPoint(verts2, -axis); 650 | 651 | return verts1[index1] - verts2[index2]; 652 | } 653 | 654 | /// 655 | /// Calculates the index of the vector within verts which is most along the 656 | /// given axis. 657 | /// 658 | /// The array of vertices 659 | /// The axis 660 | /// The index within verts furthest along axis 661 | public static int IndexOfFurthestPoint(Vector2[] verts, Vector2 axis) 662 | { 663 | // performance sensitive section 664 | // force inlining of dots 665 | float max = verts[0].X * axis.X + verts[0].Y * axis.Y; 666 | int index = 0; 667 | for (int i = 1, len = verts.Length; i < len; i++) 668 | { 669 | float dot = verts[i].X * axis.X + verts[i].Y * axis.Y; 670 | if (dot > max) 671 | { 672 | max = dot; 673 | index = i; 674 | } 675 | } 676 | return index; 677 | } 678 | 679 | public static void DumpInfo(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, bool strict) 680 | { 681 | Console.WriteLine("Polygon2 poly1 = new Polygon2(new Vector2[]"); 682 | Console.WriteLine("{"); 683 | foreach (Vector2 v in poly1.Vertices) { 684 | Console.WriteLine($" new Vector2({v.X}f, {v.Y}f),"); 685 | } 686 | Console.WriteLine("});"); 687 | Console.WriteLine("Polygon2 poly2 = new Polygon2(new Vector2[]"); 688 | Console.WriteLine("{"); 689 | foreach (Vector2 v in poly2.Vertices) 690 | { 691 | Console.WriteLine($" new Vector2({v.X}f, {v.Y}f),"); 692 | } 693 | Console.WriteLine("});"); 694 | 695 | Console.WriteLine($"Vector2 pos1 = new Vector2({pos1.X}f, {pos1.Y}f);"); 696 | Console.WriteLine($"Vector2 pos2 = new Vector2({pos2.X}f, {pos2.Y}f);"); 697 | Console.WriteLine($"bool strict = {strict.ToString().ToLower()};"); 698 | } 699 | 700 | public static void DesmosReady(Vector2[] verts) 701 | { 702 | foreach (Vector2 v in verts) 703 | { 704 | Console.Write($"({v.X},{v.Y}),"); 705 | } 706 | Console.WriteLine(); 707 | } 708 | 709 | /// 710 | /// Determines the mtv to move pos1 by to prevent poly1 at pos1 from intersecting poly2 at pos2. 711 | /// Returns null if poly1 and poly2 do not intersect. 712 | /// 713 | /// First polygon 714 | /// Second polygon 715 | /// Position of the first polygon 716 | /// Position of the second polygon 717 | /// Rotation of the first polyogn 718 | /// Rotation of the second polygon 719 | /// MTV to move poly1 to prevent intersection with poly2 720 | public static Tuple IntersectMTV(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2) 721 | { 722 | Vector2 bestAxis = Vector2.Zero; 723 | float bestMagn = float.MaxValue; 724 | 725 | foreach (var norm in poly1.Normals.Select((v) => Tuple.Create(v, rot1)).Union(poly2.Normals.Select((v) => Tuple.Create(v, rot2)))) 726 | { 727 | var axis = Math2.Rotate(norm.Item1, Vector2.Zero, norm.Item2); 728 | var mtv = IntersectMTVAlongAxis(poly1, poly2, pos1, pos2, rot1, rot2, axis); 729 | if (!mtv.HasValue) 730 | return null; 731 | else if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) 732 | { 733 | bestAxis = axis; 734 | bestMagn = mtv.Value; 735 | } 736 | } 737 | 738 | return Tuple.Create(bestAxis, bestMagn); 739 | } 740 | 741 | /// 742 | /// Determines if polygon 1 and polygon 2 at position 1 and position 2, respectively, intersect along axis. 743 | /// 744 | /// polygon 1 745 | /// polygon 2 746 | /// Origin of polygon 1 747 | /// Origin of polygon 2 748 | /// Rotation of the first polygon 749 | /// Rotation of the second polygon 750 | /// If overlapping is required for intersection 751 | /// The axis to check 752 | /// If poly1 at pos1 intersects poly2 at pos2 along axis 753 | public static bool IntersectsAlongAxis(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, bool strict, Vector2 axis) 754 | { 755 | var proj1 = ProjectAlongAxis(poly1, pos1, rot1, axis); 756 | var proj2 = ProjectAlongAxis(poly2, pos2, rot2, axis); 757 | 758 | return AxisAlignedLine2.Intersects(proj1, proj2, strict); 759 | } 760 | 761 | /// 762 | /// Determines the distance along axis, if any, that polygon 1 should be shifted by 763 | /// to prevent intersection with polygon 2. Null if no intersection along axis. 764 | /// 765 | /// polygon 1 766 | /// polygon 2 767 | /// polygon 1 origin 768 | /// polygon 2 origin 769 | /// polygon 1 rotation 770 | /// polygon 2 rotation 771 | /// Axis to check 772 | /// a number to shift pos1 along axis by to prevent poly1 at pos1 from intersecting poly2 at pos2, or null if no int. along axis 773 | public static float? IntersectMTVAlongAxis(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2, Vector2 axis) 774 | { 775 | var proj1 = ProjectAlongAxis(poly1, pos1, rot1, axis); 776 | var proj2 = ProjectAlongAxis(poly2, pos2, rot2, axis); 777 | 778 | return AxisAlignedLine2.IntersectMTV(proj1, proj2); 779 | } 780 | /// 781 | /// Projects the polygon at position onto the specified axis. 782 | /// 783 | /// The polygon 784 | /// The polygons origin 785 | /// the rotation of the polygon 786 | /// The axis to project onto 787 | /// poly at pos projected along axis 788 | public static AxisAlignedLine2 ProjectAlongAxis(Polygon2 poly, Vector2 pos, Rotation2 rot, Vector2 axis) 789 | { 790 | return ProjectAlongAxis(axis, pos, rot, poly.Center, poly.Vertices); 791 | } 792 | 793 | /// 794 | /// Calculates the shortest distance from the specified polygon to the specified point, 795 | /// and the axis from polygon to pos. 796 | /// 797 | /// Returns null if pt is contained in the polygon (not strictly). 798 | /// 799 | /// The distance form poly to pt. 800 | /// The polygon 801 | /// Origin of the polygon 802 | /// Rotation of the polygon 803 | /// Point to check. 804 | public static Tuple MinDistance(Polygon2 poly, Vector2 pos, Rotation2 rot, Vector2 pt) 805 | { 806 | /* 807 | * Definitions 808 | * 809 | * For each line in the polygon, find the normal of the line in the direction of outside the polygon. 810 | * Call the side of the original line that contains none of the polygon "above the line". The other side is "below the line". 811 | * 812 | * If the point falls above the line: 813 | * Imagine two additional lines that are normal to the line and fall on the start and end, respectively. 814 | * For each of those two lines, call the side of the line that contains the original line "below the line". The other side is "above the line" 815 | * 816 | * If the point is above the line containing the start: 817 | * The shortest vector is from the start to the point 818 | * 819 | * If the point is above the line containing the end: 820 | * The shortest vector is from the end to the point 821 | * 822 | * Otherwise 823 | * The shortest vector is from the line to the point 824 | * 825 | * If this is not true for ANY of the lines, the polygon does not contain the point. 826 | */ 827 | 828 | var last = Math2.Rotate(poly.Vertices[poly.Vertices.Length - 1], poly.Center, rot) + pos; 829 | for (var i = 0; i < poly.Vertices.Length; i++) 830 | { 831 | var curr = Math2.Rotate(poly.Vertices[i], poly.Center, rot) + pos; 832 | var axis = curr - last; 833 | Vector2 norm; 834 | if (poly.Clockwise) 835 | norm = new Vector2(-axis.Y, axis.X); 836 | else 837 | norm = new Vector2(axis.Y, -axis.X); 838 | norm = Vector2.Normalize(norm); 839 | axis = Vector2.Normalize(axis); 840 | 841 | var lineProjOnNorm = Vector2.Dot(norm, last); 842 | var ptProjOnNorm = Vector2.Dot(norm, pt); 843 | 844 | if (ptProjOnNorm > lineProjOnNorm) 845 | { 846 | var ptProjOnAxis = Vector2.Dot(axis, pt); 847 | var stProjOnAxis = Vector2.Dot(axis, last); 848 | 849 | if (ptProjOnAxis < stProjOnAxis) 850 | { 851 | var res = pt - last; 852 | return Tuple.Create(Vector2.Normalize(res), res.Length()); 853 | } 854 | 855 | var enProjOnAxis = Vector2.Dot(axis, curr); 856 | 857 | if (ptProjOnAxis > enProjOnAxis) 858 | { 859 | var res = pt - curr; 860 | return Tuple.Create(Vector2.Normalize(res), res.Length()); 861 | } 862 | 863 | 864 | var distOnNorm = ptProjOnNorm - lineProjOnNorm; 865 | return Tuple.Create(norm, distOnNorm); 866 | } 867 | 868 | last = curr; 869 | } 870 | 871 | return null; 872 | } 873 | 874 | private static IEnumerable GetExtraMinDistanceVecsPolyPoly(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2) 875 | { 876 | foreach (var vert in poly1.Vertices) 877 | { 878 | foreach (var vert2 in poly2.Vertices) 879 | { 880 | var roughAxis = ((vert2 + pos2) - (vert + pos1)); 881 | roughAxis.Normalize(); 882 | yield return Math2.MakeStandardNormal(roughAxis); 883 | } 884 | } 885 | } 886 | 887 | /// 888 | /// Calculates the shortest distance and direction to go from poly1 at pos1 to poly2 at pos2. Returns null 889 | /// if the polygons intersect. 890 | /// 891 | /// The distance. 892 | /// First polygon 893 | /// Second polygon 894 | /// Origin of first polygon 895 | /// Origin of second polygon 896 | /// Rotation of first polygon 897 | /// Rotation of second polygon 898 | public static Tuple MinDistance(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Rotation2 rot2) 899 | { 900 | if (rot1.Theta != 0 || rot2.Theta != 0) 901 | { 902 | throw new NotSupportedException("Finding the minimum distance between polygons requires calculating the rotated polygons. This operation is expensive and should be cached. " + 903 | "Create the rotated polygons with Polygon2#GetRotated and call this function with Rotation2.Zero for both rotations."); 904 | } 905 | 906 | var axises = poly1.Normals.Union(poly2.Normals).Union(GetExtraMinDistanceVecsPolyPoly(poly1, poly2, pos1, pos2)); 907 | Vector2? bestAxis = null; // note this is the one with the longest distance 908 | float bestDist = 0; 909 | foreach (var norm in axises) 910 | { 911 | var proj1 = ProjectAlongAxis(poly1, pos1, rot1, norm); 912 | var proj2 = ProjectAlongAxis(poly2, pos2, rot2, norm); 913 | 914 | var dist = AxisAlignedLine2.MinDistance(proj1, proj2); 915 | if (dist.HasValue && (bestAxis == null || dist.Value > bestDist)) 916 | { 917 | bestDist = dist.Value; 918 | if (proj2.Min < proj1.Min && dist > 0) 919 | bestAxis = -norm; 920 | else 921 | bestAxis = norm; 922 | } 923 | } 924 | 925 | if (!bestAxis.HasValue || Math2.Approximately(bestDist, 0)) 926 | return null; // they intersect 927 | 928 | return Tuple.Create(bestAxis.Value, bestDist); 929 | } 930 | 931 | /// 932 | /// Returns a polygon that is created by rotated the original polygon 933 | /// about its center by the specified amount. Returns the original polygon if 934 | /// rot.Theta == 0. 935 | /// 936 | /// The rotated polygon. 937 | /// Original. 938 | /// Rot. 939 | public static Polygon2 GetRotated(Polygon2 original, Rotation2 rot) 940 | { 941 | if (rot.Theta == 0) 942 | return original; 943 | 944 | var rotatedVerts = new Vector2[original.Vertices.Length]; 945 | for (var i = 0; i < original.Vertices.Length; i++) 946 | { 947 | rotatedVerts[i] = Math2.Rotate(original.Vertices[i], original.Center, rot); 948 | } 949 | 950 | return new Polygon2(rotatedVerts); 951 | } 952 | 953 | 954 | /// 955 | /// Creates the ray trace polygons from the given polygon moving from start to end. The returned set of polygons 956 | /// may not be the smallest possible set of polygons which perform this job. 957 | /// 958 | /// In order to determine if polygon A intersects polygon B during a move from position S to E, you can check if 959 | /// B intersects any of the polygons in CreateRaytraceAblesFromPolygon(A, E - S) when they are placed at S. 960 | /// 961 | /// 962 | /// 963 | /// Polygon2 a = ShapeUtils.CreateCircle(10, 0, 0, 5); 964 | /// Polygon2 b = ShapeUtils.CreateCircle(15, 0, 0, 7); 965 | /// 966 | /// Vector2 from = new Vector2(3, 3); 967 | /// Vector2 to = new Vector2(15, 3); 968 | /// Vector2 bloc = new Vector2(6, 3); 969 | /// 970 | /// List<Polygon2> traces = Polygon2.CreateRaytraceAbles(a, to - from); 971 | /// foreach (var trace in traces) 972 | /// { 973 | /// if (Polygon2.Intersects(trace, b, from, bloc, true)) 974 | /// { 975 | /// Console.WriteLine("Intersects!"); 976 | /// break; 977 | /// } 978 | /// } 979 | /// 980 | /// 981 | /// The polygon that you want to move 982 | /// The direction and magnitude that the polygon moves 983 | /// A set of polygons which completely contain the area that the polygon will intersect during a move 984 | /// from the origin to offset. 985 | public static List CreateRaytraceAbles(Polygon2 poly, Vector2 offset) 986 | { 987 | var ourLinesAsRects = new List(); 988 | if (Math2.Approximately(offset, Vector2.Zero)) 989 | { 990 | ourLinesAsRects.Add(poly); 991 | return ourLinesAsRects; 992 | } 993 | 994 | for (int lineIndex = 0, nLines = poly.Lines.Length; lineIndex < nLines; lineIndex++) 995 | { 996 | var line = poly.Lines[lineIndex]; 997 | if (!Math2.IsOnLine(line.Start, line.End, line.Start + offset)) 998 | { 999 | ourLinesAsRects.Add(new Polygon2(new Vector2[] 1000 | { 1001 | line.Start, 1002 | line.End, 1003 | line.End + offset, 1004 | line.Start + offset 1005 | })); 1006 | } 1007 | } 1008 | 1009 | return ourLinesAsRects; 1010 | } 1011 | 1012 | #region NoRotation 1013 | /// 1014 | /// Determines if the specified polygons intersect when at the specified positions and not rotated. 1015 | /// 1016 | /// First polygon 1017 | /// Second polygon 1018 | /// Origin of first polygon 1019 | /// Origin of second polygon 1020 | /// If overlap is required for intersection 1021 | /// If poly1 at pos1 not rotated and poly2 at pos2 not rotated intersect 1022 | public static bool Intersects(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2, bool strict) 1023 | { 1024 | return Intersects(poly1, poly2, pos1, pos2, Rotation2.Zero, Rotation2.Zero, strict); 1025 | } 1026 | 1027 | /// 1028 | /// Determines if the first polygon at position 1 intersects the second polygon at position 2, where 1029 | /// neither polygon is rotated. 1030 | /// 1031 | /// First polygon 1032 | /// Second polygon 1033 | /// Origin of first polygon 1034 | /// Origin of second polygon 1035 | /// If poly1 at pos1 not rotated intersects poly2 at pos2 not rotated 1036 | public static Tuple IntersectMTV(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2) 1037 | { 1038 | return IntersectMTV(poly1, poly2, pos1, pos2, Rotation2.Zero, Rotation2.Zero); 1039 | } 1040 | 1041 | /// 1042 | /// Determines the shortest way for the specified polygon at the specified position with 1043 | /// no rotation to get to the specified point, if point is not (non-strictly) intersected 1044 | /// the polygon when it's at the specified position with no rotation. 1045 | /// 1046 | /// Polygon 1047 | /// Position of the polygon 1048 | /// Point to check 1049 | /// axis to go in, distance to go if pos is not in poly, otherwise null 1050 | public static Tuple MinDistance(Polygon2 poly, Vector2 pos, Vector2 pt) 1051 | { 1052 | return MinDistance(poly, pos, Rotation2.Zero, pt); 1053 | } 1054 | 1055 | /// 1056 | /// Determines the shortest way for the first polygon at position 1 to touch the second polygon at 1057 | /// position 2, assuming the polygons do not intersect (not strictly) and are not rotated. 1058 | /// 1059 | /// First polygon 1060 | /// Second polygon 1061 | /// Position of first polygon 1062 | /// Position of second polygon 1063 | /// axis to go in, distance to go if poly1 does not intersect poly2, otherwise null 1064 | public static Tuple MinDistance(Polygon2 poly1, Polygon2 poly2, Vector2 pos1, Vector2 pos2) 1065 | { 1066 | return MinDistance(poly1, poly2, pos1, pos2, Rotation2.Zero, Rotation2.Zero); 1067 | } 1068 | #endregion 1069 | } 1070 | } 1071 | -------------------------------------------------------------------------------- /Math2/Rect2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | /// 11 | /// Describes a rectangle. Meant to be reused. 12 | /// 13 | public class Rect2 : Shape2 14 | { 15 | /// 16 | /// The vertices of this rectangle as a clockwise array. 17 | /// 18 | public readonly Vector2[] Vertices; 19 | 20 | /// 21 | /// The corner with the smallest x and y coordinates on this 22 | /// rectangle. 23 | /// 24 | public Vector2 Min => Vertices[0]; 25 | 26 | /// 27 | /// The corner with the largest x and y coordinates on this 28 | /// rectangle 29 | /// 30 | public Vector2 Max => Vertices[2]; 31 | 32 | /// 33 | /// The corner with the largest x and smallest y coordinates on 34 | /// this rectangle 35 | /// 36 | public Vector2 UpperRight => Vertices[1]; 37 | 38 | /// 39 | /// The corner with the smallest x and largest y coordinates on this 40 | /// rectangle 41 | /// 42 | public Vector2 LowerLeft => Vertices[3]; 43 | 44 | /// 45 | /// The center of this rectangle 46 | /// 47 | public readonly Vector2 Center; 48 | 49 | /// 50 | /// The width of this rectangle 51 | /// 52 | public readonly float Width; 53 | 54 | /// 55 | /// The height of this rectangle 56 | /// 57 | public readonly float Height; 58 | 59 | /// 60 | /// Creates a bounding box with the specified upper-left and bottom-right. 61 | /// Will autocorrect if min.X > max.X or min.Y > max.Y 62 | /// 63 | /// Min x, min y 64 | /// Max x, max y 65 | /// If min and max do not make a box 66 | public Rect2(Vector2 min, Vector2 max) 67 | { 68 | float area = (max.X - min.X) * (max.Y - min.Y); 69 | if(area > -Math2.DEFAULT_EPSILON && area < Math2.DEFAULT_EPSILON) 70 | throw new ArgumentException($"min={min}, max={max} - that's a line or a point, not a box (area below epsilon {Math2.DEFAULT_EPSILON} (got {area}))"); 71 | 72 | float tmpX1 = min.X, tmpX2 = max.X; 73 | float tmpY1 = min.Y, tmpY2 = max.Y; 74 | 75 | min.X = Math.Min(tmpX1, tmpX2); 76 | min.Y = Math.Min(tmpY1, tmpY2); 77 | max.X = Math.Max(tmpX1, tmpX2); 78 | max.Y = Math.Max(tmpY1, tmpY2); 79 | 80 | Vertices = new Vector2[] 81 | { 82 | min, new Vector2(max.X, min.Y), max, new Vector2(min.X, max.Y) 83 | }; 84 | 85 | Center = new Vector2((Min.X + Max.X) / 2, (Min.Y + Max.Y) / 2); 86 | 87 | Width = Max.X - Min.X; 88 | Height = Max.Y - Min.Y; 89 | } 90 | 91 | /// 92 | /// Creates a bounding box from the specified points. Will correct if minX > maxX or minY > maxY. 93 | /// 94 | /// Min or max x (different from maxX) 95 | /// Min or max y (different from maxY) 96 | /// Min or max x (different from minX) 97 | /// Min or max y (different from minY) 98 | public Rect2(float minX, float minY, float maxX, float maxY) : this(new Vector2(minX, minY), new Vector2(maxX, maxY)) 99 | { 100 | } 101 | 102 | /// 103 | /// Determines if box1 with origin pos1 intersects box2 with origin pos2. 104 | /// 105 | /// Box 1 106 | /// Box 2 107 | /// Origin of box 1 108 | /// Origin of box 2 109 | /// If overlap is required for intersection 110 | /// If box1 intersects box2 when box1 is at pos1 and box2 is at pos2 111 | public static bool Intersects(Rect2 box1, Rect2 box2, Vector2 pos1, Vector2 pos2, bool strict) 112 | { 113 | return AxisAlignedLine2.Intersects(box1.Min.X + pos1.X, box1.Max.X + pos1.X, box2.Min.X + pos2.X, box2.Max.X + pos2.X, strict, false) 114 | && AxisAlignedLine2.Intersects(box1.Min.Y + pos1.Y, box1.Max.Y + pos1.Y, box2.Min.Y + pos2.Y, box2.Max.Y + pos2.Y, strict, false); 115 | } 116 | 117 | /// 118 | /// Determines if the box when at pos contains point. 119 | /// 120 | /// The box 121 | /// Origin of box 122 | /// Point to check 123 | /// true if the edges do not count 124 | /// If the box at pos contains point 125 | public static bool Contains(Rect2 box, Vector2 pos, Vector2 point, bool strict) 126 | { 127 | return AxisAlignedLine2.Contains(box.Min.X + pos.X, box.Max.X + pos.X, point.X, strict, false) 128 | && AxisAlignedLine2.Contains(box.Min.Y + pos.Y, box.Max.Y + pos.Y, point.Y, strict, false); 129 | } 130 | 131 | /// 132 | /// Determines if innerBox is contained entirely in outerBox 133 | /// 134 | /// the (bigger) box that you want to check contains the inner box 135 | /// the (smaller) box that you want to check is contained in the outer box 136 | /// where the outer box is located 137 | /// where the inner box is located 138 | /// true to return false if innerBox touches an edge of outerBox, false otherwise 139 | /// true if innerBox is contained in outerBox, false otherwise 140 | public static bool Contains(Rect2 outerBox, Rect2 innerBox, Vector2 posOuter, Vector2 posInner, bool strict) 141 | { 142 | return Contains(outerBox, posOuter, innerBox.Min + posInner, strict) && Contains(outerBox, posOuter, innerBox.Max + posInner, strict); 143 | } 144 | 145 | /// 146 | /// Deterimines in the box contains the specified polygon 147 | /// 148 | /// The box 149 | /// The polygon 150 | /// Where the box is located 151 | /// Where the polygon is located 152 | /// true if we return false if the any part of the polygon is on the edge, false otherwise 153 | /// true if the poly is contained in box, false otherwise 154 | public static bool Contains(Rect2 box, Polygon2 poly, Vector2 boxPos, Vector2 polyPos, bool strict) 155 | { 156 | return Contains(box, poly.AABB, boxPos, polyPos, strict); 157 | } 158 | 159 | /// 160 | /// Projects the rectangle at pos along axis. 161 | /// 162 | /// The rectangle to project 163 | /// The origin of the rectangle 164 | /// The axis to project on 165 | /// The projection of rect at pos along axis 166 | public static AxisAlignedLine2 ProjectAlongAxis(Rect2 rect, Vector2 pos, Vector2 axis) 167 | { 168 | //return ProjectAlongAxis(axis, pos, Rotation2.Zero, rect.Center, rect.Min, rect.UpperRight, rect.LowerLeft, rect.Max); 169 | return ProjectAlongAxis(axis, pos, rect.Vertices); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Math2/RelativeRectangle2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Xna.Framework; 3 | 4 | namespace SharpMath2 5 | { 6 | /// 7 | /// Describes a rectangle that is describing the percentages to go 8 | /// of the true rectangle. Useful in some UI circumstances. 9 | /// 10 | public class RelativeRectangle2 : Rect2 11 | { 12 | /// 13 | /// Create a new relative rectangle 14 | /// 15 | /// vector of smallest x and y coordinates 16 | /// vector of largest x and y coordinates 17 | public RelativeRectangle2(Vector2 min, Vector2 max) : base(min, max) 18 | { 19 | } 20 | 21 | /// 22 | /// Create a new relative rectangle 23 | /// 24 | /// smallest x 25 | /// smallest y 26 | /// width 27 | /// height 28 | public RelativeRectangle2(float x, float y, float w, float h) : base(new Vector2(x, y), new Vector2(x + w, y + h)) 29 | { 30 | } 31 | 32 | /// 33 | /// Multiply our min with original min and our max with original max and return 34 | /// as a rect 35 | /// 36 | /// the original 37 | /// scaled rect 38 | public Rect2 ToRect(Rect2 original) 39 | { 40 | return new Rect2(original.Min * Min, original.Max * Max); 41 | } 42 | 43 | #if !NOT_MONOGAME 44 | /// 45 | /// Multiply our min with original min and our max with original max and return 46 | /// as a rect 47 | /// 48 | /// the monogame original 49 | /// the rect 50 | public Rect2 ToRect(Rectangle original) { 51 | return new Rect2( 52 | new Vector2(original.Left + original.Width * Min.X, original.Top + original.Height * Min.Y), 53 | new Vector2(original.Left + original.Width * Max.X, original.Top + original.Height * Max.Y) 54 | ); 55 | } 56 | #endif 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Math2/Rotation2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SharpMath2 6 | { 7 | /// 8 | /// Describes a rotation about the z axis, with sin and cos of theta 9 | /// cached. 10 | /// 11 | public struct Rotation2 12 | { 13 | /// 14 | /// Rotation Theta=0 15 | /// 16 | public static readonly Rotation2 Zero = new Rotation2(0, 1, 0); 17 | 18 | /// 19 | /// Theta in radians. 20 | /// 21 | public readonly float Theta; 22 | 23 | /// 24 | /// Math.Cos(Theta) 25 | /// 26 | public readonly float CosTheta; 27 | 28 | /// 29 | /// Math.Sin(Theta) 30 | /// 31 | public readonly float SinTheta; 32 | 33 | /// 34 | /// Create a new rotation by specifying the theta, its cosin, and its sin. 35 | /// 36 | /// Theta will be normalized to 0 <= theta <= 2pi 37 | /// 38 | /// 39 | /// 40 | /// 41 | public Rotation2(float theta, float cosTheta, float sinTheta) 42 | { 43 | if (float.IsInfinity(theta) || float.IsNaN(theta)) 44 | throw new ArgumentException($"Invalid theta: {theta}", nameof(theta)); 45 | 46 | Theta = Standardize(theta); 47 | CosTheta = cosTheta; 48 | SinTheta = sinTheta; 49 | } 50 | 51 | /// 52 | /// Create a new rotation at the specified theta, calculating the cos and sin. 53 | /// 54 | /// Theta will be normalized to 0 <= theta <= 2pi 55 | /// 56 | /// 57 | public Rotation2(float theta) : this(theta, (float)Math.Cos(theta), (float)Math.Sin(theta)) 58 | { 59 | } 60 | 61 | /// 62 | /// Determine if the two rotations have the same theta 63 | /// 64 | /// First rotation 65 | /// Second rotation 66 | /// if r1 and r2 are the same logical rotation 67 | public static bool operator ==(Rotation2 r1, Rotation2 r2) 68 | { 69 | if (ReferenceEquals(r1, null) || ReferenceEquals(r2, null)) 70 | return ReferenceEquals(r1, r2); 71 | 72 | return r1.Theta == r2.Theta; 73 | } 74 | 75 | /// 76 | /// Determine if the two rotations are not the same 77 | /// 78 | /// first rotation 79 | /// second rotation 80 | /// if r1 and r2 are not the same logical rotation 81 | public static bool operator !=(Rotation2 r1, Rotation2 r2) 82 | { 83 | if (ReferenceEquals(r1, null) || ReferenceEquals(r2, null)) 84 | return ReferenceEquals(r1, r2); 85 | 86 | return r1.Theta != r2.Theta; 87 | } 88 | 89 | /// 90 | /// Determine if obj is a rotation that is logically equal to this one 91 | /// 92 | /// the object 93 | /// if it is logically equal 94 | public override bool Equals(object obj) 95 | { 96 | if (obj.GetType() != typeof(Rotation2)) 97 | return false; 98 | 99 | return this == ((Rotation2)obj); 100 | } 101 | 102 | /// 103 | /// The hashcode of this rotation based on just Theta 104 | /// 105 | /// 106 | public override int GetHashCode() 107 | { 108 | return Theta.GetHashCode(); 109 | } 110 | 111 | /// 112 | /// Create a human-readable representation of this rotation 113 | /// 114 | /// string representation 115 | public override string ToString() 116 | { 117 | return $"{Theta} rads"; 118 | } 119 | 120 | /// 121 | /// Standardizes the given angle to fall between 0 <= theta < 2 * PI 122 | /// 123 | /// The radian angle to standardize 124 | /// The standardized theta 125 | public static float Standardize(float theta) 126 | { 127 | if (theta < 0) 128 | { 129 | int numToAdd = (int)Math.Ceiling((-theta) / (Math.PI * 2)); 130 | return theta + (float)Math.PI * 2 * numToAdd; 131 | } 132 | else if (theta >= Math.PI * 2) 133 | { 134 | int numToReduce = (int)Math.Floor(theta / (Math.PI * 2)); 135 | return theta - (float)Math.PI * 2 * numToReduce; 136 | } 137 | return theta; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Math2/Shape2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | 8 | namespace SharpMath2 9 | { 10 | /// 11 | /// Parent class for shapes - contains functions for comparing different shapes. 12 | /// 13 | public class Shape2 14 | { 15 | /// 16 | /// Determines if polygon at position 1 intersects the rectangle at position 2. Polygon may 17 | /// be rotated, but the rectangle cannot (use a polygon if you want to rotate it). 18 | /// 19 | /// Polygon 20 | /// Rectangle 21 | /// Origin of polygon 22 | /// Origin of rectangle 23 | /// Rotation of the polygon. 24 | /// If overlapping is required for intersection 25 | /// if poly at pos1 intersects rect at pos2 26 | public static bool Intersects(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, Rotation2 rot1, bool strict) 27 | { 28 | bool checkedX = false, checkedY = false; 29 | for (int i = 0; i < poly.Normals.Count; i++) 30 | { 31 | var norm = Math2.Rotate(poly.Normals[i], Vector2.Zero, rot1); 32 | if (!IntersectsAlongAxis(poly, rect, pos1, pos2, rot1, strict, norm)) 33 | return false; 34 | 35 | if (norm.X == 0) 36 | checkedY = true; 37 | if (norm.Y == 0) 38 | checkedX = true; 39 | } 40 | 41 | if (!checkedX && !IntersectsAlongAxis(poly, rect, pos1, pos2, rot1, strict, Vector2.UnitX)) 42 | return false; 43 | if (!checkedY && !IntersectsAlongAxis(poly, rect, pos1, pos2, rot1, strict, Vector2.UnitY)) 44 | return false; 45 | 46 | return true; 47 | } 48 | 49 | /// 50 | /// Determines the vector, if any, to move poly at pos1 rotated rot1 to prevent intersection of rect 51 | /// at pos2. 52 | /// 53 | /// Polygon 54 | /// Rectangle 55 | /// Origin of polygon 56 | /// Origin of rectangle 57 | /// Rotation of the polygon. 58 | /// The vector to move pos1 by or null 59 | public static Tuple IntersectMTV(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, Rotation2 rot1) 60 | { 61 | bool checkedX = false, checkedY = false; 62 | 63 | Vector2 bestAxis = Vector2.Zero; 64 | float bestMagn = float.MaxValue; 65 | 66 | for (int i = 0; i < poly.Normals.Count; i++) 67 | { 68 | var norm = Math2.Rotate(poly.Normals[i], Vector2.Zero, rot1); 69 | var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, norm); 70 | if (!mtv.HasValue) 71 | return null; 72 | 73 | if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) 74 | { 75 | bestAxis = norm; 76 | bestMagn = mtv.Value; 77 | } 78 | 79 | if (norm.X == 0) 80 | checkedY = true; 81 | if (norm.Y == 0) 82 | checkedX = true; 83 | } 84 | 85 | if (!checkedX) 86 | { 87 | var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, Vector2.UnitX); 88 | if (!mtv.HasValue) 89 | return null; 90 | 91 | if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) 92 | { 93 | bestAxis = Vector2.UnitX; 94 | bestMagn = mtv.Value; 95 | } 96 | } 97 | 98 | if (!checkedY) 99 | { 100 | var mtv = IntersectMTVAlongAxis(poly, rect, pos1, pos2, rot1, Vector2.UnitY); 101 | if (!mtv.HasValue) 102 | return null; 103 | 104 | if (Math.Abs(mtv.Value) < Math.Abs(bestMagn)) 105 | { 106 | bestAxis = Vector2.UnitY; 107 | bestMagn = mtv.Value; 108 | } 109 | } 110 | 111 | return Tuple.Create(bestAxis, bestMagn); 112 | } 113 | 114 | /// 115 | /// Determines the vector to move pos1 to get rect not to intersect poly at pos2 rotated 116 | /// by rot2 radians. 117 | /// 118 | /// The rectangle 119 | /// The polygon 120 | /// Origin of rectangle 121 | /// Origin of 122 | /// Rotation of the polygon 123 | /// Offset of pos1 to get rect not to intersect poly 124 | public static Tuple IntersectMTV(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2) 125 | { 126 | var res = IntersectMTV(poly, rect, pos2, pos1, rot2); 127 | return res != null ? Tuple.Create(-res.Item1, res.Item2) : res; 128 | } 129 | 130 | /// 131 | /// Determines if the rectangle at pos1 intersects the polygon at pos2. 132 | /// 133 | /// The rectangle 134 | /// The polygon 135 | /// Origin of retangle 136 | /// Origin of polygon 137 | /// Rotation of the polygon. 138 | /// If overlap is required for intersection 139 | /// If rect at pos1 intersects poly at pos2 140 | public static bool Intersects(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, bool strict) 141 | { 142 | return Intersects(poly, rect, pos2, pos1, rot2, strict); 143 | } 144 | 145 | 146 | /// 147 | /// Determines if the specified polygon and rectangle where poly is at pos1 and rect is at pos2 intersect 148 | /// along the specified axis. 149 | /// 150 | /// polygon 151 | /// Rectangle 152 | /// Origin of polygon 153 | /// Origin of rectangle 154 | /// Rotation of the polygon. 155 | /// If overlap is required for intersection 156 | /// Axis to check 157 | /// If poly at pos1 intersects rect at pos2 along axis 158 | public static bool IntersectsAlongAxis(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, Rotation2 rot1, bool strict, Vector2 axis) 159 | { 160 | var proj1 = Polygon2.ProjectAlongAxis(poly, pos1, rot1, axis); 161 | var proj2 = Rect2.ProjectAlongAxis(rect, pos2, axis); 162 | 163 | return AxisAlignedLine2.Intersects(proj1, proj2, strict); 164 | } 165 | 166 | /// 167 | /// Determines if the specified rectangle and polygon where rect is at pos1 and poly is at pos2 intersect 168 | /// along the specified axis. 169 | /// 170 | /// Rectangle 171 | /// Polygon 172 | /// Origin of rectangle 173 | /// Origin of polygon 174 | /// Rotation of polygon 175 | /// 176 | /// 177 | /// 178 | public static bool IntersectsAlongAxis(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, bool strict, Vector2 axis) 179 | { 180 | return IntersectsAlongAxis(poly, rect, pos2, pos1, rot2, strict, axis); 181 | } 182 | 183 | /// 184 | /// Determines the mtv along axis to move poly at pos1 to prevent intersection with rect at pos2. 185 | /// 186 | /// polygon 187 | /// Rectangle 188 | /// Origin of polygon 189 | /// Origin of rectangle 190 | /// Rotation of polygon in radians 191 | /// Axis to check 192 | /// Number if poly intersects rect along axis, null otherwise 193 | public static float? IntersectMTVAlongAxis(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, Rotation2 rot1, Vector2 axis) 194 | { 195 | var proj1 = Polygon2.ProjectAlongAxis(poly, pos1, rot1, axis); 196 | var proj2 = Rect2.ProjectAlongAxis(rect, pos2, axis); 197 | 198 | return AxisAlignedLine2.IntersectMTV(proj1, proj2); 199 | } 200 | 201 | /// 202 | /// Determines the mtv along axis to move rect at pos1 to prevent intersection with poly at pos2 203 | /// 204 | /// Rectangle 205 | /// polygon 206 | /// Origin of rectangle 207 | /// Origin of polygon 208 | /// Rotation of the polygon in radians 209 | /// Axis to check 210 | /// Number if rect intersects poly along axis, null otherwise 211 | public static float? IntersectMTVAlongAxis(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, Vector2 axis) 212 | { 213 | var proj1 = Rect2.ProjectAlongAxis(rect, pos1, axis); 214 | var proj2 = Polygon2.ProjectAlongAxis(poly, pos2, rot2, axis); 215 | 216 | return AxisAlignedLine2.IntersectMTV(proj1, proj2); 217 | } 218 | 219 | /// 220 | /// Determines if the specified polygon at the specified position and rotation 221 | /// intersects the specified circle at it's respective position. 222 | /// 223 | /// The polygon 224 | /// The circle 225 | /// The origin for the polygon 226 | /// The top-left of the circles bounding box 227 | /// The rotation of the polygon 228 | /// If overlap is required for intersection 229 | /// If poly at pos1 with rotation rot1 intersects the circle at pos2 230 | public static bool Intersects(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2, Rotation2 rot1, bool strict) 231 | { 232 | // look at pictures of https://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection if you don't 233 | // believe this is true 234 | return poly.Lines.Any((l) => CircleIntersectsLine(circle, l, pos2, pos1, rot1, poly.Center, strict)) || Polygon2.Contains(poly, pos1, rot1, new Vector2(pos2.X + circle.Radius, pos2.Y + circle.Radius), strict); 235 | } 236 | 237 | /// 238 | /// Determines the minimum translation that must be applied the specified polygon (at the given position 239 | /// and rotation) to prevent intersection with the circle (at its given rotation). If the two are not overlapping, 240 | /// returns null. 241 | /// 242 | /// Returns a tuple of the axis to move the polygon in (unit vector) and the distance to move the polygon. 243 | /// 244 | /// The polygon 245 | /// The circle 246 | /// The origin of the polygon 247 | /// The top-left of the circles bounding box 248 | /// The rotation of the polygon 249 | /// 250 | public static Tuple IntersectMTV(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2, Rotation2 rot1) 251 | { 252 | // We have two situations, either the circle is not strictly intersecting the polygon, or 253 | // there exists at least one shortest line that you could push the polygon to prevent 254 | // intersection with the circle. 255 | 256 | // That line will either go from a vertix of the polygon to a point on the edge of the circle, 257 | // or it will go from a point on a line of the polygon to the edge of the circle. 258 | 259 | // If the line comes from a vertix of the polygon, the MTV will be along the line produced 260 | // by going from the center of the circle to the vertix, and the distance can be found by 261 | // projecting the cirle on that axis and the polygon on that axis and doing 1D overlap. 262 | 263 | // If the line comes from a point on the edge of the polygon, the MTV will be along the 264 | // normal of that line, and the distance can be found by projecting the circle on that axis 265 | // and the polygon on that axis and doing 1D overlap. 266 | 267 | // As with all SAT, if we find any axis that the circle and polygon do not overlap, we've 268 | // proven they do not intersect. 269 | 270 | // The worst case performance is related to 2x the number of vertices of the polygon, the same speed 271 | // as for 2 polygons of equal number of vertices. 272 | 273 | HashSet checkedAxis = new HashSet(); 274 | 275 | Vector2 bestAxis = Vector2.Zero; 276 | float shortestOverlap = float.MaxValue; 277 | 278 | Func checkAxis = (axis) => 279 | { 280 | var standard = Math2.MakeStandardNormal(axis); 281 | if (!checkedAxis.Contains(standard)) 282 | { 283 | checkedAxis.Add(standard); 284 | var polyProj = Polygon2.ProjectAlongAxis(poly, pos1, rot1, axis); 285 | var circleProj = Circle2.ProjectAlongAxis(circle, pos2, axis); 286 | 287 | var mtv = AxisAlignedLine2.IntersectMTV(polyProj, circleProj); 288 | if (!mtv.HasValue) 289 | return false; 290 | 291 | if (Math.Abs(mtv.Value) < Math.Abs(shortestOverlap)) 292 | { 293 | bestAxis = axis; 294 | shortestOverlap = mtv.Value; 295 | } 296 | } 297 | return true; 298 | }; 299 | 300 | var circleCenter = new Vector2(pos2.X + circle.Radius, pos2.Y + circle.Radius); 301 | int last = poly.Vertices.Length - 1; 302 | var lastVec = Math2.Rotate(poly.Vertices[last], poly.Center, rot1) + pos1; 303 | for(int curr = 0; curr < poly.Vertices.Length; curr++) 304 | { 305 | var currVec = Math2.Rotate(poly.Vertices[curr], poly.Center, rot1) + pos1; 306 | 307 | // Test along circle center -> vector 308 | if (!checkAxis(Vector2.Normalize(currVec - circleCenter))) 309 | return null; 310 | 311 | // Test along line normal 312 | if (!checkAxis(Vector2.Normalize(Math2.Perpendicular(currVec - lastVec)))) 313 | return null; 314 | 315 | last = curr; 316 | lastVec = currVec; 317 | } 318 | 319 | return Tuple.Create(bestAxis, shortestOverlap); 320 | } 321 | 322 | /// 323 | /// Determines if the specified circle, at the given position, intersects the specified polygon, 324 | /// at the given position and rotation. 325 | /// 326 | /// The circle 327 | /// The polygon 328 | /// The top-left of the circles bounding box 329 | /// The origin of the polygon 330 | /// The rotation of the polygon 331 | /// If overlap is required for intersection 332 | /// If circle at pos1 intersects poly at pos2 with rotation rot2 333 | public static bool Intersects(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2, bool strict) 334 | { 335 | return Intersects(poly, circle, pos2, pos1, rot2, strict); 336 | } 337 | 338 | /// 339 | /// Determines the minimum translation vector that must be applied to the circle at the given position to 340 | /// prevent overlap with the polygon at the given position and rotation. If the circle and the polygon do 341 | /// not overlap, returns null. Otherwise, returns a tuple of the unit axis to move the circle in, and the 342 | /// distance to move the circle. 343 | /// 344 | /// The circle 345 | /// The polygon 346 | /// The top-left of the circles bounding box 347 | /// The origin of the polygon 348 | /// The rotation of the polygon 349 | /// The mtv to move the circle at pos1 to prevent overlap with the poly at pos2 with rotation rot2 350 | public static Tuple IntersectMTV(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2, Rotation2 rot2) 351 | { 352 | var res = IntersectMTV(poly, circle, pos2, pos1, rot2); 353 | if (res != null) 354 | return Tuple.Create(-res.Item1, res.Item2); 355 | return null; 356 | } 357 | 358 | /// 359 | /// Determines if the specified circle an rectangle intersect at their given positions. 360 | /// 361 | /// The circle 362 | /// The rectangle 363 | /// The top-left of the circles bounding box 364 | /// The origin of the rectangle 365 | /// If overlap is required for intersection 366 | /// If circle at pos1 intersects rect at pos2 367 | public static bool Intersects(Circle2 circle, Rect2 rect, Vector2 pos1, Vector2 pos2, bool strict) 368 | { 369 | var circleCenter = new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius); 370 | return CircleIntersectsHorizontalLine(circle, new Line2(rect.Min + pos2, rect.UpperRight + pos2), circleCenter, strict) 371 | || CircleIntersectsHorizontalLine(circle, new Line2(rect.LowerLeft + pos2, rect.Max + pos2), circleCenter, strict) 372 | || CircleIntersectsVerticalLine(circle, new Line2(rect.Min + pos2, rect.LowerLeft + pos2), circleCenter, strict) 373 | || CircleIntersectsVerticalLine(circle, new Line2(rect.UpperRight + pos2, rect.Max + pos2), circleCenter, strict) 374 | || Rect2.Contains(rect, pos2, new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius), strict); 375 | } 376 | 377 | /// 378 | /// Determines if the specified rectangle and circle intersect at their given positions. 379 | /// 380 | /// The rectangle 381 | /// The circle 382 | /// The origin of the rectangle 383 | /// The top-left of the circles bounding box 384 | /// If overlap is required for intersection 385 | /// 386 | public static bool Intersects(Rect2 rect, Circle2 circle, Vector2 pos1, Vector2 pos2, bool strict) 387 | { 388 | return Intersects(circle, rect, pos2, pos1, strict); 389 | } 390 | 391 | /// 392 | /// Determines the minimum translation vector to be applied to the circle to 393 | /// prevent overlap with the rectangle, when they are at their given positions. 394 | /// 395 | /// The circle 396 | /// The rectangle 397 | /// The top-left of the circles bounding box 398 | /// The rectangles origin 399 | /// MTV for circle at pos1 to prevent overlap with rect at pos2 400 | public static Tuple IntersectMTV(Circle2 circle, Rect2 rect, Vector2 pos1, Vector2 pos2) 401 | { 402 | // Same as polygon rect, just converted to rects points 403 | HashSet checkedAxis = new HashSet(); 404 | 405 | Vector2 bestAxis = Vector2.Zero; 406 | float shortestOverlap = float.MaxValue; 407 | 408 | Func checkAxis = (axis) => 409 | { 410 | var standard = Math2.MakeStandardNormal(axis); 411 | if (!checkedAxis.Contains(standard)) 412 | { 413 | checkedAxis.Add(standard); 414 | var circleProj = Circle2.ProjectAlongAxis(circle, pos1, axis); 415 | var rectProj = Rect2.ProjectAlongAxis(rect, pos2, axis); 416 | 417 | var mtv = AxisAlignedLine2.IntersectMTV(circleProj, rectProj); 418 | if (!mtv.HasValue) 419 | return false; 420 | 421 | if (Math.Abs(mtv.Value) < Math.Abs(shortestOverlap)) 422 | { 423 | bestAxis = axis; 424 | shortestOverlap = mtv.Value; 425 | } 426 | } 427 | return true; 428 | }; 429 | 430 | var circleCenter = new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius); 431 | int last = 4; 432 | var lastVec = rect.UpperRight + pos2; 433 | for (int curr = 0; curr < 4; curr++) 434 | { 435 | Vector2 currVec = Vector2.Zero; 436 | switch(curr) 437 | { 438 | case 0: 439 | currVec = rect.Min + pos2; 440 | break; 441 | case 1: 442 | currVec = rect.LowerLeft + pos2; 443 | break; 444 | case 2: 445 | currVec = rect.Max + pos2; 446 | break; 447 | case 3: 448 | currVec = rect.UpperRight + pos2; 449 | break; 450 | } 451 | 452 | // Test along circle center -> vector 453 | if (!checkAxis(Vector2.Normalize(currVec - circleCenter))) 454 | return null; 455 | 456 | // Test along line normal 457 | if (!checkAxis(Vector2.Normalize(Math2.Perpendicular(currVec - lastVec)))) 458 | return null; 459 | 460 | last = curr; 461 | lastVec = currVec; 462 | } 463 | 464 | return Tuple.Create(bestAxis, shortestOverlap); 465 | } 466 | 467 | /// 468 | /// Determines the minimum translation vector to be applied to the rectangle to 469 | /// prevent overlap with the circle, when they are at their given positions. 470 | /// 471 | /// The rectangle 472 | /// The circle 473 | /// The origin of the rectangle 474 | /// The top-left of the circles bounding box 475 | /// MTV for rect at pos1 to prevent overlap with circle at pos2 476 | public static Tuple IntersectMTV(Rect2 rect, Circle2 circle, Vector2 pos1, Vector2 pos2) 477 | { 478 | var res = IntersectMTV(circle, rect, pos2, pos1); 479 | if (res != null) 480 | return Tuple.Create(-res.Item1, res.Item2); 481 | return null; 482 | } 483 | 484 | /// 485 | /// Projects the polygon from the given points with origin pos along the specified axis. 486 | /// 487 | /// Axis to project onto 488 | /// Origin of polygon 489 | /// Rotation of the polygon in radians 490 | /// Center of the polygon 491 | /// Points of polygon 492 | /// Projection of polygon of points at pos along axis 493 | protected static AxisAlignedLine2 ProjectAlongAxis(Vector2 axis, Vector2 pos, Rotation2 rot, Vector2 center, params Vector2[] points) 494 | { 495 | if (rot == Rotation2.Zero) 496 | return ProjectAlongAxis(axis, pos, points); 497 | 498 | float min = 0; 499 | float max = 0; 500 | 501 | for (int i = 0; i < points.Length; i++) 502 | { 503 | var polyPt = Math2.Rotate(points[i], center, rot); 504 | var tmp = Math2.Dot(polyPt.X + pos.X, polyPt.Y + pos.Y, axis.X, axis.Y); 505 | 506 | if (i == 0) 507 | { 508 | min = max = tmp; 509 | } 510 | else 511 | { 512 | min = Math.Min(min, tmp); 513 | max = Math.Max(max, tmp); 514 | } 515 | } 516 | 517 | return new AxisAlignedLine2(axis, min, max); 518 | } 519 | 520 | /// 521 | /// A faster variant of ProjectAlongAxis that assumes no rotation. 522 | /// 523 | /// The axis that the points are being projected along 524 | /// The offset for the points 525 | /// The points in the convex polygon 526 | /// The projectino of the polygon comprised of points at pos along axis 527 | protected unsafe static AxisAlignedLine2 ProjectAlongAxis(Vector2 axis, Vector2 pos, Vector2[] points) 528 | { 529 | int len = points.Length; 530 | if (len == 0) 531 | return new AxisAlignedLine2(axis, 0, 0); 532 | 533 | float min; 534 | float max; 535 | fixed(Vector2* pt = points) 536 | { 537 | min = axis.X * (pt[0].X + pos.X) + axis.Y * (pt[0].Y + pos.Y); 538 | max = min; 539 | for (int i = 1; i < len; i++) 540 | { 541 | float tmp = axis.X * (pt[i].X + pos.X) + axis.Y * (pt[i].Y + pos.Y); 542 | 543 | if (tmp < min) 544 | min = tmp; 545 | if (tmp > max) 546 | max = tmp; 547 | } 548 | } 549 | 550 | return new AxisAlignedLine2(axis, min, max); 551 | } 552 | 553 | /// 554 | /// Determines if the circle whose bounding boxs top left is at the first postion intersects the line 555 | /// at the second position who is rotated the specified amount about the specified point. 556 | /// 557 | /// The circle 558 | /// The line 559 | /// The top-left of the circles bounding box 560 | /// The origin of the line 561 | /// What rotation the line is under 562 | /// What the line is rotated about 563 | /// If overlap is required for intersection 564 | /// If the circle at pos1 intersects the line at pos2 rotated rot2 about about2 565 | protected static bool CircleIntersectsLine(Circle2 circle, Line2 line, Vector2 pos1, Vector2 pos2, Rotation2 rot2, Vector2 about2, bool strict) 566 | { 567 | // Make more math friendly 568 | var actualLine = new Line2(Math2.Rotate(line.Start, about2, rot2) + pos2, Math2.Rotate(line.End, about2, rot2) + pos2); 569 | var circleCenter = new Vector2(pos1.X + circle.Radius, pos1.Y + circle.Radius); 570 | 571 | // Check weird situations 572 | if (actualLine.Horizontal) 573 | return CircleIntersectsHorizontalLine(circle, actualLine, circleCenter, strict); 574 | if (actualLine.Vertical) 575 | return CircleIntersectsVerticalLine(circle, actualLine, circleCenter, strict); 576 | 577 | // Goal: 578 | // 1. Find closest distance, closestDistance, on the line to the circle (assuming the line was infinite) 579 | // 1a Determine if closestPoint is intersects the circle according to strict 580 | // - If it does not, we've shown there is no intersection. 581 | // 2. Find closest point, closestPoint, on the line to the circle (assuming the line was infinite) 582 | // 3. Determine if closestPoint is on the line (including edges) 583 | // - If it is, we've shown there is intersection. 584 | // 4. Determine which edge, edgeClosest, is closest to closestPoint 585 | // 5. Determine if edgeClosest intersects the circle according to strict 586 | // - If it does, we've shown there is intersection 587 | // - If it does not, we've shown there is no intersection 588 | 589 | // Step 1 590 | // We're trying to find closestDistance 591 | 592 | // Recall that the shortest line from a line to a point will be normal to the line 593 | // Thus, the shortest distance from a line to a point can be found by projecting 594 | // the line onto it's own normal vector and projecting the point onto the lines 595 | // normal vector; the distance between those points is the shortest distance from 596 | // the two points. 597 | 598 | // The projection of a line onto its normal will be a single point, and will be same 599 | // for any point on that line. So we pick a point that's convienent (the start or end). 600 | var lineProjectedOntoItsNormal = Vector2.Dot(actualLine.Start, actualLine.Normal); 601 | var centerOfCircleProjectedOntoNormalOfLine = Vector2.Dot(circleCenter, actualLine.Normal); 602 | var closestDistance = Math.Abs(centerOfCircleProjectedOntoNormalOfLine - lineProjectedOntoItsNormal); 603 | 604 | // Step 1a 605 | if(strict) 606 | { 607 | if (closestDistance >= circle.Radius) 608 | return false; 609 | }else 610 | { 611 | if (closestDistance > circle.Radius) 612 | return false; 613 | } 614 | 615 | // Step 2 616 | // We're trying to find closestPoint 617 | 618 | // We can just walk the vector from the center to the closest point, which we know is on 619 | // the normal axis and the distance closestDistance. However it's helpful to get the signed 620 | // version End - Start to walk. 621 | var signedDistanceCircleCenterToLine = lineProjectedOntoItsNormal - centerOfCircleProjectedOntoNormalOfLine; 622 | var closestPoint = circleCenter - actualLine.Normal * signedDistanceCircleCenterToLine; 623 | 624 | // Step 3 625 | // Determine if closestPoint is on the line (including edges) 626 | 627 | // We're going to accomplish this by projecting the line onto it's own axis and the closestPoint onto the lines 628 | // axis. Then we have a 1D comparison. 629 | var lineStartProjectedOntoLineAxis = Vector2.Dot(actualLine.Start, actualLine.Axis); 630 | var lineEndProjectedOntoLineAxis = Vector2.Dot(actualLine.End, actualLine.Axis); 631 | 632 | var closestPointProjectedOntoLineAxis = Vector2.Dot(closestPoint, actualLine.Axis); 633 | 634 | if (AxisAlignedLine2.Contains(lineStartProjectedOntoLineAxis, lineEndProjectedOntoLineAxis, closestPointProjectedOntoLineAxis, false, true)) 635 | { 636 | return true; 637 | } 638 | 639 | // Step 4 640 | // We're trying to find edgeClosest. 641 | // 642 | // We're going to reuse those projections from step 3. 643 | // 644 | // (for each "point" in the next paragraph I mean "point projected on the lines axis" but that's wordy) 645 | // 646 | // We know that the start is closest iff EITHER the start is less than the end and the 647 | // closest point is less than the start, OR the start is greater than the end and 648 | // closest point is greater than the end. 649 | 650 | var closestEdge = Vector2.Zero; 651 | if (lineStartProjectedOntoLineAxis < lineEndProjectedOntoLineAxis) 652 | closestEdge = (closestPointProjectedOntoLineAxis <= lineStartProjectedOntoLineAxis) ? actualLine.Start : actualLine.End; 653 | else 654 | closestEdge = (closestPointProjectedOntoLineAxis >= lineEndProjectedOntoLineAxis) ? actualLine.Start : actualLine.End; 655 | 656 | // Step 5 657 | // Circle->Point intersection for closestEdge 658 | 659 | var distToCircleFromClosestEdgeSq = (circleCenter - closestEdge).LengthSquared(); 660 | if (strict) 661 | return distToCircleFromClosestEdgeSq < (circle.Radius * circle.Radius); 662 | else 663 | return distToCircleFromClosestEdgeSq <= (circle.Radius * circle.Radius); 664 | 665 | // If you had trouble following, see the horizontal and vertical cases which are the same process but the projections 666 | // are simpler 667 | } 668 | 669 | /// 670 | /// Determines if the circle at the specified position intersects the line, 671 | /// which is at its true position and rotation, when the line is assumed to be horizontal. 672 | /// 673 | /// The circle 674 | /// The line 675 | /// The center of the circle 676 | /// If overlap is required for intersection 677 | /// If the circle with center circleCenter intersects the horizontal line 678 | protected static bool CircleIntersectsHorizontalLine(Circle2 circle, Line2 line, Vector2 circleCenter, bool strict) 679 | { 680 | // This is exactly the same process as CircleIntersectsLine, except the projetions are easier 681 | var lineY = line.Start.Y; 682 | 683 | // Step 1 - Find closest distance 684 | var vecCircleCenterToLine1D = lineY - circleCenter.Y; 685 | var closestDistance = Math.Abs(vecCircleCenterToLine1D); 686 | 687 | // Step 1a 688 | if(strict) 689 | { 690 | if (closestDistance >= circle.Radius) 691 | return false; 692 | }else 693 | { 694 | if (closestDistance > circle.Radius) 695 | return false; 696 | } 697 | 698 | // Step 2 - Find closest point 699 | var closestPointX = circleCenter.X; 700 | 701 | // Step 3 - Is closest point on line 702 | if (AxisAlignedLine2.Contains(line.Start.X, line.End.X, closestPointX, false, true)) 703 | return true; 704 | 705 | // Step 4 - Find edgeClosest 706 | float edgeClosestX; 707 | if (line.Start.X < line.End.X) 708 | edgeClosestX = (closestPointX <= line.Start.X) ? line.Start.X : line.End.X; 709 | else 710 | edgeClosestX = (closestPointX >= line.Start.X) ? line.Start.X : line.End.X; 711 | 712 | // Step 5 - Circle-point intersection on closest point 713 | var distClosestEdgeToCircleSq = new Vector2(circleCenter.X - edgeClosestX, circleCenter.Y - lineY).LengthSquared(); 714 | 715 | if (strict) 716 | return distClosestEdgeToCircleSq < circle.Radius * circle.Radius; 717 | else 718 | return distClosestEdgeToCircleSq <= circle.Radius * circle.Radius; 719 | } 720 | 721 | /// 722 | /// Determines if the circle at the specified position intersects the line, which 723 | /// is at its true position and rotation, when the line is assumed to be vertical 724 | /// 725 | /// The circle 726 | /// The line 727 | /// The center of the circle 728 | /// If overlap is required for intersection 729 | /// If the circle with center circleCenter intersects the line 730 | protected static bool CircleIntersectsVerticalLine(Circle2 circle, Line2 line, Vector2 circleCenter, bool strict) 731 | { 732 | // Same process as horizontal, but axis flipped 733 | var lineX = line.Start.X; 734 | // Step 1 - Find closest distance 735 | var vecCircleCenterToLine1D = lineX - circleCenter.X; 736 | var closestDistance = Math.Abs(vecCircleCenterToLine1D); 737 | 738 | // Step 1a 739 | if (strict) 740 | { 741 | if (closestDistance >= circle.Radius) 742 | return false; 743 | } 744 | else 745 | { 746 | if (closestDistance > circle.Radius) 747 | return false; 748 | } 749 | 750 | // Step 2 - Find closest point 751 | var closestPointY = circleCenter.Y; 752 | 753 | // Step 3 - Is closest point on line 754 | if (AxisAlignedLine2.Contains(line.Start.Y, line.End.Y, closestPointY, false, true)) 755 | return true; 756 | 757 | // Step 4 - Find edgeClosest 758 | float edgeClosestY; 759 | if (line.Start.Y < line.End.Y) 760 | edgeClosestY = (closestPointY <= line.Start.Y) ? line.Start.Y : line.End.Y; 761 | else 762 | edgeClosestY = (closestPointY >= line.Start.Y) ? line.Start.Y : line.End.Y; 763 | 764 | // Step 5 - Circle-point intersection on closest point 765 | var distClosestEdgeToCircleSq = new Vector2(circleCenter.X - lineX, circleCenter.Y - edgeClosestY).LengthSquared(); 766 | 767 | if (strict) 768 | return distClosestEdgeToCircleSq < circle.Radius * circle.Radius; 769 | else 770 | return distClosestEdgeToCircleSq <= circle.Radius * circle.Radius; 771 | } 772 | #region NoRotation 773 | /// 774 | /// Determines if the specified polygon at pos1 with no rotation and rectangle at pos2 intersect 775 | /// 776 | /// Polygon to check 777 | /// Rectangle to check 778 | /// Origin of polygon 779 | /// Origin of rect 780 | /// If overlap is required for intersection 781 | /// If poly at pos1 intersects rect at pos2 782 | public static bool Intersects(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2, bool strict) 783 | { 784 | return Intersects(poly, rect, pos1, pos2, Rotation2.Zero, strict); 785 | } 786 | 787 | /// 788 | /// Determines if the specified rectangle at pos1 intersects the specified polygon at pos2 with 789 | /// no rotation. 790 | /// 791 | /// The rectangle 792 | /// The polygon 793 | /// Origin of rectangle 794 | /// Origin of polygon 795 | /// If overlap is required for intersection 796 | /// If rect at pos1 no rotation intersects poly at pos2 797 | public static bool Intersects(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2, bool strict) 798 | { 799 | return Intersects(rect, poly, pos1, pos2, Rotation2.Zero, strict); 800 | } 801 | 802 | /// 803 | /// Determines if the specified polygon at pos1 with no rotation intersects the specified 804 | /// 805 | /// 806 | /// 807 | /// 808 | /// 809 | /// 810 | /// 811 | public static Tuple IntersectMTV(Polygon2 poly, Rect2 rect, Vector2 pos1, Vector2 pos2) 812 | { 813 | return IntersectMTV(poly, rect, pos1, pos2, Rotation2.Zero); 814 | } 815 | 816 | /// 817 | /// Determines the minimum translation vector to be applied to the rect to prevent 818 | /// intersection with the specified polygon, when they are at the given positions. 819 | /// 820 | /// The rect 821 | /// The polygon 822 | /// The origin of the rect 823 | /// The origin of the polygon 824 | /// MTV to move rect at pos1 to prevent overlap with poly at pos2 825 | public static Tuple IntersectMTV(Rect2 rect, Polygon2 poly, Vector2 pos1, Vector2 pos2) 826 | { 827 | return IntersectMTV(rect, poly, pos1, pos2, Rotation2.Zero); 828 | } 829 | 830 | /// 831 | /// Determines if the polygon and circle intersect when at the given positions. 832 | /// 833 | /// The polygon 834 | /// The circle 835 | /// The origin of the polygon 836 | /// The top-left of the circles bounding box 837 | /// If overlap is required for intersection 838 | /// If poly at pos1 intersects circle at pos2 839 | public static bool Intersects(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2, bool strict) 840 | { 841 | return Intersects(poly, circle, pos1, pos2, Rotation2.Zero, strict); 842 | } 843 | 844 | /// 845 | /// Determines if the circle and polygon intersect when at the given positions. 846 | /// 847 | /// The circle 848 | /// The polygon 849 | /// The top-left of the circles bounding box 850 | /// The origin of the polygon 851 | /// If overlap is required for intersection 852 | /// If circle at pos1 intersects poly at pos2 853 | public static bool Intersects(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2, bool strict) 854 | { 855 | return Intersects(circle, poly, pos1, pos2, Rotation2.Zero, strict); 856 | } 857 | 858 | /// 859 | /// Determines the minimum translation vector the be applied to the polygon to prevent 860 | /// intersection with the specified circle, when they are at the given positions. 861 | /// 862 | /// The polygon 863 | /// The circle 864 | /// The position of the polygon 865 | /// The top-left of the circles bounding box 866 | /// MTV to move poly at pos1 to prevent overlap with circle at pos2 867 | public static Tuple IntersectMTV(Polygon2 poly, Circle2 circle, Vector2 pos1, Vector2 pos2) 868 | { 869 | return IntersectMTV(poly, circle, pos1, pos2, Rotation2.Zero); 870 | } 871 | 872 | /// 873 | /// Determines the minimum translation vector to be applied to the circle to prevent 874 | /// intersection with the specified polyogn, when they are at the given positions. 875 | /// 876 | /// The circle 877 | /// The polygon 878 | /// The top-left of the circles bounding box 879 | /// The origin of the polygon 880 | /// 881 | public static Tuple IntersectMTV(Circle2 circle, Polygon2 poly, Vector2 pos1, Vector2 pos2) 882 | { 883 | return IntersectMTV(circle, poly, pos1, pos2, Rotation2.Zero); 884 | } 885 | #endregion 886 | } 887 | } 888 | -------------------------------------------------------------------------------- /Math2/ShapeUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Xna.Framework; 5 | 6 | #if !NOT_MONOGAME 7 | using Microsoft.Xna.Framework.Graphics; 8 | #endif 9 | 10 | namespace SharpMath2 11 | { 12 | /// 13 | /// A class containing utilities that help creating shapes. 14 | /// 15 | public class ShapeUtils 16 | { 17 | /// 18 | /// A dictionary containing the circle shapes. 19 | /// 20 | private static Dictionary, Polygon2> CircleCache = new Dictionary, Polygon2>(); 21 | 22 | /// 23 | /// A dictionary containing the rectangle shapes. 24 | /// 25 | private static Dictionary, Polygon2> RectangleCache = new Dictionary, Polygon2>(); 26 | 27 | /// 28 | /// A dictionary containing the convex polygon shapes. 29 | /// 30 | private static Dictionary ConvexPolygonCache = new Dictionary(); 31 | 32 | #if !NOT_MONOGAME 33 | /// 34 | /// Fetches the convex polygon (the smallest possible polygon containing all the non-transparent pixels) of the given texture. 35 | /// 36 | /// The texture. 37 | public static Polygon2 CreateConvexPolygon(Texture2D Texture) 38 | { 39 | var Key = Texture.GetHashCode(); 40 | 41 | if (ConvexPolygonCache.ContainsKey(Key)) 42 | return ConvexPolygonCache[Key]; 43 | 44 | var uints = new uint[Texture.Width * Texture.Height]; 45 | Texture.GetData(uints); 46 | 47 | var Points = new List(); 48 | 49 | for (var i = 0; i < Texture.Width; i++) 50 | for (var j = 0; j < Texture.Height; j++) 51 | if (uints[j * Texture.Width + i] != 0) 52 | Points.Add(new Vector2(i, j)); 53 | 54 | if (Points.Count <= 2) 55 | throw new Exception("Can not create a convex hull from a line."); 56 | 57 | int n = Points.Count, k = 0; 58 | var h = new List( 59 | new Vector2[2 * n] 60 | ); 61 | 62 | Points.Sort( 63 | (a, b) => 64 | a.X == b.X ? 65 | a.Y.CompareTo(b.Y) 66 | : (a.X > b.X ? 1 : -1) 67 | ); 68 | 69 | for (var i = 0; i < n; ++i) 70 | { 71 | while (k >= 2 && cross(h[k - 2], h[k - 1], Points[i]) <= 0) 72 | k--; 73 | h[k++] = Points[i]; 74 | } 75 | 76 | for (int i = n - 2, t = k + 1; i >= 0; i--) 77 | { 78 | while (k >= t && cross(h[k - 2], h[k - 1], Points[i]) <= 0) 79 | k--; 80 | h[k++] = Points[i]; 81 | } 82 | 83 | Points = h.Take(k - 1).ToList(); 84 | return ConvexPolygonCache[Key] = new Polygon2(Points.ToArray()); 85 | } 86 | #endif 87 | 88 | /// 89 | /// Returns the cross product of the given three vectors. 90 | /// 91 | /// Vector 1. 92 | /// Vector 2. 93 | /// Vector 3. 94 | /// 95 | private static double cross(Vector2 v1, Vector2 v2, Vector2 v3) 96 | { 97 | return (v2.X - v1.X) * (v3.Y - v1.Y) - (v2.Y - v1.Y) * (v3.X - v1.X); 98 | } 99 | 100 | /// 101 | /// Fetches a rectangle shape with the given width, height, x and y center. 102 | /// 103 | /// The width of the rectangle. 104 | /// The height of the rectangle. 105 | /// The X center of the rectangle. 106 | /// The Y center of the rectangle. 107 | /// A rectangle shape with the given width, height, x and y center. 108 | public static Polygon2 CreateRectangle(float width, float height, float x = 0, float y = 0) 109 | { 110 | var Key = new Tuple(width, height, x, y); 111 | 112 | if (RectangleCache.ContainsKey(Key)) 113 | return RectangleCache[Key]; 114 | 115 | return RectangleCache[Key] = new Polygon2(new[] { 116 | new Vector2(x, y), 117 | new Vector2(x + width, y), 118 | new Vector2(x + width, y + height), 119 | new Vector2(x, y + height) 120 | }); 121 | } 122 | 123 | /// 124 | /// Fetches a circle shape with the given radius, center, and segments. Because of the discretization 125 | /// of the circle, it is not possible to perfectly get the AABB to match both the radius and the position. 126 | /// This will match the position. 127 | /// 128 | /// The radius of the circle. 129 | /// The X center of the circle. 130 | /// The Y center of the circle. 131 | /// The amount of segments (more segments equals higher detailed circle) 132 | /// A circle with the given radius, center, and segments, as a polygon2 shape. 133 | public static Polygon2 CreateCircle(float radius, float x = 0, float y = 0, int segments = 32) 134 | { 135 | var Key = new Tuple(radius, x, y, segments); 136 | 137 | if (CircleCache.ContainsKey(Key)) 138 | return CircleCache[Key]; 139 | 140 | var Center = new Vector2(radius + x, radius + y); 141 | var increment = (Math.PI * 2.0) / segments; 142 | var theta = 0.0; 143 | var verts = new List(segments); 144 | 145 | Vector2 correction = new Vector2(radius, radius); 146 | for (var i = 0; i < segments; i++) 147 | { 148 | Vector2 vert = radius * new Vector2( 149 | (float)Math.Cos(theta), 150 | (float)Math.Sin(theta) 151 | ); 152 | 153 | if (vert.X < correction.X) 154 | correction.X = vert.X; 155 | if (vert.Y < correction.Y) 156 | correction.Y = vert.Y; 157 | 158 | verts.Add( 159 | Center + vert 160 | ); 161 | theta += increment; 162 | } 163 | 164 | correction.X += radius; 165 | correction.Y += radius; 166 | 167 | for(var i = 0; i < segments; i++) 168 | { 169 | verts[i] -= correction; 170 | } 171 | 172 | return CircleCache[Key] = new Polygon2(verts.ToArray()); 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /Math2/Triangle2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Xna.Framework; 5 | 6 | namespace SharpMath2 7 | { 8 | /// 9 | /// Describes a triangle, which is a collection of three points. This is 10 | /// used for the implementation of the Polygon2. 11 | /// 12 | public class Triangle2 13 | { 14 | /// 15 | /// The 3 vertices of this triangle. 16 | /// 17 | public Vector2[] Vertices; 18 | 19 | /// 20 | /// This is used to determine if points are inside the triangle. 21 | /// This has 4 values where the first 2 correspond to row 1 and 22 | /// the second 2 to row 2 of a 2x2 matrix. When that matrix is 23 | /// matrix-multiplied by a point, if the result has a sum less 24 | /// than 1 and each component is positive, the point is in the 25 | /// triangle. 26 | /// 27 | private float[] InvContainsBasis; 28 | 29 | /// 30 | /// The centroid of the triangle 31 | /// 32 | public readonly Vector2 Center; 33 | 34 | /// 35 | /// The edges of the triangle, where the first edge is from 36 | /// Vertices[0] to Vertices[1], etc. 37 | /// 38 | public readonly Line2[] Edges; 39 | 40 | /// 41 | /// The area of the triangle. 42 | /// 43 | public readonly float Area; 44 | 45 | /// 46 | /// Constructs a triangle with the given vertices, assuming that 47 | /// the vertices define a triangle (i.e., are not collinear) 48 | /// 49 | /// The vertices of the triangle 50 | public Triangle2(Vector2[] vertices) 51 | { 52 | Vertices = vertices; 53 | 54 | Vector2 vertSum = Vector2.Zero; 55 | for(int i = 0; i < 3; i++) 56 | { 57 | vertSum += vertices[i]; 58 | } 59 | 60 | Center = vertSum / 3.0f; 61 | float a = vertices[1].X - vertices[0].X; 62 | float b = vertices[2].X - vertices[0].X; 63 | float c = vertices[1].Y - vertices[0].Y; 64 | float d = vertices[2].Y - vertices[0].Y; 65 | 66 | float det = a * d - b * c; 67 | Area = Math.Abs(0.5f * det); 68 | 69 | float invDet = 1 / det; 70 | InvContainsBasis = new float[4] 71 | { 72 | invDet * d, -invDet * b, 73 | -invDet * c, invDet * a 74 | }; 75 | 76 | Edges = new Line2[] 77 | { 78 | new Line2(Vertices[0], Vertices[1]), 79 | new Line2(Vertices[1], Vertices[2]), 80 | new Line2(Vertices[2], Vertices[0]) 81 | }; 82 | } 83 | 84 | /// 85 | /// Checks if this triangle contains the given point. This is 86 | /// never strict. 87 | /// 88 | /// The triangle 89 | /// The position of the triangle 90 | /// The point to check 91 | /// true if this triangle contains the point or the point 92 | /// is along an edge of this polygon 93 | public static bool Contains(Triangle2 tri, Vector2 pos, Vector2 pt) 94 | { 95 | Vector2 relPt = pt - pos - tri.Vertices[0]; 96 | float r = tri.InvContainsBasis[0] * relPt.X + tri.InvContainsBasis[1] * relPt.Y; 97 | if (r < -Math2.DEFAULT_EPSILON) 98 | return false; 99 | 100 | float t = tri.InvContainsBasis[2] * relPt.X + tri.InvContainsBasis[3] * relPt.Y; 101 | if (t < -Math2.DEFAULT_EPSILON) 102 | return false; 103 | 104 | return (r + t) < 1 + Math2.DEFAULT_EPSILON; 105 | } 106 | 107 | /// 108 | /// An optimized check to determine if a triangle made up of the given 109 | /// points strictly contains the origin. This is generally slower than reusing 110 | /// a triangle, but much faster than creating a triangle and then doing 111 | /// a single contains check. There are aspects of the constructor which 112 | /// do not speed up the Contains check, which this skips. 113 | /// 114 | /// The 3 points making up the triangle 115 | /// True if the given triangle contains the origin, false otherwise 116 | public static bool ContainsOrigin(Vector2[] vertices) 117 | { 118 | float a = vertices[1].X - vertices[0].X; 119 | float b = vertices[2].X - vertices[0].X; 120 | float c = vertices[1].Y - vertices[0].Y; 121 | float d = vertices[2].Y - vertices[0].Y; 122 | float det = a * d - b * c; 123 | float invDet = 1 / det; 124 | /*{ 125 | invDet * d, -invDet * b, 126 | -invDet * c, invDet * a 127 | };*/ 128 | 129 | // relPt = -vertices[0] 130 | float r = (invDet * d) * (-(vertices[0].X)) + (-invDet * b) * (-(vertices[0].Y)); 131 | if (r < -Math2.DEFAULT_EPSILON) 132 | return false; 133 | 134 | float t = (-invDet * c) * (-(vertices[0].X)) + (invDet * a) * (-(vertices[0].Y)); 135 | if (t < -Math2.DEFAULT_EPSILON) 136 | return false; 137 | 138 | return (r + t) < 1 + Math2.DEFAULT_EPSILON; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Microsoft/Xna/Framework/Point.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | /* 3 | MIT License 4 | Copyright © 2006 The Mono.Xna Team 5 | 6 | All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | #endregion License 27 | using System; 28 | 29 | #if NOT_MONOGAME 30 | namespace Microsoft.Xna.Framework 31 | { 32 | public struct Point : IEquatable 33 | { 34 | #region Private Fields 35 | 36 | private static Point zeroPoint = new Point(); 37 | 38 | #endregion Private Fields 39 | 40 | 41 | #region Public Fields 42 | 43 | public int X; 44 | public int Y; 45 | 46 | #endregion Public Fields 47 | 48 | 49 | #region Properties 50 | 51 | public static Point Zero 52 | { 53 | get { return zeroPoint; } 54 | } 55 | 56 | #endregion Properties 57 | 58 | 59 | #region Constructors 60 | 61 | public Point(int x, int y) 62 | { 63 | this.X = x; 64 | this.Y = y; 65 | } 66 | 67 | #endregion Constructors 68 | 69 | 70 | #region Public methods 71 | 72 | public static bool operator ==(Point a, Point b) 73 | { 74 | return a.Equals(b); 75 | } 76 | 77 | public static bool operator !=(Point a, Point b) 78 | { 79 | return !a.Equals(b); 80 | } 81 | 82 | public bool Equals(Point other) 83 | { 84 | return ((X == other.X) && (Y == other.Y)); 85 | } 86 | 87 | public override bool Equals(object obj) 88 | { 89 | return (obj is Point) ? Equals((Point)obj) : false; 90 | } 91 | 92 | public override int GetHashCode() 93 | { 94 | return X ^ Y; 95 | } 96 | 97 | public override string ToString() 98 | { 99 | return string.Format("{{X:{0} Y:{1}}}", X, Y); 100 | } 101 | 102 | #endregion 103 | } 104 | } 105 | #endif -------------------------------------------------------------------------------- /Microsoft/Xna/Framework/Vector2.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | /* 3 | MIT License 4 | Copyright © 2006 The Mono.Xna Team 5 | 6 | All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | #endregion License 27 | // Modified to not require outside libraries 28 | 29 | #if NOT_MONOGAME 30 | using System; 31 | using System.Diagnostics; 32 | using System.Runtime.Serialization; 33 | 34 | namespace Microsoft.Xna.Framework 35 | { 36 | /// 37 | /// Describes a 2D-vector. 38 | /// 39 | #if XNADESIGNPROVIDED 40 | [System.ComponentModel.TypeConverter(typeof(Microsoft.Xna.Framework.Design.Vector2TypeConverter))] 41 | #endif 42 | [DebuggerDisplay("{DebugDisplayString,nq}")] 43 | public struct Vector2 : IEquatable 44 | { 45 | #region Private Fields 46 | 47 | private static readonly Vector2 zeroVector = new Vector2(0f, 0f); 48 | private static readonly Vector2 unitVector = new Vector2(1f, 1f); 49 | private static readonly Vector2 unitXVector = new Vector2(1f, 0f); 50 | private static readonly Vector2 unitYVector = new Vector2(0f, 1f); 51 | 52 | #endregion 53 | 54 | #region Public Fields 55 | 56 | /// 57 | /// The x coordinate of this . 58 | /// 59 | public float X; 60 | 61 | /// 62 | /// The y coordinate of this . 63 | /// 64 | public float Y; 65 | 66 | #endregion 67 | 68 | #region Properties 69 | 70 | /// 71 | /// Returns a with components 0, 0. 72 | /// 73 | public static Vector2 Zero 74 | { 75 | get { return zeroVector; } 76 | } 77 | 78 | /// 79 | /// Returns a with components 1, 1. 80 | /// 81 | public static Vector2 One 82 | { 83 | get { return unitVector; } 84 | } 85 | 86 | /// 87 | /// Returns a with components 1, 0. 88 | /// 89 | public static Vector2 UnitX 90 | { 91 | get { return unitXVector; } 92 | } 93 | 94 | /// 95 | /// Returns a with components 0, 1. 96 | /// 97 | public static Vector2 UnitY 98 | { 99 | get { return unitYVector; } 100 | } 101 | 102 | #endregion 103 | 104 | #region Internal Properties 105 | 106 | internal string DebugDisplayString 107 | { 108 | get 109 | { 110 | return string.Concat( 111 | this.X.ToString(), " ", 112 | this.Y.ToString() 113 | ); 114 | } 115 | } 116 | 117 | #endregion 118 | 119 | #region Constructors 120 | 121 | /// 122 | /// Constructs a 2d vector with X and Y from two values. 123 | /// 124 | /// The x coordinate in 2d-space. 125 | /// The y coordinate in 2d-space. 126 | public Vector2(float x, float y) 127 | { 128 | this.X = x; 129 | this.Y = y; 130 | } 131 | 132 | /// 133 | /// Constructs a 2d vector with X and Y set to the same value. 134 | /// 135 | /// The x and y coordinates in 2d-space. 136 | public Vector2(float value) 137 | { 138 | this.X = value; 139 | this.Y = value; 140 | } 141 | 142 | #endregion 143 | 144 | #region Operators 145 | 146 | /// 147 | /// Inverts values in the specified . 148 | /// 149 | /// Source on the right of the sub sign. 150 | /// Result of the inversion. 151 | public static Vector2 operator -(Vector2 value) 152 | { 153 | value.X = -value.X; 154 | value.Y = -value.Y; 155 | return value; 156 | } 157 | 158 | /// 159 | /// Adds two vectors. 160 | /// 161 | /// Source on the left of the add sign. 162 | /// Source on the right of the add sign. 163 | /// Sum of the vectors. 164 | public static Vector2 operator +(Vector2 value1, Vector2 value2) 165 | { 166 | value1.X += value2.X; 167 | value1.Y += value2.Y; 168 | return value1; 169 | } 170 | 171 | /// 172 | /// Subtracts a from a . 173 | /// 174 | /// Source on the left of the sub sign. 175 | /// Source on the right of the sub sign. 176 | /// Result of the vector subtraction. 177 | public static Vector2 operator -(Vector2 value1, Vector2 value2) 178 | { 179 | value1.X -= value2.X; 180 | value1.Y -= value2.Y; 181 | return value1; 182 | } 183 | 184 | /// 185 | /// Multiplies the components of two vectors by each other. 186 | /// 187 | /// Source on the left of the mul sign. 188 | /// Source on the right of the mul sign. 189 | /// Result of the vector multiplication. 190 | public static Vector2 operator *(Vector2 value1, Vector2 value2) 191 | { 192 | value1.X *= value2.X; 193 | value1.Y *= value2.Y; 194 | return value1; 195 | } 196 | 197 | /// 198 | /// Multiplies the components of vector by a scalar. 199 | /// 200 | /// Source on the left of the mul sign. 201 | /// Scalar value on the right of the mul sign. 202 | /// Result of the vector multiplication with a scalar. 203 | public static Vector2 operator *(Vector2 value, float scaleFactor) 204 | { 205 | value.X *= scaleFactor; 206 | value.Y *= scaleFactor; 207 | return value; 208 | } 209 | 210 | /// 211 | /// Multiplies the components of vector by a scalar. 212 | /// 213 | /// Scalar value on the left of the mul sign. 214 | /// Source on the right of the mul sign. 215 | /// Result of the vector multiplication with a scalar. 216 | public static Vector2 operator *(float scaleFactor, Vector2 value) 217 | { 218 | value.X *= scaleFactor; 219 | value.Y *= scaleFactor; 220 | return value; 221 | } 222 | 223 | /// 224 | /// Divides the components of a by the components of another . 225 | /// 226 | /// Source on the left of the div sign. 227 | /// Divisor on the right of the div sign. 228 | /// The result of dividing the vectors. 229 | public static Vector2 operator /(Vector2 value1, Vector2 value2) 230 | { 231 | value1.X /= value2.X; 232 | value1.Y /= value2.Y; 233 | return value1; 234 | } 235 | 236 | /// 237 | /// Divides the components of a by a scalar. 238 | /// 239 | /// Source on the left of the div sign. 240 | /// Divisor scalar on the right of the div sign. 241 | /// The result of dividing a vector by a scalar. 242 | public static Vector2 operator /(Vector2 value1, float divider) 243 | { 244 | float factor = 1 / divider; 245 | value1.X *= factor; 246 | value1.Y *= factor; 247 | return value1; 248 | } 249 | 250 | /// 251 | /// Compares whether two instances are equal. 252 | /// 253 | /// instance on the left of the equal sign. 254 | /// instance on the right of the equal sign. 255 | /// true if the instances are equal; false otherwise. 256 | public static bool operator ==(Vector2 value1, Vector2 value2) 257 | { 258 | return value1.X == value2.X && value1.Y == value2.Y; 259 | } 260 | 261 | /// 262 | /// Compares whether two instances are not equal. 263 | /// 264 | /// instance on the left of the not equal sign. 265 | /// instance on the right of the not equal sign. 266 | /// true if the instances are not equal; false otherwise. 267 | public static bool operator !=(Vector2 value1, Vector2 value2) 268 | { 269 | return value1.X != value2.X || value1.Y != value2.Y; 270 | } 271 | 272 | #endregion 273 | 274 | #region Public Methods 275 | 276 | /// 277 | /// Performs vector addition on and . 278 | /// 279 | /// The first vector to add. 280 | /// The second vector to add. 281 | /// The result of the vector addition. 282 | public static Vector2 Add(Vector2 value1, Vector2 value2) 283 | { 284 | value1.X += value2.X; 285 | value1.Y += value2.Y; 286 | return value1; 287 | } 288 | 289 | /// 290 | /// Performs vector addition on and 291 | /// , storing the result of the 292 | /// addition in . 293 | /// 294 | /// The first vector to add. 295 | /// The second vector to add. 296 | /// The result of the vector addition. 297 | public static void Add(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 298 | { 299 | result.X = value1.X + value2.X; 300 | result.Y = value1.Y + value2.Y; 301 | } 302 | 303 | /// 304 | /// Returns the distance between two vectors. 305 | /// 306 | /// The first vector. 307 | /// The second vector. 308 | /// The distance between two vectors. 309 | public static float Distance(Vector2 value1, Vector2 value2) 310 | { 311 | float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y; 312 | return (float)Math.Sqrt((v1 * v1) + (v2 * v2)); 313 | } 314 | 315 | /// 316 | /// Returns the distance between two vectors. 317 | /// 318 | /// The first vector. 319 | /// The second vector. 320 | /// The distance between two vectors as an output parameter. 321 | public static void Distance(ref Vector2 value1, ref Vector2 value2, out float result) 322 | { 323 | float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y; 324 | result = (float)Math.Sqrt((v1 * v1) + (v2 * v2)); 325 | } 326 | 327 | /// 328 | /// Returns the squared distance between two vectors. 329 | /// 330 | /// The first vector. 331 | /// The second vector. 332 | /// The squared distance between two vectors. 333 | public static float DistanceSquared(Vector2 value1, Vector2 value2) 334 | { 335 | float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y; 336 | return (v1 * v1) + (v2 * v2); 337 | } 338 | 339 | /// 340 | /// Returns the squared distance between two vectors. 341 | /// 342 | /// The first vector. 343 | /// The second vector. 344 | /// The squared distance between two vectors as an output parameter. 345 | public static void DistanceSquared(ref Vector2 value1, ref Vector2 value2, out float result) 346 | { 347 | float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y; 348 | result = (v1 * v1) + (v2 * v2); 349 | } 350 | 351 | /// 352 | /// Divides the components of a by the components of another . 353 | /// 354 | /// Source . 355 | /// Divisor . 356 | /// The result of dividing the vectors. 357 | public static Vector2 Divide(Vector2 value1, Vector2 value2) 358 | { 359 | value1.X /= value2.X; 360 | value1.Y /= value2.Y; 361 | return value1; 362 | } 363 | 364 | /// 365 | /// Divides the components of a by the components of another . 366 | /// 367 | /// Source . 368 | /// Divisor . 369 | /// The result of dividing the vectors as an output parameter. 370 | public static void Divide(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 371 | { 372 | result.X = value1.X / value2.X; 373 | result.Y = value1.Y / value2.Y; 374 | } 375 | 376 | /// 377 | /// Divides the components of a by a scalar. 378 | /// 379 | /// Source . 380 | /// Divisor scalar. 381 | /// The result of dividing a vector by a scalar. 382 | public static Vector2 Divide(Vector2 value1, float divider) 383 | { 384 | float factor = 1 / divider; 385 | value1.X *= factor; 386 | value1.Y *= factor; 387 | return value1; 388 | } 389 | 390 | /// 391 | /// Divides the components of a by a scalar. 392 | /// 393 | /// Source . 394 | /// Divisor scalar. 395 | /// The result of dividing a vector by a scalar as an output parameter. 396 | public static void Divide(ref Vector2 value1, float divider, out Vector2 result) 397 | { 398 | float factor = 1 / divider; 399 | result.X = value1.X * factor; 400 | result.Y = value1.Y * factor; 401 | } 402 | 403 | /// 404 | /// Returns a dot product of two vectors. 405 | /// 406 | /// The first vector. 407 | /// The second vector. 408 | /// The dot product of two vectors. 409 | public static float Dot(Vector2 value1, Vector2 value2) 410 | { 411 | return (value1.X * value2.X) + (value1.Y * value2.Y); 412 | } 413 | 414 | /// 415 | /// Returns a dot product of two vectors. 416 | /// 417 | /// The first vector. 418 | /// The second vector. 419 | /// The dot product of two vectors as an output parameter. 420 | public static void Dot(ref Vector2 value1, ref Vector2 value2, out float result) 421 | { 422 | result = (value1.X * value2.X) + (value1.Y * value2.Y); 423 | } 424 | 425 | /// 426 | /// Compares whether current instance is equal to specified . 427 | /// 428 | /// The to compare. 429 | /// true if the instances are equal; false otherwise. 430 | public override bool Equals(object obj) 431 | { 432 | if (obj is Vector2) 433 | { 434 | return Equals((Vector2)obj); 435 | } 436 | 437 | return false; 438 | } 439 | 440 | /// 441 | /// Compares whether current instance is equal to specified . 442 | /// 443 | /// The to compare. 444 | /// true if the instances are equal; false otherwise. 445 | public bool Equals(Vector2 other) 446 | { 447 | return (X == other.X) && (Y == other.Y); 448 | } 449 | 450 | /// 451 | /// Gets the hash code of this . 452 | /// 453 | /// Hash code of this . 454 | public override int GetHashCode() 455 | { 456 | unchecked 457 | { 458 | return (X.GetHashCode() * 397) ^ Y.GetHashCode(); 459 | } 460 | } 461 | 462 | /// 463 | /// Returns the length of this . 464 | /// 465 | /// The length of this . 466 | public float Length() 467 | { 468 | return (float)Math.Sqrt((X * X) + (Y * Y)); 469 | } 470 | 471 | /// 472 | /// Returns the squared length of this . 473 | /// 474 | /// The squared length of this . 475 | public float LengthSquared() 476 | { 477 | return (X * X) + (Y * Y); 478 | } 479 | 480 | /// 481 | /// Creates a new that contains a maximal values from the two vectors. 482 | /// 483 | /// The first vector. 484 | /// The second vector. 485 | /// The with maximal values from the two vectors. 486 | public static Vector2 Max(Vector2 value1, Vector2 value2) 487 | { 488 | return new Vector2(value1.X > value2.X ? value1.X : value2.X, 489 | value1.Y > value2.Y ? value1.Y : value2.Y); 490 | } 491 | 492 | /// 493 | /// Creates a new that contains a maximal values from the two vectors. 494 | /// 495 | /// The first vector. 496 | /// The second vector. 497 | /// The with maximal values from the two vectors as an output parameter. 498 | public static void Max(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 499 | { 500 | result.X = value1.X > value2.X ? value1.X : value2.X; 501 | result.Y = value1.Y > value2.Y ? value1.Y : value2.Y; 502 | } 503 | 504 | /// 505 | /// Creates a new that contains a minimal values from the two vectors. 506 | /// 507 | /// The first vector. 508 | /// The second vector. 509 | /// The with minimal values from the two vectors. 510 | public static Vector2 Min(Vector2 value1, Vector2 value2) 511 | { 512 | return new Vector2(value1.X < value2.X ? value1.X : value2.X, 513 | value1.Y < value2.Y ? value1.Y : value2.Y); 514 | } 515 | 516 | /// 517 | /// Creates a new that contains a minimal values from the two vectors. 518 | /// 519 | /// The first vector. 520 | /// The second vector. 521 | /// The with minimal values from the two vectors as an output parameter. 522 | public static void Min(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 523 | { 524 | result.X = value1.X < value2.X ? value1.X : value2.X; 525 | result.Y = value1.Y < value2.Y ? value1.Y : value2.Y; 526 | } 527 | 528 | /// 529 | /// Creates a new that contains a multiplication of two vectors. 530 | /// 531 | /// Source . 532 | /// Source . 533 | /// The result of the vector multiplication. 534 | public static Vector2 Multiply(Vector2 value1, Vector2 value2) 535 | { 536 | value1.X *= value2.X; 537 | value1.Y *= value2.Y; 538 | return value1; 539 | } 540 | 541 | /// 542 | /// Creates a new that contains a multiplication of two vectors. 543 | /// 544 | /// Source . 545 | /// Source . 546 | /// The result of the vector multiplication as an output parameter. 547 | public static void Multiply(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 548 | { 549 | result.X = value1.X * value2.X; 550 | result.Y = value1.Y * value2.Y; 551 | } 552 | 553 | /// 554 | /// Creates a new that contains a multiplication of and a scalar. 555 | /// 556 | /// Source . 557 | /// Scalar value. 558 | /// The result of the vector multiplication with a scalar. 559 | public static Vector2 Multiply(Vector2 value1, float scaleFactor) 560 | { 561 | value1.X *= scaleFactor; 562 | value1.Y *= scaleFactor; 563 | return value1; 564 | } 565 | 566 | /// 567 | /// Creates a new that contains a multiplication of and a scalar. 568 | /// 569 | /// Source . 570 | /// Scalar value. 571 | /// The result of the multiplication with a scalar as an output parameter. 572 | public static void Multiply(ref Vector2 value1, float scaleFactor, out Vector2 result) 573 | { 574 | result.X = value1.X * scaleFactor; 575 | result.Y = value1.Y * scaleFactor; 576 | } 577 | 578 | /// 579 | /// Creates a new that contains the specified vector inversion. 580 | /// 581 | /// Source . 582 | /// The result of the vector inversion. 583 | public static Vector2 Negate(Vector2 value) 584 | { 585 | value.X = -value.X; 586 | value.Y = -value.Y; 587 | return value; 588 | } 589 | 590 | /// 591 | /// Creates a new that contains the specified vector inversion. 592 | /// 593 | /// Source . 594 | /// The result of the vector inversion as an output parameter. 595 | public static void Negate(ref Vector2 value, out Vector2 result) 596 | { 597 | result.X = -value.X; 598 | result.Y = -value.Y; 599 | } 600 | 601 | /// 602 | /// Turns this to a unit vector with the same direction. 603 | /// 604 | public void Normalize() 605 | { 606 | float val = 1.0f / (float)Math.Sqrt((X * X) + (Y * Y)); 607 | X *= val; 608 | Y *= val; 609 | } 610 | 611 | /// 612 | /// Creates a new that contains a normalized values from another vector. 613 | /// 614 | /// Source . 615 | /// Unit vector. 616 | public static Vector2 Normalize(Vector2 value) 617 | { 618 | float val = 1.0f / (float)Math.Sqrt((value.X * value.X) + (value.Y * value.Y)); 619 | value.X *= val; 620 | value.Y *= val; 621 | return value; 622 | } 623 | 624 | /// 625 | /// Creates a new that contains a normalized values from another vector. 626 | /// 627 | /// Source . 628 | /// Unit vector as an output parameter. 629 | public static void Normalize(ref Vector2 value, out Vector2 result) 630 | { 631 | float val = 1.0f / (float)Math.Sqrt((value.X * value.X) + (value.Y * value.Y)); 632 | result.X = value.X * val; 633 | result.Y = value.Y * val; 634 | } 635 | 636 | /// 637 | /// Creates a new that contains reflect vector of the given vector and normal. 638 | /// 639 | /// Source . 640 | /// Reflection normal. 641 | /// Reflected vector. 642 | public static Vector2 Reflect(Vector2 vector, Vector2 normal) 643 | { 644 | Vector2 result; 645 | float val = 2.0f * ((vector.X * normal.X) + (vector.Y * normal.Y)); 646 | result.X = vector.X - (normal.X * val); 647 | result.Y = vector.Y - (normal.Y * val); 648 | return result; 649 | } 650 | 651 | /// 652 | /// Creates a new that contains reflect vector of the given vector and normal. 653 | /// 654 | /// Source . 655 | /// Reflection normal. 656 | /// Reflected vector as an output parameter. 657 | public static void Reflect(ref Vector2 vector, ref Vector2 normal, out Vector2 result) 658 | { 659 | float val = 2.0f * ((vector.X * normal.X) + (vector.Y * normal.Y)); 660 | result.X = vector.X - (normal.X * val); 661 | result.Y = vector.Y - (normal.Y * val); 662 | } 663 | 664 | /// 665 | /// Creates a new that contains subtraction of on from a another. 666 | /// 667 | /// Source . 668 | /// Source . 669 | /// The result of the vector subtraction. 670 | public static Vector2 Subtract(Vector2 value1, Vector2 value2) 671 | { 672 | value1.X -= value2.X; 673 | value1.Y -= value2.Y; 674 | return value1; 675 | } 676 | 677 | /// 678 | /// Creates a new that contains subtraction of on from a another. 679 | /// 680 | /// Source . 681 | /// Source . 682 | /// The result of the vector subtraction as an output parameter. 683 | public static void Subtract(ref Vector2 value1, ref Vector2 value2, out Vector2 result) 684 | { 685 | result.X = value1.X - value2.X; 686 | result.Y = value1.Y - value2.Y; 687 | } 688 | 689 | /// 690 | /// Returns a representation of this in the format: 691 | /// {X:[] Y:[]} 692 | /// 693 | /// A representation of this . 694 | public override string ToString() 695 | { 696 | return "{X:" + X + " Y:" + Y + "}"; 697 | } 698 | 699 | /// 700 | /// Gets a representation for this object. 701 | /// 702 | /// A representation for this object. 703 | public Point ToPoint() 704 | { 705 | return new Point((int)X, (int)Y); 706 | } 707 | 708 | #endregion 709 | } 710 | } 711 | 712 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpMath2 2 | 3 | ![banner](imgs/banner.png) 4 | 5 | This is a C# math library. It is built as the bare minimum to get up and running with a 2D game in C#. It 6 | is compatible with or without monogame. To compile with monogame, use the compiler directive "NOT_MONOGAME". 7 | This will provide versions of Microsoft.XNA.Framework.Vector2 and Microsoft.XNA.Framework.Point that do not 8 | require any external libraries (with reduced functionality). 9 | 10 | ## Examples 11 | 12 | ### Import tags 13 | 14 | ```cs 15 | using SharpMath2; 16 | using Microsoft.XNA.Framework; 17 | ``` 18 | 19 | ### Polygon construction 20 | 21 | ```cs 22 | var triangle1 = new Polygon2(new[] { new Vector2(0, 0), new Vector2(1, 1), new Vector2(2, 0) }); 23 | var triangle2 = ShapeUtils.CreateCircle(1, segments=3); // this is not the same triangle as triangle1, this will be equilateral 24 | var octogon = ShapeUtils.CreateCircle(1, segments=8); 25 | ``` 26 | 27 | ### Polygon intersection 28 | 29 | ```cs 30 | // Check intersection of two entities, both of which have the same triangle bounds but one is 31 | // rotated at rotation1 and located at position1, where the other is rotated at rotation2 and 32 | // located at position2 33 | 34 | var triangle = new Polygon2(new[] { new Vector2(0, 0), new Vector2(1, 1), new Vector2(2, 0) }); 35 | 36 | // Rotation2 caches Math.Sin and Math.Cos of the given angle, so if you know you are going to reuse 37 | // rotations often (like 0) they should be cached (Rotation2.Zero is provided) 38 | var rotation1 = Rotation2.Zero; 39 | var rotation2 = Rotation2.Zero; // new Rotation2((float)(Math.PI / 6)) would be 30degrees 40 | var position1 = new Vector2(5, 3); 41 | var position2 = new Vector2(6, 3); 42 | 43 | // Determine if the polygons overlap or touch: 44 | Polygon2.Intersects(triangle, triangle, position1, position2, rotation1, rotation2, false); // True 45 | 46 | // Determine if the polygons overlap 47 | Polygon2.Intersects(triangle, triangle, position1, position2, rotation1, rotation2, true); // False 48 | 49 | // Note that in the special case of no rotation (rotation1 == rotation2 == Rotation2.Zero) we can 50 | // use the shorter function definition by omitting the rotation parameters 51 | Polygon2.Intersects(triangle, triangle, position1, position2, true); // False 52 | ``` 53 | 54 | ### Polygon collision detection + handling 55 | 56 | ```cs 57 | // Suppose we have two entities, entity1 and entity2, both of which use the polygon "triangle" and are at position1, rotation1 and 58 | // position2, rotation2 respectively. If we are updating entity1 and we want to detect and handle collision with entity2 we would 59 | // do: 60 | 61 | // note we do not check for intersection first - while intersection is faster to check than intersection + MTV, it is not 62 | // faster to check intersection then intersection + MTV if you will need the MTV. 63 | 64 | // Note strict is not an option for MTV - if two triangles are touching but not overlapping then 65 | // it doesn't make sense to try and get an MTV 66 | Tuple mtv = Polygon2.IntersectMTV(triangle, triangle, position1, position2, rotation1, rotation2); 67 | if(mtv != null) 68 | { 69 | // The two entites are colliding. 70 | position1 += mtv.Item1 * mtv.Item2; 71 | 72 | // Polygon2.Intersects(triangle, triangle, position1, position2, rotation1, rotation2, true); -> False 73 | // Polygon2.Intersects(triangle, triangle, position1, position2, rotation1, rotation2, false); -> True 74 | } 75 | ``` 76 | 77 | ### Polygon -> AABB collision 78 | 79 | It is very common to need to check polygons against unrotated rectangles in square-grid systems. In this case 80 | there are functions in Shape2 that provide these comparisons that is slightly faster than complete polygon to 81 | polygon collision that you would get from `ShapeUtils.CreateRectangle(width, height)` rather than `new Rect(minx, miny, maxx, maxy)` 82 | 83 | ```cs 84 | var triangle = ShapeUtils.CreateCircle(1, segments=3); 85 | var tile = new Rect2(0, 0, 1, 1); // minX, minY, maxX, maxY NOT x, y, w, h. 86 | 87 | var triPos = new Vector2(3.3, 4.1); 88 | var triRot = new Rotation2((float)(Math.PI / 6)); 89 | 90 | Vector2 tmp = Vector2.Zero; // Vector2 is a struct so this is safe 91 | int xMin = (int)triPos.x; 92 | int xMax = (int)Math.Ceiling(triPos.x + triangle.LongestAxisLength); 93 | int yMin = (int)triPos.y; 94 | int yMax = (int)Math.Ceiling(triPos.y + triangle.LongestAxisLength); 95 | for(int y = yMin; y <= yMax; y++) 96 | { 97 | tmp.Y = y; 98 | for(int x = xMin; x <= xMax; x++) 99 | { 100 | tmp.X = x; 101 | var intersectsTileAtXY = Shape2.Intersects(triangle, tile, triPos, tmp, triRot, true); 102 | Console.Write($"({x},{y})={intersectsTileAtXY}") 103 | if(intersectsTileAtXY) 104 | Console.Write(" "); // true is 1 letter shorter than false 105 | else 106 | Console.Write(" "); 107 | } 108 | Console.WriteLine(); 109 | } 110 | ``` 111 | 112 | ### Polygon AABB checking 113 | 114 | Note that this is only faster for fairly complicated polygons (theoretical breakeven at 6 unique normals each). 115 | Further note that it's almost *never* faster for rotated polygons - finding the AABB for rotated polygons is not 116 | supported (though not complicated). 117 | 118 | The provided AABB is most often used in UI elements which do not anticipate rotation and can have somewhat complicated 119 | polygons but don't have rotation, which is where AABBs shine. 120 | 121 | ```cs 122 | var complicatedShape = ShapeUtils.CreateCircle(5); // radius 5, 32 segments 123 | 124 | // Note we are not providing rotation - rect2 does not support rotation 125 | // (use ShapeUtils.CreateRectangle for that, which returns a Polygon2) 126 | Rect2.Intersects(complicatedShape.AABB, complicatedShape.AABB, Vector2.Zero, new Vector2(3, 0), true); // True 127 | ```` 128 | 129 | ### Circles 130 | 131 | Circles have similiar functions to polygons. The only thing to note is that all API functions will use the top-left 132 | of the bounding box of the circle for the circles position, rather than the center of the circle. This makes switching 133 | things between circles and polygons easier in return for a very small performance cost. 134 | 135 | ```cs 136 | var circle = new Circle2(3); // The only argument is the radius of the circle. 137 | var anotherCircle = new Circle2(5); 138 | var triangle = ShapeUtils.CreateCircle(2, segments=3); 139 | 140 | // Circle -> Circle collision using the same underlying circle object 141 | Circle2.Intersects(circle, circle, Vector2.Zero, new Vector(1, 0), true); // True 142 | 143 | // Circle -> Circle collision can be done using just the radius 144 | Circle2.Intersects(3.0f, 3.0f, Vector2.Zero, new Vector2(1, 0), true); // Identical to above 145 | 146 | // Circle -> Polygon collision must pass in a circle, not the radius of the circle 147 | Shape2.Intersects(circle, triangle, Vector2.Zero, new Vector2(1, 1), true); // True 148 | 149 | // Circle -> AABB collision 150 | Shape2.Intersects(circle, triangle.AABB, Vector2.Zero, new Vector2(10, 0), true); // False 151 | ``` 152 | 153 | ## Performance notes 154 | 155 | This library is designed for when: 156 | 157 | 1. You have only a few different polygon types 158 | 2. You need to check collision on those polygon types when they are in rapidly changing positions and rotations. 159 | 160 | For example in a 2D game where everything is either a triangle or hexagon, in this library you would only need 161 | to construct two polygons, then reuse those two polygons everywhere else. This allows the library to cache certain 162 | operations. 163 | 164 | The library is designed such that changing rotations or position is fast, but the downside is when rotation 165 | or position does *not* change there is only a minor improvement in performance. -------------------------------------------------------------------------------- /SharpMath2.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | {53290E9B-7A54-4098-AB3D-E936D93BDF97} 7 | 8 | 9 | SharpMath2 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /SharpMath2.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {53290E9B-7A54-4098-AB3D-E936D93BDF97} 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /imgs/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tjstretchalot/SharpMath2/954a9a3fef542dec85e32fb5f49fff70b328adea/imgs/banner.png --------------------------------------------------------------------------------