Skip to content

Commit 010be57

Browse files
committed
Merge branch 'develop'
2 parents 46e9d4d + bc132f0 commit 010be57

13 files changed

Lines changed: 1658 additions & 2 deletions

docs/wiki/bounds-and-geometry.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,13 @@ Choose the represented boundary carefully:
218218
This distinction is why the API exposes named sweep methods instead of treating
219219
every query as an expanded axis-aligned bound.
220220

221+
`FixedConvexPrismRelations.IntersectsSweptUprightCylinderStrict` answers the
222+
joint continuous relation between a translated upright cylinder and a rotated
223+
vertical convex prism. It keeps one exact parameter domain for both footprint
224+
and height overlap, so planar tangency, vertical tangency, and intervals that
225+
meet only at one boundary parameter do not become false positive volume
226+
intersections through independently rounded roots.
227+
221228
## Triangles and contacts
222229

223230
Triangles preserve vertex order. `FixedTriangle2d` exposes planar barycentric
@@ -277,6 +284,7 @@ physics-response API.
277284
| Swept sphere versus cylinder or box | `FixedSegment` |
278285
| Centered shape support, containment, or materialization | `FixedSegment2d`, `FixedSegment` static helpers |
279286
| Shape support inside a world-Y layer | `FixedSlabProjection` |
287+
| Swept upright cylinder versus a vertical convex prism | `FixedConvexPrismRelations` |
280288
| Triangle contacts or finite-shape relations | `FixedTriangle` |
281289
| Relative witnesses outside ordinary world-coordinate range | `FixedPointAnchor`, `FixedPointAnchor2d`, contact-anchor types |
282290

src/FixedMathSharp/Geometry/Primitives/Relations/FixedConvexPrismRelations.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,59 @@ namespace FixedMathSharp;
1616
/// </summary>
1717
public static class FixedConvexPrismRelations
1818
{
19+
/// <summary>
20+
/// Determines whether an upright cylinder translated between two
21+
/// bottom-center points has strict positive-volume overlap with a rotated
22+
/// vertical convex prism at any shared continuous parameter in [0, 1].
23+
/// </summary>
24+
/// <remarks>
25+
/// Planar tangency, vertical tangency, and contact that exists only where
26+
/// the planar and vertical strict intervals meet at one boundary parameter
27+
/// return <see langword="false"/>. A zero radius requires the swept axis
28+
/// point to enter the strict prism footprint. The joint parameter relation
29+
/// is evaluated with exact wide intermediates and does not publish rounded
30+
/// interval endpoints.
31+
/// </remarks>
32+
/// <param name="bottomStart">The cylinder bottom center at parameter zero.</param>
33+
/// <param name="bottomEnd">The cylinder bottom center at parameter one.</param>
34+
/// <param name="radius">The nonnegative cylinder radius.</param>
35+
/// <param name="height">The positive full cylinder height.</param>
36+
/// <param name="prismOrigin">The center of the vertical convex prism.</param>
37+
/// <param name="prismRotation">The prism footprint rotation about world Y.</param>
38+
/// <param name="prismLocalOffsets">At least three ordered convex-footprint offsets.</param>
39+
/// <param name="prismHalfThickness">The positive prism half-thickness along world Y.</param>
40+
/// <exception cref="ArgumentOutOfRangeException">
41+
/// <paramref name="radius"/> is negative, <paramref name="height"/> is not
42+
/// positive, or <paramref name="prismHalfThickness"/> is not positive.
43+
/// </exception>
44+
/// <exception cref="ArgumentException">
45+
/// Fewer than three ordered prism offsets were supplied.
46+
/// </exception>
47+
public static bool IntersectsSweptUprightCylinderStrict(
48+
Vector3d bottomStart,
49+
Vector3d bottomEnd,
50+
Fixed64 radius,
51+
Fixed64 height,
52+
Vector3d prismOrigin,
53+
Fixed64 prismRotation,
54+
ReadOnlySpan<Vector2d> prismLocalOffsets,
55+
Fixed64 prismHalfThickness)
56+
{
57+
if (height <= Fixed64.Zero)
58+
throw new ArgumentOutOfRangeException(nameof(height));
59+
ValidatePrism(radius, prismLocalOffsets, prismHalfThickness);
60+
return WideConvexPrismRelations
61+
.IntersectsSweptUprightCylinderStrict(
62+
bottomStart,
63+
bottomEnd,
64+
radius,
65+
height,
66+
prismOrigin,
67+
prismRotation,
68+
prismLocalOffsets,
69+
prismHalfThickness);
70+
}
71+
1972
/// <summary>
2073
/// Attempts to construct canonical contact anchors between a rigidly
2174
/// transformed triangle and a rotated vertical convex prism.

src/FixedMathSharp/Geometry/Primitives/Segments/FixedSegment.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ public FixedBoundBox Bounds
121121
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122122
public Vector3d ClosestPoint(Vector3d point) => Vector3d.ClosestPointOnLineSegment(point, Start, End);
123123

124+
/// <summary>
125+
/// Determines whether the supplied point lies exactly on this finite segment.
126+
/// </summary>
127+
/// <remarks>
128+
/// Collinearity and finite bounds use the complete fixed-point raw domain and
129+
/// do not compare a rounded closest-point projection.
130+
/// </remarks>
131+
public readonly bool Contains(Vector3d point)
132+
{
133+
Signed192 lengthSquared = GetDifferenceDot(End, Start, End, Start);
134+
return PointOnSegment(point, this, lengthSquared);
135+
}
136+
124137
/// <summary>
125138
/// Computes the squared distance from the supplied point to this finite segment.
126139
/// </summary>

src/FixedMathSharp/Geometry/Primitives/Segments/FixedSegment2d.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,34 @@ public Fixed64 DistanceSquared(Vector2d point)
120120
return Vector2d.DistanceSquared(point, ClosestPoint(point));
121121
}
122122

