rj-action-library/Runtime/Math/Geometry/Shape2.cs

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;
}
}
}