Sharing vertices in export plugin

Sharing vertices in export plugin

luca_vezza
Enthusiast Enthusiast
1,932 Views
8 Replies
Message 1 of 9

Sharing vertices in export plugin

luca_vezza
Enthusiast
Enthusiast

Hello,

for an export plugin we have, we are exporting the geometry using the usual approach going through the COM API and relying on the InwSimplePrimitivesCB class. In particular, we use the Triangle() method to do our stuff.

The problem is that method gets called for each triangle independently; if the original geometry is sharing vertices among triangles, that optimization is lost. Suppose for example you have a cube: 8 vertices creating 12 triangles. With this approach we'll output 12 triangles with no sharing, for a total of 36 vertices.

I have examined what the default FBX exporter dumps, and it actually shares the vertices as it should. Is there any way to achieve the same result from the API?

 

  Luca

1,933 Views
8 Replies
Replies (8)
Message 2 of 9

luca_vezza
Enthusiast
Enthusiast

While waiting for feedback on this, one more question that is related. I see that the InwSimpleVertex class used by the Triangle() method has two member called nwID and nwHandle. Is there any chance to rely on them to identify shared vertices? I mean, if two triangles share the same vertex, will that method get called twice but with one of the vertices sharing the same ID or Handle? If yes, this is a possible way to implement the needed code to share vertices in my plugin...

 

    Luca

0 Likes
Message 3 of 9

xiaodong_liang
Autodesk Support
Autodesk Support

Hi @luca_vezza ,

 

It also looks to me nwID could be useful to filter out the shared vertex. nwID returns the internal wrapped pointer of COM object. basically, it should be unique in one session. I do not either see any other ways. while I need to double check with engineer team if there is any comments.

 

as to nwHandle, was originally meant for internal use only. The blog below tells a bit more:

https://adndevblog.typepad.com/aec/2012/06/get-nwhandle-on-64bits.html

 

Message 4 of 9

awmcc90VZTT2
Contributor
Contributor

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.

0 Likes
Message 5 of 9

luca_vezza
Enthusiast
Enthusiast

Hi Andrew,

thanks a lot for your reply, I will certainly try that route as it looks like the best way so far. What really surprises me is that this question was apparently esacalated to Dev but no reply from that side has been received! I would have expected some feedback from them at this point...

 

Also, there is one part in your reply that sounds surprising to me:


@awmcc90VZTT2 wrote:

[...] 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. [...]


Wow, if that is true, it is the exact opposite of what I have read in several other threads in this forum, where people were suggesting to go the COM way for example to read properties, as that was the fastest way... Can we get a clear statement from Dev here, to help clarify this big point?

 

    Luca

0 Likes
Message 6 of 9

awmcc90VZTT2
Contributor
Contributor


Wow, if that is true, it is the exact opposite of what I have read in several other threads in this forum, where people were suggesting to go the COM way for example to read properties, as that was the fastest way... Can we get a clear statement from Dev here, to help clarify this big point?


 

I would like to know what a Dev has to say about this as well. I just know from my experience profiling NW plugins that this is the case. Just to clarify, the COM operations themselves are internally very efficient. And if you are creating a plugin using C++ and directly working with the COM objects then that too is very efficient. But when accessing individual fields of COM objects in the .NET API, those operations are very slow and this is a result of wrapping that takes place to allow the .NET application to work with a COM object. 

0 Likes
Message 7 of 9

luca_vezza
Enthusiast
Enthusiast

No reply yet from Dev... 😞

 

In the meantime, I have run a first test based on your suggestion and yes, it works. Note however that your code lacks an important part when testing equality: normals. For two vertices to be shared, they also have to share the same normal, otherwise you may get bad smoothing artifacts.

Thanks for your input anyway!

 

   Luca

Message 8 of 9

xiaodong_liang
Autodesk Support
Autodesk Support

Hi @luca_vezza @awmcc90VZTT2 ,

 

Sorry for late response. I was discussing with engineer team on this case. Finally we got to know the thing is that most of the vertex data you are accessing via the SimplePrimitives approach is that it is all indexed behind the scenes in core code of Navisworks. However  it gets “de-indexed” in the SimplePrimitives process.  So I logged a wish NW55011 : expose vertex index of triangle with SimplePrimitives

 

It might have to be the current workaround by comparing the vertices as you have discussed. My colleague Jeremy also shared some experiences to compare vertex:

 

  • Implement a comparison operator for the mesh vertices
  • Store all mesh vertices in their own dictionary, e.g., Dictionary<XYZ,int>. The int is just a dummy. I normally count the number of vertices received.
  • Each time I encounter a new mesh vertex, I check whether it is already listed in the dictionary. The comparison operator includes enough fuzz.
0 Likes
Message 9 of 9

peter_hirn
Enthusiast
Enthusiast

@xiaodong_liang Was NW55011 implemented?

0 Likes