├── .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 | 
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
--------------------------------------------------------------------------------