Skip to content

Commit

Permalink
Fudging epsilons to make things more reliable
Browse files Browse the repository at this point in the history
  • Loading branch information
Metapyziks committed Aug 17, 2023
1 parent 17af724 commit d6d0f02
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 45 deletions.
95 changes: 53 additions & 42 deletions Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,57 @@ namespace Sandbox.Polygons;

internal static class Helpers
{
public static Vector2 NormalizeSafe( in Vector2 vec )
{
var length = vec.Length;

if ( length > 9.9999997473787516E-06 )
{
return vec / length;
}
else
{
return 0f;
}
}

public static Vector2 Rotate90( Vector2 v )
{
return new Vector2( v.y, -v.x );
}

public static Vector3 RotateNormal( Vector3 oldNormal, float sin, float cos )
{
var normal2d = new Vector2( oldNormal.x, oldNormal.y );

if ( normal2d.LengthSquared <= 0.000001f )
{
return oldNormal;
}

normal2d = NormalizeSafe( normal2d );

return new Vector3( normal2d.x * cos, normal2d.y * cos, sin ).Normal;
}

public static float GetEpsilon( Vector2 vec, float frac = 0.0001f )
{
return Math.Max( Math.Abs( vec.x ), Math.Abs( vec.y ) ) * frac;
}

public static float GetEpsilon( Vector2 a, Vector2 b, float frac = 0.0001f )
{
return Math.Max( GetEpsilon( a, frac ), GetEpsilon( b, frac ) );
}
public static Vector2 NormalizeSafe( in Vector2 vec )
{
var length = vec.Length;

if ( length > 9.9999997473787516E-06 )
{
return vec / length;
}
else
{
return 0f;
}
}

public static Vector2 Rotate90( Vector2 v )
{
return new Vector2( v.y, -v.x );
}

public static float Cross( Vector2 a, Vector2 b )
{
return a.x * b.y - a.y * b.x;
}

public static bool LineSegmentsIntersect( Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1 )
{
return Math.Sign( Cross( a0 - b0, b1 - b0 ) ) != Math.Sign( Cross( a1 - b0, b1 - b0 ) )
&& Math.Sign( Cross( b0 - a0, a1 - a0 ) ) != Math.Sign( Cross( b1 - a0, a1 - a0 ) );
}

public static Vector3 RotateNormal( Vector3 oldNormal, float sin, float cos )
{
var normal2d = new Vector2( oldNormal.x, oldNormal.y );

if ( normal2d.LengthSquared <= 0.000001f )
{
return oldNormal;
}

normal2d = NormalizeSafe( normal2d );

return new Vector3( normal2d.x * cos, normal2d.y * cos, sin ).Normal;
}

public static float GetEpsilon( Vector2 vec, float frac = 0.0001f )
{
return Math.Max( Math.Abs( vec.x ), Math.Abs( vec.y ) ) * frac;
}

public static float GetEpsilon( Vector2 a, Vector2 b, float frac = 0.0001f )
{
return Math.Max( GetEpsilon( a, frac ), GetEpsilon( b, frac ) );
}
}
3 changes: 2 additions & 1 deletion PolygonMeshBuilder.Bevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public PolygonMeshBuilder Bevel( float width, float height, float prevAngle, flo
throw new ArgumentOutOfRangeException( nameof( width ) );
}

Validate();
Bevel_UpdateExistingVertices( width, height, prevAngle, nextAngle );

var cutList = Bevel_PossibleCutList ??= new List<(int A, int B)>();
Expand Down Expand Up @@ -445,7 +446,7 @@ private static void UpdateMaxDistance( ref Edge edge, in Edge nextEdge )

