Limit SplineKnot tangent
-
I want to limit the maximum and minimum values of the tangent of SplineKnot so that it does not exceed the maximum and minimum values of SplineKnot. What should I do?
From
to
Thank,
AiMiDi -
Hi @AiMiDi,
thank you for reaching out to us.
In principle your question of clamping a parameter is answered with overwriting
NodeData::Message
. Where one then listens forMSG_DESCRIPTION_POSTSETPARAMETER
to modify the just set value according to ones needs and then writes it back. This works fine for atomic data types likeDTYPE_REAL
, but there is a problem when approaching it like that with more complex data types with more complex GUIs, e.g,SplineData
.The problem is that one cannot access an instance of a parameter, but only a copy of it with
NodeData::GetParameter
, which in turn forces one to write back the modified data. ForSplineData
this means that this will flush the current vertex selection state of the gadget, making this approach unviable, as it will render the user incapable of selecting a vertex. This also applies to the case ofMSG_DESCRIPTION_USERINTERACTION_START
andMSG_DESCRIPTION_USERINTERACTION_END
, resulting in the same problem of making a vertex selection in theSplineData
will immediately flush it.An alternative route is to use
SplineData::SetUserCallback
to establish a callback with which to intercept changes made to theSplineData
viaSPLINE_CALLBACK_CORE_MESSAGE
. The problem here is that this will work likeMSG_DESCRIPTION_USERINTERACTION_END
, i.e., it is only going to be raised once the user does release the mouse - ends the interaction. And not while dragging a value likeMSG_DESCRIPTION_POSTSETPARAMETER
is being raised for. I currently do not see a more elegant way to do this. You will find an example for that at the end of the posting.There is also the problem of a slight ambiguity in your question, since:
I want to limit the maximum and minimum values of the tangent of SplineKnot so that it does not exceed the maximum and minimum values of SplineKnot.
could be interpreted in multiple ways. I went in my solution for the simplest interpretation of projecting the spline handles orthogonally back onto the [0, 1] interval box in the GUI, if you want this to be fancier, you will have to do the line-line intersection calculation (between a border of the box and your tangent vector) yourself.
I hope this helps and cheers,
FerdinandThe code (for an
ObjectData
example):// For https://developers.maxon.net/forum/topic/13277 #include "c4d.h" #include "c4d_symbols.h" #include "obase.h" #include "maxon/apibase.h" #include "c4d_objectdata.h" #include "c4d_basedocument.h" #include "lib_description.h" #include "customgui_splinecontrol.h" // The callback function which does clamp the SplineData static bool SplineDataCallBack(Int32 cid, const void* data) { // There are other callback types we are just using SPLINE_CALLBACK_CORE_MESSAGE here. if (cid == SPLINE_CALLBACK_CORE_MESSAGE) { if (data == nullptr) return true; // Some data massaging ... SplineDataCallbackCoreMessage* coreMessageData = (SplineDataCallbackCoreMessage*) data; SplineCustomGui* splineCustomGui = coreMessageData->pGUI; SplineData* splineData = splineCustomGui->GetSplineData(); if (splineData == nullptr) return true; // Now we just go over all knots and clamp them. This could also be done more elegantly // with line-line intersections, I am just clamping each tangent here to the interval [0, 1], // which effectively equates orthogonally projecting the tangents onto that "[0, 1] box". If you // want to scale vectors so that a handle which is outside that [0, 1] box, is scaled in such // manner that the handle touches the border of that box, you will have to use line-line // intersections. for (int i = 0; i < splineData->GetKnotCount(); i++) { // Get the current knot. CustomSplineKnot* knot = splineData->GetKnot(i); // Tangents live in a vector space relative to their vertex, so we make the tangent vectors // global first and then clamp them. Vector globalLeftTangent = (knot->vPos + knot->vTangentLeft).Clamp01(); Vector globalRightTangent = (knot->vPos + knot->vTangentRight).Clamp01(); // And then we convert the tangents back to their local tangent space and write them back. knot->vTangentLeft = globalLeftTangent - knot->vPos; knot->vTangentRight = globalRightTangent - knot->vPos; } } return true; } class Pc13277 : public ObjectData { INSTANCEOF(Pc13277, ObjectData) public: static NodeData* Alloc() { return NewObjClear(Pc13277); } // We bind the callback on the initialization of the node. virtual Bool Init(GeListNode* node) { if (node == nullptr || !SUPER::Init(node)) return false; BaseContainer* bc = ((BaseList2D*)node)->GetDataInstance(); // Create a SplineData instance and set the callback function. GeData geData(CUSTOMDATATYPE_SPLINE, DEFAULTVALUE); SplineData* splineData = (SplineData*)geData.GetCustomDataType(CUSTOMDATATYPE_SPLINE); splineData->SetUserCallback(SplineDataCallBack, nullptr); // Populate the SplineData as usual ... if (splineData) splineData->MakeLinearSplineBezier(2); bc->SetData(ID_SPLINEDATA, geData); return true; } // And when something replaces our spline data instance. virtual bool SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags) { if (id[0].id == ID_SPLINEDATA) { SplineData* splineData = (SplineData*)t_data.GetCustomDataType(CUSTOMDATATYPE_SPLINE); splineData->SetUserCallback(SplineDataCallBack, nullptr); } return SUPER::SetDParameter(node, id, t_data, flags); } };
How it will work:
-
Thanks for your answer, it solved my problem.
I used:Vector::ClampMax()
andVector::ClampMin()
to limit the tangent to the value I want.Thank,
AiMiDi