A better computeWorldMatrix is needed

Currently there is no easy way to get the world matrices for nodes in a hierarchy without lots of re-computation.

Looking in the computeWorldMatrix code, the worldMatrix does not actually update even if the parent is updated.
The only way is to set the force flag = true, which re-triggers the entire worldMatrix computation up the hierarchy.
The issue is that this re-computes the matrices of the parent nodes even if they are not dirty.

Suppose we have the following hierarchy:
NodeA
–>NodeB
---->NodeC
---->NodeD

If NodeA/NodeB transforms have changed, NodeD.computeWorldMatrix() doesn’t do anything; it will be the same as if the parents have not moved. The only way to get the real worldmatrix is to run NodeD.computeWorldMatrix(true), which re-computes all the Nodes world matrices.

Running
NodeC.computeWorldMatrix(true), then
NodeD.computeWorldMatrix(true)
literally forces NodeA+NodeB worldmatrix computation twice.

@Evgeni_Popov @sebavan could really use some advice on this! :grinning:

Couple suggestions:

  1. We don’t run this early return in codeblock even if force=false.
  2. Expose a new function for this purpose that goes up the hierarchy and checks if any node is dirty. If so, recompute the worldMatrix for all up to the dirty node
  3. In any case there should be a worldMatrix function that updates purely as a function of parentMatrix * localMatrix
  4. Why isn’t _isDirty available as a flag btw? At least .isDirty() would be helpful

Perhaps a better suggestion.
Have a worldMatrixDirty flag. If this becomes dirty, recursively set all the childrens worldMatrixDirty=true. Then in computeWorldMatrix, check for this flag instead of the _isDirty flag. If true, compute worldMatrix up the hierarchy

An implementation suggestion

Basically, it only force updates the all the parents’ world matrices in the first pass (depth==0). Then if depth >= 1 it doesnt force update the parents anymore and instead re-computes the children worldmatrix only.

But this feels like a workaround hack due to the computeWorldMatrix isDirty=true limitation

Hi mrlooi,

Sorry, I think I’m misunderstanding something. From a quick look at the code, it looks like computeWorldMatrix should end up in line 1104 unless it hits a special case which takes it down a different path, and 1104 should cause the world matrix to incorporate changes from the parent node’s world matrix. In fact, whether or not we reach 1104 appears to be completely disassociated from the force parameter as that doesn’t factor into any of the conditions leading up to there. Is that correct, or have I misunderstood this?

Speculating a little here about what exactly you may have encountered, one nuance of this system is that Babylon (I think) doesn’t proactively update world matrices when things like position/rotation/scale variables are changed. They’re updated eventually, but it’s often done “lazily” as a part of rendering unless you explicitly tell Babylon to recompute now. As a consequence of this, if you change a position/rotation/scale variable and then immediately query the world matrix without explicitly telling Babylon to recompute it, it may not reflect your changes yet.

Because you have a node hierarchy, then under certain circumstances if you call

nodeA.position.x += 1;
console.log(nodeC.getWorldMatrix().getTranslation().x);

the output will not reflect your change because it hasn’t been propagated up the hierarchy yet. I made a quick Playground to explore this nuance.

Babylon.js Playground (babylonjs.com)

All four of the test cases should be interesting, but I think the ones you’ll care about most are cases 1 and 3. Case 1 sort of goes through the steps of the nuance manually. It starts by computing a world matrix for nodeC so that that gets cached, then updates nodeA and checks again. The updated value isn’t reflected because the cached value is being queried. It then recomputes the world matrix for nodeA and tries again, but this doesn’t help because nodeC is still recomputing itself from the cached world matrix on nodeB. This is validated by then recomputing nodeB's world matrix and trying again, at which point the updated value is shown.

Test case 3 shows a more practical approach than manually updating each level in the hierarchy. After updating nodeA, we only have to call computeWorldMatrix once with force true because that will update nodeB's cached world matrix. We can then call computeWorldMatrix with force false on nodeD and still get the correct result (though getWorldMatrix will of course initially return the un-updated cached value) because computeWorldMatrix will recompute the world matrix from nodeB's cached world matrix, which was updated by the force call on nodeC. I believe that test case 3 shows a way to get the updated world matrices on the same frame while only force-computing the world matrix for each node once, then reusing the cached values for the nodeD computation.

