391 lines
9.8 KiB
C#
391 lines
9.8 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Godot;
|
|
using TriangleNet.Geometry;
|
|
using TriangleNet.Meshing;
|
|
using TriangleNet.Meshing.Algorithm;
|
|
|
|
namespace Rokojori
|
|
{
|
|
using ClipperPath = List<ClipperLib.IntPoint>;
|
|
using ClipperShape = List<List<ClipperLib.IntPoint>>;
|
|
|
|
public enum ShapeFillRule
|
|
{
|
|
NonZero,
|
|
EvenOdd
|
|
}
|
|
|
|
public class Shape2
|
|
{
|
|
public List<Path2> paths = new List<Path2>();
|
|
public ShapeFillRule fillRule = ShapeFillRule.NonZero;
|
|
|
|
public Shape2( params Path2[] paths )
|
|
{
|
|
this.paths.AddRange( paths );
|
|
}
|
|
|
|
public Shape2( List<Path2> 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<Path2>();
|
|
|
|
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<List<ClipperLib.IntPoint>> ToClipperPaths( Shape2 s )
|
|
{
|
|
var paths = new List<List<ClipperLib.IntPoint>>();
|
|
|
|
s.paths.ForEach( p => paths.Add( Path2.ToClipperPath( p ) ) );
|
|
|
|
return paths;
|
|
}
|
|
|
|
public static Shape2 FromClipperPaths( List<List<ClipperLib.IntPoint>> 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<Path2> 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<List<ClipperLib.IntPoint>>();
|
|
|
|
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<string>();
|
|
|
|
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<Vector2> 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<Vector2>();
|
|
|
|
}
|
|
|
|
var debugPoints = new List<Vector2>();
|
|
|
|
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<Vector3>( vertexCount );
|
|
meshGeometry.uvs = new List<Vector2>( vertexCount );
|
|
meshGeometry.indices = new List<int>( triangleCount * 3 );
|
|
meshGeometry.normals = new List<Vector3>( 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<string>();
|
|
|
|
|
|
for ( int i = 0; i < vertices.Length; i++ )
|
|
{
|
|
meshGeometry.indices.Add( t.GetVertexID( i ) );
|
|
}
|
|
}
|
|
|
|
|
|
return meshGeometry;
|
|
}
|
|
}
|
|
} |