[SOLVED] How to Attach/Detach from InstancedNode

Hello,

I’m trying to figure out the right way to attach/detach child geometries from an InstancedNode. Essentially the goal is to have different geometries appear or disappear from the InstancedNode based on distance to the camera. I don’t want to use LOD because these geometries need to either appear or disappear, not change mesh complexity.

At the moment, the geometries appear briefly then disappear completely. I probably have the wrong mental model for how to use InstancedNode, so any nudges gratefully received.

SSCE:

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.instancing.InstancedNode;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;

// Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode.
public class TestInstancedNodeAttachDetach extends SimpleApplication {
    public static void main(String[] args) {
        TestInstancedNodeAttachDetach app = new TestInstancedNodeAttachDetach();
        app.setShowSettings(false);  // disable the initial settings dialog window
        app.start();
    }

    private InstancedNode instancedNode;

    private Vector3f[] locations = new Vector3f[10];
    private Geometry[] spheres = new Geometry[10];
    private Geometry[] boxes = new Geometry[10];

    @Override
    public void simpleInitApp() {
        addPointLight();
        addAmbientLight();

        Material material = createInstancedLightingMaterial();

        instancedNode = new InstancedNode("theParentInstancedNode");
        rootNode.attachChild(instancedNode);

        // create 10 spheres & boxes, positioned along Z-axis successively further from the camera
        for (int i = 0; i < 10; i++) {
            Vector3f location = new Vector3f(0, -3, -(i*5));
            locations[i] = location;

            Geometry sphere = new Geometry("sphere", new Sphere(16, 16, 1f));
            sphere.setMaterial(material);
            sphere.setLocalTranslation(location);
            instancedNode.attachChild(sphere);       // initially just add the spheres to the InstancedNode
            spheres[i] = sphere;

            Geometry box = new Geometry("box", new Box(0.7f, 0.7f, 0.7f));
            box.setMaterial(material);
            box.setLocalTranslation(location);
            boxes[i] = box;
        }
        instancedNode.instance();

        flyCam.setMoveSpeed(30);
    }

    @Override
    public void simpleUpdate(float tpf) {
        // Each frame, determine the distance to each sphere/box from the camera.
        // If the object is > 25 units away, switch in the Box.  If it's nearer, switch in the Sphere.
        // Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc.

        instancedNode.detachAllChildren();

        for (int i = 0; i < 10; i++) {
            Vector3f location = locations[i];
            float distance = location.distance(cam.getLocation());

            if( distance > 25.0f ) {
                instancedNode.attachChild(boxes[i]);
            } else {
                instancedNode.attachChild(spheres[i]);
            }
        }

        instancedNode.instance();
    }

    private Material createInstancedLightingMaterial() {
        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setBoolean("UseMaterialColors", true);
        material.setBoolean("UseInstancing", true);
        material.setColor("Ambient", ColorRGBA.Red);
        material.setColor("Diffuse", ColorRGBA.Red);
        material.setColor("Specular", ColorRGBA.Red);
        material.setFloat("Shininess", 1.0f);
        return material;
    }

    private void addAmbientLight() {
        AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
        rootNode.addLight(ambientLight);
    }

    private void addPointLight() {
        PointLight pointLight = new PointLight();
        pointLight.setColor(ColorRGBA.White);
        pointLight.setRadius(100f);
        pointLight.setPosition(new Vector3f(10f, 10f, 0));
        rootNode.addLight(pointLight);
    }
}

From adding a breakpoint and debugging the InstancedNode, I can see that it has the children that I expect, but it doesn’t seem to clear or rebuild it’s instancesMap - maybe this is related?

If anyone could help shine a light on my misunderstandings that would be excellent.

Thanks,

Duncan.

In this case, I think you should use instancedNode.detachChild(child); to remove a single child from InstancedNode this should automatically ungroup it from the InstancedGeometry.

And for attaching it again you should use instancedNode.attachChild(child); also must call instancedNode.instance(child); to regroup it.

hmm… but do not know why it is private!? :thinking:

Anybody know why?

I scrolled down a few lines:

BTW, this whole approach is going to DESTROY performance. You might as well not use InstancedNode at all because you are going to be rebuilding the batch every frame which is guaranteed to be slower then letting the system just try to render all of that stuff.

If your scene has millions of objects so batching and distance is actually a concern then it would be better to group them spatially (like in a grid, etc.) into separate InstancedNodes.

Yep, I completely agree with the point about performance. This is just test code. In my real code, I’m using a spatial grid of chunks, each with their InstancedNodes and only changing the children of the InstancedNodes when a player moves a significant distance, (so it’s not such a performance hit).

