TransformNode.computeWorldMatrix & .markAsDirty causing (frozen) transforms to not apply

Hello again :slight_smile:
This is a post to highlight some weirdness we’ve experienced with transforms, I’m unsure what to label as a bug here and not, but I wanted to share the issues we’ve experienced regardless. It should be noted that we use transform freezing.

In short, what we’re seeing is that certain combinations of calls to TransformNode.markAsDirty, TransformNode.computeWorldMatrix & Scene.incrementRenderId, cause transforms to never be applied.

Repro that shows & explains the issues (2 of them):

The first issue is the call to markAsDirty. If we do not call it, our transform change is never applied (and no call to incrementRenderId can help us). It feels a bit quirky that we need to mark a completely new TransformNode dirty, and unsure why.

The second issue is the call to computeWorldMatrix. If we don’t call it, everything works as expected. But when called, it prevents our transform change on the parent to ever be visible to us (and remember, the transform change on the parent has been set before we call computeWorldMatrix on the child).
This feels like a bug, as trying to compute the world matrix after a change to our parent causes us to never see the change in the parent.

I would guess part of the cause of the second issue here is the “render id” check in the compute world matrix, so another workaround is to simply pass the force flag for any call to computeWorldMatrix, but then we lose the early out of the dirty flag in the case where we don’t use transform freezing. Plus it means we need to add a linter rule in our project so no one ever forgets to send the force flag.

So, not sure where to continue with this. It’s not blocking us of course, and maybe it is more of a quirk than a bug. But I wanted to lift it anyway, for searchability’s sake if anyone else runs into it, but also to see what you think.

Best regards, Tore

Hey Tore,

Interesting issue and probably a long discussion :slight_smile:

Here is my opinion. The node that should be marked as dirty is not the sphere but its parent, as you are changing the parent’s transformation. Marking the parent’s transformation to dirty makes sure it all works as expected:

The workarounds you found are correct. Both forcing computation of the world matrix or increasing the render ID will result in the same result - it will force-compute the parent’s work matrix while computing the sphere’s world matrix using the newly-adjusted parent world matrix.

When you freeze the world matrix it will not be computed by the standard render loop, so this is the reason it doesn’t work “out of the box”.

Now the general question here is - if I mark an object “dirty”, should we also mark its parent “dirty”. For performance-reasons it would make little sense IMO.
Is it a bug? I am not sure if other team members will agree with me, but I don’t think it is. I think it behaves as expected in the defined context.

1 Like

I would also tend to not qualify as a bug for the same performance reason. We have to take a stance on either perf or easiness.

The deal when using freeze API is that several parts will start to not work automatically in exchange for perf

2 Likes

Hey! :slight_smile:
Yeah, as I said, not sure what to call bug and not :smiley:
There’s a lot to unpack here, having a bit of a hard time narrowing it down. Will probably come in pieces :slight_smile:

But either way, I realized I made the mistake of freezing the parent in the original playground, so here is an updated one:

Here I don’t call markAsDirty on the parent (but it’s _dirty = true anyway from the scaling setter), as I don’t believe it should be necessary if it’s not frozen.
On the bottom 3 lines, you can see that calling force does not help in getting the scaling to show up on the instance, and the only way to get it to work is to call markAsDirty on the instance, which I haven’t changed anything on (although it is frozen, so some caution must be taken). So it also begs the question why forcing a compute world matrix still early outs.

When you freeze the world matrix it will not be computed by the standard render loop, so this is the reason it doesn’t work “out of the box”.

On the topic of freezing, I think it’s a bit unfortunate that it’s very hard to know what works and doesn’t work when you enable freezing. It’s not as simple as that the world matrix can’t change unless someone explicitly says markAsDirty or similar.
Rather, if I’m interpreting the code correctly, the effect of disabling freezing is that once per frame all matrices will be recomputed regardless of hierarchy (the renderId check will make it recalculate, regardless of the state of the dirty flag). This means visually, everything will look correct.

In our case, using freezing or not only makes a marginal difference in cases such as this one. This is because we need to be able to write and query transform data without having a rendering pass inbetween. For example, our users (programmers who use our code) move a mesh and then raycast against it.

This means we have to be super careful to set all dirty flags whenever needed manually. This I don’t really have an issue with, because it’s not what the Babylon system is made for as far as I can tell.

But the issue I want to highlight is that when I add a seemingly innocent computeWorldMatrix, it has the side effect of “soft-locking” the transform. And if I disable freezing, I can start expecting it to be up-to-date when the next frame renders, but what if I need the data before that? Usually the call to computeWorldMatrix is because I need the transform at that point in the code, not after the next frame.

as soon as you freeze a matrix you should consider it readonly.
We allowed some mechanism in the past on the entity itself to let the system refresh a frozen matrix but this was a mistake (if you change position,rotation or scaling on a frozen mesh, we will still update the wm, which retrospectively I find stupid)

The goal of the “moral” contract when freezing a matrix is that the user agreed to take responsibility for the matrix maintenance.

I stated it (poorly :)) in the doc: Babylon.js docs

1 Like

if nothing is frozen, calling computeWorldMatrix will get you the good result instantaneously.

If this is not the case I have a bug to fix:D

Ah, okay. Because if I understand you correctly then, it’s simply “undefined behaviour” territory to use matrix freezing where we actually expect the matrices to change sometimes.

I.e., you could get it to work but you might need to call “scene.incrementRenderId” before you call computeWorldMatrix :stuck_out_tongue:

So if that’s Babylon’s stance, I think that’s a pretty good answer. But I think it then should be clearly stated in the docstrings and documentation (sorry if I’ve missed it) regarding freezing, that as soon as any parent’s transform or your transform changes, you can’t expect it to work. Because it will work most of the time currently, which has then deceived us :smiley: When I joined this project freezing has always been in use, so I’ve maybe taken it too much for granted.

Either way, I think there are still elements of my original discussion that will still be valid when no matrix freezing is involved, just not the exact examples I used. But I need to rethink it and reset a bit, and I’ll be off tomorrow (Thursday) so I’ll get back to you here on Friday or next week :slight_smile:
But would appreciate confirmation if I understood you correctly about the freezing and “undefined behaviour” :slight_smile:

Thanks!

No you did not miss them. We need to be clearer. As soon as you call freezematrix you have to expect that it will not be updated. The recommend approach is to unfreeze, do the change and then freeze.

Your approach is still valid though (incrementRenderId to force the update)

@rapid.tore.levenstam does it make sense ?
Update freezeWorldMatrix documentation by deltakosh · Pull Request #16546 · BabylonJS/Babylon.js

2 Likes