using System.Collections; using System.Collections.Generic; using Godot; using TriangleNet.Geometry; using TriangleNet.Meshing; using TriangleNet.Meshing.Algorithm; namespace Rokojori { using ClipperPath = List; using ClipperShape = List>; public enum ShapeFillRule { NonZero, EvenOdd } public class Shape2 { public List paths = new List(); public ShapeFillRule fillRule = ShapeFillRule.NonZero; public Shape2( params Path2[] paths ) { this.paths.AddRange( paths ); } public Shape2( List paths ) { this.paths.AddRange( paths ); } public Shape2 CleanUp( ShapeFillRule fillRule = ShapeFillRule.NonZero ) { var clipperPath = ToClipperPaths( this ); var clipper = new ClipperLib.Clipper(); var fillType = _ConvertFillRule( fillRule ); clipperPath = ClipperLib.Clipper.SimplifyPolygons( clipperPath, fillType ); return FromClipperPaths( clipperPath ); } Polygon _CreateTNetPolygon( bool simplify = true ) { // RJLog.Log( "Creating polygon", paths.Count ); if ( paths.Count == 0 ) { RJLog.Log( "No paths" ); return null; } var polyPaths = paths; if ( simplify && false ) { var resultPaths = Lists.Map( polyPaths, p => Path2.ToClipperPath( p ) ); resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths, ClipperLib.PolyFillType.pftEvenOdd ); polyPaths = new List(); resultPaths.ForEach( ( r ) => { polyPaths.Add( Path2.FromClipperPath( r ) ); } ); } // RJLog.Log( "Using paths", polyPaths.Count ); var polygon = new Polygon(); var index = 0; var firstIsClockWise = polyPaths.Count > 0 && polyPaths[ 0 ].isClockwise; polyPaths.ForEach( ( path )=> { var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) ); var center = path.center; var isClockwise = path.isClockwise; var isHole = firstIsClockWise != isClockwise; // RJLog.Log( "Adding contour", center, vertices.Count, isHole ? "Hole" : "Fill" ); polygon.Add( new Contour( vertices, index ), isHole ); index ++; } ); return polygon; } public static List> ToClipperPaths( Shape2 s ) { var paths = new List>(); s.paths.ForEach( p => paths.Add( Path2.ToClipperPath( p ) ) ); return paths; } public static Shape2 FromClipperPaths( List> paths ) { var shape = new Shape2(); paths.ForEach( p => shape.paths.Add( Path2.FromClipperPath( p ) ) ); return shape; } static ClipperLib.PolyFillType _ConvertFillRule( ShapeFillRule fillRule ) { return ShapeFillRule.EvenOdd == fillRule ? ClipperLib.PolyFillType.pftEvenOdd : ClipperLib.PolyFillType.pftNonZero; } public string ToSVGPath() { var svgPaths = Lists.Map( paths, p => p.ToSVGPath() ); return Lists.Join( svgPaths, " " ); } public static Shape2 UnionAll( List paths ) { paths.ForEach( p => { if ( ! p.isClockwise ) { p.Reverse(); } // RJLog.Log( pathIndex++, p.isClockwise ); } ); var shape = new Shape2(); shape.fillRule = ShapeFillRule.EvenOdd; // shape.paths = paths; // shape.CleanUp(); shape.paths.Add( paths[ 0 ] ); for ( int i = 1; i < paths.Count; i++ ) { var otherShape = new Shape2(); otherShape.fillRule = ShapeFillRule.EvenOdd; otherShape.paths.Add( paths[ i ] ); var pathsBefore = shape.paths.Count; shape = Shape2.Boolean( shape, otherShape, Geometry2D.PolyBooleanOperation.Union ); // RJLog.Log( "Union:", pathsBefore, shape.paths.Count ); } return shape; } public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true ) { // RJLog.Log( "Using Clipper Library" ); var clipperPathsA = ToClipperPaths( a ); var clipperPathsB = ToClipperPaths( b ); var resultPaths = new List>(); var type = ClipperLib.ClipType.ctUnion; if ( Geometry2D.PolyBooleanOperation.Difference == booleanOperation ) { type = ClipperLib.ClipType.ctDifference; } else if ( Geometry2D.PolyBooleanOperation.Intersection == booleanOperation ) { type = ClipperLib.ClipType.ctIntersection; } else if ( Geometry2D.PolyBooleanOperation.Xor == booleanOperation ) { type = ClipperLib.ClipType.ctXor; } var subjectPolyType = _ConvertFillRule( a.fillRule ); var clipPolyType = _ConvertFillRule( b.fillRule ); // RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count ); var clipper = new ClipperLib.Clipper(); clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true); clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true); clipper.Execute( type, resultPaths, subjectPolyType, clipPolyType ); if ( simplify ) { resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths ); } var s = new Shape2(); resultPaths.ForEach( ( r ) => { s.paths.Add( Path2.FromClipperPath( r ) ); } ); return s; } public static Shape2 FromSVGPath( string pathData, float resolution ) { var shape = new Shape2(); var commands = SVGPathParser.Parse( pathData ); if ( commands != null ) { /* RJLog.Log( "Commands:", commands.Count ); var commandsInfo = new List(); var cindex = 0; commands.ForEach( ( c )=> { commandsInfo.Add( "[" + cindex + "] " + c.type + ":\n" + RJLog.Stringify( c.paramaters ) ); cindex ++; } ); RJLog.Log( Lists.Join( commandsInfo, "\n" ) ); */ var extractor = new SVGPathExtractor(); List pathPoints = null; var index = 0; extractor.onInstruction.AddAction( ( si )=> { var newPathIsStarting = SVGPathInstructionType.MoveTo == si.type || ( SVGPathInstructionType.MoveTo != si.type && pathPoints == null ); if ( newPathIsStarting ) { if ( pathPoints != null ) { shape.paths.Add( new Path2( pathPoints ) ); } pathPoints = new List(); } var debugPoints = new List(); si.AddPoints( debugPoints, resolution ); var cm = si.sourceCommand; var parameterLength = SVGPathCommand.GetParameterLengthForCommand( cm.type ); var cmInfo = "[" + cm.pathIndex + "]" + si.sourceCommand.type; if ( parameterLength > 0 ) { cmInfo += " "; var offset = parameterLength * si.sourceCommandIndex; for ( int i = 0; i < parameterLength; i++ ) { if ( i != 0 ) { cmInfo += ", "; } cmInfo += RegexUtility.NumberToString( cm.paramaters[ offset + i ] ); } } // RJLog.Log( index, newPathIsStarting ? "+" : " ", si.type, debugPoints, ">>", cmInfo ); index++; si.AddPoints( pathPoints, resolution ); } ); extractor.Process( commands ); if ( pathPoints != null ) { shape.paths.Add( new Path2( pathPoints ) ); } } else { RJLog.Log( "No commands found for:", pathData ); var messages = SVGPathParser.GetMessages( pathData ); RJLog.Log( messages ); } // RJLog.Log( "Shape has", shape.paths.Count, "paths >> ", Lists.Map( shape.paths, p => p.points.Count + "" ) ); return shape; } public MeshGeometry CreateFillMeshGeometry() { /*if ( paths.Count == 1 ) { // RJLog.Log( "Only 1 path" ); return paths[ 0 ].CreateMeshGeometry(); }*/ var polygon = _CreateTNetPolygon(); if ( polygon == null ) { RJLog.Log( "Could not create polygon" ); return null; } var options = new ConstraintOptions(); var quality = new QualityOptions(); var triangulator = new Dwyer(); IMesh mesh = polygon.Triangulate( options, quality, triangulator ); int vertexCount = mesh.Vertices.Count; int triangleCount = mesh.Triangles.Count; var meshGeometry = new MeshGeometry(); meshGeometry.vertices = new List( vertexCount ); meshGeometry.uvs = new List( vertexCount ); meshGeometry.indices = new List( triangleCount * 3 ); meshGeometry.normals = new List( vertexCount ); foreach ( var v in mesh.Vertices ) { var x = (float)v.x; var y = (float)v.y; meshGeometry.vertices.Add( new Vector3( x, 0, y ) ); meshGeometry.uvs.Add( new Vector2( x, y ) ); meshGeometry.normals.Add( Vector3.Up ); } foreach ( var t in mesh.Triangles ) { var vertices = t.vertices; var indicesList = new List(); for ( int i = 0; i < vertices.Length; i++ ) { meshGeometry.indices.Add( t.GetVertexID( i ) ); } } return meshGeometry; } } }