Numa

Keep child's world position when parenting

Recommended Posts

Hi there, 

I'm working on a little project where I do a lot of parenting and swapping parents/children around, etc..

Is there an easy way to preserve a child's world position when changing its parent? I'm really new to Babylon so I don't always know what's available to me.

If this doesn't exist how would I go about it? Inverse the parent's world matrix and apply to it its child? If someone has an example handy I'd appreciate :)

Thanks!

Share this post


Link to post
Share on other sites

2 questions on this:

1. Do I need to call updateWorldMatrix() after setting the parent or after setting the new position? on both child and parent?

2. What about rotation and scale? There is no getAbsoluteRotation() / Scaling

Is there an easy trick to only change the parenting without moving the object at all? Would multiplying the child's world matrix by the the inverted parent's world matrix do it?

 

Thanks! (also, a little bool to preserve original matrix or not when parenting would be great :D)

Share this post


Link to post
Share on other sites

Yes I did and it didn't seem to work. I'm only asking for someone to confirm the theory here. If I tried it and it didn't work, does it mean my code is wrong? or is there a bug in Babylon? or is it not the way to go at all? :) 

I'm very new to Babylon and Coming from Unity I make a lot of assumptions about how things work :D And later realize I need to do some special handling when working at a lower level.

So, is that the way to go?

Share this post


Link to post
Share on other sites

I'm looking into it, I think it'll work. I'm wondering why there is a getAbsolutePosition, but nothing for rotation and scale... instead I have to get the world matrix and decompose it because there is a getWorldMatrix but no setter, so I have to set each component manually.

I'll post updates later, I'm onto a different problem now :)

Share this post


Link to post
Share on other sites

That makes sense, how would I go about getting and setting the absolute rotation and scale then? Or even better, is there a way to set the world matrix directly? I tried doing myMesh._worldMatrix = myWorldMatrix but it didn't seem to have an effect even if I called computeWorldMatrix(true)

Share this post


Link to post
Share on other sites

Fixed it, the problem was that computeWorldMatrix() doesn't go down the hierarchy (I assumed it would), so after changing parents none of the children would be aware of the change. I had to add something like:

function UpdateWorldMatrix(object)
{
    object.computeWorldMatrix(true);

    for (var i = 0; i < object.getChildren().length; i++) {
        UpdateWorldMatrix(object.getChildren()[i]);
    }
}

note that you need to check that the hierarchy is valid beforehand to avoid running into infinite loops in some sneaky scenarios like (A---> B ---> C ---> A).

That's an area where Babylon could really use some improvement :) beside the .parent =  , everything has to be handled manually. I might contribute some code later when I feel more comfortable with the library :) 

All it needs is a object.Attach(parent, keepWorldTransform) function that handles the attaching / detaching / propagating updated matrices, and lets you choose whether you want your child to keep its world or local transform.

Here is what the matrix operations look like if you want to preserve the world position after changing parents:

 

function Attach(object, parent) {

    object.parent = parent;

    // Get parent's inverse matrix
    var invertParentWorldMatrix = object.parent.getWorldMatrix().clone();
    invertParentWorldMatrix.invert();

    // Combine it with the child's
    var newWorldMatrix = object.getWorldMatrix().multiply(invertParentWorldMatrix);

    // Apply it
    var components = decomposeTranslationRotationScalingMatrix(newWorldMatrix);
    object.position = components.translation;
    object.rotationQuaternion = components.rotation;
    object.scaling = components.scaling;

    // Update matrix
    UpdateWorldMatrix(object);
}

function decomposeTranslationRotationScalingMatrix(matrix)
{
    var innerMatrix = matrix.clone();

    // Translation
    var positionX = innerMatrix.m[12];
    var positionY = innerMatrix.m[13];
    var positionZ = innerMatrix.m[14];

    var translation = new BABYLON.Vector3(positionX, positionY, positionZ);
    var translationMatrixInv = BABYLON.Matrix.Translation(-positionX, -positionY, -positionZ);

    //
    // Scaling
    var scalingX = Math.sqrt(innerMatrix.m[0] * innerMatrix.m[0] + innerMatrix.m[1] * innerMatrix.m[1] + innerMatrix.m[2] * innerMatrix.m[2]);
    var scalingY = Math.sqrt(innerMatrix.m[4] * innerMatrix.m[4] + innerMatrix.m[5] * innerMatrix.m[5] + innerMatrix.m[6] * innerMatrix.m[6]);
    var scalingZ = Math.sqrt(innerMatrix.m[8] * innerMatrix.m[8] + innerMatrix.m[9] * innerMatrix.m[9] + innerMatrix.m[10] * innerMatrix.m[10]);

    var scaling = new BABYLON.Vector3(scalingX, scalingY, scalingZ);

    //
    // Rotation
    var rotationMatrix = innerMatrix.multiply(translationMatrixInv);

    // Normalize to remove scaling.
    if (scalingX) {
        rotationMatrix.m[0] /= scalingX;
        rotationMatrix.m[1] /= scalingX;
        rotationMatrix.m[2] /= scalingX;
    }
    if (scalingY) {
        rotationMatrix.m[4] /= scalingY;
        rotationMatrix.m[5] /= scalingY;
        rotationMatrix.m[6] /= scalingY;
    }
    if (scalingZ) {
        rotationMatrix.m[8] /= scalingZ;
        rotationMatrix.m[9] /= scalingZ;
        rotationMatrix.m[10] /= scalingZ;
    }

    //
    var rotationX = Math.asin(-rotationMatrix.m[9]);
    var rotationY = Math.atan2(rotationMatrix.m[8], rotationMatrix.m[10]);
    var rotationZ = Math.atan2(rotationMatrix.m[1], rotationMatrix.m[5]);

    var quaternion = new BABYLON.Quaternion.FromRotationMatrix(rotationMatrix);

    var result = {
        translation: translation,
        scaling: scaling,
        rotation: quaternion
    };

    return result;
}

