MorphControl question

Hi

Can someone please explain why this code needs to be run in every update loop?

I mean why the target list is cleared and refilled every frame? How can it be different?

Regards

1 Like

I havn’t used the MorphControl yet but i assume it can be added to a Node which might have children attached and detached at runtime.
It is strange though that a SafeArrayList is used for the targets, not using the toArray() method when iterating but instead creating an iterator each frame (internally creating an array) so it makes for quite some garbage each frame i guess, so just using an ArrayList might be an easier approach with the downside that it wouldnt shrink when the number of targets is reduced

2 Likes

I wonder if that code is meant to initialize like the first time or something… since controls don’t really have an initialize().

It’s weird. The git blame was not particularly illuminating either… but does add some context.

2 Likes

2 more thoughts reading the MorphControl class again:

the way the maximum number of targets is determined is performance heavy but precise, however the result is cached (probably because its performance heavy). But this cached value is only reset when a new Material is set, although in theory this could change when changing a material parameter that is bound to a define, while such vertex attrib wouldnt neccessarily need to be directly surrounded by an #ifdef something but instead could be optimized away by the compiler when it finds the attrib is not used dues to an #ifdef somewhere else. Not sure any built-in shader is a potential candidate for it but might be worth at least stating in the javadoc

secondly, i am wondering about the comment stating whats done in the update method needs to be done in the update method. I dont see why it cannot be done in the render method?
(so it would be skipped when its culled, but potentially be run several times if its part of and visible in several viewports scenes)

2 Likes

Guys, thanks for the response

I think so. That’s unfortunate.

I modified it like this in my test and works without issue.

   public void resetTargets() {
        // gathering geometries in the sub graph.
        // This must be done in the update phase as the gathering might add a matparam override
        targets.clear();
        spatial.depthFirstTraversal(targetLocator);
    }

    @Override
    protected void controlUpdate(float tpf) {
        if (!enabled) {
            return;
        }
        if (targets.isEmpty()) {
            resetTargets();
        }
    }
1 Like

Hi guys, a little tip:
the MorphControl class extends AbstractControl. The controlUpdate and controlRenderer methods are not invoked if the controller is disabled. So checking if the controller is enabled in such methods is useless right? You could remove these instructions:

@Override
protected void controlUpdate(float tpf) { 
    if (!enabled) { // this check is useless
        return;
    }

....
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
    if (!enabled) { // this check is useless
        return;
    }

...
}
  1. here
  1. here

From AbstractControl class:

   @Override
    public void update(float tpf) {
        if (!enabled)
            return;

        controlUpdate(tpf);
    }

    @Override
    public void render(RenderManager rm, ViewPort vp) {
        if (!enabled)
            return;

        controlRender(rm, vp);
    }
3 Likes

Right, thanks.

2 Likes

If I call it from the render method, it throws this exception

SEVERE: Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.IllegalStateException: Scene graph is not properly updated for rendering.
State was changed after rootNode.updateGeometricState() call. 
Make sure you do not modify the scene from another thread!
Problem spatial name: cloth_0000061:Mesh
	at com.jme3.scene.Spatial.checkCulling(Spatial.java:365)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:717)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:731)
	at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:710)
	at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1096)
	at com.jme3.renderer.RenderManager.render(RenderManager.java:1158)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:271)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:160)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:196)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:242)
	at java.lang.Thread.run(Thread.java:748)

which seems to be caused by mat param overriding as If I comment this line it wont throw the exception.

2 Likes

Oh ye that makes sence, didnt realize matParamOverrides are tracked in the refresh flags the same way other state is but it makes sence as they can be inherited though the scenegraph :man_facepalming:
thanks for playing around with it

2 Likes

I was testing morph animation (the Synchronicity) on different types of models and here is the result.

1- Model has only morph animation. (do not have bone (skin) animation)

I can play animations asynchronously on multiple clones without issue at the same time.

2- Model has morph and bone (skin) animation on the same geometry.

like this model

I can play animations asynchronously on multiple clones without issue at the same time. Note, in this case, meshes will be a semi-deep clone because they are skinned. (cloned with mesh.cloneForAnim())

3- Model has morph and bone (skin) animation but on different geometries.

Like this model

The body has bone animation and the skirt has morph animation.

I can not play animation asynchronously on multiple clones unless I deep clone the morphed meshes. (skirt in this case). Note in this case body will use a semi-deep clone and the skirt will use a shallow clone.

I wonder why morph works fine with shallow clone in case 1 but does not in case 3?

2 Likes

Just to get sure I am understanding this line correctly!

So does the cloner clone the targets list from the original spatial and automatically replace the items in it with their clones?

@sgold?

Edit:

Ok, never mind, seems it does :wink:

1 Like

so as i see you fixed it in:

? i dont understand what was the issue but i see things like removeMatParamOverride there.

And the PR settles it so it will just run once and not every frame.

2 Likes

sounds serious, thanks for PR :slight_smile:

2 Likes

In the PR I submitted, this won’t be done automatically so the user needs to detach and reattach the MorphControl for the list to get updated. This is a rare usecase, though.

1 Like

That seems worth mentioning in the javadoc.

1 Like

Done. Thanks for the feedback @sgold :slight_smile:

1 Like

Okay, I was wrong. :stuck_out_tongue:

In my test, the model had only two morph targets. After testing with another model with more morph targets I noticed the glitches on the mesh when animation is played asynchronously.

I do not know why this happens. Are morphs handled on GPU or CPU? If they all modify the same mesh buffers?

Anyway!

In the first video, I use a simple morphed model with 2 morph targets. there seems to be no issue when playing asynchronously.

The 2nd one has 3 morph targets and you can notice some glitches on the mesh while one of them is playing and the other one is stopped.

3rd one has around 30 morph targets on the skirt mesh. As you see if I stop one of them the other one is fully influencing it and if they played asynchronously you can notice the glitches on the mesh.

I was able to fix the issue by doing

model.depthFirstTraversal(new SceneGraphVisitorAdapter() {
            @Override
            public void visit(Geometry geom) {
                if(geom.getMesh().hasMorphTargets()) {
                    geom.setMesh(geom.getMesh().cloneForAnim());
                }
            }
        });

So to sum it up. I believe we need to update the Geometry.cloneField() to use cloneForAnim if the mesh is morphed. Similar to what we are doing for skinned meshes here:

@sgold @pspeed any thought?

3 Likes

I believe morphing can be performed by either the CPU or the GPU. The actual mix depends on the number of morph targets and the number of available slots, which is determined dynamically.

1 Like

I think you’re right.

1 Like