|
| 1 | +// This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | +// License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | +// file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 4 | +// |
| 5 | +// Geometric predicates — exact (arbitrary-precision) paths. |
| 6 | + |
| 7 | +namespace CDT.Predicates; |
| 8 | + |
| 9 | +/// <summary> |
| 10 | +/// Geometric predicates using arbitrary-precision floating-point arithmetic. |
| 11 | +/// These produce exact results regardless of degeneracy but are significantly |
| 12 | +/// slower than <see cref="PredicatesAdaptive"/>. Provided primarily for |
| 13 | +/// reference, verification, and degenerate-case testing. |
| 14 | +/// Corresponds to C++ <c>predicates::exact</c>. |
| 15 | +/// </summary> |
| 16 | +/// <remarks> |
| 17 | +/// Reference: https://www.cs.cmu.edu/~quake/robust.html |
| 18 | +/// </remarks> |
| 19 | +/// <seealso cref="PredicatesAdaptive"/> |
| 20 | +public static class PredicatesExact |
| 21 | +{ |
| 22 | + // ========================================================================= |
| 23 | + // Orient2d |
| 24 | + // ========================================================================= |
| 25 | + |
| 26 | + /// <summary> |
| 27 | + /// Exact orient2d predicate for <see cref="double"/> coordinates using |
| 28 | + /// arbitrary-precision arithmetic. Always produces a correct sign even for |
| 29 | + /// nearly-collinear points. |
| 30 | + /// Returns the determinant of {{ax-cx, ay-cy}, {bx-cx, by-cy}}. |
| 31 | + /// Positive = c is left/above the line a→b; zero = collinear; negative = right/below. |
| 32 | + /// </summary> |
| 33 | + /// <param name="ax">X-coordinate of point a.</param> |
| 34 | + /// <param name="ay">Y-coordinate of point a.</param> |
| 35 | + /// <param name="bx">X-coordinate of point b.</param> |
| 36 | + /// <param name="by">Y-coordinate of point b.</param> |
| 37 | + /// <param name="cx">X-coordinate of point c.</param> |
| 38 | + /// <param name="cy">Y-coordinate of point c.</param> |
| 39 | + /// <returns> |
| 40 | + /// A positive value if c is to the left of line a→b, |
| 41 | + /// zero if collinear, or a negative value if to the right. |
| 42 | + /// </returns> |
| 43 | + /// <seealso cref="PredicatesAdaptive.Orient2d(double, double, double, double, double, double)"/> |
| 44 | + public static double Orient2d( |
| 45 | + double ax, double ay, double bx, double by, double cx, double cy) |
| 46 | + { |
| 47 | + double acx = ax - cx, bcx = bx - cx; |
| 48 | + double acy = ay - cy, bcy = by - cy; |
| 49 | + |
| 50 | + Span<double> s1 = stackalloc double[4]; |
| 51 | + Span<double> s2 = stackalloc double[4]; |
| 52 | + Span<double> s3 = stackalloc double[4]; |
| 53 | + Span<double> b = stackalloc double[4]; |
| 54 | + |
| 55 | + int bLen = PredicatesAdaptive.TwoTwoDiff(acx, bcy, acy, bcx, b); |
| 56 | + |
| 57 | + double acxtail = PredicatesAdaptive.MinusTail(ax, cx, acx); |
| 58 | + double bcxtail = PredicatesAdaptive.MinusTail(bx, cx, bcx); |
| 59 | + double acytail = PredicatesAdaptive.MinusTail(ay, cy, acy); |
| 60 | + double bcytail = PredicatesAdaptive.MinusTail(by, cy, bcy); |
| 61 | + |
| 62 | + int s1Len = PredicatesAdaptive.TwoTwoDiff(acxtail, bcy, acytail, bcx, s1); |
| 63 | + int s2Len = PredicatesAdaptive.TwoTwoDiff(acx, bcytail, acy, bcxtail, s2); |
| 64 | + int s3Len = PredicatesAdaptive.TwoTwoDiff(acxtail, bcytail, acytail, bcxtail, s3); |
| 65 | + |
| 66 | + Span<double> t1 = stackalloc double[8]; |
| 67 | + int t1Len = PredicatesAdaptive.ExpansionSum(b, bLen, s1, s1Len, t1); |
| 68 | + Span<double> t2 = stackalloc double[12]; |
| 69 | + int t2Len = PredicatesAdaptive.ExpansionSum(t1, t1Len, s2, s2Len, t2); |
| 70 | + Span<double> d = stackalloc double[16]; |
| 71 | + int dLen = PredicatesAdaptive.ExpansionSum(t2, t2Len, s3, s3Len, d); |
| 72 | + return PredicatesAdaptive.MostSignificant(d, dLen); |
| 73 | + } |
| 74 | + |
| 75 | + /// <summary> |
| 76 | + /// Exact orient2d predicate for <see cref="float"/> coordinates using |
| 77 | + /// arbitrary-precision arithmetic. |
| 78 | + /// Returns the determinant of {{ax-cx, ay-cy}, {bx-cx, by-cy}}. |
| 79 | + /// Positive = c is left/above the line a→b; zero = collinear; negative = right/below. |
| 80 | + /// </summary> |
| 81 | + /// <param name="ax">X-coordinate of point a.</param> |
| 82 | + /// <param name="ay">Y-coordinate of point a.</param> |
| 83 | + /// <param name="bx">X-coordinate of point b.</param> |
| 84 | + /// <param name="by">Y-coordinate of point b.</param> |
| 85 | + /// <param name="cx">X-coordinate of point c.</param> |
| 86 | + /// <param name="cy">Y-coordinate of point c.</param> |
| 87 | + /// <returns> |
| 88 | + /// A positive value if c is to the left of line a→b, |
| 89 | + /// zero if collinear, or a negative value if to the right. |
| 90 | + /// </returns> |
| 91 | + /// <seealso cref="PredicatesAdaptive.Orient2d(float, float, float, float, float, float)"/> |
| 92 | + public static float Orient2d( |
| 93 | + float ax, float ay, float bx, float by, float cx, float cy) |
| 94 | + { |
| 95 | + // Each product of two 24-bit floats is exact in 53-bit double. |
| 96 | + float acx = ax - cx, bcx = bx - cx; |
| 97 | + float acy = ay - cy, bcy = by - cy; |
| 98 | + double exact = (double)acx * bcy - (double)acy * bcx; |
| 99 | + return (float)exact; |
| 100 | + } |
| 101 | + |
| 102 | + // ========================================================================= |
| 103 | + // InCircle |
| 104 | + // ========================================================================= |
| 105 | + |
| 106 | + /// <summary> |
| 107 | + /// Exact incircle predicate for <see cref="double"/> coordinates using |
| 108 | + /// arbitrary-precision arithmetic. |
| 109 | + /// Returns positive if <c>d</c> is inside the circumcircle of (a, b, c), |
| 110 | + /// zero if on, negative if outside. |
| 111 | + /// </summary> |
| 112 | + /// <param name="ax">X-coordinate of triangle vertex a.</param> |
| 113 | + /// <param name="ay">Y-coordinate of triangle vertex a.</param> |
| 114 | + /// <param name="bx">X-coordinate of triangle vertex b.</param> |
| 115 | + /// <param name="by">Y-coordinate of triangle vertex b.</param> |
| 116 | + /// <param name="cx">X-coordinate of triangle vertex c.</param> |
| 117 | + /// <param name="cy">Y-coordinate of triangle vertex c.</param> |
| 118 | + /// <param name="dx">X-coordinate of query point d.</param> |
| 119 | + /// <param name="dy">Y-coordinate of query point d.</param> |
| 120 | + /// <returns> |
| 121 | + /// A positive value if d is inside the circumcircle of (a, b, c), |
| 122 | + /// zero if on, or a negative value if outside. |
| 123 | + /// </returns> |
| 124 | + /// <seealso cref="PredicatesAdaptive.InCircle(double, double, double, double, double, double, double, double)"/> |
| 125 | + public static double InCircle( |
| 126 | + double ax, double ay, double bx, double by, |
| 127 | + double cx, double cy, double dx, double dy) |
| 128 | + { |
| 129 | + Span<double> abE = stackalloc double[4]; |
| 130 | + Span<double> bcE = stackalloc double[4]; |
| 131 | + Span<double> cdE = stackalloc double[4]; |
| 132 | + Span<double> daE = stackalloc double[4]; |
| 133 | + Span<double> acE = stackalloc double[4]; |
| 134 | + Span<double> bdE = stackalloc double[4]; |
| 135 | + int abLen = PredicatesAdaptive.TwoTwoDiff(ax, by, bx, ay, abE); |
| 136 | + int bcLen = PredicatesAdaptive.TwoTwoDiff(bx, cy, cx, by, bcE); |
| 137 | + int cdLen = PredicatesAdaptive.TwoTwoDiff(cx, dy, dx, cy, cdE); |
| 138 | + int daLen = PredicatesAdaptive.TwoTwoDiff(dx, ay, ax, dy, daE); |
| 139 | + int acLen = PredicatesAdaptive.TwoTwoDiff(ax, cy, cx, ay, acE); |
| 140 | + int bdLen = PredicatesAdaptive.TwoTwoDiff(bx, dy, dx, by, bdE); |
| 141 | + |
| 142 | + // abc = ab + bc - ac |
| 143 | + Span<double> negAc = stackalloc double[4]; |
| 144 | + PredicatesAdaptive.NegateInto(acE, acLen, negAc); |
| 145 | + Span<double> abbc = stackalloc double[8]; |
| 146 | + int abbcLen = PredicatesAdaptive.ExpansionSum(abE, abLen, bcE, bcLen, abbc); |
| 147 | + Span<double> abc = stackalloc double[12]; |
| 148 | + int abcLen = PredicatesAdaptive.ExpansionSum(abbc, abbcLen, negAc, acLen, abc); |
| 149 | + |
| 150 | + // bcd = bc + cd - bd |
| 151 | + Span<double> negBd = stackalloc double[4]; |
| 152 | + PredicatesAdaptive.NegateInto(bdE, bdLen, negBd); |
| 153 | + Span<double> bccd = stackalloc double[8]; |
| 154 | + int bccdLen = PredicatesAdaptive.ExpansionSum(bcE, bcLen, cdE, cdLen, bccd); |
| 155 | + Span<double> bcd = stackalloc double[12]; |
| 156 | + int bcdLen = PredicatesAdaptive.ExpansionSum(bccd, bccdLen, negBd, bdLen, bcd); |
| 157 | + |
| 158 | + // cda = cd + da + ac |
| 159 | + Span<double> cdda = stackalloc double[8]; |
| 160 | + int cddaLen = PredicatesAdaptive.ExpansionSum(cdE, cdLen, daE, daLen, cdda); |
| 161 | + Span<double> cda = stackalloc double[12]; |
| 162 | + int cdaLen = PredicatesAdaptive.ExpansionSum(cdda, cddaLen, acE, acLen, cda); |
| 163 | + |
| 164 | + // dab = da + ab + bd |
| 165 | + Span<double> daab = stackalloc double[8]; |
| 166 | + int daabLen = PredicatesAdaptive.ExpansionSum(daE, daLen, abE, abLen, daab); |
| 167 | + Span<double> dab = stackalloc double[12]; |
| 168 | + int dabLen = PredicatesAdaptive.ExpansionSum(daab, daabLen, bdE, bdLen, dab); |
| 169 | + |
| 170 | + // adet = bcd*ax*ax + bcd*ay*ay |
| 171 | + Span<double> adet = stackalloc double[96]; |
| 172 | + int adetLen = PredicatesAdaptive.ScaleExpansionSum(bcd, bcdLen, ax, ay, adet); |
| 173 | + |
| 174 | + // bdet = -(cda*bx*bx + cda*by*by) |
| 175 | + Span<double> bdetPos = stackalloc double[96]; |
| 176 | + int bdetPosLen = PredicatesAdaptive.ScaleExpansionSum(cda, cdaLen, bx, by, bdetPos); |
| 177 | + Span<double> bdet = stackalloc double[96]; |
| 178 | + PredicatesAdaptive.NegateInto(bdetPos, bdetPosLen, bdet); |
| 179 | + int bdetLen = bdetPosLen; |
| 180 | + |
| 181 | + // cdet = dab*cx*cx + dab*cy*cy |
| 182 | + Span<double> cdet = stackalloc double[96]; |
| 183 | + int cdetLen = PredicatesAdaptive.ScaleExpansionSum(dab, dabLen, cx, cy, cdet); |
| 184 | + |
| 185 | + // ddet = -(abc*dx*dx + abc*dy*dy) |
| 186 | + Span<double> ddetPos = stackalloc double[96]; |
| 187 | + int ddetPosLen = PredicatesAdaptive.ScaleExpansionSum(abc, abcLen, dx, dy, ddetPos); |
| 188 | + Span<double> ddet = stackalloc double[96]; |
| 189 | + PredicatesAdaptive.NegateInto(ddetPos, ddetPosLen, ddet); |
| 190 | + int ddetLen = ddetPosLen; |
| 191 | + |
| 192 | + // deter = (adet + bdet) + (cdet + ddet) |
| 193 | + Span<double> ab2 = stackalloc double[192]; |
| 194 | + int ab2Len = PredicatesAdaptive.ExpansionSum(adet, adetLen, bdet, bdetLen, ab2); |
| 195 | + Span<double> cd2 = stackalloc double[192]; |
| 196 | + int cd2Len = PredicatesAdaptive.ExpansionSum(cdet, cdetLen, ddet, ddetLen, cd2); |
| 197 | + Span<double> deter = stackalloc double[384]; |
| 198 | + int deterLen = PredicatesAdaptive.ExpansionSum(ab2, ab2Len, cd2, cd2Len, deter); |
| 199 | + |
| 200 | + return PredicatesAdaptive.MostSignificant(deter, deterLen); |
| 201 | + } |
| 202 | + |
| 203 | + /// <summary> |
| 204 | + /// Exact incircle predicate for <see cref="float"/> coordinates using |
| 205 | + /// arbitrary-precision arithmetic. |
| 206 | + /// Returns positive if <c>d</c> is inside the circumcircle of (a, b, c), |
| 207 | + /// zero if on, negative if outside. |
| 208 | + /// </summary> |
| 209 | + /// <param name="ax">X-coordinate of triangle vertex a.</param> |
| 210 | + /// <param name="ay">Y-coordinate of triangle vertex a.</param> |
| 211 | + /// <param name="bx">X-coordinate of triangle vertex b.</param> |
| 212 | + /// <param name="by">Y-coordinate of triangle vertex b.</param> |
| 213 | + /// <param name="cx">X-coordinate of triangle vertex c.</param> |
| 214 | + /// <param name="cy">Y-coordinate of triangle vertex c.</param> |
| 215 | + /// <param name="dx">X-coordinate of query point d.</param> |
| 216 | + /// <param name="dy">Y-coordinate of query point d.</param> |
| 217 | + /// <returns> |
| 218 | + /// A positive value if d is inside the circumcircle of (a, b, c), |
| 219 | + /// zero if on, or a negative value if outside. |
| 220 | + /// </returns> |
| 221 | + /// <seealso cref="PredicatesAdaptive.InCircle(float, float, float, float, float, float, float, float)"/> |
| 222 | + public static float InCircle( |
| 223 | + float ax, float ay, float bx, float by, |
| 224 | + float cx, float cy, float dx, float dy) |
| 225 | + { |
| 226 | + float adx = ax - dx, bdx = bx - dx, cdx = cx - dx; |
| 227 | + float ady = ay - dy, bdy = by - dy, cdy = cy - dy; |
| 228 | + decimal madx = (decimal)adx, mbdx = (decimal)bdx, mcdx = (decimal)cdx; |
| 229 | + decimal mady = (decimal)ady, mbdy = (decimal)bdy, mcdy = (decimal)cdy; |
| 230 | + decimal mdet = (madx * madx + mady * mady) * (mbdx * mcdy - mcdx * mbdy) |
| 231 | + + (mbdx * mbdx + mbdy * mbdy) * (mcdx * mady - madx * mcdy) |
| 232 | + + (mcdx * mcdx + mcdy * mcdy) * (madx * mbdy - mbdx * mady); |
| 233 | + return mdet > 0m ? float.Epsilon : mdet < 0m ? -float.Epsilon : 0f; |
| 234 | + } |
| 235 | +} |
0 commit comments