function UpdateWorldMatrix(object)
{
    object.computeWorldMatrix(true);

    for (var i = 0; i < object.getChildren().length; i++) {
        UpdateWorldMatrix(object.getChildren()[i]);
    }
}

Thanks for your help everyone :)

 

Numa

 

Share this post


Link to post
Share on other sites

Actually there is no need to go down the hiearchy.

All matrices are rebuilt (if needed) on every frame. If you want to force a complete update of a specific mesh, you can just do:

scene.incrementRenderId();
object.computeWorldMatrix(true);

incrementRenderId just simulates a new frame to invalidate all caches

Share this post


Link to post
Share on other sites

Oh ahah perfect very nice trick! There is no way I could have found out on my own though :) 

I needed to refresh some matrices early because I'm doing several coordinates calculations within one frame before I can render it. All good now, I'll remember that trick next time! :) 

 

Thanks

Share this post


Link to post
Share on other sites

@Numa

Trying to do something similar myself, for my Vishva project.

I found there is a Matrix.decompose() function available

http://doc.babylonjs.com/classes/2.3/Matrix#decompose-scale-rotation-translation-rarr-boolean

Something like below seems to work for me

function Attach(object, parent) {
  var invParentMatrix = Matrix.Invert(parent.getWorldMatrix());  
  var newMatrix = object.getWorldMatrix().multiply(invParentMatrix);
  object.parent = parent;
  newMatrix.decompose(object.scaling, object.rotationQuaternion, object.position);

}

 

Share this post


Link to post
Share on other sites

damn, no I haven't but I definitely will one day. Did the last proposed solution work for you?

I'm not using the decompose function, I'm using my own which basically does the same thing but returns all 3 components separately. I then assign them back to the object.  (see snippet in one of my posts above) Maybe you could do that and assign the correct scale separately? I don't know if that would work.

Share this post


Link to post
Share on other sites

This seems like the most fitting thread I found for the problem I'm currently having, so here it goes:

I'm currently building an object viewer with an ArcRotateCamera. I want the user to be able to position two of the light sources I have in the scene (one point light and one spot light) by attaching them to the camera and detaching them with a button click. Being the BabylonJS-Newbie that I am (actually this whole 3D stuff is quite new to me, I'm a web developer and this is my first project using any 3D engine at all) I assumed that simply assigning null to the parent would do the trick but of course that wasn't the case, as the light immediately jumped back to its initial position after it was detached from the camera. A quick google search led me here and the content of this thread helped me to achive what I wanted for the point light like this:

var pos = myPointLight.getAbsolutePosition();
myPointLight.parent = null;
myPointLight.position = pos;

setAbsolutePosition wasn't available, by the way. I assumed that is the case because I'm working with a light here and not with a mesh, so I simply assiged it to position and it worked, the point light now gets correctly returned to the position it had when it was last attached.

But when I tried it for the spot light, things got very weird, as I wasn't really sure what to do to correctly read and then restore its rotation. Normally, the spot light recieves a direction and not a rotation, but grabbing that before the null assignment and re-applying it didn't work. Same for rotation or rotationQuaternion. So apparently it won't work without doing some serious math. The last time I actually had to do those kind of calculations was 15 years ago at my university of applied sciences and I'm afraid I don't remember much of it. So, any help would be greatly appreciated.

Oh, and I hope this is the correct place to ask this question. Most of the answers in this thread apply to problems with meshes in parent/child relations and I assume things work quite a bit different with lights, but since my problem still fits this thread's title, it didn't seem appropriate to open a new discussion for this...

Share this post


Link to post
Share on other sites

Hi Deltakosh. Here it is: http://www.babylonjs-playground.com/#1EZHCT#0

As you can see, the scene has a point light and a spot light attached to the camera. The first right-click in the scene drops the point light at its current position. The second right-click is supposed to do the same for the spot light, but instead it shows the weirdest behavior, totally unpredictable. It only works if the camera wasn't moved at all.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.