Getting an Object to Follow Another Regardless of Hierarchy



  • Hello,
    I'd like to drive an object's transformation attribute by another object's transformations, regardless of where they are in the hierarchy. The driven object would follow the driver object if it was a parent, child, or sibling. There are a few caveats (I'm using position as an example, but this could also include individual dimensions of rotation & scale):

    • My project requires that I do this in code (so I cannot use Constraints).
    • I have to start from the Driver's local coordinates
    • I do not want to transform the entire matrix - just one attribute at a time (so something like driven.SetMg(driver.GetMg()) wouldn't work).
    • I have to maintain the difference in position between the two objects.

    I have read the Matrix Fundamentals article, searched this forum, documentation, & online, and tried many things but I haven't found a working solution.

    In this example Python Tag code, the Driven object is jumping between two positions as if there is a loop. I thought this was because the driven.GetMg() used to apply a Matrix to the driver value needed to be a static value, but it snaps to the wrong position and double transforms when I use one in its place (myMat, below):

    import c4d
    
    #myMat = c4d.Matrix()
    #myMat.off = c4d.Vector(500,0,0) #have tried this instead of driven.GetMg()
    
    def main():
        driver = doc.SearchObject("Driver")
        driven = doc.SearchObject("Driven")
        posX = driver[c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_X] #starting with local pos X
        parentMat = driver.GetUpMg() #get Parent's matrix
        newPosVec = c4d.Vector(posX,0,0) #creating a Vector for the next step
        newPosVec= newPosVec * parentMat #applying Parent Matrix to get driver's global X
        newPosVec = newPosVec * ~driven.GetMg()
        print "newPosVec.x: %s"%(newPosVec.x)
        driven[c4d.ID_BASEOBJECT_ABS_POSITION,c4d.VECTOR_X] = newPosVec.x
    

    It is giving me a lot of trouble.

    How can I drive an object in one dimension with another object's local coordinates, maintaining the objects' offsets, and regardless of hierarchy?

    Thank you.



  • hi,

    The offset is a vector between the two objects. This offset can be calculated by subtracting the two matrix of the objects and retrieve the offset argument of the Matrix.
    You have to store this vector as a local's coordinates of the target. (target's inverse matrix * vector)
    Now you have that, you just have to pick your object's matrix and set the offset parameter to your stored offset.
    If you only want to update the X axis, just write the X axis it's simple, just update matrix.off.x

    In the tag i've added a button and the target's field. This could go with something like this.

    import c4d
    #Welcome to the world of Python
    
    storedData = 123456 # baseContainer
    offsetInitializedID = 1000 # Bool
    offsetID = 1001 #Vector
    matrixLockID = 1002 #Matrix
    
    
    
    
    def WriteOffset(obj, vec, init = True):
        # write the offset in the BaseContainer
        global storedData
        data = obj.GetDataInstance()
        if data is None:
            return False
        bc = data.GetContainer(storedData)
        if bc is None:
            bc = c4d.BaseContainer()
        bc.SetBool(offsetInitializedID, init)
        bc.SetVector(offsetID, vec)
        bc.SetMatrix(matrixLockID,obj.GetMg())
        data.SetContainer(storedData, bc)
        return True
    
    
    def ReadOffset(obj):
        # Read the offset from the BaseContainer
        global storedData
        data = obj.GetDataInstance()
        if data is None:
            return False, c4d.Vector(0), c4d.Matrix()
        bc = data.GetContainer(storedData)
        return  bc.GetBool(offsetInitializedID), bc.GetVector(offsetID), bc.GetMatrix(matrixLockID)
    
    
    def message(id, data):
        if id == c4d.MSG_DESCRIPTION_COMMAND:
            if data['id'].GetDepth() > 1 and  data['id'][1].id == 3:
                # get the target's matrix
                target = op[c4d.ID_USERDATA,1]
                obj = op.GetObject()
                if target is None or obj is None:
                    return
                #Get the object's matrix'
                tmg = target.GetMg()
                objmg = obj.GetMg()
                diffMatrix = objmg -  tmg
                offset = diffMatrix.off
                if not WriteOffset(obj, offset):
                    raise ValueError ("couldn't write value in the basecontaier")
    
    
    def main():
        # Retrieve the object
    
        # option 0 all param
        # option 1 only rotation
        # option 2 only offset
        # option 3 only X axis
        # optino 4 even if B is child of A only X
        option  = 4
    
        obj = op.GetObject()
        objMg = obj.GetMg()
        offsetActive, offset, oldMatrix  =  ReadOffset(obj)
        
        if offsetActive:
            target = op[c4d.ID_USERDATA,1]
            tmg = target.GetMg()
    
            if option  == 0:
                # Get the global coordinate of the offset.
                tmg.off = tmg * offset
    
            elif option == 1:
                # Only add the offset to the target's matrix
                tmg.off = objMg.off
    
            elif option == 2:
                # Build the new matrix based on the object's matrix and the global coordinate of the offset
                objMg.off = tmg * offset
                tmg = objMg
    
            elif option == 3:
                objMg.off = c4d.Vector(tmg.off.x +  offset.x, objMg.off.y, objMg.off.z)
                tmg = objMg
                
            elif option == 4:
                oldMatrix.off = c4d.Vector(tmg.off.x +  offset.x, oldMatrix.off.y, oldMatrix.off.z)
                tmg = oldMatrix
    
            # remove the scale by normalizing the
            tmg.Normalize()
            obj.SetMg(tmg)
    
    
    
             
    

    Cheers,
    Manuel



  • @m_magalhaes Manuel, I am SO grateful for your help, thank you. 🌻 I've spent a lot of time so far and was feeling very frustrated. Thank you!

    I have a couple of questions about your code if I may:

    1. As mentioned in the original post, I'm starting with a single, local space dimension value. I try and change tmg's position X value then change it to global space, as in the example below, (posX). This code works when the objects are siblings or children of other objects, but when the Object is parented to the Target, the Object rotates and scales (even with the Option set to 2). Can you please teach me how to change a single value like this that will work anywhere in the hierarchy in your code?
            posX = curve.GetValue(newTime, fps) # 'curve' is the CCurve for the relative Pos.x CTrack
            tmg = target.GetMg()
            vec = c4d.Vector(posX, tmg.off.y, tmg.off.z)
            parentMat = target.GetUpMg() #get Parent's matrix
            tmg.off = vec * parentMat #convert position vector from Curve to Global Matrix
    

    Scenario.png

    1. # option 1 only rotation
      This didn't only affect rotation when I set the option to 1. The position & scale were also affected. The code is adding the offset with tmg.off += offset[1], but would adding the target's rotation involve MatrixToHPB?

    Thanks so much, again!



  • hi,

    I've modified my code a bit, just to remove that bug and clean it a bit. I've also added the posibility to "freeze" the object except the X axis even if B is a child of A (and you are moving A)

    The scale is store in the vectors of the matrix itsellf (v1, v2, v3) the length of the vector is the scale in that particular axis.

    Have a look at matrix fundamentals

    so if you want to set the scale back to 1, you have to just set all the vector lenght to 1. In other words, you have to normalized those vector, or in a matrix you just normalize the matrix itself.

    If you want to retrieve or keep the scale, you can retrieve the scale of on object's matrix with GetScale and apply with Scale (same link)

    Don't over think how matrix works, just focus on how you can use them.

    .off is the displacement information
    .v1 is the x axis information about the rotation (where the vector is pointing to) and the scale (how long the vector is, it's magnitude)
    .v2 is the y axis
    .v3 is the z axis.

    the identity matrix (no displacement, no rotation, scale 1) can be created with c4d.Matrix()
    With that if you only want to add the displacement information just add your information in .off
    etc etc.

    Just be careful that the axis v1, v2, v3 must be orthogonal

    Cheers,
    Manuel



  • @m_magalhaes Hi Manuel, thank you, again, for your help with the code and further explanations.

    I've been reading Matrix Fundamentals a lot during this process and still have a lot to learn.

    I tried out the modified code and it works for position & rotation, but discovered some unexpected shearing behavior when animating the Target's scale with Option 4 when the objects are parent/child.
    hpIk7nE.gif
    Project File

    Am I doing something wrong or is this a bug?

    Thank you.



  • hi,

    i finally asked the dev if they got a better idea than myself.

    It's reproducible with our constrain tag, that's the combinaison of rotation and scale of the parent that apply to the child.

    I'm afraid it's a bit of a limitation in our system and this particular case.

    Let's see.

    Cheers,
    Manuel



  • @m_magalhaes Hi Manuel, it feels like your idea is very close. Thank you for taking the extra steps.



  • Sorry for barging in, but as the same Q was asked on two more forums, I stumbled over it.

    I couldn't find the actual error in the code, but after reducing the whole thing to the crucial lines, I am fairly convinced that it is a bug. I created a completely new scene (R21) to exclude any strangenesses from the original file, and it's still easily reproductible:

    Shear.jpg

    The script here is very simple:

    import c4d
    
    def main():
        obj = op.GetObject()
        parent = obj.GetUp()
        parentMat = parent.GetMg()
        obj.SetMl(~parentMat)
    

    It does nothing but assign the child the inverted parent matrix as local matrix. Logically, this should make the child stay constant and in place, regardless of whatever changes you perform to the parent.

    It works very well as long as you only rotate or translate the parent; as soon as you add some scaling, you get the result above. The child's matrix starts shearing, and the coordinate system is no longer orthogonal (which isn't even supported by C4D natively).

    I can only conclude that the matrix inversion has a bug and is not handling scaled matrices correctly (as strange as it sounds - such a basic error should show up in every second scene...).



  • I have been following this topic in all three (!) forums since beginning and I'm really looking forward to the outcome of this topic.

    I'd call myself quite a Matrix mutant, so just ignore me, if my question is completely off topic or bogus in this context. My expectation would have been the same as @Cairyn's.
    Yet, there has always been this last sentence on the Matrix fundamentals page, which reads pretty suspicious to me.

    [Local Matrix] = MatrixMove(op.GetFrozenPos()) * HPBToMatrix(op.GetFrozenRot()) * MatrixMove(op.GetRelPos()) * HPBToMatrix(op.GetRelRot()) * MatrixScale(op.GetFrozenScale) * MatrixScale(op.GetRelScale())

    Please note that the both scales are applied at the very beginning, so the local matrix is not the same as [Frozen Matrix] * [Relative Matrix]. This is necessary in order to guarantee that your local matrix always stays a rectangular system; distorted sy

    I'm not questioning the correctness of this sentence. I'm just wondering if it may be related to this issue? And if one would maybe need to separate the scale from the equation and calculate it in correct order?



  • @m_magalhaes Hi Manuel, I just wanted to check and see if the developers had any update on this issue? Thank you!



  • hi,

    sorry for the late reply, but other task and vacations came in : /
    I don't have any solution for that particular case. (that's also why the same happen with our tag)

    Cheers,
    Manuel



  • @m_magalhaes That's okay. Thank you for letting me know!


Log in to reply