Files
tech5/source/shared/idlib/geometry/geometry.cpp
T
2026-08-08 13:54:26 -07:00

228 lines
6.2 KiB
C++

#include "idlib/geometry/geometry.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace {
constexpr float kSmallestNormal = std::numeric_limits<float>::min();
constexpr float kParallelEpsilonSqr = 0.0001f;
float ClampUnit(const float value) {
return std::max(0.0f, std::min(1.0f, value));
}
float Cross2D(const idVec2& left, const idVec2& right) {
return left.x * right.y - left.y * right.x;
}
idVec2 Subtract2D(const idVec2& left, const idVec2& right) {
return idVec2(left.x - right.x, left.y - right.y);
}
idVec3 NormalizeSafely(const idVec3& vector) {
const float lengthSqr = vector.LengthSqr();
if (lengthSqr < kSmallestNormal) {
return idVec3(0.0f, 0.0f, 0.0f);
}
return vector * (1.0f / std::sqrt(lengthSqr));
}
} // namespace
float idGeometry::PositionOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < kSmallestNormal) {
return 0.0f;
}
return (point - start).Dot(segment) / lengthSqr;
}
bool idGeometry::ClosestPointOnLineSegment(
const idVec3& point,
const idVec3& start,
const idVec3& end,
idVec3& closest
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < kSmallestNormal) {
closest = start;
return false;
}
const float position = (point - start).Dot(segment) / lengthSqr;
if (position < 0.0f) {
closest = start;
return false;
}
if (position > 1.0f) {
closest = end;
return false;
}
closest = start + segment * position;
return true;
}
void idGeometry::ClosestPointOnLine(
const idVec3& point,
const idVec3& start,
const idVec3& dir,
idVec3& closest
) {
closest = start + dir * (point - start).Dot(dir);
}
idVec3 idGeometry::TriangleNormal(
const idVec3& a,
const idVec3& b,
const idVec3& c
) {
// The recovered winding is intentionally (c-a) x (b-a), opposite the
// common formulation. Renderer-facing callers depend on that sign.
return NormalizeSafely((c - a).Cross(b - a));
}
bool idGeometry::IntersectRayWithLineSegment2D(
const idVec2& rayStart,
const idVec2& rayDir,
const idVec2& segStart,
const idVec2& segEnd,
float& dist
) {
const idVec2 segment = Subtract2D(segEnd, segStart);
const idVec2 offset = Subtract2D(segStart, rayStart);
const float denominator = Cross2D(rayDir, segment);
if (denominator * denominator > kParallelEpsilonSqr) {
const float inverseDenominator = 1.0f / denominator;
const float segmentPosition = -Cross2D(rayDir, offset)
* inverseDenominator;
if (segmentPosition < 0.0f || segmentPosition > 1.0f) {
return false;
}
const float rayPosition = Cross2D(offset, segment)
* inverseDenominator;
if (rayPosition < 0.0f) {
return false;
}
dist = rayPosition;
return true;
}
if (Cross2D(rayDir, offset) * Cross2D(rayDir, offset)
> kParallelEpsilonSqr) {
return false;
}
// The PPC output is difficult to read in this collinear branch. This is
// the equivalent geometric result: the nearest forward overlap measured
// in ray parameter units. Keep it isolated for later binary trace checks.
const float rayLengthSqr = rayDir.x * rayDir.x + rayDir.y * rayDir.y;
if (rayLengthSqr < kSmallestNormal) {
return false;
}
const idVec2 endOffset = Subtract2D(segEnd, rayStart);
const float startPosition = (offset.x * rayDir.x + offset.y * rayDir.y)
/ rayLengthSqr;
const float endPosition = (endOffset.x * rayDir.x + endOffset.y * rayDir.y)
/ rayLengthSqr;
const float nearPosition = std::min(startPosition, endPosition);
const float farPosition = std::max(startPosition, endPosition);
if (farPosition < 0.0f) {
return false;
}
dist = std::max(0.0f, nearPosition);
return true;
}
float idGeometry::SquarePointLineSegmentDistance(
const idVec3& point,
const idVec3& start,
const idVec3& end
) {
const idVec3 segment = end - start;
const float lengthSqr = segment.LengthSqr();
if (lengthSqr < 0.01f) {
return (point - start).LengthSqr();
}
const float position = ClampUnit((point - start).Dot(segment) / lengthSqr);
return (point - (start + segment * position)).LengthSqr();
}
void idGeometry::SegmentSegmentClosestPoints(
const idVec3& start1,
const idVec3& end1,
const idVec3& start2,
const idVec3& end2,
idVec3& out1,
idVec3& out2,
float& t1,
float& t2,
const bool clampTValues
) {
const idVec3 direction1 = end1 - start1;
const idVec3 direction2 = end2 - start2;
const idVec3 offset = start1 - start2;
const float length1Sqr = direction1.LengthSqr();
const float length2Sqr = direction2.LengthSqr();
const float directionsDot = direction1.Dot(direction2);
const float offsetDot1 = direction1.Dot(offset);
const float offsetDot2 = direction2.Dot(offset);
const float denominator = length1Sqr * length2Sqr
- directionsDot * directionsDot;
if (length1Sqr < kSmallestNormal
|| length2Sqr < kSmallestNormal
|| denominator < kSmallestNormal) {
out1 = start1;
out2 = start2;
t1 = 1.0f;
t2 = 1.0f;
return;
}
t1 = (directionsDot * offsetDot2 - offsetDot1 * length2Sqr)
/ denominator;
if (clampTValues) {
t1 = ClampUnit(t1);
}
t2 = (t1 * directionsDot + offsetDot2) / length2Sqr;
if (clampTValues) {
t2 = ClampUnit(t2);
}
out1 = start1 + direction1 * t1;
out2 = start2 + direction2 * t2;
}
idVec3 idGeometry::FindNearestPerpendicular(
const idVec3& input,
const idVec3& up,
const idVec3& hint
) {
idVec3 perpendicular = input.Cross(up);
if (perpendicular.Dot(hint) < 0.0f) {
perpendicular = -perpendicular;
}
return NormalizeSafely(perpendicular);
}
float idGeometry::AreaOfTriangle(
const idVec3& a,
const idVec3& b,
const idVec3& c
) {
return 0.5f * (b - a).Cross(c - a).Length();
}