ArcRotateCamera's Radius affects the Rotation/Position of attachToBone Meshes

Please find below a Playground link:
https://playground.babylonjs.com/#N3RH3R#19
Note that the player is intentionally spawned below the camera (since this bug deals with the vertical offset of meshes relative to ArcRotateCamera’s radius), so please click inside the Playground and either:

  • Hold down the Up arrow key
  • Click and drag the left mouse button from up to down

Please refer to the lines:

// BUG: If radius <= 3, gun points one way. If radius >= 4, gun points other way.
const radius = 3;

Note that when radius = 3, the gun points outwards.

But when radius = 4, the gun points inwards.

Some may suggest to remove the first forced render after initializing the player model and setting its parent to the green rectangular prism:

scene.render(); // Force updates by rendering

However, this won’t solve the issue I’m facing in development, since there will be situations where a new gun mesh is attached to a player model after the first scene.render() call with the player model in the scene (for example: a player picking up a new gun).

This first forced scene.render() is an attempt to make a gun’s rotation consistent regardless of whether the gun was attached to a player model during initialization or several minutes into a game.

Thank you for your time and help :smiley:

Another interesting observation. Please see the linked Playground below:
https://playground.babylonjs.com/#N3RH3R#20

Please refer to the lines:

// BUG: If beta >= PI/2, gun points one way. If beta <= PI/3, gun points other way.
const beta = Math.PI / 6;

Depending on the initial viewing angle (e.g. beta) of the ArcRotateCamera, the gun may point in different directions.

This makes it seem more likely that scene.render() and the meshes it updates are affected by what the ArcRotateCamera can “see”.

The previous observation was interesting, but this one is mind blowing!
https://playground.babylonjs.com/#N3RH3R#23

The player model is composed of 2 meshes (cyan parts and black ball joints). If I only parent the cyan part to the green rectangular prism, the gun has a consistent rotation regardless of the ArcRotateCamera's viewing angle and radius. This is good news!

This means a possible workaround is to make sure my imported meshes only has a single mesh. Regardless, this still seems like a bug, since parenting multiple meshes causes unexpected rotation behavior.

Edit: Nevermind, this possible workaround did not work :frowning: Intuition is telling me that as long as the ArcRotateCamera can see one of the multiple player model meshes, scene.render() will properly update all gun meshes.

I think this is a clearer example of what the bug is:
https://playground.babylonjs.com/#N3RH3R#27

Note that the gun is attached to the bone after a timeout of 2 seconds (giving time for some scene renders to occur):

setTimeout(() => ybot.attachToBone(ak47.meshes[0], 37), 2000);

2 scenarios:

  • If you wait for 2 seconds, and then look at the player (using mouse drag or arrow keys), the gun will point outwards.

  • If you look at the player before 2 seconds have elapsed, the gun will point inwards.

Video of Bug:

Edit: An extremely hacky way to work around this is to use Multi Views (2 cameras with one always looking directly at the player), as shown in this Playground.

attachToBone is implemented like this in TransformNode:

public attachToBone(bone: Bone, affectedTransformNode: TransformNode): TransformNode {
    this._transformToBoneReferal = affectedTransformNode;
    this.parent = bone;

    if (bone.getWorldMatrix().determinant() < 0) {
        this.scalingDeterminant *= -1;
    }
    return this;
}

If I comment out the this.scalingDeterminant *= -1; it does make work your use case.

Indeed, if calling this.skeleton.bones[boneIndex].getWorldMatrix().determinant() in your PG attachToBone method, we can see it returns a negative value when the mesh is visible when you attach the AK47 to the bone. When the mesh is not visible yet, the bone matrices are all 0 so the determinant is 0 and thus scalingDeterminant is not multiplied by -1.

As scalingDeterminant is a public property, you can reset it to 1 and make the PG work:

https://playground.babylonjs.com/#N3RH3R#30

However, I’m not sure it’s the right way to fix the problem…

I can’t help more, I don’t know anything about animation code, but maybe it can help someone more knowledgeable about this.

1 Like

Hum, I thought the right orientation of the riffle was “outward”, but now I don’t think so, and then the right fix is to call skeleton.prepare() before attaching to the bone:

https://playground.babylonjs.com/#N3RH3R#31

Indeed, when the mesh is out of view when you attach to the bone, the skeleton has not been prepared yet and all the bone matrices are empty. Calling prepare will initialize the bone matrices with the right values.

[…] I wonder if the TransformNode.attachToBone should call prepare itself to make sure everything is in order before getting the bone world matrix:

public attachToBone(bone: Bone, affectedTransformNode: TransformNode): TransformNode {
    this._transformToBoneReferal = affectedTransformNode;
    this.parent = bone;

    bone.getSkeleton().prepare();

    if (bone.getWorldMatrix().determinant() < 0) {
        this.scalingDeterminant *= -1;
    }
    return this;
}

There’s a dirty flag in the skeleton, so if it’s alreay up to date, the call to prepare won’t do anything bad.

@Deltakosh?

1 Like

Agree this should be totally safe

1 Like

PR done:

1 Like

Wow, thank you so much, @Evgeni_Popov and @Deltakosh! :smiley:

@Evgeni_Popov, I’m really thankful for all the time you spent debugging and deep diving into the source code. Could I ask about your workflow with the Playground code and the Babylon.js source code? Have you cloned the full Babylon.js source code and linked it as a source for the Playground code?

Thank you again for all your help! :stuck_out_tongue:

Yes, I have a fork of Babylon on my computer, so it’s very easy to make changes in the source code and see the result on a playground (run locally).

1 Like

Awesome, thank you, @Evgeni_Popov!