Hi, there is a very simple algorithm you can apply. See this console application example
written in C#. Takes on my medium PC less than 100ms for 10000 points.
It can further be optimized, but the logic is quite obvious when you follow the comments.
internal class Program
{
private static void Main(string[] args)
{
// Given 8 points, 2 duplicates; expect 6 unique points
// X [5,6,7,3,3,7,8,2]
// Y [3,4,7,5,5,7,4,1]
// Z [2,3,7,3,3,7,3,3]
// VertexIndices [0,1,2,3,4,5,6,7]
// FaceIndices [0,0,0,0,1,1,1,1]
// Duplicates [ A,B,B,A ]
// Mask(0=unset) [1,2,3,4,4,3,5,6]
// because:
// 1.Run [1 0 0 0 0 0 0 0] (1 * '1')
// 2.Run [1 2 0 0 0 0 0 0] (1 * '2')
// 3.Run [1 2 3 0 0 3 0 0] (2 * '3')
// 4.Run [1 2 3 4 4 3 0 0] (2 * '4')
// 5.Run [1 2 3 4 4 3 5 0] (1 * '5')
// 6.Run [1 2 3 4 4 3 5 6] (1 * '6')
// Create points...
var points = new Point3d[8];
points[0] = new Point3d(5, 3, 2);
points[1] = new Point3d(6, 4, 3);
points[2] = new Point3d(7, 7, 7);
points[3] = new Point3d(3, 5, 3);
points[4] = new Point3d(3, 5, 3);
points[5] = new Point3d(7, 7, 7);
points[6] = new Point3d(8, 4, 3);
points[7] = new Point3d(2, 1, 3);
var facemask = new int[8];
facemask[0] = 0;
facemask[1] = 0;
facemask[2] = 0;
facemask[3] = 0;
facemask[4] = 1;
facemask[5] = 1;
facemask[6] = 1;
facemask[7] = 1;
// This is where the magic happens 'Cull and reindex'
var uniquePoints = new List<Point3d>();
int currentIndex = 1;
var vertexmask = new int[points.Length];
for (int i = 0;i < points.Length; i++)
{
if (vertexmask[i] != 0) continue; // Skip when mask is set
vertexmask[i] = currentIndex;
uniquePoints.Add(points[i]);
for (int j = i+1;j < points.Length; j++)
{
if (vertexmask[j] != 0) continue;
if (Point3d.EpsilonEquals(points[j], points[i], epsilon: 0.0001))
{
vertexmask[j] = currentIndex;
}
}
currentIndex++;
}
// Add indices to a data tree. This will be slightly different in Rhino/GH
var indexTree = new DataTree<int>();
int lastIndex = -1;
for (int i = 0; i < facemask.Length; i++)
{
currentIndex = facemask[i];
if (lastIndex != currentIndex)
{
indexTree.AddBranch(currentIndex);
lastIndex = currentIndex;
}
var current = indexTree.Branches.ElementAt(currentIndex);
current.AddItem(vertexmask[i]-1); //-1 because we change back to 0 based indexing
}
// Print the result
indexTree.Print();
Console.WriteLine("\nUnique points:");
uniquePoints.ForEach(p => Console.WriteLine($"({p.X} {p.Y} {p.Z})"));
}
}
public struct Point3d
{
public double X;
public double Y;
public double Z;
public Point3d(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public static bool EpsilonEquals(Point3d p1, Point3d p2, double epsilon = 0.00001)
{
return Math.Abs(p1.X - p2.X) <= epsilon &&
Math.Abs(p1.Y - p2.Y) <= epsilon &&
Math.Abs(p1.Z - p2.Z) <= epsilon;
}
}
public class DataTree<T>
{
public List<Branch<T>> Branches { get; } = new List<Branch<T>>();
public void AddBranch(int path)
{
Branches.Add(new Branch<T>(path));
}
public void Print()
{
Console.WriteLine("Tree:");
Branches.ForEach(b=>b.Print());
}
}
public class Branch<T>
{
public int Path { get; }
public List<T> Data { get; } = new List<T>();
public Branch(int path)
{
Path = path;
}
public void AddItem(T item)
{
Data.Add(item);
}
public void Print()
{
Console.WriteLine($" Branch {Path}");
Data.ForEach(item =>
Console.WriteLine($" └─ {item}"));
}
}
This simple console app will yield:
Tree
Branch 0
└─ 0
└─ 1
└─ 2
└─ 3
Branch 1
└─ 3
└─ 2
└─ 4
└─ 5
Unique points
(5 3 2)
(6 4 3)
(7 7 7)
(3 5 3)
(8 4 3)
(2 1 3)