if ( dPrev - dNext <= 0.001f )
{
var epsilon = GetEpsilon( thisOrigin, nextOrigin );
var epsilon = GetEpsilon( thisOrigin, nextOrigin, 0.001f );

This comment has been minimized.

Copy link
@Metapyziks

Metapyziks Aug 17, 2023

Author Contributor

This seems to help with Facepunch/sbox-sdf#17

edge.MaxDistance = posDist <= epsilon ? baseDistance : float.PositiveInfinity;
}
else
Expand Down
2 changes: 2 additions & 0 deletions PolygonMeshBuilder.Fill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ partial class PolygonMeshBuilder
/// </summary>
public PolygonMeshBuilder Fill()
{
Validate();

Fill_UpdateExistingVertices();
Fill_SplitIntoMonotonicPolygons();
Fill_Triangulate();
Expand Down
112 changes: 112 additions & 0 deletions PolygonMeshBuilder.Validate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;

namespace Sandbox.Polygons;

partial class PolygonMeshBuilder
{
[ThreadStatic]
private static List<int> Validate_EdgeList;

private string _svg;

private void Validate()
{
if ( _validated )
{
return;
}

_validated = true;

return;

_svg = ToSvg();

// Check active edge loops:
// * Referenced edges must also be active
// * Make sure references are correct in both directions
// * Edges can't reference themselves

foreach ( var edgeIndex in _activeEdges )
{
ref var edge = ref _allEdges[edgeIndex];

if ( !_activeEdges.Contains( edge.NextEdge ) )
{
throw InvalidPolygonException();
}

if ( !_activeEdges.Contains( edge.PrevEdge ) )
{
throw InvalidPolygonException();
}

if ( edge.NextEdge == edge.Index )
{
throw InvalidPolygonException();
}

ref var next = ref _allEdges[edge.NextEdge];

if ( next.PrevEdge != edge.Index )
{
throw InvalidPolygonException();
}
}

// Check for intersecting edges
// TODO: Bentley–Ottmann?

Validate_EdgeList ??= new List<int>();
Validate_EdgeList.Clear();
Validate_EdgeList.AddRange( _activeEdges );

for ( var i = 0; i < Validate_EdgeList.Count; ++i )
{
ref var edgeA0 = ref _allEdges[Validate_EdgeList[i]];
ref var edgeA1 = ref _allEdges[edgeA0.NextEdge];

var a0 = edgeA0.Origin;
var a1 = edgeA1.Origin;

var minA = Vector2.Min( a0 ,a1 );
var maxA = Vector2.Max( a0, a1 );

for ( var j = i + 1; j < Validate_EdgeList.Count; ++j )
{
ref var edgeB0 = ref _allEdges[Validate_EdgeList[j]];

if ( edgeA0.NextEdge == edgeB0.Index || edgeA0.PrevEdge == edgeB0.Index )
{
continue;
}

ref var edgeB1 = ref _allEdges[edgeB0.NextEdge];

var b0 = edgeA0.Origin;
var b1 = edgeA1.Origin;

var minB = Vector2.Min( b0, b1 );
var maxB = Vector2.Max( b0, b1 );

if ( minA.x >= maxB.x || minA.y >= maxB.y || minB.x >= maxA.x || minB.y >= maxA.y )
{
continue;
}

if ( Helpers.LineSegmentsIntersect( a0, a1, b0, b1 ) )
{
throw InvalidPolygonException();
}
}
}

_validated = true;
}

private static Exception InvalidPolygonException()
{
return new Exception( "Invalid polygon" );
}
}
15 changes: 13 additions & 2 deletions PolygonMeshBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public partial class PolygonMeshBuilder : Pooled<PolygonMeshBuilder>

private float _minSmoothNormalDot;

private bool _validated;

/// <summary>
/// Number of edges that will be affected by calls to methods like <see cref="Bevel"/>, <see cref="Round"/>, and <see cref="Close"/>.
/// </summary>
Expand Down Expand Up @@ -100,6 +102,8 @@ public PolygonMeshBuilder Clear()

_minSmoothNormalDot = 0f;

_validated = true;

return this;
}

Expand Down Expand Up @@ -141,6 +145,11 @@ private int AddEdge( Vector2 origin, Vector2 tangent, float distance, int? twinO
return edge.Index;
}

private void Invalidate()
{
_validated = false;
}

/// <summary>
/// Add a set of active edges forming a loop. Clockwise loops will be a solid polygon, and count-clockwise
/// will form a hole. Holes must be inside of solid polygons, otherwise the mesh can't be closed correctly.
Expand All @@ -159,8 +168,9 @@ public PolygonMeshBuilder AddEdgeLoop( IReadOnlyList<Vector2> vertices, int offs
var firstIndex = _nextEdgeIndex;

EnsureCapacity( count );
Invalidate();

var prevVertex = position + vertices[offset + count - 1] * scale;
var prevVertex = position + vertices[offset + count - 1] * scale;
for ( var i = 0; i < count; ++i )
{
var nextVertex = position + vertices[offset + i] * scale;
Expand Down Expand Up @@ -205,8 +215,9 @@ public void AddEdges( IReadOnlyList<Vector2> vertices, IReadOnlyList<(int Prev,
AddEdges_VertexMap.Clear();

EnsureCapacity( edges.Count );
Invalidate();

foreach ( var (i, j) in edges )
foreach ( var (i, j) in edges )
{
var prev = vertices[i];
var next = vertices[j];
Expand Down

0 comments on commit d6d0f02

Please sign in to comment.