123+
/// <summary>
124+
/// Determines whether the exact minimum distance to another finite segment
125+
/// is at least the supplied threshold.
126+
/// </summary>
127+
/// <remarks>
128+
/// The comparison uses wide rational distances and does not materialize a
129+
/// rounded closest point or square root. Equality is accepted.
130+
/// </remarks>
131+
/// <exception cref="ArgumentOutOfRangeException">
132+
/// Thrown when <paramref name="minimumDistance"/> is negative.
133+
/// </exception>
134+
public readonly bool IsDistanceAtLeast(
135+
FixedSegment2d other,
136+
Fixed64 minimumDistance)
137+
{
138+
if (minimumDistance < Fixed64.Zero)
139+
throw new ArgumentOutOfRangeException(nameof(minimumDistance));
140+
if (minimumDistance == Fixed64.Zero)
141+
return true;
142+
if (TryGetUniqueIntersection(other, out _, out _))
143+
return false;
144+
145+
return WidePlanarProjection.AreSegmentEndpointDistancesAtLeast(
146+
this,
147+
other,
148+
minimumDistance);
149+
}
150+
123151
/// <summary>
124152
/// Finds the closed parameter interval where this segment intersects a capsule.
125153
/// </summary>
@@ -143,6 +171,41 @@ public readonly bool TryGetCapsuleIntersectionInterval(
143171
out entryParameter,
144172
out exitParameter);
145173

174+
/// <summary>
175+
/// Finds a conservative parameter enclosure for the closed interval where
176+
/// this segment intersects an endpoint-authored capsule.
177+
/// </summary>
178+
/// <remarks>
179+
/// The exact finite-axis solve rounds its representable interval bounds to
180+
/// nearest. This method expands each returned bound outward by one raw
181+
/// quantum, clamped to [0, 1], so the mathematical interval is enclosed.
182+
/// A zero-length capsule axis is treated as a circle.
183+
/// </remarks>
184+
/// <exception cref="ArgumentOutOfRangeException">
185+
/// Thrown when <paramref name="radius"/> is negative.
186+
/// </exception>
187+
public readonly bool TryGetCapsuleIntersectionParameterEnclosure(
188+
FixedSegment2d capsuleAxis,
189+
Fixed64 radius,
190+
out Fixed64 entryParameter,
191+
out Fixed64 exitParameter)
192+
{
193+
if (!TryGetCapsuleIntersectionInterval(
194+
capsuleAxis,
195+
radius,
196+
out entryParameter,
197+
out exitParameter))
198+
{
199+
return false;
200+
}
201+
202+
if (entryParameter > Fixed64.Zero)
203+
entryParameter = Fixed64.FromRaw(entryParameter.m_rawValue - 1L);
204+
if (exitParameter < Fixed64.One)
205+
exitParameter = Fixed64.FromRaw(exitParameter.m_rawValue + 1L);
206+
return true;
207+
}
208+
146209
/// <summary>
147210
/// Finds the closed parameter interval where this segment intersects a radially
148211
/// expanded capsule.
@@ -335,6 +398,37 @@ public readonly bool TryGetUniqueIntersection(
335398
return TryGetUniqueIntersection(other, out thisParameter, out _);
336399
}
337400

