first commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace SixLabors.ImageSharp.Drawing.Helpers {
|
||||
/// <summary>
|
||||
/// Extension methods for arrays.
|
||||
/// </summary>
|
||||
internal static class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Concatenates two arrays into one.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type.</typeparam>
|
||||
/// <param name="source1">The first source array.</param>
|
||||
/// <param name="source2">The second source array.</param>
|
||||
/// <returns>
|
||||
/// A new array containing the elements of both source arrays, or <paramref name="source1"/>
|
||||
/// when <paramref name="source2"/> is empty.
|
||||
/// </returns>
|
||||
public static T[] Concat<T>(this T[] source1, T[] source2)
|
||||
{
|
||||
if (source2 is null || source2.Length == 0)
|
||||
{
|
||||
return source1;
|
||||
}
|
||||
|
||||
T[] target = new T[source1.Length + source2.Length];
|
||||
source1.AsSpan().CopyTo(target);
|
||||
source2.AsSpan().CopyTo(target.AsSpan(source1.Length));
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SixLabors.ImageSharp.Drawing.Helpers {
|
||||
/// <summary>
|
||||
/// Provides helper methods for extracting properties from transformation matrices.
|
||||
/// </summary>
|
||||
internal static class MatrixUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts the average 2D scale factor from a <see cref="Matrix4x4"/>.
|
||||
/// This is the mean of the X and Y axis scale magnitudes, suitable for
|
||||
/// uniformly scaling radii under non-uniform or projective transforms.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The transformation matrix.</param>
|
||||
/// <returns>The average scale factor.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float GetAverageScale(in Matrix4x4 matrix)
|
||||
{
|
||||
float sx = MathF.Sqrt((matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12));
|
||||
float sy = MathF.Sqrt((matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22));
|
||||
return (sx + sy) * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using SixLabors.ImageSharp.Drawing;
|
||||
|
||||
namespace SixLabors.ImageSharp.Drawing.Helpers {
|
||||
/// <summary>
|
||||
/// Provides low-level geometry helpers for polygon winding and segment intersection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Polygon methods expect a closed ring where the first point is repeated as the last point.
|
||||
/// Orientation signs are defined using world-space math conventions (Y points up):
|
||||
/// positive signed area is counter-clockwise and negative signed area is clockwise.
|
||||
/// In screen space (Y points down), the visual winding appears inverted.
|
||||
/// </remarks>
|
||||
internal static class PolygonUtilities
|
||||
{
|
||||
// Epsilon used for floating-point tolerance. Values within +-Eps are treated as zero.
|
||||
// This reduces instability when segments are nearly parallel or endpoints are close.
|
||||
private const float Eps = 1e-3f;
|
||||
private const float MinusEps = -Eps;
|
||||
private const float OnePlusEps = 1 + Eps;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a closed polygon ring matches the expected orientation.
|
||||
/// </summary>
|
||||
/// <param name="polygon">Polygon ring to normalize in place.</param>
|
||||
/// <param name="expectedOrientation">
|
||||
/// Expected orientation sign:
|
||||
/// positive for counter-clockwise in world space, negative for clockwise in world space.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The ring is reversed only when its orientation sign disagrees with
|
||||
/// <paramref name="expectedOrientation"/>. Degenerate rings (zero area) are not changed.
|
||||
/// </remarks>
|
||||
public static void EnsureOrientation(Span<PointF> polygon, int expectedOrientation)
|
||||
{
|
||||
if (GetPolygonOrientation(polygon) * expectedOrientation < 0)
|
||||
{
|
||||
polygon.Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the orientation sign of a closed polygon ring using the shoelace sum.
|
||||
/// </summary>
|
||||
/// <param name="polygon">Closed polygon ring.</param>
|
||||
/// <returns>
|
||||
/// -1 for clockwise, 1 for counter-clockwise, or 0 for degenerate (zero-area) input.
|
||||
/// </returns>
|
||||
private static int GetPolygonOrientation(ReadOnlySpan<PointF> polygon)
|
||||
{
|
||||
float sum = 0f;
|
||||
for (int i = 0; i < polygon.Length - 1; ++i)
|
||||
{
|
||||
PointF current = polygon[i];
|
||||
PointF next = polygon[i + 1];
|
||||
sum += (current.X * next.Y) - (next.X * current.Y);
|
||||
}
|
||||
|
||||
// A tolerant compare could be used here, but edge scanning does not special-case
|
||||
// zero-area or near-zero-area input, so we keep this strict sign check.
|
||||
return Math.Sign(sum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two line segments intersect, excluding collinear overlap cases.
|
||||
/// </summary>
|
||||
/// <param name="a0">Start point of segment A.</param>
|
||||
/// <param name="a1">End point of segment A.</param>
|
||||
/// <param name="b0">Start point of segment B.</param>
|
||||
/// <param name="b1">End point of segment B.</param>
|
||||
/// <param name="intersectionPoint">
|
||||
/// Receives the intersection point when an intersection is found.
|
||||
/// If no intersection is detected, the value is not modified.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> when the segments intersect within their extents
|
||||
/// (including endpoints); otherwise <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This solves the two segment equations in parametric form and accepts values in [0, 1]
|
||||
/// with an epsilon margin for floating-point tolerance.
|
||||
/// Parallel and collinear pairs are rejected early (cross product ~= 0).
|
||||
/// </remarks>
|
||||
public static bool LineSegmentToLineSegmentIgnoreCollinear(
|
||||
Vector2 a0,
|
||||
Vector2 a1,
|
||||
Vector2 b0,
|
||||
Vector2 b1,
|
||||
ref Vector2 intersectionPoint)
|
||||
{
|
||||
// Direction vectors of the segments.
|
||||
float dax = a1.X - a0.X;
|
||||
float day = a1.Y - a0.Y;
|
||||
float dbx = b1.X - b0.X;
|
||||
float dby = b1.Y - b0.Y;
|
||||
|
||||
// Cross product of the direction vectors. Near zero means parallel/collinear.
|
||||
float crossD = (-dbx * day) + (dax * dby);
|
||||
|
||||
// Reject parallel and collinear lines. Collinear overlap is intentionally not handled.
|
||||
if (crossD is > MinusEps and < Eps)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Solve for parameters s and t where:
|
||||
// a0 + t * (a1 - a0) = b0 + s * (b1 - b0)
|
||||
float s = ((-day * (a0.X - b0.X)) + (dax * (a0.Y - b0.Y))) / crossD;
|
||||
float t = ((dbx * (a0.Y - b0.Y)) - (dby * (a0.X - b0.X))) / crossD;
|
||||
|
||||
// If both parameters are within [0,1] (with tolerance), the segments intersect.
|
||||
if (s > MinusEps && s < OnePlusEps && t > MinusEps && t < OnePlusEps)
|
||||
{
|
||||
intersectionPoint.X = a0.X + (t * dax);
|
||||
intersectionPoint.Y = a0.Y + (t * day);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user