Solved 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?

相信我,可以的!

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

相信我,可以的!

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!

相信我,可以的!

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)

相信我,可以的!