401+
/// <summary>
402+
/// Attempts to enclose the unique intersection parameter on this segment.
403+
/// </summary>
404+
/// <remarks>
405+
/// The exact rational intersection is enclosed by expanding the nearest
406+
/// representable parameter by one raw unit and clamping to [0, 1]. Collinear
407+
/// positive-length overlaps retain the unique-intersection failure behavior.
408+
/// </remarks>
409+
public readonly bool TryGetUniqueIntersectionParameterEnclosure(
410+
FixedSegment2d other,
411+
out Fixed64 nearestParameter,
412+
out Fixed64 lowerParameter,
413+
out Fixed64 upperParameter)
414+
{
415+
if (!TryGetUniqueIntersection(other, out nearestParameter))
416+
{
417+
nearestParameter = default;
418+
lowerParameter = default;
419+
upperParameter = default;
420+
return false;
421+
}
422+
423+
lowerParameter = nearestParameter > Fixed64.Zero
424+
? Fixed64.FromRaw(nearestParameter.m_rawValue - 1L)
425+
: Fixed64.Zero;
426+
upperParameter = nearestParameter < Fixed64.One
427+
? Fixed64.FromRaw(nearestParameter.m_rawValue + 1L)
428+
: Fixed64.One;
429+
return true;
430+
}
431+
338432
/// <summary>
339433
/// Returns the closest finite points on this segment and another segment.
340434
/// </summary>

src/FixedMathSharp/Geometry/Wide/Common/WidePlanarProjection.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,53 @@ internal RationalDistance(
5252

5353
#endregion
5454

55+
internal static bool AreSegmentEndpointDistancesAtLeast(
56+
FixedSegment2d first,
57+
FixedSegment2d second,
58+
Fixed64 minimumDistance)
59+
{
60+
Signed192 denominator = Signed192.Signed(1L);
61+
RationalPoint firstStart = GetRawPoint(first.Start);
62+
RationalPoint firstEnd = GetRawPoint(first.End);
63+
RationalPoint secondStart = GetRawPoint(second.Start);
64+
RationalPoint secondEnd = GetRawPoint(second.End);
65+
Signed192 minimumRaw = Signed192.Raw(minimumDistance);
66+
67+
return CompareDistanceToRaw(
68+
GetSegmentDistance(
69+
first.Start,
70+
denominator,
71+
secondStart,
72+
secondEnd),
73+
minimumRaw) >= 0
74+
&& CompareDistanceToRaw(
75+
GetSegmentDistance(
76+
first.End,
77+
denominator,
78+
secondStart,
79+
secondEnd),
80+
minimumRaw) >= 0
81+
&& CompareDistanceToRaw(
82+
GetSegmentDistance(
83+
second.Start,
84+
denominator,
85+
firstStart,
86+
firstEnd),
87+
minimumRaw) >= 0
88+
&& CompareDistanceToRaw(
89+
GetSegmentDistance(
90+
second.End,
91+
denominator,
92+
firstStart,
93+
firstEnd),
94+
minimumRaw) >= 0;
95+
}
96+
97+
private static RationalPoint GetRawPoint(Vector2d point) =>
98+
new(
99+
Signed320.ExtendValue(Signed192.Raw(point.X)),
100+
Signed320.ExtendValue(Signed192.Raw(point.Y)));
101+
55102
internal static bool TryGetSphereRelation(
56103
Vector2d circleCenter,
57104
Fixed64 circleRadius,

src/FixedMathSharp/Geometry/Wide/Convex/WideConvex2dRelations.ProjectionMath.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ private static void GetWorldPoint(
468468
secondRotatedY);
469469
}
470470

471-
private static void GetRotatedOffset(
471+
internal static void GetRotatedOffset(
472472
RotationFrame2d rotation,
473473
Vector2d localPoint,
474474
out Signed192 x,

src/FixedMathSharp/Geometry/Wide/Convex/WideConvex2dRelations.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal WideAxis2d(Signed192 x, Signed192 y)
3333
new(WideArithmetic.Negate(value.X), WideArithmetic.Negate(value.Y));
3434
}
3535

36-
private readonly struct RotationFrame2d
36+
internal readonly struct RotationFrame2d
3737
{
3838
internal static readonly RotationFrame2d Identity = new(
3939
Signed192.One,

0 commit comments

Comments
 (0)