using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
namespace FeMM.Grasshopper.Components.Patterning
{
public class MeshRemapComponent : GH_Component
{
/// <summary>
/// Initializes a new instance of the MeshRemap class.
/// </summary>
public MeshRemapComponent()
: base("Mesh Remap", "MR", "Remap points from a mesh to another", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_PATTERNING)
{
}
/// <summary>
/// Registers all the input parameters for this component.
/// </summary>
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddMeshParameter("Mesh A", "MA", "Mesh A", GH_ParamAccess.item);
pManager.AddMeshParameter("Mesh B", "MB", "Mesh B", GH_ParamAccess.item);
pManager.AddGeometryParameter("Geometry A", "GA", "Geometry on mesh A that will be remapped on mesh B", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddGeometryParameter("Geometry B", "GB", "Geometry on mesh B that will be remapped on mesh A", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddIntegerParameter("Curve Samples", "CS", "Number of samples per curve", GH_ParamAccess.item, 100);
pManager.AddNumberParameter("Tolerance", "T", "Tolerance of the mesh closest point operation", GH_ParamAccess.item, 100);
}
/// <summary>
/// Registers all the output parameters for this component.
/// </summary>
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGeometryParameter("A to B", "A", "Geometry A mapped to Mesh B", GH_ParamAccess.list);
pManager.AddGeometryParameter("B to A", "B", "Geometry B mapped to Mesh A", GH_ParamAccess.list);
}
/// <summary>
/// This is the method that actually does the work.
/// </summary>
/// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
protected override void SolveInstance(IGH_DataAccess DA)
{
var meshA = new GH_Mesh();
var meshB = new GH_Mesh();
var geomA = new List<IGH_GeometricGoo>();
var geomB = new List<IGH_GeometricGoo>();
int samples = 100;
double tol = 100;
if (!DA.GetData(0, ref meshA))
{
return;
}
if (!DA.GetData(1, ref meshB))
{
return;
}
DA.GetDataList(2, geomA);
DA.GetDataList(3, geomB);
DA.GetData(4, ref samples);
DA.GetData(5, ref tol);
var AtoB = new List<IGH_GeometricGoo>();
var BtoA = new List<IGH_GeometricGoo>();
if (meshA.Value.Vertices.Count != meshB.Value.Vertices.Count || meshA.Value.Faces.Count != meshB.Value.Faces.Count)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Mesh A and mesh B have a different number of vertices or faces.");
}
for (int i = 0; i < geomA.Count; ++i)
{
Remap(meshA, meshB, geomA[i], ref AtoB, samples, tol);
}
for (int i = 0; i < geomB.Count; ++i)
{
Remap(meshB, meshA, geomB[i], ref BtoA, samples, tol);
}
DA.SetDataList(0, AtoB);
DA.SetDataList(1, BtoA);
}
private void Remap(GH_Mesh meshA, GH_Mesh meshB, IGH_GeometricGoo geometry, ref List<IGH_GeometricGoo> outList, int samples, double tol)
{
// Remap one point
GH_Point pt = geometry as GH_Point;
if (pt != null)
{
if (FromAtoB(meshA.Value, meshB.Value, pt.Value, tol, out Point3d newPoint))
{
outList.Add(new GH_Point(newPoint));
return;
}
}
// Remap a curve
GH_Curve crv = geometry as GH_Curve;
if (crv != null)
{
// If samples is set, resample the curve
if (samples > 0)
{
var poly = new Polyline();
double sampleInterval = 1.0 / samples;
for (int j = 0; j <= samples; ++j)
{
if (FromAtoB(meshA.Value, meshB.Value, crv.Value.PointAtNormalizedLength(j * sampleInterval), tol, out Point3d newPoint))
{
poly.Add(newPoint);
continue;
}
}
if (poly.Count > 1)
{
outList.Add(new GH_Curve(poly.ToPolylineCurve()));
}
}
// If samples is not set, transpose the curve
else
{
// Transpose a polyline curve
// @TODO: It sometimes exit with an invalid curve
PolylineCurve polyC = crv.Value as PolylineCurve;
if (polyC != null)
{
var poly = new Polyline();
for (int n = 0; n < polyC.PointCount; ++n)
{
if (FromAtoB(meshA.Value, meshB.Value, polyC.Point(n), tol, out Point3d newpt))
{
poly.Add(newpt);
}
}
outList.Add(new GH_Curve(poly.ToPolylineCurve()));
return;
}
// Default case: map start and end point
var outLine = new LineCurve();
if (FromAtoB(meshA.Value, meshB.Value, crv.Value.PointAtStart, tol, out Point3d newStartPoint) &&
FromAtoB(meshA.Value, meshB.Value, crv.Value.PointAtEnd, tol, out Point3d newEndPoint))
{
outLine.SetStartPoint(newStartPoint);
outLine.SetEndPoint(newEndPoint);
outList.Add(new GH_Curve(outLine));
}
return;
}
}
}
/// <summary>
/// Map a point from meshA to mesh B.
/// </summary>
private bool FromAtoB(Mesh meshA, Mesh meshB, Point3d pt, double tol, out Point3d newPoint)
{
newPoint = Point3d.Unset;
var closest = meshA.ClosestMeshPoint(pt, tol);
if (closest == null)
return false;
var face = meshB.Faces[closest.FaceIndex];
var pt3d = new Point3d(0, 0, 0);
pt3d += new Vector3d(meshB.Vertices[face.A]) * closest.T[0];
pt3d += new Vector3d(meshB.Vertices[face.B]) * closest.T[1];
pt3d += new Vector3d(meshB.Vertices[face.C]) * closest.T[2];
pt3d += new Vector3d(meshB.Vertices[face.D]) * closest.T[3];
newPoint = pt3d;
return true;
}
public override GH_Exposure Exposure => GH_Exposure.tertiary;
/// <summary>
/// Provides an Icon for the component.
/// </summary>
protected override System.Drawing.Bitmap Icon => Properties.Resources.MeshRemapIcon;
/// <summary>
/// Gets the unique ID for this component. Do not change this ID after release.
/// </summary>
public override Guid ComponentGuid => new("4bc2da90-9230-4fb8-b044-7b70e826270c");
}
}