Set Vector Y to 0 (in World Space)



  • Hi,

    I understand retrieving world space has been discussed before like in this thread (https://plugincafe.maxon.net/topic/11586/get-deformed-points-from-skin-deformer)

    Sorry for asking the questions, but I can't seem to set the Vector Y of the points to 0 (in world space). Basically, I'm mimicking the "Set Point Value" command.

    You can see an illustration of the problem here:
    https://www.dropbox.com/s/v7ty3z52hh0ie89/c4d103_set_y_vector_to_world_zero.png?dl=0

    For the illustration file, you can check it here:
    https://www.dropbox.com/s/takdka6wkj4jw71/c4d103_set_y_vector_to_world_zero.c4d?dl=0

    The code used is as follow:

    import c4d
    
    def set_Y_vector(obj, value):
    
        oldPoints = obj.GetAllPoints()
        obj_mat = obj.GetMg()
        inv_obj_mat = ~obj_mat # Not used
    
        newLocalPoints = [c4d.Vector(p[0], p[1]*value, p[2]) for p in oldPoints]
        
        # The zero Y value in newLocalPoints is in local space. So I'm trying to convert  it on global space below
        
        newWorldPoints = [p * obj_mat for p in newLocalPoints]
    
        obj.SetAllPoints(newWorldPoints)
    
        obj.Message (c4d.MSG_UPDATE) 
        c4d.EventAdd()
        
    set_Y_vector(op, 0)
    

    Is there a way around this?

    Thank you for looking at the problem.



  • Without actually trying the code:

    1. newLocalPoints is in local space. This is where you zero the y value. Which means that the point will now have a y of 0 in local space.

    2. Then you multiply the world matrix with the new positions. BUT: This world matrix is already part of the object that contains the points. Now your points have applied the matrix twice, first through the object and then through this multiplication.

    3. While the local y is 0, the multiplication with obj_mat will give the point a global y anyway because obj_mat may contain a translation in space. Moreover, the obj itself does, too. So, your point's y will definitely not be world 0 in the end.

    What you really need to do:

    1. Determine the world vector for the points (it's only a vector and not a matrix since a point does not contain a rotation or scale). This is done by applying obj_mat to each point vector, resulting in a new vector for each point. Why? Because your object's transformation in space (which is the meaning of obj_mat) is affecting all points in the object's local system.

    2. For this global vector, you set the y component to 0.

    3. Then you transform this global vector back to the object's local space by applying inv_obj_mat. Note that after this inverse transformation, the local y may no longer be 0!

    4. Profit.

    Okay, I wrote the script after all. You owe me a beer.

    import c4d
    from c4d import gui
    
    def set_Y_vector(obj, value):
        oldPoints = obj.GetAllPoints()
        obj_mat = obj.GetMg()
        inv_obj_mat = ~obj_mat
    
        newWorldPoints = [p * obj_mat for p in oldPoints]
        newLocalPoints = [inv_obj_mat * c4d.Vector(p[0], p[1]*value, p[2]) for p in newWorldPoints]
    
        obj.SetAllPoints(newLocalPoints)
        obj.Message (c4d.MSG_UPDATE) 
        c4d.EventAdd()
        
    
    def main():
        set_Y_vector(op, 0)
    
    if __name__=='__main__':
        main()
    


  • Hi Bentraje, thanks for reaching out us.

    With regard to your request, I second all the considerations that @Cairyn kindly provided reminding once again that points positions are always stored in local space and must be always defined in local space to unpredictable results due to transformation matrices being applied twice.

    On top of this, looking at your snippet, if your intent is to locate the spline points Y-coordinate at a certain value in global space, I'd refrain from using the multiplication but rather use the passed value directly as new value.

    Last but not least, I've optimized the code with one single for-loop

    def set_Y_vector(obj, value):
    
        oldLocalPoints = obj.GetAllPoints()
        obj_mat = obj.GetMg()
        inv_obj_mat = ~obj_mat
    
        # Non-preallocation penalty is neglectable 
        newLocalPoints = [] 
        for p in oldLocalPoints:
            # get the world position of the current point
            worldpoint = obj_mat * p
           # set the local position on the new point by setting the y-coordinate to the desired value and transform by the inverted matrix
            newLocalPoint = inv_obj_mat * c4d.Vector(worldpoint[0], value, worldpoint[2] )
            # just append
            newLocalPoints.append(newLocalPoint)
    
        # set the new points
        obj.SetAllPoints(newLocalPoints)
        
        # notify Cinema 
        obj.Message (c4d.MSG_UPDATE) 
        c4d.EventAdd()
    

    Best, Riccardo



  • @Cairyn and @r_gigante

    RE: BUT: This world matrix is already part of the object that contains the points. Now your points have applied the matrix twice, first through the object and then through this multiplication.

    Thanks for the clarification.

    Works as expected. (And I agree, I owe you a beer Cairyn hehehe).

    Have a great day ahead!


Log in to reply