{"TotalCount":9,"Files":[{"Ident":"facepunch.libpolygon","Path":"Code/PolygonModelRenderer.cs","FileName":"PolygonModelRenderer.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"\r\nusing System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\npublic class PolygonModelRenderer : ModelRenderer\r\n{\r\n\tprivate Mesh _mesh;\r\n\r\n\tprivate string _svg;\r\n\tprivate bool _meshDirty;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Scalable Vector Graphics source string for this model.\r\n\t/// \u003C/summary\u003E\r\n\t[Property]\r\n\tpublic string Svg\r\n\t{\r\n\t\tget =\u003E _svg;\r\n\t\tset\r\n\t\t{\r\n\t\t\t_svg = value;\r\n\t\t\t_meshDirty = true;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate int _lastHash = 0;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tbase.OnEnabled();\r\n\r\n\t\tUpdateModel();\r\n\t}\r\n\r\n\tprotected override void OnValidate()\r\n\t{\r\n\t\tbase.OnValidate();\r\n\r\n\t\t_meshDirty = true;\r\n\t}\r\n\r\n\tprivate void UpdateModel()\r\n\t{\r\n\t\tif ( !_meshDirty )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar hash = Svg?.FastHash() ?? 0;\r\n\t\tif ( _lastHash == hash )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( Model?.IsProcedural is not true )\r\n\t\t{\r\n\t\t\tModel = null;\r\n\t\t}\r\n\r\n\t\t_lastHash = hash;\r\n\r\n\t\tif ( !string.IsNullOrEmpty( Svg ) )\r\n\t\t{\r\n\t\t\tusing var builder = PolygonMeshBuilder.Rent();\r\n\r\n\t\t\tbuilder.MaxSmoothAngle = 33f.DegreeToRadian();\r\n\r\n\t\t\tbuilder.AddSvg( _svg, new AddSvgOptions\r\n\t\t\t{\r\n\t\t\t\tThrowIfNotSupported = true\r\n\t\t\t}, new Rect( -128f, -128f, 256f, 256f ) );\r\n\t\t\tbuilder.Extrude( 8f );\r\n\t\t\tbuilder.Arc( 2f, 2 );\r\n\t\t\tbuilder.Fill();\r\n\t\t\tbuilder.Mirror();\r\n\r\n\t\t\t_mesh ??= new Mesh( Material.Load( \u0022materials/default/white.vmat\u0022 ) );\r\n\t\t\t_mesh.UpdateMesh( PolygonMeshBuilder.Vertex.Layout, builder.Vertices, builder.Indices );\r\n\t\t\t\r\n\t\t\tModel ??= new ModelBuilder()\r\n\t\t\t\t.AddMesh( _mesh )\r\n\t\t\t\t.Create();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_mesh?.SetIndexRange( 0, 0 );\r\n\t\t}\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tUpdateModel();\r\n\r\n\t\tbase.OnUpdate();\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.Edge.cs","FileName":"PolygonMeshBuilder.Edge.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"\r\nnamespace Sandbox.Polygons;\r\n\r\npartial class PolygonMeshBuilder\r\n{\r\n\tprivate struct Edge\r\n\t{\r\n\t\tpublic int Index { get; }\r\n\r\n\t\tpublic Vector2 Origin { get; }\r\n\t\tpublic Vector2 Tangent { get; }\r\n\t\tpublic Vector2 Normal { get; }\r\n\r\n\t\tpublic Vector2 Velocity { get; set; }\r\n\r\n\t\tpublic int PrevEdge { get; set; }\r\n\t\tpublic int NextEdge { get; set; }\r\n\r\n\t\tpublic float Distance { get; set; }\r\n\t\tpublic float MaxDistance { get; set; }\r\n\r\n\t\tpublic (int Prev, int Next) Vertices { get; set; }\r\n\r\n\t\tpublic int Twin { get; }\r\n\r\n\t\tpublic Edge( int index, Vector2 origin, Vector2 tangent, float distance, int twin = -1 )\r\n\t\t{\r\n\t\t\tIndex = index;\r\n\r\n\t\t\tOrigin = origin;\r\n\t\t\tTangent = tangent;\r\n\t\t\tNormal = Helpers.Rotate90( tangent );\r\n\r\n\t\t\tVelocity = Vector2.Zero;\r\n\r\n\t\t\tPrevEdge = -1;\r\n\t\t\tNextEdge = -1;\r\n\r\n\t\t\tVertices = (-1, -1);\r\n\r\n\t\t\tDistance = distance;\r\n\t\t\tMaxDistance = float.PositiveInfinity;\r\n\r\n\t\t\tTwin = twin;\r\n\t\t}\r\n\r\n\t\tpublic readonly Vector2 Project( float distance )\r\n\t\t{\r\n\t\t\treturn Origin \u002B Velocity * (distance - Distance);\r\n\t\t}\r\n\r\n\t\tpublic override string ToString()\r\n\t\t{\r\n\t\t\treturn $\u0022{(char) (\u0027A\u0027 \u002B Index)}\u0022;\r\n\t\t}\r\n\r\n\t\tpublic bool Equals( Edge other )\r\n\t\t{\r\n\t\t\treturn Index == other.Index;\r\n\t\t}\r\n\r\n\t\tpublic override bool Equals( object obj )\r\n\t\t{\r\n\t\t\treturn obj is Edge other \u0026\u0026 Equals( other );\r\n\t\t}\r\n\r\n\t\tpublic override int GetHashCode()\r\n\t\t{\r\n\t\t\treturn Index;\r\n\t\t}\r\n\t}\r\n}"},{"Ident":"facepunch.libpolygon","Path":"Code/Helpers.cs","FileName":"Helpers.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\ninternal static class Helpers\r\n{\r\n\tpublic static Vector2 NormalizeSafe( in Vector2 vec )\r\n\t{\r\n\t\tvar length = vec.Length;\r\n\r\n\t\tif ( length \u003E 9.9999997473787516E-06 )\r\n\t\t{\r\n\t\t\treturn vec / length;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static Vector2 Rotate90( Vector2 v )\r\n\t{\r\n\t\treturn new Vector2( v.y, -v.x );\r\n\t}\r\n\r\n\tpublic static float Cross( Vector2 a, Vector2 b )\r\n\t{\r\n\t\treturn a.x * b.y - a.y * b.x;\r\n\t}\r\n\r\n\tpublic static bool LineSegmentsIntersect( Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1 )\r\n\t{\r\n\t\treturn Math.Sign( Cross( a0 - b0, b1 - b0 ) ) != Math.Sign( Cross( a1 - b0, b1 - b0 ) )\r\n\t\t\t\u0026\u0026 Math.Sign( Cross( b0 - a0, a1 - a0 ) ) != Math.Sign( Cross( b1 - a0, a1 - a0 ) );\r\n\t}\r\n\r\n\tpublic static Vector3 RotateNormal( Vector3 oldNormal, float sin, float cos )\r\n\t{\r\n\t\tvar normal2d = new Vector2( oldNormal.x, oldNormal.y );\r\n\r\n\t\tif ( normal2d.LengthSquared \u003C= 0.000001f )\r\n\t\t{\r\n\t\t\treturn oldNormal;\r\n\t\t}\r\n\r\n\t\tnormal2d = NormalizeSafe( normal2d );\r\n\r\n\t\treturn new Vector3( normal2d.x * cos, normal2d.y * cos, sin ).Normal;\r\n\t}\r\n\r\n\tpublic static float GetEpsilon( Vector2 vec, float frac = 0.0001f )\r\n\t{\r\n\t\treturn Math.Max( Math.Abs( vec.x ), Math.Abs( vec.y ) ) * frac;\r\n\t}\r\n\r\n\tpublic static float GetEpsilon( Vector2 a, Vector2 b, float frac = 0.0001f )\r\n\t{\r\n\t\treturn Math.Max( GetEpsilon( a, frac ), GetEpsilon( b, frac ) );\r\n\t}\r\n\r\n\tpublic static void UpdateMesh\u003CT\u003E( this Mesh mesh, VertexAttribute[] layout, List\u003CT\u003E vertices, List\u003Cint\u003E indices )\r\n\t\twhere T : unmanaged\r\n\t{\r\n\t\tif ( !mesh.HasIndexBuffer )\r\n\t\t{\r\n\t\t\tmesh.CreateVertexBuffer( vertices.Count, layout, vertices );\r\n\t\t\tmesh.CreateIndexBuffer( indices.Count, indices );\r\n\t\t}\r\n\t\telse if ( indices.Count \u003E 0 \u0026\u0026 vertices.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tmesh.SetIndexBufferSize( indices.Count );\r\n\t\t\tmesh.SetVertexBufferSize( vertices.Count );\r\n\r\n\t\t\tmesh.SetVertexBufferData( vertices );\r\n\t\t\tmesh.SetIndexBufferData( indices );\r\n\t\t}\r\n\r\n\t\tmesh.SetIndexRange( 0, indices.Count );\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.Fill.cs","FileName":"PolygonMeshBuilder.Fill.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\npartial class PolygonMeshBuilder\r\n{\r\n\t/// \u003Csummary\u003E\r\n\t/// Triangulate any remaining active edges so that the generated mesh is closed.\r\n\t/// \u003C/summary\u003E\r\n\tpublic PolygonMeshBuilder Fill()\r\n\t{\r\n\t\tValidate();\r\n\r\n\t\tFill_UpdateExistingVertices();\r\n\t\tFill_SplitIntoMonotonicPolygons();\r\n\t\tFill_Triangulate();\r\n\r\n\t\tPostBevel();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate enum SweepEvent\r\n\t{\r\n\t\tStart,\r\n\t\tEnd,\r\n\t\tSplit,\r\n\t\tMerge,\r\n\t\tUpper,\r\n\t\tLower\r\n\t}\r\n\r\n\tprivate static SweepEvent CategorizeEvent( in Edge prev, in Edge curr, in Edge next )\r\n\t{\r\n\t\tvar prevLeft = Compare( prev.Origin, curr.Origin ) \u003C 0;\r\n\t\tvar nextLeft = Compare( next.Origin, curr.Origin ) \u003C 0;\r\n\r\n\t\tvar nextBelow = curr.Tangent.y \u003C -prev.Tangent.y;\r\n\r\n\t\tswitch (prevLeft, nextLeft, nextBelow)\r\n\t\t{\r\n\t\t\tcase (false, false, false ):\r\n\t\t\t\treturn SweepEvent.Start;\r\n\r\n\t\t\tcase (true, true, true ):\r\n\t\t\t\treturn SweepEvent.End;\r\n\r\n\t\t\tcase (false, false, true ):\r\n\t\t\t\treturn SweepEvent.Split;\r\n\r\n\t\t\tcase (true, true, false ):\r\n\t\t\t\treturn SweepEvent.Merge;\r\n\r\n\t\t\tcase (true, false, _ ):\r\n\t\t\t\treturn SweepEvent.Upper;\r\n\r\n\t\t\tcase (false, true, _ ):\r\n\t\t\t\treturn SweepEvent.Lower;\r\n\t\t}\r\n\t}\r\n\r\n\t[ThreadStatic]\r\n\tprivate static List\u003Cint\u003E Fill_SortedEdges;\r\n\r\n\t[ThreadStatic]\r\n\tprivate static Dictionary\u003Cint, (int Index, bool WasMerge)\u003E Fill_Helpers;\r\n\r\n\t[ThreadStatic]\r\n\tprivate static List\u003CSweepEdge\u003E Fill_SweepEdges;\r\n\r\n\tprivate readonly struct SweepEdge\r\n\t{\r\n\t\tpublic int Index { get; }\r\n\r\n\t\tpublic Vector2 Origin { get; }\r\n\t\tpublic float DeltaY { get; }\r\n\r\n\t\tpublic SweepEdge( in Edge edge )\r\n\t\t{\r\n\t\t\tIndex = edge.Index;\r\n\r\n\t\t\tOrigin = edge.Origin;\r\n\t\t\tDeltaY = Math.Abs( edge.Tangent.x ) \u003C= 0.0001f\r\n\t\t\t\t? 0f : edge.Tangent.y / edge.Tangent.x;\r\n\t\t}\r\n\r\n\t\tpublic float GetEdgeY( float x )\r\n\t\t{\r\n\t\t\treturn Origin.y \u002B DeltaY * (x - Origin.x);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate int ConnectTwoWay( ref Edge a, ref Edge b )\r\n\t{\r\n\t\tref var prevA = ref _allEdges[a.PrevEdge];\r\n\t\tref var prevB = ref _allEdges[b.PrevEdge];\r\n\r\n\t\tref var aNew = ref _allEdges[AddEdge( a.Origin, (b.Origin - a.Origin).Normal, a.Distance )];\r\n\t\tref var bNew = ref _allEdges[AddEdge( b.Origin, (a.Origin - b.Origin).Normal, b.Distance )];\r\n\r\n\t\taNew.Vertices = AddVertices( ref a );\r\n\t\tbNew.Vertices = AddVertices( ref b );\r\n\r\n\t\tSimpleConnectEdges( ref prevA, ref aNew );\r\n\t\tSimpleConnectEdges( ref aNew, ref b );\r\n\r\n\t\tSimpleConnectEdges( ref prevB, ref bNew );\r\n\t\tSimpleConnectEdges( ref bNew, ref a );\r\n\r\n\t\t_activeEdges.Add( aNew.Index );\r\n\t\t_activeEdges.Add( bNew.Index );\r\n\r\n\t\treturn aNew.Index;\r\n\t}\r\n\r\n\tprivate int FixUp( ref Edge v, in Edge e )\r\n\t{\r\n\t\tvar helperInfo = Fill_Helpers[e.Index];\r\n\r\n\t\tif ( helperInfo.WasMerge )\r\n\t\t{\r\n\t\t\treturn ConnectTwoWay( ref v, ref _allEdges[helperInfo.Index] );\r\n\t\t}\r\n\r\n\t\treturn v.Index;\r\n\t}\r\n\r\n\tprivate void SetHelper( in Edge edge, in Edge helper, bool wasMerge )\r\n\t{\r\n\t\tFill_Helpers[edge.Index] = (helper.Index, wasMerge);\r\n\t}\r\n\r\n\tprivate void AddSweepEdge( in Edge edge )\r\n\t{\r\n\t\t// TODO: could binary search for insertion point\r\n\r\n\t\tvar origin = edge.Origin;\r\n\t\tFill_SweepEdges.Add( new SweepEdge( edge ) );\r\n\t\tFill_SweepEdges.Sort( ( a, b ) =\u003E\r\n\t\t\ta.GetEdgeY( origin.x ).CompareTo( b.GetEdgeY( origin.x ) ) );\r\n\t}\r\n\r\n\tprivate void ReplaceSweepEdge( in Edge old, in Edge replacement )\r\n\t{\r\n\t\t// TODO: could binary search\r\n\r\n\t\tfor ( var i = 0; i \u003C Fill_SweepEdges.Count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tif ( Fill_SweepEdges[i].Index == old.Index )\r\n\t\t\t{\r\n\t\t\t\tFill_SweepEdges[i] = new SweepEdge( in replacement );\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void RemoveSweepEdge( in Edge edge )\r\n\t{\r\n\t\t// TODO: could binary search\r\n\r\n\t\tfor ( var i = 0; i \u003C Fill_SweepEdges.Count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tif ( Fill_SweepEdges[i].Index == edge.Index )\r\n\t\t\t{\r\n\t\t\t\tFill_SweepEdges.RemoveAt( i );\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate int FindAboveSweepEdge( in Edge edge )\r\n\t{\r\n\t\t// TODO: could binary search\r\n\r\n\t\tforeach ( var other in Fill_SweepEdges )\r\n\t\t{\r\n\t\t\tif ( edge.PrevEdge == other.Index || edge.Index == other.Index )\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( other.GetEdgeY( edge.Origin.x ) - edge.Origin.y \u003E= 0f )\r\n\t\t\t{\r\n\t\t\t\treturn other.Index;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthrow new Exception();\r\n\t}\r\n\r\n\tprivate void Fill_UpdateExistingVertices()\r\n\t{\r\n\t\t_nextAngle = MathF.PI * 0.5f;\r\n\t\t_nextDistance = float.PositiveInfinity;\r\n\r\n\t\tif ( !SkipNormals \u0026\u0026 Math.Abs( _prevAngle - _nextAngle ) \u003E= 0.001f )\r\n\t\t{\r\n\t\t\tforeach ( var index in _activeEdges )\r\n\t\t\t{\r\n\t\t\t\tref var edge = ref _allEdges[index];\r\n\t\t\t\tedge.Vertices = (-1, -1);\r\n\r\n\t\t\t\tAddVertices( ref edge, true );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_prevAngle = _nextAngle;\r\n\t}\r\n\r\n\tprivate void Fill_SplitIntoMonotonicPolygons()\r\n\t{\r\n\t\tFill_SortedEdges ??= new List\u003Cint\u003E();\r\n\t\tFill_SortedEdges.Clear();\r\n\r\n\t\tFill_SortedEdges.AddRange( _activeEdges );\r\n\r\n\t\tFill_SortedEdges.Sort( ( a, b ) =\u003E Compare( _allEdges[a].Origin, _allEdges[b].Origin ) );\r\n\r\n\t\tFill_Helpers ??= new Dictionary\u003Cint, (int Index, bool WasMerge)\u003E();\r\n\t\tFill_Helpers.Clear();\r\n\r\n\t\tFill_SweepEdges ??= new List\u003CSweepEdge\u003E();\r\n\t\tFill_SweepEdges.Clear();\r\n\r\n\t\t// Based on https://www.cs.umd.edu/class/spring2020/cmsc754/Lects/lect05-triangulate.pdf\r\n\r\n\t\t// Add pairs of edges to split into x-monotonic polygons\r\n\r\n\t\tforeach ( var index in Fill_SortedEdges )\r\n\t\t{\r\n\t\t\tEnsureCapacity( 4 );\r\n\r\n\t\t\tref var edge = ref _allEdges[index];\r\n\t\t\tref var next = ref _allEdges[edge.NextEdge];\r\n\t\t\tref var prev = ref _allEdges[edge.PrevEdge];\r\n\r\n\t\t\tswitch ( CategorizeEvent( in prev, in edge, in next ) )\r\n\t\t\t{\r\n\t\t\t\tcase SweepEvent.Start:\r\n\t\t\t\t\tAddSweepEdge( in edge );\r\n\t\t\t\t\tSetHelper( in edge, in edge, false );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase SweepEvent.End:\r\n\t\t\t\t\tFixUp( ref edge, in prev );\r\n\t\t\t\t\tRemoveSweepEdge( in prev );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase SweepEvent.Split:\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tref var above = ref _allEdges[FindAboveSweepEdge( in edge )];\r\n\t\t\t\t\t\tref var helper = ref _allEdges[Fill_Helpers[above.Index].Index];\r\n\t\t\t\t\t\tref var fixedUp = ref _allEdges[ConnectTwoWay( ref edge, ref helper )];\r\n\t\t\t\t\t\tAddSweepEdge( in edge );\r\n\t\t\t\t\t\tSetHelper( in above, in fixedUp, false );\r\n\t\t\t\t\t\tSetHelper( in edge, in edge, false );\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\tcase SweepEvent.Merge:\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tref var above = ref _allEdges[FindAboveSweepEdge( in edge )];\r\n\t\t\t\t\t\tRemoveSweepEdge( in prev );\r\n\t\t\t\t\t\tref var new1 = ref _allEdges[FixUp( ref edge, in above )];\r\n\t\t\t\t\t\tFixUp( ref new1, in prev );\r\n\t\t\t\t\t\tSetHelper( in above, in new1, true );\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\tcase SweepEvent.Upper:\r\n\t\t\t\t\tFixUp( ref edge, in prev );\r\n\t\t\t\t\tReplaceSweepEdge( in prev, in edge );\r\n\t\t\t\t\tSetHelper( in edge, in edge, false );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase SweepEvent.Lower:\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tref var above = ref _allEdges[FindAboveSweepEdge( in edge )];\r\n\t\t\t\t\t\tref var helper = ref _allEdges[FixUp( ref edge, in above )];\r\n\t\t\t\t\t\tSetHelper( in above, in helper, false );\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate readonly struct CloseVertex\r\n\t{\r\n\t\tpublic Vector2 Position { get; }\r\n\r\n\t\t/// \u003Csummary\u003E\r\n\t\t/// Difference to this vertex from the previous one.\r\n\t\t/// \u003C/summary\u003E\r\n\t\tpublic Vector2 Delta { get; }\r\n\r\n\t\tpublic int Vertex { get; }\r\n\t\tpublic bool IsUpper { get; }\r\n\r\n\t\tpublic CloseVertex( Vector2 position, Vector2 delta, int vertex, bool isUpper )\r\n\t\t{\r\n\t\t\tPosition = position;\r\n\t\t\tDelta = delta;\r\n\t\t\tVertex = vertex;\r\n\t\t\tIsUpper = isUpper;\r\n\t\t}\r\n\t}\r\n\r\n\t[ThreadStatic]\r\n\tprivate static List\u003CCloseVertex\u003E Fill_Vertices;\r\n\r\n\t[ThreadStatic]\r\n\tprivate static Stack\u003CCloseVertex\u003E Fill_Stack;\r\n\r\n\tprivate static bool IsReflex( Vector2 prevDelta, Vector2 nextDelta )\r\n\t{\r\n\t\treturn Vector2.Dot( Helpers.Rotate90( nextDelta ), prevDelta ) \u003E= 0f;\r\n\t}\r\n\r\n\tprivate static int Compare( Vector2 a, Vector2 b )\r\n\t{\r\n\t\tvar xCompare = a.x.CompareTo( b.x );\r\n\t\tif ( xCompare != 0 ) return xCompare;\r\n\t\treturn a.y.CompareTo( b.y );\r\n\t}\r\n\r\n\tprivate void Fill_Triangulate()\r\n\t{\r\n\t\tFill_Vertices ??= new List\u003CCloseVertex\u003E();\r\n\t\tFill_Stack ??= new Stack\u003CCloseVertex\u003E();\r\n\r\n\t\twhile ( _activeEdges.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tvar firstIndex = _activeEdges.First();\r\n\t\t\t_activeEdges.Remove( firstIndex );\r\n\r\n\t\t\tvar first = _allEdges[firstIndex];\r\n\r\n\t\t\tvar minPos = first.Origin;\r\n\t\t\tvar maxPos = first.Origin;\r\n\t\t\tvar minEdgeIndex = firstIndex;\r\n\t\t\tvar maxEdgeIndex = firstIndex;\r\n\r\n\t\t\tvar edge = first;\r\n\r\n\t\t\twhile ( edge.NextEdge != first.Index )\r\n\t\t\t{\r\n\t\t\t\tedge = _allEdges[edge.NextEdge];\r\n\t\t\t\t_activeEdges.Remove( edge.Index );\r\n\r\n\t\t\t\tif ( Compare( edge.Origin, minPos ) \u003C 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\tminPos = edge.Origin;\r\n\t\t\t\t\tminEdgeIndex = edge.Index;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( Compare( edge.Origin, maxPos ) \u003E 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\tmaxPos = edge.Origin;\r\n\t\t\t\t\tmaxEdgeIndex = edge.Index;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tFill_Vertices.Clear();\r\n\r\n\t\t\tedge = _allEdges[minEdgeIndex];\r\n\t\t\tFill_Vertices.Add( new CloseVertex( edge.Origin, default, edge.Vertices.Prev, true ) );\r\n\r\n\t\t\twhile ( edge.NextEdge != maxEdgeIndex )\r\n\t\t\t{\r\n\t\t\t\tvar next = _allEdges[edge.NextEdge];\r\n\t\t\t\tFill_Vertices.Add( new CloseVertex( next.Origin, next.Origin - edge.Origin, next.Vertices.Prev, true ) );\r\n\t\t\t\tedge = next;\r\n\t\t\t}\r\n\r\n\t\t\tedge = _allEdges[maxEdgeIndex];\r\n\r\n\t\t\twhile ( edge.Index != minEdgeIndex )\r\n\t\t\t{\r\n\t\t\t\tvar next = _allEdges[edge.NextEdge];\r\n\t\t\t\tFill_Vertices.Add( new CloseVertex( edge.Origin, edge.Origin - next.Origin, edge.Vertices.Prev, false ) );\r\n\t\t\t\tedge = next;\r\n\t\t\t}\r\n\r\n\t\t\tFill_Vertices.Sort( ( a, b ) =\u003E Compare( a.Position, b.Position ) );\r\n\r\n\t\t\tFill_Stack.Clear();\r\n\t\t\tFill_Stack.Push( Fill_Vertices[0] );\r\n\t\t\tFill_Stack.Push( Fill_Vertices[1] );\r\n\r\n\t\t\tfor ( var i = 2; i \u003C Fill_Vertices.Count; \u002B\u002Bi )\r\n\t\t\t{\r\n\t\t\t\tvar next = Fill_Vertices[i];\r\n\t\t\t\tvar top = Fill_Stack.Peek();\r\n\r\n\t\t\t\tif ( top.IsUpper != next.IsUpper )\r\n\t\t\t\t{\r\n\t\t\t\t\t// Case 1\r\n\r\n\t\t\t\t\twhile ( Fill_Stack.Count \u003E 1 )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar curr = Fill_Stack.Pop();\r\n\t\t\t\t\t\tvar prev = Fill_Stack.Peek();\r\n\r\n\t\t\t\t\t\tif ( next.IsUpper )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tAddTriangle( next.Vertex, prev.Vertex, curr.Vertex );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tAddTriangle( next.Vertex, curr.Vertex, prev.Vertex );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tFill_Stack.Clear();\r\n\t\t\t\t\tFill_Stack.Push( top );\r\n\t\t\t\t\tFill_Stack.Push( new CloseVertex( next.Position,\r\n\t\t\t\t\t\tnext.Position - top.Position,\r\n\t\t\t\t\t\tnext.Vertex, next.IsUpper ) );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\twhile ( Fill_Stack.Count \u003E 1 \u0026\u0026 IsReflex( top.Delta, next.Position - top.Position ) != top.IsUpper )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar curr = Fill_Stack.Pop();\r\n\t\t\t\t\ttop = Fill_Stack.Peek();\r\n\r\n\t\t\t\t\tif ( next.IsUpper )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tAddTriangle( next.Vertex, curr.Vertex, top.Vertex );\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tAddTriangle( next.Vertex, top.Vertex, curr.Vertex );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tFill_Stack.Push( new CloseVertex( next.Position,\r\n\t\t\t\t\tnext.Position - top.Position,\r\n\t\t\t\t\tnext.Vertex, next.IsUpper ) );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.Validate.cs","FileName":"PolygonMeshBuilder.Validate.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\npartial class PolygonMeshBuilder\r\n{\r\n\t[ThreadStatic]\r\n\tprivate static List\u003Cint\u003E Validate_EdgeList;\r\n\r\n\tprivate void Validate()\r\n\t{\r\n\t\tif ( _validated )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Check active edge loops:\r\n\t\t// * Referenced edges must also be active\r\n\t\t// * Make sure references are correct in both directions\r\n\t\t// * Edges can\u0027t reference themselves\r\n\r\n\t\tforeach ( var edgeIndex in _activeEdges )\r\n\t\t{\r\n\t\t\tref var edge = ref _allEdges[edgeIndex];\r\n\r\n\t\t\tif ( !_activeEdges.Contains( edge.NextEdge ) )\r\n\t\t\t{\r\n\t\t\t\tthrow InvalidPolygonException();\r\n\t\t\t}\r\n\r\n\t\t\tif ( !_activeEdges.Contains( edge.PrevEdge ) )\r\n\t\t\t{\r\n\t\t\t\tthrow InvalidPolygonException();\r\n\t\t\t}\r\n\r\n\t\t\tif ( edge.NextEdge == edge.Index )\r\n\t\t\t{\r\n\t\t\t\tthrow InvalidPolygonException();\r\n\t\t\t}\r\n\r\n\t\t\tref var next = ref _allEdges[edge.NextEdge];\r\n\r\n\t\t\tif ( next.PrevEdge != edge.Index )\r\n\t\t\t{\r\n\t\t\t\tthrow InvalidPolygonException();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Check for intersecting edges\r\n\t\t// TODO: Bentley\u2013Ottmann?\r\n\r\n\t\tValidate_EdgeList ??= new List\u003Cint\u003E();\r\n\t\tValidate_EdgeList.Clear();\r\n\t\tValidate_EdgeList.AddRange( _activeEdges );\r\n\r\n\t\tfor ( var i = 0; i \u003C Validate_EdgeList.Count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tref var edgeA0 = ref _allEdges[Validate_EdgeList[i]];\r\n\t\t\tref var edgeA1 = ref _allEdges[edgeA0.NextEdge];\r\n\r\n\t\t\tvar a0 = edgeA0.Origin;\r\n\t\t\tvar a1 = edgeA1.Origin;\r\n\r\n\t\t\tvar minA = Vector2.Min( a0 ,a1 );\r\n\t\t\tvar maxA = Vector2.Max( a0, a1 );\r\n\r\n\t\t\tfor ( var j = i \u002B 1; j \u003C Validate_EdgeList.Count; \u002B\u002Bj )\r\n\t\t\t{\r\n\t\t\t\tref var edgeB0 = ref _allEdges[Validate_EdgeList[j]];\r\n\r\n\t\t\t\tif ( edgeA0.NextEdge == edgeB0.Index || edgeA0.PrevEdge == edgeB0.Index )\r\n\t\t\t\t{\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tref var edgeB1 = ref _allEdges[edgeB0.NextEdge];\r\n\r\n\t\t\t\tvar b0 = edgeA0.Origin;\r\n\t\t\t\tvar b1 = edgeA1.Origin;\r\n\r\n\t\t\t\tvar minB = Vector2.Min( b0, b1 );\r\n\t\t\t\tvar maxB = Vector2.Max( b0, b1 );\r\n\r\n\t\t\t\tif ( minA.x \u003E= maxB.x || minA.y \u003E= maxB.y || minB.x \u003E= maxA.x || minB.y \u003E= maxA.y )\r\n\t\t\t\t{\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( Helpers.LineSegmentsIntersect( a0, a1, b0, b1 ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tthrow InvalidPolygonException();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_validated = true;\r\n\t}\r\n\r\n\tprivate static Exception InvalidPolygonException()\r\n\t{\r\n\t\treturn new Exception( \u0022Invalid polygon\u0022 );\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.cs","FileName":"PolygonMeshBuilder.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Runtime.CompilerServices;\r\nusing System.Runtime.InteropServices;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\n/// \u003Csummary\u003E\r\n/// Helper class for building 3D meshes based on a 2D polygon. Supports\r\n/// concave polygons with holes, although edges must not intersect.\r\n/// \u003C/summary\u003E\r\npublic partial class PolygonMeshBuilder : Pooled\u003CPolygonMeshBuilder\u003E\r\n{\r\n\tpublic record struct Vertex( Vector3 Position, Vector3 Normal, Vector4 Tangent )\r\n\t{\r\n\t\tpublic static VertexAttribute[] Layout { get; } = new[]\r\n\t\t{\r\n\t\t\tnew VertexAttribute( VertexAttributeType.Position, VertexAttributeFormat.Float32 ),\r\n\t\t\tnew VertexAttribute( VertexAttributeType.Normal, VertexAttributeFormat.Float32 ),\r\n\t\t\tnew VertexAttribute( VertexAttributeType.Tangent, VertexAttributeFormat.Float32, 4 )\r\n\t\t};\r\n\t}\r\n\r\n\tprivate int _nextEdgeIndex;\r\n\tprivate Edge[] _allEdges = new Edge[64];\r\n\tprivate readonly HashSet\u003Cint\u003E _activeEdges = new ();\r\n\r\n\tprivate readonly List\u003CVertex\u003E _vertices = new ();\r\n\tprivate readonly List\u003Cint\u003E _indices = new ();\r\n\r\n\tprivate float _prevDistance;\r\n\tprivate float _nextDistance;\r\n\r\n\tprivate float _invDistance;\r\n\r\n\tprivate float _prevHeight;\r\n\tprivate float _nextHeight;\r\n\r\n\tprivate float _prevAngle;\r\n\tprivate float _nextAngle;\r\n\r\n\tprivate float _minSmoothNormalDot;\r\n\r\n\tprivate bool _validated;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Number of edges that will be affected by calls to methods like \u003Csee cref=\u0022Bevel\u0022/\u003E, \u003Csee cref=\u0022Round\u0022/\u003E, and \u003Csee cref=\u0022Close\u0022/\u003E.\r\n\t/// \u003C/summary\u003E\r\n\tpublic int ActiveEdgeCount =\u003E _activeEdges.Count;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// If true, no active edges remain because the mesh is fully closed.\r\n\t/// \u003C/summary\u003E\r\n\tpublic bool IsClosed =\u003E _activeEdges.Count == 0;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Corners of the original polygon with an interior or exterior\r\n\t/// angle less than this (in radians) will have smooth normals.\r\n\t/// \u003C/summary\u003E\r\n\tpublic float MaxSmoothAngle { get; set; } = 0f;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// If true, don\u0027t bother generating normals / tangents.\r\n\t/// \u003C/summary\u003E\r\n\tpublic bool SkipNormals { get; set; }\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Positions of each vertex in the generated mesh.\r\n\t/// \u003C/summary\u003E\r\n\tpublic IEnumerable\u003CVector3\u003E Positions =\u003E _vertices.Select( x =\u003E x.Position );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Normals of each vertex in the generated mesh.\r\n\t/// \u003C/summary\u003E\r\n\tpublic IEnumerable\u003CVector3\u003E Normals =\u003E _vertices.Select( x =\u003E x.Normal );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// U-tangents, and the signs of the V-tangents, of each vertex in the generated mesh.\r\n\t/// \u003C/summary\u003E\r\n\tpublic IEnumerable\u003CVector4\u003E Tangents =\u003E _vertices.Select( x =\u003E x.Tangent );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Positions, normals, and tangents of each vertex.\r\n\t/// \u003C/summary\u003E\r\n\tpublic List\u003CVertex\u003E Vertices =\u003E _vertices;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Indices of vertices describing the triangulation of the generated mesh.\r\n\t/// \u003C/summary\u003E\r\n\tpublic List\u003Cint\u003E Indices =\u003E _indices;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Clear all geometry from this builder.\r\n\t/// \u003C/summary\u003E\r\n\tpublic PolygonMeshBuilder Clear()\r\n\t{\r\n\t\t_nextEdgeIndex = 0;\r\n\t\t_activeEdges.Clear();\r\n\r\n\t\t_vertices.Clear();\r\n\t\t_indices.Clear();\r\n\r\n\t\t_prevDistance = 0f;\r\n\t\t_nextDistance = 0f;\r\n\r\n\t\t_invDistance = 0f;\r\n\r\n\t\t_prevHeight = 0f;\r\n\t\t_nextHeight = 0f;\r\n\r\n\t\t_prevAngle = 0f;\r\n\t\t_nextAngle = 0f;\r\n\r\n\t\t_minSmoothNormalDot = 0f;\r\n\r\n\t\t_validated = true;\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Reset this builder to be like a new instance.\r\n\t/// \u003C/summary\u003E\r\n\tpublic override void Reset()\r\n\t{\r\n\t\tClear();\r\n\r\n\t\tMaxSmoothAngle = 0f;\r\n\t\tSkipNormals = false;\r\n\t}\r\n\r\n\tprivate static int NextPowerOfTwo( int value )\r\n\t{\r\n\t\tvar po2 = 1;\r\n\t\twhile ( po2 \u003C value )\r\n\t\t{\r\n\t\t\tpo2 \u003C\u003C= 1;\r\n\t\t}\r\n\r\n\t\treturn po2;\r\n\t}\r\n\r\n\tprivate void EnsureCapacity( int toAdd )\r\n\t{\r\n\t\tif ( _nextEdgeIndex \u002B toAdd \u003E _allEdges.Length )\r\n\t\t{\r\n\t\t\tArray.Resize( ref _allEdges, NextPowerOfTwo( _nextEdgeIndex \u002B toAdd ) );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate int AddEdge( Vector2 origin, Vector2 tangent, float distance, int? twinOffset = null )\r\n\t{\r\n\t\tvar edge = new Edge( _nextEdgeIndex, origin, tangent, distance, twinOffset != null ? _nextEdgeIndex \u002B twinOffset.Value : -1 );\r\n\t\t_allEdges[edge.Index] = edge;\r\n\t\t\u002B\u002B_nextEdgeIndex;\r\n\t\treturn edge.Index;\r\n\t}\r\n\r\n\tprivate void Invalidate()\r\n\t{\r\n\t\t_validated = false;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add a set of active edges forming a loop. Clockwise loops will be a solid polygon, and count-clockwise\r\n\t/// will form a hole. Holes must be inside of solid polygons, otherwise the mesh can\u0027t be closed correctly.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022vertices\u0022\u003EList of vertices to read a range from.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022offset\u0022\u003EIndex of the first vertex in the loop.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022count\u0022\u003ENumber of vertices in the loop.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022reverse\u0022\u003EIf true, reverse the order of the vertices in the loop.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder AddEdgeLoop( IReadOnlyList\u003CVector2\u003E vertices, int offset, int count, bool reverse = false )\r\n\t{\r\n\t\treturn AddEdgeLoop( vertices, offset, count, Vector2.Zero, Vector2.One, reverse );\r\n\t}\r\n\r\n\tpublic PolygonMeshBuilder AddEdgeLoop( IReadOnlyList\u003CVector2\u003E vertices, int offset, int count, Vector2 position, Vector2 scale, bool reverse = false )\r\n\t{\r\n\t\tvar firstIndex = _nextEdgeIndex;\r\n\r\n\t\tEnsureCapacity( count );\r\n\t\tInvalidate();\r\n\r\n        var prevVertex = position \u002B vertices[offset \u002B count - 1] * scale;\r\n\t\tfor ( var i = 0; i \u003C count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tvar nextVertex = position \u002B vertices[offset \u002B i] * scale;\r\n\r\n\t\t\t_activeEdges.Add( AddEdge( prevVertex, Helpers.NormalizeSafe( nextVertex - prevVertex ), _prevDistance ) );\r\n\r\n\t\t\tprevVertex = nextVertex;\r\n\t\t}\r\n\r\n\t\tvar prevIndex = count - 1;\r\n\t\tfor ( var i = 0; i \u003C count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tref var prevEdge = ref _allEdges[firstIndex \u002B prevIndex];\r\n\t\t\tref var nextEdge = ref _allEdges[firstIndex \u002B i];\r\n\r\n\t\t\tif ( reverse )\r\n\t\t\t{\r\n\t\t\t\tConnectEdges( ref nextEdge, ref prevEdge );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tConnectEdges( ref prevEdge, ref nextEdge );\r\n\t\t\t}\r\n\r\n\t\t\tprevIndex = i;\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t[ThreadStatic]\r\n\tprivate static Dictionary\u003Cint, int\u003E AddEdges_VertexMap;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add a raw set of edges. Be careful to ensure that each loop of edges is fully closed.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022vertices\u0022\u003EPositions of vertices to connect with edges.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022edges\u0022\u003EIndices of the start and end vertices of each edge.\u003C/param\u003E\r\n\tpublic void AddEdges( IReadOnlyList\u003CVector2\u003E vertices, IReadOnlyList\u003C(int Prev, int Next)\u003E edges )\r\n\t{\r\n\t\tAddEdges_VertexMap ??= new Dictionary\u003Cint, int\u003E();\r\n\t\tAddEdges_VertexMap.Clear();\r\n\r\n\t\tEnsureCapacity( edges.Count );\r\n\t\tInvalidate();\r\n\r\n        foreach ( var (i, j) in edges )\r\n\t\t{\r\n\t\t\tvar prev = vertices[i];\r\n\t\t\tvar next = vertices[j];\r\n\r\n\t\t\tvar index = AddEdge( prev, Helpers.NormalizeSafe( next - prev ), _prevDistance );\r\n\r\n\t\t\t_activeEdges.Add( index );\r\n\t\t\tAddEdges_VertexMap.Add( i, index );\r\n\t\t}\r\n\r\n\t\tfor ( var i = 0; i \u003C edges.Count; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tvar edge = edges[i];\r\n\r\n\t\t\tref var prev = ref _allEdges[AddEdges_VertexMap[edge.Prev]];\r\n\t\t\tref var next = ref _allEdges[AddEdges_VertexMap[edge.Next]];\r\n\r\n\t\t\tConnectEdges( ref prev, ref next );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static float LerpRadians( float a, float b, float t )\r\n\t{\r\n\t\tvar delta = b - a;\r\n\t\tdelta -= MathF.Floor( delta * (0.5f / MathF.PI) ) * MathF.PI * 2f;\r\n\r\n\t\tif ( delta \u003E MathF.PI )\r\n\t\t{\r\n\t\t\tdelta -= MathF.PI * 2f;\r\n\t\t}\r\n\r\n\t\treturn a \u002B delta * Math.Clamp( t, 0f, 1f );\r\n\t}\r\n\r\n\tprivate Vector4 GetTangent( Vector3 normal )\r\n\t{\r\n\t\tvar tangent = Vector3.Cross( normal, new Vector3( 0f, 0f, 1f ) ).Normal;\r\n\r\n\t\treturn new Vector4( tangent, 1f );\r\n\t}\r\n\r\n\tprivate (int Prev, int Next) AddVertices( ref Edge edge, bool forceMaxDistance = false )\r\n\t{\r\n\t\tif ( edge.Vertices.Prev \u003E -1 )\r\n\t\t{\r\n\t\t\treturn edge.Vertices;\r\n\t\t}\r\n\r\n\t\tvar prevEdge = _allEdges[edge.PrevEdge];\r\n\r\n\t\tvar index = _vertices.Count;\r\n\t\tvar prevNormal = -prevEdge.Normal;\r\n\t\tvar nextNormal = -edge.Normal;\r\n\r\n\t\tvar t = forceMaxDistance ? 1f : (edge.Distance - _prevDistance) * _invDistance;\r\n\t\tvar height = _prevHeight \u002B t * (_nextHeight - _prevHeight);\r\n\r\n\t\tvar pos = new Vector3( edge.Origin.x, edge.Origin.y, height );\r\n\r\n\t\tif ( SkipNormals || MathF.Abs( _nextHeight - _prevHeight ) \u003C= 0.001f )\r\n\t\t{\r\n\t\t\t_vertices.Add( new(\r\n\t\t\t\tpos,\r\n\t\t\t\tnew Vector3( 0f, 0f, 1f ),\r\n\t\t\t\tnew Vector4( 1f, 0f, 0f, 1f ) ) );\r\n\r\n\t\t\tedge.Vertices = (index, index);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar angle = LerpRadians( _prevAngle, _nextAngle, t );\r\n\t\t\tvar cos = MathF.Cos( angle );\r\n\t\t\tvar sin = MathF.Sin( angle );\r\n\r\n\t\t\tif ( Vector2.Dot( prevNormal, nextNormal ) \u003E= _minSmoothNormalDot )\r\n\t\t\t{\r\n\t\t\t\tvar normal = new Vector3( (prevNormal.x \u002B nextNormal.x) * cos, (prevNormal.y \u002B nextNormal.y) * cos, sin * 2f ).Normal;\r\n\r\n\t\t\t\t_vertices.Add( new( pos, normal, GetTangent( normal ) ) );\r\n\r\n\t\t\t\tedge.Vertices = (index, index);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar normal0 = new Vector3( prevNormal.x * cos, prevNormal.y * cos, sin ).Normal;\r\n\t\t\t\tvar normal1 = new Vector3( nextNormal.x * cos, nextNormal.y * cos, sin ).Normal;\r\n\r\n\t\t\t\t_vertices.Add( new( pos, normal0, GetTangent( normal0 ) ) );\r\n\t\t\t\t_vertices.Add( new( pos, normal1, GetTangent( normal1 ) ) );\r\n\r\n\t\t\t\tedge.Vertices = (index, index \u002B 1);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn edge.Vertices;\r\n\t}\r\n\r\n\tprivate void AddTriangle( int a, int b, int c )\r\n\t{\r\n\t\t_indices.Add( a );\r\n\t\t_indices.Add( b );\r\n\t\t_indices.Add( c );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add faces on each active edge extending upwards by the given height.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022height\u0022\u003ETotal distance upwards, away from the plane of the polygon.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Extrude( float height )\r\n\t{\r\n\t\treturn Bevel( 0f, height );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add faces on each active edge extending inwards by the given width. This will close the mesh if \u003Cparamref name=\u0022width\u0022/\u003E is large enough.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022width\u0022\u003ETotal distance inwards.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Inset( float width )\r\n\t{\r\n\t\treturn Bevel( width, 0f );\r\n\t}\r\n\r\n\t[ThreadStatic]\r\n\tprivate static Dictionary\u003Cint, int\u003E Mirror_IndexMap;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Mirrors all previously created faces. The mirror plane is normal to the Z axis, with a given distance from the origin.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022z\u0022\u003EDistance of the mirror plane from the origin.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Mirror( float z = 0f )\r\n\t{\r\n\t\tMirror_IndexMap ??= new Dictionary\u003Cint, int\u003E();\r\n\t\tMirror_IndexMap.Clear();\r\n\r\n\t\t_vertices.EnsureCapacity( _vertices.Count * 2 );\r\n\t\t_indices.EnsureCapacity( _indices.Count * 2 );\r\n\r\n\t\tvar indexCount = _indices.Count;\r\n\t\tvar vertexCount = _vertices.Count;\r\n\r\n\t\tfor ( var i = 0; i \u003C vertexCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar vertex = _vertices[i];\r\n\t\t\tvar position = vertex.Position;\r\n\t\t\tvar normal = vertex.Normal;\r\n\t\t\tvar tangent = vertex.Tangent;\r\n\r\n\t\t\tif ( Math.Abs( position.z - z ) \u003C= 0.001f \u0026\u0026 (SkipNormals || Math.Abs( normal.z ) \u003C= 0.0001f \u0026\u0026 Math.Abs( tangent.z ) \u003C= 0.0001f) )\r\n\t\t\t{\r\n\t\t\t\tMirror_IndexMap.Add( i, i );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tMirror_IndexMap.Add( i, _vertices.Count );\r\n\r\n\t\t\t\t_vertices.Add( new(\r\n\t\t\t\t\tnew Vector3( position.x, position.y, z * 2f - position.z ),\r\n\t\t\t\t\tnew Vector3( normal.x, normal.y, -normal.z ),\r\n\t\t\t\t\tnew Vector4( tangent.x, tangent.y, -tangent.z, tangent.w ) ) );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor ( var i = 0; i \u003C indexCount; i \u002B= 3 )\r\n\t\t{\r\n\t\t\tvar a = Mirror_IndexMap[_indices[i \u002B 0]];\r\n\t\t\tvar b = Mirror_IndexMap[_indices[i \u002B 1]];\r\n\t\t\tvar c = Mirror_IndexMap[_indices[i \u002B 2]];\r\n\r\n\t\t\t_indices.Add( a );\r\n\t\t\t_indices.Add( c );\r\n\t\t\t_indices.Add( b );\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Perform successive \u003Csee cref=\u0022Bevel\u0022/\u003Es so that the edge of the polygon curves inwards in a quarter circle arc.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022radius\u0022\u003ERadius of the arc.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022faces\u0022\u003EHow many bevels to split the rounded edge into.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022smooth\u0022\u003EIf true, use smooth normals rather than flat shading.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022convex\u0022\u003EIf true, the faces will be pointing outwards from the center of the arc.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Arc( float radius, int faces, bool smooth = true, bool convex = true )\r\n\t{\r\n\t\treturn Arc( radius, radius, faces, smooth, convex );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Perform successive \u003Csee cref=\u0022Bevel\u0022/\u003Es so that the edge of the polygon curves inwards in a quarter circle arc.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022width\u0022\u003ETotal distance inwards.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022height\u0022\u003ETotal distance upwards, away from the plane of the polygon.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022faces\u0022\u003EHow many bevels to split the rounded edge into.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022smooth\u0022\u003EIf true, use smooth normals rather than flat shading.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022convex\u0022\u003EIf true, the faces will be pointing outwards from the center of the arc.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Arc( float width, float height, int faces, bool smooth = true, bool convex = true )\r\n\t{\r\n\t\tvar prevWidth = 0f;\r\n\t\tvar prevHeight = 0f;\r\n\t\tvar prevTheta = 0f;\r\n\r\n\t\tstatic float MapAngle( float theta, bool convex, bool positive )\r\n\t\t{\r\n\t\t\tvar min = positive ? 0f : MathF.PI * 0.5f;\r\n\t\t\treturn convex ? min \u002B theta : min \u002B MathF.PI * 0.5f - theta;\r\n\t\t}\r\n\r\n\t\tfor ( var i = 0; i \u003C faces; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tvar theta = MathF.PI * 0.5f * (i \u002B 1f) / faces;\r\n\r\n\t\t\tvar cos = MathF.Cos( theta );\r\n\t\t\tvar sin = MathF.Sin( theta );\r\n\r\n\t\t\tvar nextWidth = 1f - cos;\r\n\t\t\tvar nextHeight = sin;\r\n\r\n\t\t\tif ( smooth )\r\n\t\t\t{\r\n\t\t\t\tif ( height \u003E= 0f == convex )\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel( (nextWidth - prevWidth) * width,\r\n\t\t\t\t\t\t(nextHeight - prevHeight) * height,\r\n\t\t\t\t\t\tMapAngle( prevTheta, convex, height \u003E= 0f ),\r\n\t\t\t\t\t\tMapAngle( theta, convex, height \u003E= 0f ) );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel( (nextHeight - prevHeight) * width,\r\n\t\t\t\t\t\t(nextWidth - prevWidth) * height,\r\n\t\t\t\t\t\tMapAngle( prevTheta, convex, height \u003E= 0f ),\r\n\t\t\t\t\t\tMapAngle( theta, convex, height \u003E= 0f ) );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif ( height \u003E= 0f == convex )\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel( (nextWidth - prevWidth) * width,\r\n\t\t\t\t\t\t(nextHeight - prevHeight) * height );\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel( (nextHeight - prevHeight) * width,\r\n\t\t\t\t\t\t(nextWidth - prevWidth) * height );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tprevWidth = nextWidth;\r\n\t\t\tprevHeight = nextHeight;\r\n\t\t\tprevTheta = theta;\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.Bevel.cs","FileName":"PolygonMeshBuilder.Bevel.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\npartial class PolygonMeshBuilder\r\n{\r\n\tprivate HashSet\u003C(int A, int B)\u003E PossibleCuts { get; } = new();\r\n\r\n\t[ThreadStatic] private static List\u003C(int A, int B)\u003E Bevel_PossibleCutList;\r\n\r\n\t[ThreadStatic] private static List\u003Cint\u003E Bevel_ActiveEdgeList;\r\n\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add faces starting at each active edge, traveling inwards and upwards to produce a bevel.\r\n\t/// If the bevel distance is large enough the mesh will become closed. Otherwise, you can use\r\n\t/// \u003Csee cref=\u0022Close\u0022/\u003E to add a flat face after the bevel.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022width\u0022\u003ETotal distance inwards.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022height\u0022\u003ETotal distance upwards, away from the plane of the polygon.\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Bevel( float width, float height )\r\n\t{\r\n\t\tvar angle = MathF.Atan2( width, height );\r\n\r\n\t\treturn Bevel( width, height, angle, angle );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add faces starting at each active edge, traveling inwards and upwards to produce a bevel.\r\n\t/// Use \u003Cparamref name=\u0022prevAngle\u0022/\u003E and \u003Cparamref name=\u0022nextAngle\u0022/\u003E to control the normal directions\r\n\t/// at the start and end of the bevel faces. Angles are in radians, with 0 pointing outwards along\r\n\t/// the plane of the polygon, and PI/2 pointing upwards away from the plane.\r\n\t/// If the bevel distance is large enough the mesh will become closed. Otherwise, you can use\r\n\t/// \u003Csee cref=\u0022Close\u0022/\u003E to add a flat face after the bevel.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022width\u0022\u003ETotal distance inwards.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022height\u0022\u003ETotal distance upwards, away from the plane of the polygon.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022prevAngle\u0022\u003EAngle, in radians, to use for normals at the outside of the bevel.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022nextAngle\u0022\u003E\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder Bevel( float width, float height, float prevAngle, float nextAngle )\r\n\t{\r\n\t\tif ( width \u003C 0f )\r\n\t\t{\r\n\t\t\tthrow new ArgumentOutOfRangeException( nameof( width ) );\r\n\t\t}\r\n\r\n\t\tValidate();\r\n\t\tBevel_UpdateExistingVertices( width, height, prevAngle, nextAngle );\r\n\r\n\t\tvar cutList = Bevel_PossibleCutList ??= new List\u003C(int A, int B)\u003E();\r\n\t\tvar edgeList = Bevel_ActiveEdgeList ??= new List\u003Cint\u003E();\r\n\r\n\t\tvar finished = false;\r\n\t\tvar endDist = _nextDistance;\r\n\r\n\t\tif ( MathF.Abs( _nextDistance ) \u003E 0.001f )\r\n\t\t{\r\n\t\t\tvar maxIterations = _activeEdges.Count * _activeEdges.Count;\r\n\r\n\t\t\tint iterations;\r\n\t\t\tfor ( iterations = 0; iterations \u003C maxIterations \u0026\u0026 _activeEdges.Count \u003E 0; \u002B\u002Biterations )\r\n\t\t\t{\r\n\t\t\t\tint? closedEdge = null;\r\n\t\t\t\tint? splitEdge = null;\r\n\t\t\t\tint? splittingEdge = null;\r\n\r\n\t\t\t\tVector2 bestPos = default;\r\n\r\n\t\t\t\tvar bestDist = _nextDistance;\r\n\t\t\t\tvar bestMerge = false;\r\n\r\n\t\t\t\tforeach ( var index in _activeEdges )\r\n\t\t\t\t{\r\n\t\t\t\t\tref var edge = ref _allEdges[index];\r\n\r\n\t\t\t\t\tif ( edge.MaxDistance \u003E= bestDist ) continue;\r\n\r\n\t\t\t\t\tvar next = _allEdges[edge.NextEdge];\r\n\r\n\t\t\t\t\tbestDist = edge.MaxDistance;\r\n\t\t\t\t\tclosedEdge = edge.Index;\r\n\t\t\t\t\tbestPos = (edge.Project( edge.MaxDistance ) \u002B next.Project( edge.MaxDistance )) * 0.5f;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tcutList.Clear();\r\n\t\t\t\tcutList.AddRange( PossibleCuts );\r\n\r\n\t\t\t\tforeach ( var (index, otherIndex) in cutList )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( !_activeEdges.Contains( index ) || !_activeEdges.Contains( otherIndex ) )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tPossibleCuts.Remove( (index, otherIndex) );\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar edge = _allEdges[index];\r\n\t\t\t\t\tvar other = _allEdges[otherIndex];\r\n\r\n\t\t\t\t\tvar splitDist = CalculateSplitDistance( edge, other, _allEdges[other.NextEdge],\r\n\t\t\t\t\t\tout var splitPos, out var merge );\r\n\r\n\t\t\t\t\tif ( splitDist - _nextDistance \u003E 0.001f )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tPossibleCuts.Remove( (index, otherIndex) );\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif ( splitDist \u003E= bestDist ) continue;\r\n\r\n\t\t\t\t\tbestDist = splitDist;\r\n\t\t\t\t\tbestPos = splitPos;\r\n\t\t\t\t\tbestMerge = merge;\r\n\r\n\t\t\t\t\tclosedEdge = null;\r\n\t\t\t\t\tsplitEdge = other.Index;\r\n\t\t\t\t\tsplittingEdge = edge.Index;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( splittingEdge != null \u0026\u0026 bestMerge )\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel_Merge( splittingEdge.Value, splitEdge.Value, bestPos, bestDist );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( splittingEdge != null )\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel_Split( splittingEdge.Value, splitEdge.Value, bestPos, bestDist );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( closedEdge != null )\r\n\t\t\t\t{\r\n\t\t\t\t\tBevel_Close( closedEdge.Value, bestPos, bestDist );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tfinished = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\tif ( _activeEdges.Count \u003E 0 \u0026\u0026 iterations == maxIterations )\r\n\t\t\t{\r\n\t\t\t\tthrow new Exception( $\u0022Exploded after {iterations} with {_activeEdges.Count} active edges!\u0022 );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tfinished = true;\r\n\t\t}\r\n\r\n\t\tif ( !finished \u0026\u0026 _activeEdges.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tendDist = _activeEdges.Max( i =\u003E _allEdges[i].Distance );\r\n\t\t}\r\n\r\n\t\tEnsureCapacity( _activeEdges.Count );\r\n\r\n\t\tedgeList.Clear();\r\n\t\tedgeList.AddRange( _activeEdges );\r\n\r\n\t\t_activeEdges.Clear();\r\n\r\n\t\tforeach ( var index in edgeList )\r\n\t\t{\r\n\t\t\tref var b = ref _allEdges[index];\r\n\t\t\tref var a = ref _allEdges[b.PrevEdge];\r\n\t\t\tref var c = ref _allEdges[b.NextEdge];\r\n\t\t\tref var d = ref _allEdges[AddEdge( b.Project( endDist ), b.Tangent, endDist )];\r\n\r\n\t\t\tvar ai = AddVertices( ref a );\r\n\t\t\tvar bi = AddVertices( ref b );\r\n\t\t\tvar ci = AddVertices( ref c );\r\n\r\n\t\t\tConnectEdges( ref a, ref d );\r\n\t\t\tConnectEdges( ref d, ref c );\r\n\r\n\t\t\tvar di = AddVertices( ref d, true );\r\n\r\n\t\t\tAddTriangle( ai.Next, di.Prev, bi.Prev );\r\n\t\t\tAddTriangle( bi.Next, di.Next, ci.Prev );\r\n\r\n\t\t\t_activeEdges.Add( d.Index );\r\n\t\t}\r\n\r\n\t\tPostBevel();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate void Bevel_UpdateExistingVertices( float width, float height, float prevAngle, float nextAngle )\r\n\t{\r\n\t\t_nextDistance = _prevDistance \u002B width;\r\n\t\t_nextHeight = _prevHeight \u002B height;\r\n\t\t_nextAngle = nextAngle;\r\n\t\t_minSmoothNormalDot = MathF.Cos( Math.Clamp( MaxSmoothAngle, 0f, MathF.PI * (511f / 512f) ) );\r\n\r\n\t\t_invDistance = width \u003C= 0.0001f ? 0f : 1f / (_nextDistance - _prevDistance);\r\n\r\n\t\tif ( !SkipNormals \u0026\u0026 Math.Abs( _prevAngle - prevAngle ) \u003E= 0.001f )\r\n\t\t{\r\n\t\t\tforeach ( var index in _activeEdges )\r\n\t\t\t{\r\n\t\t\t\tref var edge = ref _allEdges[index];\r\n\t\t\t\tedge.Vertices = (-1, -1);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_prevAngle = prevAngle;\r\n\r\n\t\tPossibleCuts.Clear();\r\n\r\n\t\tforeach ( var index in _activeEdges )\r\n\t\t{\r\n\t\t\tref var edge = ref _allEdges[index];\r\n\t\t\tUpdateMaxDistance( ref edge, _allEdges[edge.NextEdge] );\r\n\r\n\t\t\tforeach ( var otherIndex in _activeEdges )\r\n\t\t\t{\r\n\t\t\t\tif ( otherIndex != index )\r\n\t\t\t\t{\r\n\t\t\t\t\tPossibleCuts.Add( (index, otherIndex) );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void Bevel_Merge( int edgeA, int edgeB, Vector2 mergePos, float bestDist )\r\n\t{\r\n\t\tEnsureCapacity( 2 );\r\n\r\n\t\tref var a = ref _allEdges[edgeA];\r\n\t\tref var b = ref _allEdges[edgeB];\r\n\r\n\t\t_activeEdges.Remove( a.Index );\r\n\t\t_activeEdges.Remove( b.Index );\r\n\r\n\t\tif ( a.NextEdge == b.Index \u0026\u0026 b.NextEdge == a.Index )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tref var aPrev = ref _allEdges[a.PrevEdge];\r\n\t\tref var bPrev = ref _allEdges[b.PrevEdge];\r\n\r\n\t\tref var aNext = ref _allEdges[a.NextEdge];\r\n\t\tref var bNext = ref _allEdges[b.NextEdge];\r\n\r\n\t\tref var aNew = ref _allEdges[AddEdge( mergePos, a.Tangent, bestDist, 1 )];\r\n\t\tref var bNew = ref _allEdges[AddEdge( mergePos, b.Tangent, bestDist, -1 )];\r\n\r\n\t\tvar aPrevi = AddVertices( ref aPrev ).Next;\r\n\t\tvar ai = AddVertices( ref a );\r\n\t\tvar aNexti = AddVertices( ref aNext ).Prev;\r\n\t\tvar bPrevi = AddVertices( ref bPrev ).Next;\r\n\t\tvar bi = AddVertices( ref b );\r\n\t\tvar bNexti = AddVertices( ref bNext ).Prev;\r\n\r\n\t\t_activeEdges.Add( aNew.Index );\r\n\t\t_activeEdges.Add( bNew.Index );\r\n\r\n\t\tConnectEdges( ref bPrev, ref aNew );\r\n\t\tConnectEdges( ref aNew, ref aNext );\r\n\r\n\t\tConnectEdges( ref aPrev, ref bNew );\r\n\t\tConnectEdges( ref bNew, ref bNext );\r\n\r\n\t\tUpdateMaxDistance( ref bPrev, aNew );\r\n\t\tUpdateMaxDistance( ref aNew, aNext );\r\n\t\tUpdateMaxDistance( ref aNext, _allEdges[aNext.NextEdge] );\r\n\r\n\t\tUpdateMaxDistance( ref aPrev, bNew );\r\n\t\tUpdateMaxDistance( ref bNew, bNext );\r\n\t\tUpdateMaxDistance( ref bNext, _allEdges[bNext.NextEdge] );\r\n\r\n\t\tvar aNewi = AddVertices( ref aNew );\r\n\t\tvar bNewi = AddVertices( ref bNew );\r\n\r\n\t\tAddTriangle( aPrevi, bNewi.Prev, ai.Prev );\r\n\t\tAddTriangle( ai.Next, aNewi.Next, aNexti );\r\n\t\tAddTriangle( bPrevi, aNewi.Prev, bi.Prev );\r\n\t\tAddTriangle( bi.Next, bNewi.Next, bNexti );\r\n\r\n\t\tAddAllPossibleCuts( aNew.Index );\r\n\t\tAddAllPossibleCuts( aNext.Index );\r\n\t\tAddAllPossibleCuts( bNew.Index );\r\n\t\tAddAllPossibleCuts( bNext.Index );\r\n\t}\r\n\r\n\tprivate void Bevel_Split( int splittingEdge, int splitEdge, Vector2 splitPos, float bestDist )\r\n\t{\r\n\t\tEnsureCapacity( 2 );\r\n\r\n\t\tref var a = ref _allEdges[splitEdge];\r\n\t\tref var d = ref _allEdges[splittingEdge];\r\n\t\tref var b = ref _allEdges[AddEdge( splitPos, a.Tangent, bestDist, 1 )];\r\n\t\tref var c = ref _allEdges[d.PrevEdge];\r\n\t\tref var e = ref _allEdges[AddEdge( splitPos, d.Tangent, bestDist, -1 )];\r\n\t\tref var aNext = ref _allEdges[a.NextEdge];\r\n\t\tref var dNext = ref _allEdges[d.NextEdge];\r\n\r\n\t\tvar ai = AddVertices( ref a ).Next;\r\n\t\tvar fi = AddVertices( ref aNext ).Prev;\r\n\t\tvar ci = AddVertices( ref c ).Next;\r\n\t\tvar di = AddVertices( ref d );\r\n\t\tvar gi = AddVertices( ref dNext ).Prev;\r\n\r\n\t\t_activeEdges.Remove( d.Index );\r\n\t\t_activeEdges.Add( b.Index );\r\n\t\t_activeEdges.Add( e.Index );\r\n\r\n\t\tConnectEdges( ref a, ref e );\r\n\t\tConnectEdges( ref e, ref dNext );\r\n\r\n\t\tConnectEdges( ref c, ref b );\r\n\t\tConnectEdges( ref b, ref aNext );\r\n\r\n\t\tUpdateMaxDistance( ref a, e );\r\n\t\tUpdateMaxDistance( ref e, dNext );\r\n\t\tUpdateMaxDistance( ref dNext, _allEdges[dNext.NextEdge] );\r\n\r\n\t\tUpdateMaxDistance( ref c, b );\r\n\t\tUpdateMaxDistance( ref b, aNext );\r\n\t\tUpdateMaxDistance( ref aNext, _allEdges[aNext.NextEdge] );\r\n\r\n\t\tvar bi = AddVertices( ref b );\r\n\t\tvar ei = AddVertices( ref e );\r\n\r\n\t\tAddTriangle( ai, bi.Next, fi );\r\n\t\tAddTriangle( ci, bi.Prev, di.Prev );\r\n\t\tAddTriangle( di.Next, ei.Next, gi );\r\n\r\n\t\tAddAllPossibleCuts( b.Index );\r\n\t\tAddAllPossibleCuts( dNext.Index );\r\n\t\tAddAllPossibleCuts( e.Index );\r\n\t\tAddAllPossibleCuts( aNext.Index );\r\n\t}\r\n\r\n\tprivate void Bevel_Close( int closedEdge, Vector2 closePos, float bestDist )\r\n\t{\r\n\t\tEnsureCapacity( 1 );\r\n\r\n\t\tref var b = ref _allEdges[closedEdge];\r\n\t\tref var a = ref _allEdges[b.PrevEdge];\r\n\t\tref var c = ref _allEdges[b.NextEdge];\r\n\t\tref var cNext = ref _allEdges[c.NextEdge];\r\n\t\tref var d = ref _allEdges[AddEdge( closePos, c.Tangent, bestDist )];\r\n\r\n\t\t_activeEdges.Remove( b.Index );\r\n\t\t_activeEdges.Remove( c.Index );\r\n\r\n\t\tif ( b.PrevEdge == b.NextEdge )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_activeEdges.Add( d.Index );\r\n\r\n\t\tConnectEdges( ref a, ref d );\r\n\t\tConnectEdges( ref d, ref cNext );\r\n\r\n\t\tUpdateMaxDistance( ref a, d );\r\n\t\tUpdateMaxDistance( ref d, cNext );\r\n\t\tUpdateMaxDistance( ref cNext, _allEdges[cNext.NextEdge] );\r\n\r\n\t\tvar ai = AddVertices( ref a );\r\n\t\tvar bi = AddVertices( ref b );\r\n\t\tvar ci = AddVertices( ref c );\r\n\t\tvar ei = AddVertices( ref cNext );\r\n\t\tvar di = AddVertices( ref d );\r\n\r\n\t\tvar fi = _vertices.Count;\r\n\r\n\t\t_vertices.Add( new(\r\n\t\t\t_vertices[di.Prev].Position,\r\n\t\t\t_vertices[bi.Next].Normal,\r\n\t\t\t_vertices[bi.Next].Tangent ) );\r\n\r\n\t\tAddTriangle( ai.Next, di.Prev, bi.Prev );\r\n\t\tAddTriangle( bi.Next, fi, ci.Prev );\r\n\t\tAddTriangle( ci.Next, di.Next, ei.Prev );\r\n\r\n\t\tAddAllPossibleCuts( d.Index );\r\n\t\tAddAllPossibleCuts( cNext.Index );\r\n\t}\r\n\r\n\tprivate void PostBevel()\r\n\t{\r\n\t\t_prevDistance = _nextDistance;\r\n\t\t_prevHeight = _nextHeight;\r\n\t\t_prevAngle = _nextAngle;\r\n\t}\r\n\r\n\tprivate void AddAllPossibleCuts( int index )\r\n\t{\r\n\t\tforeach ( var otherIndex in _activeEdges )\r\n\t\t{\r\n\t\t\tif ( otherIndex != index )\r\n\t\t\t{\r\n\t\t\t\tPossibleCuts.Add( (index, otherIndex) );\r\n\t\t\t\tPossibleCuts.Add( (otherIndex, index) );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static Vector3 RotateNormal( Vector3 oldNormal, float sin, float cos )\r\n\t{\r\n\t\tvar normal2d = new Vector2( oldNormal.x, oldNormal.y );\r\n\r\n\t\tif ( normal2d.LengthSquared \u003C= 0.000001f )\r\n\t\t{\r\n\t\t\treturn oldNormal;\r\n\t\t}\r\n\r\n\t\tnormal2d = normal2d.Normal;\r\n\r\n\t\treturn new Vector3( normal2d.x * cos, normal2d.y * cos, sin ).Normal;\r\n\t}\r\n\r\n\tprivate static float GetEpsilon( Vector2 vec, float frac = 0.0001f )\r\n\t{\r\n\t\treturn Math.Max( Math.Abs( vec.x ), Math.Abs( vec.y ) ) * frac;\r\n\t}\r\n\r\n\tprivate static float GetEpsilon( Vector2 a, Vector2 b, float frac = 0.0001f )\r\n\t{\r\n\t\treturn Math.Max( GetEpsilon( a ), GetEpsilon( b ) );\r\n\t}\r\n\r\n\tprivate static void UpdateMaxDistance( ref Edge edge, in Edge nextEdge )\r\n\t{\r\n\t\tif ( edge.NextEdge == edge.PrevEdge )\r\n\t\t{\r\n\t\t\tedge.MaxDistance = edge.Distance;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar baseDistance = Math.Max( edge.Distance, nextEdge.Distance );\r\n\t\tvar thisOrigin = edge.Project( baseDistance );\r\n\t\tvar nextOrigin = nextEdge.Project( baseDistance );\r\n\r\n\t\tvar posDist = Vector2.Dot( nextOrigin - thisOrigin, edge.Tangent );\r\n\r\n\t\tvar dPrev = Vector2.Dot( edge.Velocity, edge.Tangent );\r\n\t\tvar dNext = Vector2.Dot( nextEdge.Velocity, edge.Tangent );\r\n\r\n\t\tif ( dPrev - dNext \u003C= 0.001f )\r\n\t\t{\r\n\t\t\tvar epsilon = GetEpsilon( thisOrigin, nextOrigin, 0.001f );\r\n\t\t\tedge.MaxDistance = posDist \u003C= epsilon ? baseDistance : float.PositiveInfinity;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tedge.MaxDistance = baseDistance \u002B MathF.Max( 0f, posDist / (dPrev - dNext) );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static void SimpleConnectEdges( ref Edge prev, ref Edge next )\r\n\t{\r\n\t\tprev.NextEdge = next.Index;\r\n\t\tnext.PrevEdge = prev.Index;\r\n\t}\r\n\r\n\tprivate static void ConnectEdges( ref Edge prev, ref Edge next )\r\n\t{\r\n\t\tSimpleConnectEdges( ref prev, ref next );\r\n\r\n\t\tvar sum = prev.Normal \u002B next.Normal;\r\n\t\tvar sqrMag = sum.LengthSquared;\r\n\r\n\t\tif ( sqrMag \u003C 0.001f )\r\n\t\t{\r\n\t\t\tnext.Velocity = Vector2.Zero;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnext.Velocity = 2f * sum / sum.LengthSquared;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static float CalculateSplitDistance( in Edge edge, in Edge other, in Edge otherNext,\r\n\t\tout Vector2 splitPos, out bool merge )\r\n\t{\r\n\t\tsplitPos = default;\r\n\t\tmerge = false;\r\n\r\n\t\tif ( other.Index == edge.Index || edge.Twin == other.Index || edge.Velocity.LengthSquared \u003C= 0f )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tvar dv = Vector2.Dot( other.Velocity - edge.Velocity, other.Normal );\r\n\r\n\t\tif ( dv \u003C= GetEpsilon( edge.Velocity, other.Velocity ) )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tvar baseDistance = Math.Max( edge.Distance, Math.Max( other.Distance, otherNext.Distance ) );\r\n\t\tvar thisOrigin = edge.Project( baseDistance );\r\n\t\tvar edgeOrigin = other.Project( baseDistance );\r\n\r\n\t\tvar dx = Vector2.Dot( thisOrigin - edgeOrigin, other.Normal );\r\n\r\n\t\tif ( dx \u003C= -GetEpsilon( thisOrigin, edgeOrigin ) )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tvar t = dx / dv;\r\n\r\n\t\tif ( t \u003C= -0.0001f )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tif ( baseDistance \u002B t \u003E= edge.MaxDistance || baseDistance \u002B t \u003E= other.MaxDistance )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tsplitPos = thisOrigin \u002B edge.Velocity * t;\r\n\r\n\t\tvar prevPos = edgeOrigin \u002B other.Velocity * t;\r\n\t\tvar nextPos = otherNext.Project( baseDistance \u002B t );\r\n\r\n\t\tvar dPrev = Vector2.Dot( splitPos - prevPos, other.Tangent );\r\n\t\tvar dNext = Vector2.Dot( splitPos - nextPos, other.Tangent );\r\n\r\n\t\tvar epsilon = GetEpsilon( prevPos, nextPos );\r\n\r\n\t\tif ( dPrev \u003C= -epsilon || dNext \u003E= -epsilon )\r\n\t\t{\r\n\t\t\treturn float.PositiveInfinity;\r\n\t\t}\r\n\r\n\t\tif ( dPrev \u003C= epsilon )\r\n\t\t{\r\n\t\t\tif ( edge.NextEdge == other.Index || edge.PrevEdge == other.Index )\r\n\t\t\t{\r\n\t\t\t\treturn float.PositiveInfinity;\r\n\t\t\t}\r\n\r\n\t\t\tmerge = true;\r\n\t\t}\r\n\r\n\t\treturn baseDistance \u002B Math.Max( 0f, t );\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/PolygonMeshBuilder.SVG.cs","FileName":"PolygonMeshBuilder.SVG.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing Sandbox.Utility.Svg;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\n/// \u003Csummary\u003E\r\n/// Options for \u003Csee cref=\u0022PolygonMeshBuilder.AddSvg\u0022/\u003E.\r\n/// \u003C/summary\u003E\r\npublic class AddSvgOptions\r\n{\r\n\tpublic static AddSvgOptions Default { get; } = new();\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// If true, any unsupported path types will throw an exception. Defaults to false.\r\n\t/// \u003C/summary\u003E\r\n\tpublic bool ThrowIfNotSupported { get; set; }\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Maximum distance between vertices on curved paths. Defaults to 1.\r\n\t/// \u003C/summary\u003E\r\n\tpublic float CurveResolution { get; set; } = 1f;\r\n\r\n    public bool KeepAspectRatio { get; set; } = true;\r\n}\r\n\r\npartial class PolygonMeshBuilder\r\n{\r\n\t/// \u003Csummary\u003E\r\n\t/// Add all supported paths from the given SVG document.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022contents\u0022\u003ESVG document contents.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022options\u0022\u003EOptions for generating vertices from paths.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022targetBounds\u0022\u003ERescale and translate the imported SVG to fill the given bounds\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder AddSvg( string contents, AddSvgOptions options = null, Rect? targetBounds = null )\r\n    {\r\n        options ??= AddSvgOptions.Default;\r\n\r\n        var svg = SvgDocument.FromString( contents );\r\n\r\n\t\tif ( svg.Paths.Count == 0 )\r\n\t\t{\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif ( targetBounds == null )\r\n\t\t{\r\n\t\t\tforeach ( var path in svg.Paths )\r\n\t\t\t{\r\n\t\t\t\tAddPath( path, options );\r\n\t\t\t}\r\n\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tvar bounds = svg.Paths[0].Bounds;\r\n\r\n\t\tforeach ( var path in svg.Paths )\r\n\t\t{\r\n\t\t\tbounds.Add( path.Bounds );\r\n\t\t}\r\n\r\n\t\tvar scale = targetBounds.Value.Size / bounds.Size;\r\n        var aspectOffset = Vector2.Zero;\r\n\r\n        if ( options.KeepAspectRatio )\r\n        {\r\n            var oldScale = scale;\r\n\r\n            scale = Math.Min( scale.x, scale.y );\r\n            aspectOffset = (oldScale - scale) * targetBounds.Value.Size * 0.25f;\r\n        }\r\n\r\n\t\tvar offset = targetBounds.Value.Position - bounds.Position * scale \u002B aspectOffset;\r\n\r\n\t\tforeach ( var path in svg.Paths )\r\n\t\t{\r\n\t\t\tAddPath( path, options, offset, scale );\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate static void ThrowNotSupported( AddSvgOptions options, string message )\r\n\t{\r\n\t\tif ( !options.ThrowIfNotSupported )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tthrow new NotImplementedException( $\u0022SVG path element not supported: {message}\u0022 );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Add an individual path from an SVG document, if supported.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cparam name=\u0022path\u0022\u003ESVG path element.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022options\u0022\u003EOptions for generating vertices from paths.\u003C/param\u003E\r\n\t/// \u003Cparam name=\u0022targetBounds\u0022\u003ERescale and translate the imported SVG to fill the given bounds\u003C/param\u003E\r\n\tpublic PolygonMeshBuilder AddPath( SvgPath path, AddSvgOptions options = null )\r\n\t{\r\n\t\toptions ??= AddSvgOptions.Default;\r\n\t\treturn AddPath( path, options, Vector2.Zero, Vector2.One );\r\n\t}\r\n\r\n\tprivate PolygonMeshBuilder AddPath( SvgPath path, AddSvgOptions options, Vector2 offset, Vector2 scale )\r\n\t{\r\n\t\tif ( path.IsEmpty )\r\n\t\t{\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif ( path.FillColor == null )\r\n\t\t{\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif ( path.FillType != PathFillType.Winding )\r\n\t\t{\r\n\t\t\tif ( options.ThrowIfNotSupported )\r\n\t\t\t{\r\n\t\t\t\t//throw new NotImplementedException( \u0022Only fill-type: winding is supported.\u0022 );\r\n\t\t\t}\r\n\r\n\t\t\t//return this;\r\n\t\t}\r\n\r\n\t\tvar openPath = new List\u003CVector2\u003E();\r\n\t\tvar last = Vector2.Zero;\r\n\r\n\t\tforeach ( var cmd in path.Commands )\r\n\t\t{\r\n\t\t\tswitch ( cmd )\r\n\t\t\t{\r\n\t\t\t\tcase AddPolyPathCommand addPolyPathCommand:\r\n\t\t\t\t\tAddPolyPath( addPolyPathCommand, options, offset, scale );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase AddCirclePathCommand addCirclePathCommand:\r\n\t\t\t\t\tAddCirclePath( addCirclePathCommand, options, openPath, offset, scale );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase MoveToPathCommand moveToPathCommand:\r\n\t\t\t\t\topenPath.Clear();\r\n\t\t\t\t\topenPath.Add( new Vector2( moveToPathCommand.X, moveToPathCommand.Y ) );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase LineToPathCommand lineToPathCommand:\r\n\t\t\t\t\topenPath.Add( new Vector2( lineToPathCommand.X, lineToPathCommand.Y ) );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase CubicToPathCommand cubicToPathCommand:\r\n\t\t\t\t\tCubicToPath( cubicToPathCommand, options, openPath, last );\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tcase ClosePathCommand:\r\n\t\t\t\t\tif ( openPath.Count \u003E= 3 )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tAddEdgeLoop( openPath, 0, openPath.Count, offset, scale );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\topenPath.Clear();\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tThrowNotSupported( options, $\u0022{cmd.GetType()}\u0022 );\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\tif ( openPath.Count \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tlast = openPath[^1];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate void AddPolyPath( AddPolyPathCommand cmd, AddSvgOptions options, Vector2 offset, Vector2 scale )\r\n\t{\r\n\t\tif ( !cmd.Close )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tAddEdgeLoop( cmd.Points, 0, cmd.Points.Count, offset, scale );\r\n\t}\r\n\r\n\tprivate void AddCirclePath( AddCirclePathCommand cmd, AddSvgOptions options, List\u003CVector2\u003E openPath, Vector2 offset, Vector2 scale )\r\n\t{\r\n\t\topenPath.Clear();\r\n\r\n\t\tvar center = new Vector2( cmd.X, cmd.Y );\r\n\r\n\t\tfor ( var i = 23; i \u003E= 0; i-- )\r\n\t\t{\r\n\t\t\tvar r = i * (MathF.PI * 2f / 24f);\r\n\r\n\t\t\tvar cos = MathF.Cos( r );\r\n\t\t\tvar sin = MathF.Sin( r );\r\n\r\n\t\t\topenPath.Add( new Vector2( cos, sin ) * cmd.Radius \u002B center );\r\n\t\t}\r\n\r\n\t\tAddEdgeLoop( openPath, 0, openPath.Count, offset, scale );\r\n\t}\r\n\r\n\tprivate void CubicToPath( CubicToPathCommand cmd, AddSvgOptions options, List\u003CVector2\u003E openPath, Vector2 last )\r\n\t{\r\n\t\tvar pointCount = 6;\r\n\t\tvar tScale = 1f / pointCount;\r\n\r\n\t\tfor ( var i = 0; i \u003C pointCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar t = (i \u002B 1) * tScale;\r\n\t\t\tvar s = 1f - t;\r\n\r\n\t\t\tvar a = s * s * s;\r\n\t\t\tvar b = 3f * s * s * t;\r\n\t\t\tvar c = 3f * s * t * t;\r\n\t\t\tvar d = t * t * t;\r\n\r\n\t\t\tvar p0 = last;\r\n\t\t\tvar p1 = new Vector2( cmd.X0, cmd.Y0 );\r\n\t\t\tvar p2 = new Vector2( cmd.X1, cmd.Y1 );\r\n\t\t\tvar p3 = new Vector2( cmd.X2, cmd.Y2 );\r\n\r\n\t\t\topenPath.Add( p0 * a \u002B p1 * b \u002B p2 * c \u002B p3 * d );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic string ToSvg()\r\n\t{\r\n\t\tvar openEdges = new HashSet\u003Cint\u003E( _activeEdges );\r\n\t\tvar writer = new StringWriter();\r\n\r\n\t\twriter.WriteLine( \u0022\u003Csvg xmlns=\\\u0022http://www.w3.org/2000/svg\\\u0022\u003E\u0022 );\r\n\r\n\t\twhile ( openEdges.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tvar firstIndex = openEdges.First();\r\n\r\n\t\t\tvar edge = _allEdges[firstIndex];\r\n\r\n\t\t\twriter.Write( \u0022  \u003Cpolygon points=\\\u0022\u0022 );\r\n\r\n\t\t\twhile ( true )\r\n\t\t\t{\r\n\t\t\t\twriter.Write( $\u0022{edge.Origin.x:R},{edge.Origin.y:R} \u0022 );\r\n\t\t\t\topenEdges.Remove( edge.Index );\r\n\r\n\t\t\t\tif ( edge.NextEdge == firstIndex )\r\n\t\t\t\t{\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tedge = _allEdges[edge.NextEdge];\r\n\t\t\t}\r\n\r\n\t\t\twriter.WriteLine(\u0022\\\u0022 fill=\\\u0022black\\\u0022 stroke=\\\u0022red\\\u0022 /\u003E\u0022);\r\n\t\t}\r\n\r\n\t\twriter.WriteLine( @\u0022\u003C/svg\u003E\u0022 );\r\n\r\n\t\treturn writer.ToString();\r\n\t}\r\n}\r\n"},{"Ident":"facepunch.libpolygon","Path":"Code/Pooled.cs","FileName":"Pooled.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":55832,"IsPrivate":false,"Code":"using System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace Sandbox.Polygons;\r\n\r\npublic abstract class Pooled\u003CT\u003E : IDisposable\r\n    where T : Pooled\u003CT\u003E, new()\r\n{\r\n#pragma warning disable SB3000\r\n    private const int MaxPoolCount = 64;\r\n    private static List\u003CT\u003E Pool { get; } = new();\r\n#pragma warning restore SB3000\r\n\r\n    public static T Rent()\r\n    {\r\n        lock ( Pool )\r\n        {\r\n            if ( Pool.Count \u003C= 0 ) return new T();\r\n\r\n            var writer = Pool[^1];\r\n            Pool.RemoveAt( Pool.Count - 1 );\r\n\r\n            writer._isInPool = false;\r\n            writer.Reset();\r\n\r\n            return writer;\r\n        }\r\n    }\r\n\r\n    public void Return()\r\n    {\r\n        lock ( Pool )\r\n        {\r\n            if ( _isInPool ) throw new InvalidOperationException( \u0022Already returned.\u0022 );\r\n\r\n            Reset();\r\n\r\n            _isInPool = true;\r\n\r\n            if ( Pool.Count \u003C MaxPoolCount ) Pool.Add( (T) this );\r\n        }\r\n    }\r\n\r\n    private bool _isInPool;\r\n\r\n    public abstract void Reset();\r\n\r\n    public void Dispose()\r\n    {\r\n        Return();\r\n    }\r\n}\r\n"}]}