Does this sound similar to the behavior you were seeing?

1 Like

I love seeing you get better at this @mrlooi <3

1 Like

From the code, it seems that if the local node is not dirty, it will end at line [981] (Babylon.js/transformNode.ts at master · BabylonJS/Babylon.js · GitHub).
My issue with this is that if the local node is not “dirty”, but the parents (direct or indirect) are “dirty”, the returned world matrix will not be correct.

If it always ended at 1104 then I probably would not post this!

Unless .isSynchronized() becomes false if a parent (direct/indirect) becomes dirty? Can we guarantee this?

I think I understand your concern. However, as I mentioned before, it is expected that the returned world matrix will not be correct under certain circumstances if the force flag is not specified to require the recomputation of the entire hierarchy. I believe this is a part of Babylon’s design done for performance reasons – to avoid automatically walking the hierarchy setting flags and updating things which will not typically be read – and mechanisms are provided to force recomputation in scenarios where accuracy is more important than performance. Changing this behavior is certainly possible, but it might have much broader implications than intended because of how deep and nuanced this system is, so I think it would need to be approached with great caution and strongly justified by a scenario. Can you provide a Playground showing an example where you believe Babylon should behave differently?

1 Like

Yea I’m aware of performance reasons, primarily the benefits of ‘lazy’ updating. Not suggesting we do so in computeWorldMatrix necessarily.

The ideal case would be where the developer does not have to run computeWorldMatrix at all. Unity is a great example where updating a parent value will immediately reflect changes in the child’s global transforms, and the developer does not have to mess with worldmatrix computations at all.

Haha! You mean the ‘automagical’ feature that will switch from one approach/hierarchy to another. Just hang on a minute, I’m sure the BJS ‘big brains’ out there will implement this for us in the next 48h. Please let me simply know when it’s done :crazy_face:

Right, I have some familiarity with that. The reason Unity is able to do this is because they proactively propagate transform changes throughout the entire child transform hierarchy. It’s possibly they’ve found a better way to do this since – I can only speak authoritatively about a particular Unity version, which I think was 5.1 from around 2015 – but the last time I worked professionally on a Unity project, I did a significant portion of our optimization work, and this transform propagation operation had a meaningful impact on performance. Our project in particular had large, deep transform hierarchies which had to be moved per frame, and consequently the simple act of updating transform.position.x on the root of that hierarchy became a heavy CPU operation we could observe in the traces, and even in the framerate directly. Most people never noticed this about Unity because, at least at the time, you generally didn’t want to use Unity in situations where performance was both paramount and constrained; but because of how hard we were pushing the engine, we ended up sinking a lot of optimization work into working around this Unity feature. Given Web’s threading constraints and the fact that JavaScript is an inherently slower language than C#, Babylon has a much shorter performance leash on this operation than Unity does. If we spent CPU time proactively updating transform hierarchies under the hood, as Unity at least used to do a number of years ago, I suspect the performance penalties would be even more noticeable, so at least for me I consider it a Babylon feature that incurring this compute penalty is optional, not automatic and consequently mandatory.

I think, for most people, this is currently the case. Though I haven’t been specifically looking for them, I haven’t seen a huge number of people asking questions about computeWorldMatrix, so my impression is that most people either don’t need it or don’t object to the way it currently works. If you believe your use case justifies reevaluating this, can you provide a Playground showing an example where you believe Babylon should behave differently?

2 Likes

Well, I would actually have a big political, tech and commercial question about this (high level only;). Or about left-handed vs right-handed, axis changing depending on apps, etc…Why is it that we need all this crap at all? Oh, I think I actually have the answer: it’s called something like ‘free commerce’:wink:

I agree (very personal opinion). But for me BJS is not Unity (or Unreal, or Cry…). It’s a webGL native engine and has also a place to take in (i.e.) the commercial Internet (not only games) AND it has to somehow remain true to its roots. I think (although I agree it can sometimes be cumbersome), BJS is doing its best to maintain an optimal balance (and I praise the ENGs and contributors for this).

Thanks for clearing that up @syntheticmagus , that’s a super helpful response!!

