restore weight to BaseContainer and get it Vs Direct get weight from weight Tag



  • I tried to get the weight from the weight tag, then stored it in the BaseContainer and then applied it to the custom skin deformer, but it was much slower than getting the weight directly from the weight tag. The speed is about one tenth of the latter. What influenced it?
    (In addition, even if I get the weight directly from the weight tag, the fps can only reach half of the c4d skin deformer. )
    Can you have any suggestions?


  • Global Moderator

    Hi @chuanzhen, without any code it's actually hard to help you. Why do you need to store data in a base container?
    As you may already know, a BaseContainer can't store a list, so you have iterate all the joint of the weight tag, then for each join, create a BaseContainer which will hold, the pt_ID and the weight value.
    Then you have to read it again. So it's very inefficient.

    Note if speed is important for you (and it's for any stuff which deals with animation) you should consider switching to c++.
    But without any more information, code I can't really help you more.

    Cheers,
    Maxime!



  • @m_adam Thanks for your some suggestions.
    feel that the code is not very readable , so I made a special comment!
    this image is my plugin ,
    0_1539945119657_new_skin.png

    Because of some of my needs, I decided to rewrite a skin.(After working in another skin, in order to ensure the real-time refresh of the window, a "check link box" was set up.)

    this is my code:(Compared to c4d skin it seems to be half its speed)

    class newskin(plugins.ObjectData):
    
        lasttagdirty = None   #for weight change update
    
        jointdir = None       #for joint change update
    
        cachedir = None       #for other bindmesh check
    
        def CheckDirty(self, op, doc):
    
            #check first run
    
            if op[c4d.NEWSKINTAG] is None:
    
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
    
                return 
    
            #check weight change
    
            if op[c4d.NEWSKINTAG].GetDirty(c4d.DIRTYFLAGS_DATA) != self.lasttagdirty:
    
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
    
                return 
    
            #check matrix change
    
            checktime = op[c4d.NEWSKINTAG].GetJointCount()
    
            cdirty = 0
    
            for i in xrange(checktime):
    
                cdirty += op[c4d.NEWSKINTAG].GetJoint(i).GetDirty(c4d.DIRTYFLAGS_MATRIX)
    
            if cdirty != self.jointdir or self.cachedir != op[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE): 
    
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
    
                return 
    
            return 
    
                    
    
        def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):
    
            if mod[c4d.NEWSKINTAG] is None or mod[c4d.NEWSKINCHECK] is None:
    
                return True
    
            tag = mod[c4d.NEWSKINTAG]
    
            self.lasttagdirty = tag.GetDirty(c4d.DIRTYFLAGS_DATA)  #get current weight tag dirty
    
            self.cachedir = mod[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE)    #get bindmesh check
    
            plist = op.GetAllPoints()
    
            pcount = op.GetPointCount()   
    
            jcount = tag.GetJointCount()
    
            self.jointdir = 0
    
            for m in xrange(jcount):
    
                joint = tag.GetJoint(m)
    
                self.jointdir += joint.GetDirty(c4d.DIRTYFLAGS_MATRIX)   #all joint current matrix dirtycount
    
            for n in xrange(pcount):    #n:point index   
    
                pos = op_mg * plist[n]  #global postion
    
                temp = c4d.Vector()
    
                for m in xrange(jcount):
    
                    joint = tag.GetJoint(m) 
    
                    weight = tag.GetWeight(m, n)
    
                    if not weight :
    
                        continue   
    
                    cjmg = joint.GetMg()
    
                    jdict = tag.GetJointRestState(m)
    
                    jmg = jdict["m_bMg"]
    
                    jmi = jdict["m_bMi"]
    
                    temp += weight * cjmg * jmi * pos  #defrmer global pos 
    
                op.SetPoint(n,~op_mg * temp)
    
            op.Message(c4d.MSG_UPDATE)
    
            return True

  • Global Moderator

    Actually, I don't see any big issue in your code and our role is not to develop for you.
    The only suggestion I can tell is instead of calling SetPoint for each point, maybe it's worth calling directly SetAllPoints.

    Moreover, you could also do a proper profiling by splitting your ModifyObject into separate functions in order to know which part is taking longer and after it's up to you to rework your algorithm. Niklas wrote a great article about that.

    And finally switching to C++ will definitely give you performance improvement. Even with a similar code base.

    Cheers,
    Maxime.



  • @m_adam Thanks ,this really helped me!


  • Global Moderator

    Hi @chuanzhen actually the problem interested me and I took some personal time on my weekend to do some tests.

    I did 2 test scenes, which was a cube with 163970 points driven by 3 joints
    With your original source code 0.53 fps
    optimize python: 1.09 fps (x2.05)
    C++: 19.36 fps (x36.52)

    And the other one was the same cube but driven by 11 joints.
    With your original source code 0.37 fps
    optimize python: 0.64 fps (x1.73)
    C++: 12.08 (x32.65)

    So here is the optimized python code. As you can see I try to avoid as much as possible doing operation in the nested loop.

    class newskin(plugins.ObjectData):
     
        lasttagdirty = None   #for weight change update
        jointdir = None       #for joint change update
        cachedir = None       #for other bindmesh check
     
        def CheckDirty(self, op, doc):
            #check first run
            if op[c4d.NEWSKINTAG] is None:
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            #check weight change
            if op[c4d.NEWSKINTAG].GetDirty(c4d.DIRTYFLAGS_DATA) != self.lasttagdirty:
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            #check matrix change
            checktime = op[c4d.NEWSKINTAG].GetJointCount()
            cdirty = 0
     
            for i in xrange(checktime):
                cdirty += op[c4d.NEWSKINTAG].GetJoint(i).GetDirty(c4d.DIRTYFLAGS_MATRIX)
     
            if cdirty != self.jointdir or self.cachedir != op[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE):
                op.SetDirty(c4d.DIRTYFLAGS_DATA)
                return
     
            return
     
       
        def ModifyObject(self, mod, doc, op, op_mg, mod_mg, lod, flags, thread):
     
            if mod[c4d.NEWSKINTAG] is None or mod[c4d.NEWSKINCHECK] is None:
                return True
     
            tag = mod[c4d.NEWSKINTAG]
            self.lasttagdirty = tag.GetDirty(c4d.DIRTYFLAGS_DATA)  #get current weight tag dirty
            self.cachedir = mod[c4d.NEWSKINCHECK].GetDirty(c4d.DIRTYFLAGS_CACHE)    #get bindmesh check
            plist = [pos * op_mg for pos in op.GetAllPoints()]
            pcount = op.GetPointCount()  
            jcount = tag.GetJointCount()
            self.jointdir = 0
     
            for m in xrange(jcount):
                joint = tag.GetJoint(m)
                self.jointdir += joint.GetDirty(c4d.DIRTYFLAGS_MATRIX)   #all joint current matrix dirtycount
     
            temp = c4d.Vector()
            for n in xrange(pcount):    #n:point index  
                temp %= temp
     
                for m in xrange(jcount):
                    joint = tag.GetJoint(m)
                    weight = tag.GetWeight(m, n)
     
                    if not weight :
                        continue  
     
                    cjmg = joint.GetMg()
                    jdict = tag.GetJointRestState(m)
                    jmg = jdict["m_bMg"]
                    jmi = jdict["m_bMi"]
     
                    temp += weight * cjmg * jmi * plist[n]  #defrmer global pos
                plist[n] = temp
           
            plist = [~op_mg * pos for pos in plist]
            op.SetAllPoints(plist)
            op.Message(c4d.MSG_UPDATE)
     
            return True
    

    And here the C++ version (compatible R20 only). It uses paralellFor, not sure it's really worth didn't do proper profiling about it but I wanted to try them. Moreover please do not consider my C++ code has fully optimized since you could probably improve it depending on the situation.

    #include "c4d_symbols.h"
    #include "main.h"
    #include "c4d_objectdata.h"
    #include "lib_ca.h"
    
    // Local resources
    #include "oskinmodifier.h"
    #include "maxon/parallelfor.h"
    #include "maxon/basearray.h"
    
    /**A unique plugin ID. You must obtain this from http://www.plugincafe.com. Use this ID to create new instances of this object.*/
    static const Int32 ID_OBJECTDATA_SKINMODIFIER = 1000002;
    
    class SkinModifier : public ObjectData
    {
    	INSTANCEOF(SkinModifier, ObjectData)
    
    public:
    	static NodeData* Alloc() { return NewObj(SkinModifier) iferr_ignore("SkinModifier plugin not instanced"); }
    
    	virtual void CheckDirty(BaseObject *op, BaseDocument *doc);
    	virtual Bool ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread);
    
    private:
    
    	BaseList2D* GetBaseLink(BaseObject* op, DescID id, Int excepted);
    	Int lasttagdirty; /// get current weight tag dirty
    	Int jointdir;
    	Int cachedir;
    };
    
    void SkinModifier::CheckDirty(BaseObject *op, BaseDocument *doc)
    {
    	BaseList2D* t = GetBaseLink(op, DescID(NEWSKINTAG), Tweights);
    	BaseList2D* l = GetBaseLink(op, DescID(NEWSKINCHECK), Opolygon);
    	if (!t || !l)
    		return;
    
    	PointObject* linkOp = static_cast<PointObject*>(l);
    	CAWeightTag* tag = static_cast<CAWeightTag*>(t);
    
    	// check first run
    	if (!tag)
    	{
    		op->SetDirty(DIRTYFLAGS::DATA);
    		return;
    	}
    
    	// check weight change
    	if (tag->GetDirty(DIRTYFLAGS::DATA) != lasttagdirty)
    	{
    		op->SetDirty(DIRTYFLAGS::DATA);
    		return;
    	}
    
    	// check matrix change
    	Int cdirty = 0;
    	for (Int i = 0; i < tag->GetJointCount(); i++)
    	{
    		cdirty += tag->GetJoint(i, tag->GetDocument())->GetDirty(DIRTYFLAGS::MATRIX);
    		if (cdirty != jointdir || cachedir != linkOp->GetDirty(DIRTYFLAGS::CACHE))
    		{
    			op->SetDirty(DIRTYFLAGS::DATA);
    			return;
    		}
    	}
    
    	return;
    }
    
    BaseList2D* SkinModifier::GetBaseLink(BaseObject* op, DescID id, Int excepted)
    {
    	GeData data;
    	if (!op->GetParameter(id, data, DESCFLAGS_GET::NONE))
    		return nullptr;
    
    	return data.GetLink(op->GetDocument(), excepted);
    }
    
    
    Bool SkinModifier::ModifyObject(BaseObject* mod, BaseDocument* doc, BaseObject* op, const Matrix& op_mg, const Matrix& mod_mg, Float lod, Int32 flags, BaseThread* thread)
    {
    	if (!mod || !op || !doc || !thread)
    		return false;
    
    	BaseList2D* t = GetBaseLink(mod, DescID(NEWSKINTAG), Tweights);
    	BaseList2D* l = GetBaseLink(mod, DescID(NEWSKINCHECK), Opolygon);
    	if (!t || !l)
    		return false;
    
    	PointObject* linkOp = static_cast<PointObject*>(l);
    	CAWeightTag* tag = static_cast<CAWeightTag*>(t);
    
    	lasttagdirty = tag->GetDirty(DIRTYFLAGS::DATA);
    	cachedir = linkOp->GetDirty(DIRTYFLAGS::CACHE);
    
    	PointObject* pObj = ToPoint(op);
    	const Vector * pListR = pObj->GetPointR();
    	Vector * pListW = pObj->GetPointW();
    
    	const Int pcount = pObj->GetPointCount();
    	const Int jcount = tag->GetJointCount();
    	jointdir = 0;
    
    	for (Int i = 0; i < jcount; i++)
    	{
    		BaseObject* joint = tag->GetJoint(i, doc);
    		jointdir += joint->GetDirty(DIRTYFLAGS::MATRIX);
    	}
    
    
    	auto worker = [jcount, op_mg, &pListR, &pListW, &doc, &tag](maxon::Int i)
    	{
    		Vector temp;
    		Vector pos = op_mg * pListR[i];
    
    		for (Int m = 0; m < jcount; m++)
    		{
    			Vector pos = op_mg * pListR[i];
    			BaseObject* joint = tag->GetJoint(m, doc);
    			Float weight = tag->GetWeight(m, i);
    
    			if (weight == 0.0)
    				continue;
    
    			Matrix cjmg = joint->GetMg();
    			JointRestState jrest = tag->GetJointRestState(m);
    			Matrix jmg = jrest.m_bMg;
    			Matrix jmi = jrest.m_bMi;
    
    			temp += weight * cjmg * jmi * pos;
    		}
    		pListW[i] = ~op_mg * temp;
    	};
    
    	maxon::ParallelFor::Dynamic(0, pcount, worker);
    
    	// Notify Cinema about the internal data update.
    	op->Message(MSG_UPDATE);
    
    	return true;
    }
    
    Bool RegisterSkinModifier()
    {
    	return RegisterObjectPlugin(ID_OBJECTDATA_SKINMODIFIER, "C++ oskinmodifier"_s, OBJECT_MODIFIER, SkinModifier::Alloc, "oskinmodifier"_s, nullptr, 0);
    }
    

    Cheers,
    Maxime.



  • @m_adam Wow, I learned some tricks from your code! ( so important to me)
    Thank you very much for your optimization and testing of your personal time, which is very helpful to me!
    (C++ is so fast)