Applying a Target Expression tag to a child object of GVO
-
Hi,
I want to apply a Target Expression tag to one of a child objects of GetVirtualObjects. is it possible?
I tried with following code but nothing happens (The Target Expression tag is was added to the "Orbit" Null object, together with the target link but the Null object is not looking to the target).BaseContainer* data = op->GetDataInstance(); ... AutoAlloc<BaseObject> ParentPtr(Onull); AutoAlloc<BaseObject> OrbitPtr(Onull); if (!ParentPtr || !OrbitPtr) return false; BaseDocument* doc = op->GetDocument(); ParentPtr->SetName("Parent"); OrbitPtr->SetName("Orbit"); BaseDocument* doc = op->GetDocument(); BaseTag* tagTarget = OrbitPtr->MakeTag(Ttargetexpression); BaseList2D* oTarget = data->GetLink(MYOBJECT_TARGET, doc); tagTarget->GetDataInstance()->SetLink(TARGETEXPRESSIONTAG_LINK, oTarget); OrbitPtr->InsertUnder(ParentPtr); ...
-
Hi,
the reason is probably that Cinema does not not execute tags on caches (otherwise it would not really be a cache) and what you are effectively doing in
GVO
is building the cache of your object. Solutions would be either creating a temporary document, adding your objects, execute the expressions pass on that document and then remove and return the objects from that temporary document. Or as a solution which much less overhead not using a target expression at all and simply construct the frame of your object yourself.When
p
is the world space position of your object,q
is the world space position of your target andr
is the up-vector (either user defined or derived from the object), then your frame for a z-axis look-at will be (in pseudo code):tangent = (q - p).normalized # x denotes the cross product normal = (tangent x r).normalized binormal = (tangent x normal).normalized # where a frame is defined as [x, y, z] frame = [normal, binormal, tangent]
Cheers,
zipit -
Hello,
as @zipit said, you should create this hierarchy in a temporary document and ExecutesPasses there.
The more straight forward is to calculate yourself the matrix or you can use VectorToHPB
in python we could do something like this. (the method are the same in c++)
# where a need to point to b apos = a.GetRelPos() bpos = b.GetRelPos() # be careful at the order of the operation we want to point toward b so bpos-apos direction = bpos-apos hpb = c4d.utils.VectorToHPB(direction) a.SetRelRot(hpb)
Some comment on your code :
- as you are in GVO this line is not good :
if (!ParentPtr || !OrbitPtr) return false;
as said here : return "The newly allocated object chain, or nullptr if a memory error occurred."
- we don't know what you are doing after that snippet but using AutoAlloc is good but you have to release is if you want to return the result. Otherwise, AutoAlloc will delete the object. Have a look at this manual
Cheers,
Manuel -
@m_magalhaes
Hi,
Thank you so much @m_magalhaes and @zipitI used the following code:
Vector direction = (target - eye).GetNormalized(); Vector hpb = VectorToHPB(direction); eyePtr->SetRelRot(hpb);
This line is good:
if (!ParentPtr || !OrbitPtr) return false;
This line is inside a global function and not directly inside the GVO function. like the function CreateTempleBase() from the SDK Greek Temple example.
static Bool CreateTempleRoof(BaseObject* &parentObj, const Float& baseUnit, const Vector& objSize, const Int32* objSegsPtr = nullptr); static Bool CreateTempleRoof(BaseObject* &parentObj, const Float& baseUnit, const Vector& objSize, const Int32* objSegsPtr /*= nullptr*/) { ... return true; }
-
@m_magalhaes
Hi,
I have an additional question concerning this subject.
When I link object via the link box (Description Resource), The targetObj is not taken into account by the eyeObj immediately, but only after I change any parameter of my plugin object. The rotation change is not updated when I moving the target object.... BaseList2D* targetLink = data->GetLink(MYOBJECT_TARGET_OBJECT, doc); BaseObject* targetObj = static_cast<BaseObject*> (targetLink); Vector = eye = eyeObj->GetAbsPos(); Vector = target = targetObj->GetAbsPos(); Vector direction = (target - eye).GetNormalized(); Vector hpb = VectorToHPB(direction); eyePtr->SetRelRot(hpb);
Thanks.
-
hi,
what come to mind is that GetVirtualObject is probably returning the cache of the object and doesn't redo the calculation.
You need to check the dirtiness of the target and compare with what you stored in your generator.
You have to override the function GetDirty in your generator. Check there the dirtiness of the target and change a bool. In GVO, check that bool also.Cheers,
Manuel. -
@m_magalhaes
Hi,
I tried with the following code, it's work only when I drag my target object into the link box, but when I move the target object nothing happens.Bool bIsDirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS_DATA | DIRTYFLAGS_MATRIX); if (!bIsDirty) return op->GetCache(hh);
-
@m_magalhaes
Hi,The following code works, but the op dirty is updated every time an object in my scene is moved. Not only when the target object is changing position. I don't know if this is Ok or not.
Float checkDirty; BaseObject* MagicStudio::GetVirtualObjects(BaseObject* op, HierarchyHelp* hh) { // Check the passed pointer. if (!op) return BaseObject::Alloc(Onull); BaseDocument* doc = op->GetDocument(); BaseObject* activeObj = doc->GetActiveObject(); if (activeObj) { Float activeObjDirty = activeObj->GetDirty(DIRTYFLAGS_MATRIX); if (activeObjDirty) { if (checkDirty != activeObjDirty) { checkDirty = activeObjDirty; op->SetDirty(DIRTYFLAGS_DATA | DIRTYFLAGS_MATRIX); } } } Bool bIsDirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS_DATA | DIRTYFLAGS_MATRIX); if (!bIsDirty) return op->GetCache(hh); ... }
-
hello,
sorry for what we can call the latest reply ever.
you can store the dirtyCheck of your target in a local private variable. Just check its state and update the cache if it has changed. You also have to check for the dirtiness of the generator's matrix.
virtual BaseObject* GetVirtualObjects(BaseObject *op, HierarchyHelp *hh) { // Basic check for error if (op == nullptr) return BaseObject::Alloc(Onull); BaseDocument* doc = op->GetDocument(); if (doc == nullptr) return BaseObject::Alloc(Onull); // Verify if object cache already exist and check its status. Bool bIsDirty = op->CheckCache(hh) || op->IsDirty(DIRTYFLAGS::DATA | DIRTYFLAGS::MATRIX); // retrieves the target if exist GeData data; op->GetParameter(ID_LINK, data, DESCFLAGS_GET::NONE); BaseLink *link = data.GetBaseLink(); BaseObject* target = nullptr; Bool bTargetDirty = false; if (link != nullptr) { target = static_cast<BaseObject*>(link->GetLink(doc, Onull)); if (target != nullptr) // check the target's dirty value with the one store in our generator. // targetDirty_ is a local private variable to store the state of the target. It can be initialize in the Init function to -1 for example. if (targetDirty_ != target->GetDirty(DIRTYFLAGS::MATRIX)) { targetDirty_ = target->GetDirty(DIRTYFLAGS::MATRIX); bTargetDirty = true; } } bIsDirty |= bTargetDirty; // In case it's not dirty return the cache data without doing any calculation. if (!bIsDirty) return op->GetCache(hh); ApplicationOutput("rebuild the cache"); BaseObject* pyr = BaseObject::Alloc(Opyramid); pyr->SetParameter(PRIM_AXIS, GeData(4), DESCFLAGS_SET::NONE); if (target != nullptr) { Vector targetPos = target->GetMg().off; // don't forget to get the world coordinate of the object. Vector eyePos = op->GetMg() * pyr->GetMg().off ; Vector direction = (targetPos - eyePos).GetNormalized(); Vector hpb = VectorToHPB(direction); pyr->SetRelRot(hpb); } return pyr; }
Cheers,
Manuel -
@m_magalhaes
Hello, Thank you so much.