| | | 1 | | //======================================================================= |
| | | 2 | | // FixedPointAnchor2d.cs |
| | | 3 | | //======================================================================= |
| | | 4 | | // MIT License, Copyright (c) 2024–present David Oravsky (mrdav30) |
| | | 5 | | // See LICENSE file in the project root for full license information. |
| | | 6 | | //======================================================================= |
| | | 7 | | |
| | | 8 | | using System; |
| | | 9 | | |
| | | 10 | | namespace FixedMathSharp.Geometry; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Identifies a conceptual 2D point by an origin, scalar rotation, and local |
| | | 14 | | /// point without requiring the transformed point to be representable. |
| | | 15 | | /// </summary> |
| | | 16 | | /// <remarks> |
| | | 17 | | /// Relation-produced anchors can retain an exact sub-lattice feature term that |
| | | 18 | | /// is intentionally hidden behind the narrow comparison and materialization |
| | | 19 | | /// APIs. Anchors with identical rounded public local components can therefore |
| | | 20 | | /// remain distinct when they identify different exact features. |
| | | 21 | | /// </remarks> |
| | | 22 | | public readonly struct FixedPointAnchor2d : IEquatable<FixedPointAnchor2d> |
| | | 23 | | { |
| | | 24 | | /// <summary> |
| | | 25 | | /// Gets the point frame's world-space origin. |
| | | 26 | | /// </summary> |
| | | 27 | | public Vector2d Origin { get; } |
| | | 28 | | |
| | | 29 | | /// <summary> |
| | | 30 | | /// Gets the point frame's counterclockwise rotation in radians. |
| | | 31 | | /// </summary> |
| | | 32 | | public Fixed64 Rotation { get; } |
| | | 33 | | |
| | | 34 | | /// <summary> |
| | | 35 | | /// Gets the point in its supplied local frame. |
| | | 36 | | /// </summary> |
| | | 37 | | public Vector2d LocalPoint { get; } |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// An additional local-space feature displacement that exact operations |
| | | 41 | | /// add to <see cref="LocalPoint"/> without a scalar intermediate. |
| | | 42 | | /// </summary> |
| | | 43 | | public Vector2d LocalDisplacement { get; } |
| | | 44 | | |
| | | 45 | | /// <summary> |
| | | 46 | | /// Creates a transformed point anchor. |
| | | 47 | | /// </summary> |
| | | 48 | | public FixedPointAnchor2d( |
| | | 49 | | Vector2d origin, |
| | | 50 | | Fixed64 rotation, |
| | | 51 | | Vector2d localPoint) |
| | 145 | 52 | | : this(origin, rotation, localPoint, Vector2d.Zero) |
| | 145 | 53 | | { } |
| | | 54 | | |
| | | 55 | | /// <summary> |
| | | 56 | | /// Creates a transformed point anchor from two additive local-space |
| | | 57 | | /// feature components. |
| | | 58 | | /// </summary> |
| | | 59 | | public FixedPointAnchor2d( |
| | | 60 | | Vector2d origin, |
| | | 61 | | Fixed64 rotation, |
| | | 62 | | Vector2d localPoint, |
| | | 63 | | Vector2d localDisplacement) |
| | 153 | 64 | | : this( |
| | 153 | 65 | | origin, |
| | 153 | 66 | | rotation, |
| | 153 | 67 | | localPoint, |
| | 153 | 68 | | localDisplacement, |
| | 153 | 69 | | default) |
| | 153 | 70 | | { } |
| | | 71 | | |
| | | 72 | | internal FixedPointAnchor2d( |
| | | 73 | | Vector2d origin, |
| | | 74 | | Fixed64 rotation, |
| | | 75 | | Vector2d localPoint, |
| | | 76 | | Vector2d localDisplacement, |
| | | 77 | | FixedPointAnchorTerm2d exactLocalTerm) |
| | | 78 | | { |
| | 373 | 79 | | Origin = origin; |
| | 373 | 80 | | Rotation = rotation; |
| | 373 | 81 | | LocalPoint = localPoint; |
| | 373 | 82 | | LocalDisplacement = localDisplacement; |
| | 373 | 83 | | ExactLocalTerm = exactLocalTerm; |
| | 373 | 84 | | } |
| | | 85 | | |
| | | 86 | | internal FixedPointAnchorTerm2d ExactLocalTerm { get; } |
| | | 87 | | |
| | | 88 | | /// <summary> |
| | | 89 | | /// Attempts to materialize the conceptual world-space point with one |
| | | 90 | | /// final round-half-to-even conversion per component. |
| | | 91 | | /// </summary> |
| | | 92 | | public bool TryGetPoint(out Vector2d point) => |
| | 51 | 93 | | WidePointAnchor2d.TryGetPoint( |
| | 51 | 94 | | Origin, |
| | 51 | 95 | | LocalPoint, |
| | 51 | 96 | | LocalDisplacement, |
| | 51 | 97 | | ExactLocalTerm, |
| | 51 | 98 | | Rotation, |
| | 51 | 99 | | out point); |
| | | 100 | | |
| | | 101 | | /// <summary> |
| | | 102 | | /// Attempts to obtain this point's exact offset from another transformed |
| | | 103 | | /// point without materializing either world-space point. |
| | | 104 | | /// </summary> |
| | | 105 | | public bool TryGetOffsetFrom( |
| | | 106 | | in FixedPointAnchor2d other, |
| | | 107 | | out Vector2d offset) => |
| | 46 | 108 | | WidePointAnchor2d.TryGetRelativeOffset( |
| | 46 | 109 | | Origin, |
| | 46 | 110 | | LocalPoint, |
| | 46 | 111 | | LocalDisplacement, |
| | 46 | 112 | | ExactLocalTerm, |
| | 46 | 113 | | Rotation, |
| | 46 | 114 | | other.Origin, |
| | 46 | 115 | | other.LocalPoint, |
| | 46 | 116 | | other.LocalDisplacement, |
| | 46 | 117 | | other.ExactLocalTerm, |
| | 46 | 118 | | other.Rotation, |
| | 46 | 119 | | out offset); |
| | | 120 | | |
| | | 121 | | /// <summary> |
| | | 122 | | /// Compares the exact squared distance from this point to two other |
| | | 123 | | /// anchored points. |
| | | 124 | | /// </summary> |
| | | 125 | | /// <returns> |
| | | 126 | | /// A negative value when <paramref name="first"/> is closer, zero when the |
| | | 127 | | /// distances are equal, or a positive value when |
| | | 128 | | /// <paramref name="second"/> is closer. |
| | | 129 | | /// </returns> |
| | | 130 | | public int CompareSquaredDistance( |
| | | 131 | | in FixedPointAnchor2d first, |
| | | 132 | | in FixedPointAnchor2d second) => |
| | 6 | 133 | | WidePointAnchor2d.CompareSquaredDistances( |
| | 6 | 134 | | this, |
| | 6 | 135 | | first, |
| | 6 | 136 | | second); |
| | | 137 | | |
| | | 138 | | /// <summary> |
| | | 139 | | /// Attempts to express this conceptual point in another rotated frame |
| | | 140 | | /// without materializing the world-space point. |
| | | 141 | | /// </summary> |
| | | 142 | | public bool TryGetLocalPointIn( |
| | | 143 | | Vector2d frameOrigin, |
| | | 144 | | Fixed64 frameRotation, |
| | | 145 | | out Vector2d localPoint) => |
| | 76 | 146 | | WidePointAnchor2d.TryGetLocalPointIn( |
| | 76 | 147 | | Origin, |
| | 76 | 148 | | LocalPoint, |
| | 76 | 149 | | LocalDisplacement, |
| | 76 | 150 | | ExactLocalTerm, |
| | 76 | 151 | | Rotation, |
| | 76 | 152 | | frameOrigin, |
| | 76 | 153 | | frameRotation, |
| | 76 | 154 | | out localPoint); |
| | | 155 | | |
| | | 156 | | /// <summary> |
| | | 157 | | /// Attempts to express this exact conceptual point in another rotated |
| | | 158 | | /// frame without discarding sub-lattice feature information. |
| | | 159 | | /// </summary> |
| | | 160 | | /// <remarks> |
| | | 161 | | /// The operation fails when the target frame would require a general |
| | | 162 | | /// rational local coordinate that a compact point anchor cannot retain. |
| | | 163 | | /// </remarks> |
| | | 164 | | public bool TryReframe( |
| | | 165 | | Vector2d frameOrigin, |
| | | 166 | | Fixed64 frameRotation, |
| | | 167 | | out FixedPointAnchor2d anchor) |
| | | 168 | | { |
| | 37 | 169 | | if (Origin == frameOrigin && Rotation == frameRotation) |
| | | 170 | | { |
| | 1 | 171 | | anchor = this; |
| | 1 | 172 | | return true; |
| | | 173 | | } |
| | 36 | 174 | | if (!TryGetLocalPointIn( |
| | 36 | 175 | | frameOrigin, |
| | 36 | 176 | | frameRotation, |
| | 36 | 177 | | out Vector2d localPoint)) |
| | | 178 | | { |
| | 1 | 179 | | anchor = default; |
| | 1 | 180 | | return false; |
| | | 181 | | } |
| | | 182 | | |
| | 35 | 183 | | var candidate = new FixedPointAnchor2d( |
| | 35 | 184 | | frameOrigin, |
| | 35 | 185 | | frameRotation, |
| | 35 | 186 | | localPoint); |
| | 35 | 187 | | if (!WidePointAnchor2d.RepresentsSamePoint( |
| | 35 | 188 | | this, |
| | 35 | 189 | | candidate)) |
| | | 190 | | { |
| | 1 | 191 | | anchor = default; |
| | 1 | 192 | | return false; |
| | | 193 | | } |
| | | 194 | | |
| | 34 | 195 | | anchor = candidate; |
| | 34 | 196 | | return true; |
| | | 197 | | } |
| | | 198 | | |
| | | 199 | | /// <summary> |
| | | 200 | | /// Compares the complete local feature identity of this anchor with |
| | | 201 | | /// <paramref name="other"/> in deterministic component order. |
| | | 202 | | /// </summary> |
| | | 203 | | /// <remarks> |
| | | 204 | | /// Frame origin and rotation are intentionally excluded. Exact |
| | | 205 | | /// sub-lattice centered-axis residuals are included after the public local |
| | | 206 | | /// components. |
| | | 207 | | /// </remarks> |
| | | 208 | | public int CompareLocalFeature(in FixedPointAnchor2d other) |
| | | 209 | | { |
| | 15 | 210 | | int comparison = CompareRaw( |
| | 15 | 211 | | LocalPoint.X.m_rawValue, |
| | 15 | 212 | | other.LocalPoint.X.m_rawValue); |
| | 15 | 213 | | if (comparison != 0) |
| | 2 | 214 | | return comparison; |
| | 13 | 215 | | comparison = CompareRaw( |
| | 13 | 216 | | LocalPoint.Y.m_rawValue, |
| | 13 | 217 | | other.LocalPoint.Y.m_rawValue); |
| | 13 | 218 | | if (comparison != 0) |
| | 2 | 219 | | return comparison; |
| | 11 | 220 | | comparison = CompareRaw( |
| | 11 | 221 | | LocalDisplacement.X.m_rawValue, |
| | 11 | 222 | | other.LocalDisplacement.X.m_rawValue); |
| | 11 | 223 | | if (comparison != 0) |
| | 2 | 224 | | return comparison; |
| | 9 | 225 | | comparison = CompareRaw( |
| | 9 | 226 | | LocalDisplacement.Y.m_rawValue, |
| | 9 | 227 | | other.LocalDisplacement.Y.m_rawValue); |
| | 9 | 228 | | if (comparison != 0) |
| | 2 | 229 | | return comparison; |
| | 7 | 230 | | comparison = CompareRaw( |
| | 7 | 231 | | ExactLocalTerm.X, |
| | 7 | 232 | | other.ExactLocalTerm.X); |
| | 7 | 233 | | return comparison != 0 |
| | 7 | 234 | | ? comparison |
| | 7 | 235 | | : CompareRaw( |
| | 7 | 236 | | ExactLocalTerm.Y, |
| | 7 | 237 | | other.ExactLocalTerm.Y); |
| | | 238 | | } |
| | | 239 | | |
| | | 240 | | /// <summary> |
| | | 241 | | /// Returns a stable 64-bit hash of the complete local feature identity. |
| | | 242 | | /// </summary> |
| | | 243 | | public ulong GetLocalFeatureHash64() |
| | | 244 | | { |
| | 18 | 245 | | ulong hash = 14695981039346656037UL; |
| | 18 | 246 | | KeepHash(ref hash, LocalPoint.X.m_rawValue); |
| | 18 | 247 | | KeepHash(ref hash, LocalPoint.Y.m_rawValue); |
| | 18 | 248 | | KeepHash(ref hash, LocalDisplacement.X.m_rawValue); |
| | 18 | 249 | | KeepHash(ref hash, LocalDisplacement.Y.m_rawValue); |
| | 18 | 250 | | KeepHash(ref hash, ExactLocalTerm.X); |
| | 18 | 251 | | KeepHash(ref hash, ExactLocalTerm.Y); |
| | 18 | 252 | | return hash; |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | /// <summary> |
| | | 256 | | /// Returns whether both anchors have identical frames, rounded local |
| | | 257 | | /// components, and any retained exact feature identity. |
| | | 258 | | /// </summary> |
| | | 259 | | public bool Equals(FixedPointAnchor2d other) => |
| | 17 | 260 | | Origin == other.Origin |
| | 17 | 261 | | && Rotation == other.Rotation |
| | 17 | 262 | | && LocalPoint == other.LocalPoint |
| | 17 | 263 | | && LocalDisplacement == other.LocalDisplacement |
| | 17 | 264 | | && ExactLocalTerm.Equals(other.ExactLocalTerm); |
| | | 265 | | |
| | | 266 | | /// <summary> |
| | | 267 | | /// Returns a hash code for the complete frame and exact feature identity. |
| | | 268 | | /// </summary> |
| | | 269 | | public override bool Equals(object? obj) => |
| | 4 | 270 | | obj is FixedPointAnchor2d other && Equals(other); |
| | | 271 | | |
| | | 272 | | /// <inheritdoc /> |
| | | 273 | | public override int GetHashCode() |
| | | 274 | | { |
| | | 275 | | unchecked |
| | | 276 | | { |
| | 5 | 277 | | int hash = 17; |
| | 5 | 278 | | hash = (hash * 31) + Origin.GetHashCode(); |
| | 5 | 279 | | hash = (hash * 31) + Rotation.GetHashCode(); |
| | 5 | 280 | | hash = (hash * 31) + LocalPoint.GetHashCode(); |
| | 5 | 281 | | hash = (hash * 31) + LocalDisplacement.GetHashCode(); |
| | 5 | 282 | | if (!ExactLocalTerm.IsZero) |
| | 2 | 283 | | hash = (hash * 31) + ExactLocalTerm.GetHashCode(); |
| | 5 | 284 | | return hash; |
| | | 285 | | } |
| | | 286 | | } |
| | | 287 | | |
| | | 288 | | /// <summary> |
| | | 289 | | /// Returns whether two anchors have identical frame and local-point |
| | | 290 | | /// components. |
| | | 291 | | /// </summary> |
| | | 292 | | public static bool operator ==( |
| | | 293 | | FixedPointAnchor2d left, |
| | | 294 | | FixedPointAnchor2d right) => |
| | 2 | 295 | | left.Equals(right); |
| | | 296 | | |
| | | 297 | | /// <summary> |
| | | 298 | | /// Returns whether two anchors differ in any frame or local-point |
| | | 299 | | /// component. |
| | | 300 | | /// </summary> |
| | | 301 | | public static bool operator !=( |
| | | 302 | | FixedPointAnchor2d left, |
| | | 303 | | FixedPointAnchor2d right) => |
| | 2 | 304 | | !left.Equals(right); |
| | | 305 | | |
| | | 306 | | private static int CompareRaw(long left, long right) => |
| | 59 | 307 | | left < right ? -1 : left > right ? 1 : 0; |
| | | 308 | | |
| | | 309 | | private static void KeepHash(ref ulong hash, long value) |
| | | 310 | | { |
| | 108 | 311 | | hash ^= unchecked((ulong)value); |
| | 108 | 312 | | hash *= 1099511628211UL; |
| | 108 | 313 | | } |
| | | 314 | | } |