Detaching my geometries one by one and then attaching the appropriate ones back on does work, but I would have expected detachAllChildren() to have the same effect as calling detachChild for each of my child geometries, but it doesn’t. Is it removing the InstancedGeometry children as well?

Probably.

But if you already have your scene divided up into chunks then those chunks will already be culled when not in view… and you could just show/hide the whole chunk when they are far enough away.

Is there an actual performance problem that you are trying to solve? Or are you optimizing prematurely?

Yeah, I know, but that one instance the whole children which is redundant in this case and probably will break the instanced node if called multiple times. I was meaning to call it only for the newly added child that we want in this case actually.

There is. Despite chunking, instancing & culling, the number of Objects being rendered in view is still too high. Each chunk has an InstancedNode, which has child nodes, one for each plant in that chunk. There are several types of plants, with a couple of materials + meshes each. These are loaded by assetManager, so they’re the same mesh and material, just different Geometries. InstancedNode seems to be grouping the identical meshes with identical material correctly, thereby reducing the number of objects, but it’s still not enough, because each chunk ends up with a complete set of these grouped objects. When I increase the world chunk distance (number of chunks to load in front of / behind / to the side of the player) this means the number of objects now gets too high.

Example: chunks are 128x128 units in size, world chunk distance is 3, so 2 x (3+1+3) = 49 chunks being loaded. Each chunk has around 20-30 objects as a result of instanced plants, so that’s 980-1470 objects. Assuming culling means you only see about 1/4 of these at a time for a 90 degree FOV, that’s still 245-367 objects, on top of the 200 or so other objects that I have loaded.
I want to be able to increase the world chunk distance to 9, so that’s 361 chunks x 1/4 = 90. That implies 1800-2700 objects, clearly too high.

I could increase the size of the chunks, so that there are less instanced nodes and therefore less sets of grouped scene objects, but this means they’ll take longer to generate.

So my idea was to switch groups of plants in and out of the instanced nodes depending on chunk distance to the player. You only need to see the larger trees and plants when they’re a long way away, not the smaller plants. I think this might save quite a lot of scene objects.

I’m an avid reader of this forum (I wouldn’t have stood a chance of getting this far without it), so I know there are some significantly more experienced people than me here. All hints/criticism/suggestions welcome.

Yes, you should not use InstancedNode.detachAllChildren().

Note that in this case, geometries are not instanced anymore (means no performance gain) because they are automatically ungrouped from InstancedGeometry when detached so you should make sure to call instanceNode.instance() method after attaching them.

Oops, sorry I was missing this check

so it will skip the grouped ones already. :stuck_out_tongue:

But as I understand it, the chunk has its own InstanceNode? Why not one instance node per type of thing getting instanced? Then showing/hiding them becomes easier and can even be done just with the cull hint.

Without knowing more about your chunks, 128x128 already seems pretty big and at some point you are going to hit limits that cannot be solved by just better managing this approach and will require completely different approaches (see most of my most recent videos for example). But even within what you are doing, you will probably be happier managing your instance sets yourself.

It doesn’t work that way. Child gets added/removed, parent needs to update. So even if you could call instance(someNode) it’s just going to have to grab the parent and call instance on that anyway.

When I tried that, the number of objects was low, but the number of triangles and vertices skyrocketed (annoyingly I don’t have the exact numbers, apologies) and FPS dropped to 5-10. I might try looking at that again, since I thought it might be a crazy idea at the time, but now you’ve suggested it… :wink:

I will check out the videos again - thanks for the pointer.

I’ve settled (for now) on the approach of attaching and detaching geometries to/from the InstancedNode based on distance and it works well enough. The two things that were confusing me were 1) detachAllChildren (which I’ve removed in favour of detaching them one by one) and 2) bullet physics was interacting with my instanced geometries in a way that I don’t understand, causing all the plants to appear in the wrong place. Removing the bullet physics code has restored them to their proper positions.

I have an issue with a NullPointerException when picking ray collisions against the InstancedNode. Should I post this as a separate topic on the forum to keep things clean, or is it ok to continue with it here?

Either you didn’t really understand what I was saying or there was something else wrong.

Assuming you really do put InstancedNodes under your chunks, what I was describing would have ended up with the exact same amount of actual draw calls, triangles, etc…

Do not know if InstancedNode supports raycasting but anyway a separate topic would be better I guess.

Yes, I think I understood what you were saying, but there was almost certainly something else interfering. Thanks for the pointer, it’s good to know it’s a sensible approach to try.