I haven’t seen a huge number of people asking questions about computeWorldMatrix.

I think that’s because many don’t build full-fledged editors or complex apps on BJS yet. The moment people start using BJS for more complex things like physics/transforms and IK, the need for getting real world matrices for the nodes becomes apparent, and that’s where using BJS becomes not so convenient to use (or should I say, takes some getting use to). The main usecase is to be able to conveniently get the world matrices in the same frame without consistently running computeWorldMatrix OR having to worry about whether the parent, parent’s parent or child child’s world matrix has been properly updated or not just to save compute.

I’ve been using BJS for over a year now and been working with developers from Three.js, Cinema4D and UE4 transitioning to BJS - and that’s the complaint/feedback I’ve been getting consistently. Why isn’t this working like UE4/C4D, oh that’s because of world matrices. Then what’s up with the world matrices, why does it not update, and am I really forced to look into the source code to know what’s going on?
Currently, the short answer is that, if “you’re not sure”, just always run computeWorldMatrix(true), which isn’t an ideal solution.

All that said, I still love the BJS community and can’t wait for it to keep evolving (and for webGPU to finally arrive)!

1 Like

May I ask why you keep shying away from producing a playground that exemplifies the issue you are having. It does not have to be complex and would make it much easier to see if alternative approaches are possible. For example perhaps Engine | Babylon.js Documentation or similar observable could suffice to make your task easier. Without a specific example those in the know find it difficult to give specific advice.

2 Likes

I can definitely appreciate that :slightly_smiling_face:, and I’m glad to hear that your experience has been mostly positive otherwise!

You’re not wrong that that isn’t ideal, and we know this is something people have found confusing (including me at one point). As we’ve discussed, there is strong reasoning for the current design, but that doesn’t necessarily mean there’s not room for alternative usages or additional features. However, as JohnK said, it will be difficult for us to move toward discussing specific alternatives without an example. It may be that this pain point can be lessened with something that already exists but is a bit obscure – Babylon has quite a lot of “quirks” and “tricks” for use cases we see less commonly – or it may be that there’s a new capability we can introduce which can help without deteriorating the common use case; but without a Playground showing an example where you believe Babylon should behave differently, I’m afraid we’re unlikely to be able to address this much more tangibly.

1 Like

Ok I did not think a PG was required, since I thought this was supposed to be fairly obvious @JohnK . But since you asked for it, here goes

PG: https://playground.babylonjs.com/#3P8L7E#12

This is a trivial example. There are cases with chains of 100+ nodes.
There are cases where these things are spread out across different parts of the project.
There are cases of re-parenting.
Are we supposed to run computeWorldMatrix(true) every.single.time to be sure?
Or are we supposed to track all of them and only run computeWorldMatrix(false) for the ones that have been ‘updated’?

This should be the design of a good engine, not something users of the API should worry about (FYI, I’m not the only person who thinks this. Many devs have echoed the exact same thing. But sadly, a few have quit using BJS before even bothering to post. The frustration is real. Apparently too many issues with transforms, matrices and bones (which btw I think the bone transforms should be absolutely removed, they’re broken and should all be replaced by mandatory linked TransformNodes. But that’s a whole 'nother topic)).
You’ll never see this in Unity/UE4.

If BJS wants to go above and beyond and target even more users (and I really hope they do!), these things need to be more accessible. Just my 2 cents.

A proposal is adding a worldmatrix dirty flag that propagates down all children.

Then computeWorldMatrix(force=true) or something similar only needs to check if the worldmatrix flag is dirty. If force=true, traverse up the hierarchy and find the first node that is not dirty. Then compute worldmatrix backwards. We’re not forcing hierarchical transforms to update unless force=‘true’. By default we’re simply adding a flag

In the example
A
–>B
---->C
------->D

if B.position changed, mark B+C+D as dirty.
C.computeWorldMatrix(true) checks C is dirty, checks parent (B) is dirty, and updates B worldmatrix then updates C worldmatrix. B+C no longer dirty
D.computeWorldMatrix(true) checks D is dirty but since C is not dirty, only update itself. No need to re-compute the hierarchy all over again
D.computeWorldMatrix(true) again no longer needs to do anything.