I have worked extensively with the Navisworks API and specifically the geometry callback API. I have never found a way to internally identify the shared vertices but honestly, even if there was a way using nwId or something similar, I would never use it. The Navisworks COM Interop operations in the .NET API is incredibly slow in comparison to all other operations. Anytime you try to access a COM object in the .NET workspace you are bottle-necking your application due to the underlying wrapping that takes place between the COM server and the .NET framework. In a highly optimized exporter I've found that 60% of all the time used by the application is interacting with COM objects and their fields. So with that said, there is a simple solution that you could use:
1. Create a "Lookup" class that ensures only unique vertices are collected
public class Lookup<T>
{
protected readonly Dictionary<T, int> _dict;
protected readonly List<T> _list = new List<T>();
public int Count => _list.Count;
public T this[int index] => _list[index];
public Lookup() => _dict = new Dictionary<T, int>();
public Lookup(IEqualityComparer<T> comparer) => _dict = new Dictionary<T, int>(comparer);
public virtual int Add(T p)
{
if (_dict.TryGetValue(p, out int index))
return index;
else
{
_list.Add(p);
return _dict[p] = _dict.Count;
}
}
public T[] AsOrderedArray() => _list.ToArray();
}
2. Create a Vector3f class to handle the vertices retrieved from Navisworks:
public readonly struct Vec3f : IEquatable<Vec3f>
{
public readonly float X;
public readonly float Y;
public readonly float Z;
public Vec3f(float x, float y, float z)
=> (X, Y, Z) = (x, y, z);
public override int GetHashCode()
=> X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2);
public override bool Equals(object other)
{
if (!(other is Vec3f vec3f))
return false;
return Equals(vec3f);
}
public bool Equals(Vec3f other)
=> X == other.X && Y == other.Y && Z == other.Z;
}
Note: The Vec3f is an immutable struct (as all structs likely should be). This is to ensure that the value you store is the value you want stored and cannot be changed.
3. Create an instance of the Lookup class for the Vec3f class. When you add new vertices to the Lookup it will return the position within the lookup that that vertices exists. You can then use this to create your indexed triangles.
// Default constructor works because we override GetHashCode
// And Equals in our Vec3f class
private readonly Lookup<Vec3f> vertices = new Lookup<Vec3f>();
// This should be uint or int depending on the target format
private readonly List<int> indices = new List<int>();
public void AddVertex(Vec3f v)
{
indices.add(vertices.add(v));
}
Now you have a list of only unique vertices and a list of integers where each 3 in a row represent one indexed triangle. I used List<int> instead of List<Triangle> (and creating a specific triangle class) because I've found that almost all formats want your indexed triangles in the form of an Array where each 3 in a row represent one triangle.
This operation is extremely efficient. I've found that only around 5% of CPU time is used adding vertices and creating triangles, even on the largest of models. Your bottleneck is always going to be when you call v.coord on a InwSimpleVertex object as this needs to be wrapped before the .NET API can use it.