Faster way to transfer weights?



  • On 18/07/2018 at 00:42, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   18/19 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Howdy, I am a novice at C++. I have a python script that basically iterates through each point of a mesh and does SetWeight(sourceJoint.GetWeight).

    In my test scene, the python script runs in about 9.2 seconds. It seemed like C++ would be able to speed this up. So I whipped up this little plugin which is basically a port of the python code, and it's like...1 second faster maybe(I haven't exactly figured out how to time it and print out the results, but by counting it's about 8 seconds).

    Am I doing this in the best way, or is there a better way to do this? This si in my commandData plugin.

    PointObject* obj = (PointObject* )(doc->GetActiveObject());
    	CAWeightTag* wt = (CAWeightTag* )(obj->GetTag(Tweights));
    	if (wt)
    		GePrint(wt->GetName());
      
    	BaseObject* targJoint = doc->SearchObject("Joint");
    	if (targJoint)
    		GePrint(targJoint->GetName());
    	BaseObject* sourceJoint = doc->SearchObject("mixamorig:Spine2");
    	if (sourceJoint)
    		GePrint(sourceJoint->GetName());
      
    	//Add targJoint to the weight tag
    	wt->AddJoint(targJoint);
      
    	Int32 pointCount = obj->GetPointCount();
    	Int32 sourceID = wt->FindJoint(sourceJoint, doc);
    	Int32 targID = wt->FindJoint(targJoint, doc);
      
    	for (int x = 0; x < pointCount; x = x + 1) {
      
    		wt->SetWeight(targID, x, wt->GetWeight(sourceID, x));
    		wt->WeightDirty();
    		wt->SetWeight(sourceID, x, 0.0);
    		wt->WeightDirty();
    	}
      
    	GePrint("Done Mofo");
    	//wt->TransferWeightMap(doc, wt, sindex, dindex, 0, NOTOK, nullptr);
      
    	EventAdd();
    

    Forgive the dumb mistakes, I am very very new and pretty stoked I managed to get this far.



  • On 18/07/2018 at 01:34, xxxxxxxx wrote:

    I would do the WeightDirty() only once, AND outside of the for loop.
    But wouldn't you be better off by using the Get- and SetWeightMap() instead?



  • On 18/07/2018 at 02:58, xxxxxxxx wrote:

    Hi xfon5168, thanks for writing us.

    With regard to your question, the performance increase you're actually benefiting is, in large part, caused by the Python call layer being skipped. The design of the code and the functions used are pretty likely the same of your Python script which means that the business logic is exactly the same.

    A few notes though:

    • why are you calling WeightDirty twice in the for loop? Calling it at the end of the for-loop could be more efficient with, maybe, no side effects on the delivered functionality;
    • move the counter definition outside of the for-loop definition.

    Last but not least, without a proper time-logging of the different execution calls of your code, could be really argue to propose an optimal solution even considering the presence of multiple FindXXX and SearchXXX calls.

    Best, Riccardo



  • On 18/07/2018 at 07:35, xxxxxxxx wrote:

    That all sounds reasonable. Get/SetWeightMap doesn't exist in python, so I couldn't use that before.

    However, I'm a little bit confused by GetWeightMap. What is the map flag in this case? For SetWeightMap that makes sense, but I don't understand what I need to pass in as the map key.

    I am basically trying to do:

    wt->SetWeightMap(targID, wt->GetWeightMap(sourceID), pointCount)
    

    But the docs suggest that GetWeightMap needs the ID, the map, and the pointCount. So what do I use for the map in this case? Or is the map in this case  singular value you are trying to flood? ie flood everything to 1 or 0.0? If that's the case then GetWeightMap won't work because I'm not trying to flood a map to a singular value, but instead copy all the weights from joint A and put them on joint B.

    Im sure the Search and Find calls are slow too. This is a prototype for a plugin. I've opted to use those simple calls to make it easier on myself to just test this to start. Eventually, there will be a UI where it controls what goes to where, etc.

    Thanks for your help so far.



  • On 18/07/2018 at 10:30, xxxxxxxx wrote:

    Long story short, it's not a flag.

    C++ doesn't have multiple return values.  If you want to return multiple things, you pass by reference or by using pointers to allocated memory.

    It's also faster to do this.  When you return an array in Python, it allocates a new block of memory, fills it (often copying from something else), returns it, and then deallocates the original.  That's a lot of overhead.

    So think of the *map as your return value. You pass the function an array of floats, and it fills it. The array should be the same size as the number of points.

    Here's a C++11 example of how to do it.  I get the spot in memory (map.data()).  That is the same as using map[0].  If I want the next spot, I can do map.data() + 1, or map.data() + 2 for map[1] or map[2] respectively.

    #include <iostream>
    #include <vector>

    using namespace std;

    void test_func(int* something, int value) {
        *something = value;
    }

    int main(int argc, char** argv) {
        // here we allocate memory
        // using a vector means we don't have to worry about deallocating
        vector<int> map(3);
        test_func(map.data(), 15);
        test_func(map.data()+1, 30);
        test_func(map.data()+2, 45);
        
        cout << "The values are " << map[0] << ", " << map[1] << ", and " << map[2] << "." << endl;
        return 0;
    }



  • On 18/07/2018 at 21:54, xxxxxxxx wrote:

    Hmmm. I added the includes, and using namespace std lines. Then I tried the following:

    vector<Float32> map(pointCount);
    	wt->SetWeightMap(targID, wt->GetWeightMap(sourceID, map, pointCount), pointCount);
    	wt->WeightDirty();
    

    But it is still complaining. It's saying argument type void incompatible with parameter type float32(This is for the wt in wt->GetWeightMap. and map says no suitable conversion from std::vector<Float32, std::allocator<Float32>> to Float 32 exists.



  • On 19/07/2018 at 01:36, xxxxxxxx wrote:

    Hello,

    just a comment: it is not advised to use components of the standard library in a Cinema 4D plugin. Please use the API's components if needed e.g. BaseArray or HashMap.

    best wishes,
    Sebastian



  • On 19/07/2018 at 07:19, xxxxxxxx wrote:

    Why is that?

    Especially in this case, provided the container isn't passed back out and is used only within the scope of the function, what is the difference between using it and a BaseArray?

    I'll side with you on HashMap over std::map since we all know std::map is way too slow. :)



  • On 19/07/2018 at 14:47, xxxxxxxx wrote:

    I'm new, so you guys are all just talking a lot of greek to me.

    How do I properly create the map key to be able to use wt.GetWeightMap to properly plug that result into wt.SetWeightMap? :).

    I just want to be able to compare that method's speed vs what I had previously.

    Follow up question, how come Get/SetWeightMap isn't available in Python? Is there any real reason why it could not be?



  • On 20/07/2018 at 02:08, xxxxxxxx wrote:

    Hello,

    using standard library components is not advised since the standard library uses/throws exceptions. And exceptions are a concept not supported/used by the Cinema API.

    You find example on how to use GetWeightMap() in the CAWeightTag Manual.

    best wishes,
    Sebastian



  • On 20/07/2018 at 20:11, xxxxxxxx wrote:

    Thanks for pointing me to that. It got my test up and running which is great. It appears to be substantially faster to run SetWeightMap.

    Is there any reason or limitation why that function doesn't exist for Python?



  • On 23/07/2018 at 03:25, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    Is there any reason or limitation why that function doesn't exist for Python?

    There's no reason or limitation preventing CAWeightTag.GetWeightMap()/SetWeightMap() not being available in Python. I added these functions to our list of missing stuff in the Python API.


Log in to reply