SharedMesh and collision tree

I am using a shared mesh and need to test for collisions on it



I tested on the TestSharedMesh by adding rootNode.updateCollisionTree(); as the last line in simpleInitGame



I get the stack trace



java.lang.NullPointerException

at com.jme.scene.TriMesh.getMeshAsTrianglesVertices(TriMesh.java:460)

at com.jme.scene.TriMesh.getMeshAsTriangles(TriMesh.java:470)

at com.jme.bounding.OBBTree.construct(OBBTree.java:106)

at com.jme.scene.TriMesh.updateCollisionTree(TriMesh.java:288)

at com.jme.scene.TriMesh.updateCollisionTree(TriMesh.java:278)

at com.jme.scene.Node.updateCollisionTree(Node.java:375)

at com.jme.scene.Node.updateCollisionTree(Node.java:375)

at jmetest.renderer.TestSharedMesh.simpleInitGame(TestSharedMesh.java:202)

at com.jme.app.BaseSimpleGame.initGame(BaseSimpleGame.java:414)

at com.jme.app.BaseGame.start(BaseGame.java:65)

at jmetest.renderer.TestSharedMesh.main(TestSharedMesh.java:76)



How do i allow shared mesh to be collidable

Update from CVS and try again. SharedMesh should have been calling the target's getMeshAsTrianglesVertices method.

I updated earlier today before trying the above …

Update again. There was a bug.

I took the src from the nightly build



added rootNode.updateCollisionTree(); at line 202



got

java.lang.NullPointerException

at com.jme.scene.TriMesh.getMeshAsTriangles(TriMesh.java:472)

at com.jme.bounding.OBBTree.construct(OBBTree.java:106)

at com.jme.scene.TriMesh.updateCollisionTree(TriMesh.java:288)

at com.jme.scene.TriMesh.updateCollisionTree(TriMesh.java:278)

at com.jme.scene.Node.updateCollisionTree(Node.java:375)

at com.jme.scene.Node.updateCollisionTree(Node.java:375)

at jmetest.renderer.TestSharedMesh.simpleInitGame(TestSharedMesh.java:202)

at com.jme.app.BaseSimpleGame.initGame(BaseSimpleGame.java:414)

at com.jme.app.BaseGame.start(BaseGame.java:65)

at jmetest.renderer.TestSharedMesh.main(TestSharedMesh.java:76)

Wait… you shouldn't be calling updateCollisionTree on the sharedMesh but the original mesh. The shared mesh will use the original mesh's tree and transform it into the shared mesh's world space. (I'm fixing the wrong things in other words). So, sharedMesh.updateCollisionTree() will just pass that to the target now. However, this means that if you call it on the rootNode you may be generating a collision tree many times (1 time for every sharedNode you have), so you could speed things up by only calling it on the original node.



So, anyways, SharedMesh now passes updateCollisionTree to the target mesh. Update from CVS and try that. Let me know if there are still problems (I don't have the test with me currently). If you still have problems let me know how you are testing it so I can get the same test going here.

Hmmm, Interesting



What i am doing is adding trees to a scene, the user can select to add a tree at runtime, and need collision detection so the user cant walk through the tree.



After adding the tree to a scene ( which is from a shared mesh ), the rootNode update collision tree is called to allow for picking later ??, Is this the wrong usage for shared mesh.



Edit :



i had a quick look at FlagRush tutorial 9, as the fence is a shared mesh. The forcefield isnt yet working, so if you plan on activating the forcefield you may come accross the collsion issue there

Well, there was a bug in SharedMesh in that it was not overriding updateCollisionTree. That I fixed. All it does is call the target's updateCollisionTree method. What I was saying is, for every tree in your scene it is going to call updateCollisionTree on the same object. So, you might not want to call updateCollisionTree on the entire scene to help speed up initialization. I.e. just call updateCollisionTree on the main tree object and all the shared mesh trees will be ready.

I made a change in my local copy so you can create a SharedMesh using a new constructor

public SharedMesh(String name, TriMesh target, boolean updatesCollisionTree)

Where if you use false, the calls to update the collision tree of the target are blocked. This is so you can still call updateCollisionTree on one of the parent Nodes without wasting CPU time. Of course you'll still have to call it on the "targets" of any shared meshes, but that's still better than having to do this for each individual non-shared mesh.



Is this worth checking in? The old constructor uses true by default right now.

Yeah, that seems like a reasonable way to help control it.

Commited

Hey - good work, got the collision tree working on the trimesh itself - which the shared meshs now happily use :), looking forward to the new Constructor.


Noticed that TestTerrainTrees uses CloneCreator, this is a real clone and each spatial it clones is a separate object.



Im happy to change TestTerrainTrees for you to using shared meshs. If so - can have it to you on Monday ( Geez - suppose i better drop programming and go to a party tonight ).



Ill look to have several differing sized trees and textured too - if you can foresee any best practices, please state.

Party is hours away yet.



Changing from cloned to sharedmesh didnt give the performance boost i expected - back to understanding it all again for me, would have thought if the GPU only got one needed one copy of the vertices and texture, it would significantly boost things.



Any ways - heres the code.





/*
 * Copyright (c) 2003-2005 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jmetest.terrain;

import javax.swing.ImageIcon;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.CloneCreator;
import com.jme.renderer.Renderer;
import com.jme.scene.SharedMesh;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Pyramid;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;

/**
 * <code>TestTerrain</code>
 *
 * @author Mark Powell
 * @version $Id: TestTerrainTrees.java,v 1.12 2005/10/15 13:23:06 irrisor Exp $
 */
public class TestTerrainTrees extends SimpleGame {
 
   TerrainBlock tb;
   boolean asSharedMesh = true;
   
    /**
     * Entry point for the test,
     *
     * @param args
     */
    public static void main(String[] args) {
        TestTerrainTrees app = new TestTerrainTrees();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }

    /**
     * builds the trimesh.
     *
     * @see com.jme.app.SimpleGame#initGame()
     */
    protected void simpleInitGame() {
      rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      fpsNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
        display.setTitle("Terrain Test");
        cam.setLocation(new Vector3f(64 * 5, 250, 64 * 5));
        cam.update();

        FogState fs = display.getRenderer().createFogState();
        fs.setEnabled(false);
        rootNode.setRenderState(fs);

        CullState cs = display.getRenderer().createCullState();
        cs.setCullMode(CullState.CS_BACK);
        cs.setEnabled(true);

        lightState.setTwoSidedLighting(true);

        MidPointHeightMap heightMap = new MidPointHeightMap(128, 1.9f);
        Vector3f terrainScale = new Vector3f(5, 1, 5);
        tb = new TerrainBlock("Terrain", heightMap.getSize(),
                terrainScale, heightMap.getHeightMap(), new Vector3f(0, 0, 0),
                false);
        tb.setDetailTexture(1, 4);
        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
        tb.setLocalTranslation(new Vector3f(0, 0, 0));
        rootNode.attachChild(tb);
        rootNode.setRenderState(cs);

        ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
                heightMap);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
        pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
                .getResource("jmetest/data/texture/highest.jpg")), 128, 255,
                384);

        pt.createTexture(512);

        TextureState ts = display.getRenderer().createTextureState();
        ts.setEnabled(true);
        Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
                Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR, true);
        ts.setTexture(t1, 0);

        Texture t2 = TextureManager.loadTexture(TestTerrain.class
                .getClassLoader()
                .getResource("jmetest/data/texture/Detail.jpg"),
                Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);

        ts.setTexture(t2, 1);
        t2.setWrap(Texture.WM_WRAP_S_WRAP_T);

        t1.setApply(Texture.AM_COMBINE);
        t1.setCombineFuncRGB(Texture.ACF_MODULATE);
        t1.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t1.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineSrc1RGB(Texture.ACS_PRIMARY_COLOR);
        t1.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t1.setCombineScaleRGB(1.0f);

        t2.setApply(Texture.AM_COMBINE);
        t2.setCombineFuncRGB(Texture.ACF_ADD_SIGNED);
        t2.setCombineSrc0RGB(Texture.ACS_TEXTURE);
        t2.setCombineOp0RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineSrc1RGB(Texture.ACS_PREVIOUS);
        t2.setCombineOp1RGB(Texture.ACO_SRC_COLOR);
        t2.setCombineScaleRGB(1.0f);
        rootNode.setRenderState(ts);

        TextureState treeTex = display.getRenderer().createTextureState();
        treeTex.setEnabled(true);
        Texture tr = TextureManager.loadTexture(
                TestTerrainTrees.class.getClassLoader().getResource(
                        "jmetest/data/texture/grass.jpg"), Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR);
        treeTex.setTexture(tr);
       
        if(asSharedMesh) addSharedMeshTrees(treeTex);
        else addClonedTrees(treeTex);
    }
   
    private void addSharedMeshTrees(TextureState treeTex) {
       Pyramid p = new Pyramid("Pyramid", 10, 20);
        p.setModelBound(new BoundingBox());
        p.updateModelBound();
        p.setRenderState(treeTex);
        p.setTextureCombineMode(TextureState.REPLACE);
       
        SharedMesh s1;
        int randomSeed = 128 * 5;
        float x;
        float z;
        String meshName = "tree";
        for (int i = 0; i < 500; i++) {
           s1 = new SharedMesh(meshName + i, p);           
            x = (float) Math.random() * randomSeed;
            z = (float) Math.random() * randomSeed;
            s1.setLocalTranslation(new Vector3f(x, tb.getHeight(x, z)+10, z));          
            s1.setLocalScale((int)(Math.random() * 3.0) + 1);
            rootNode.attachChild(s1);
        }
    }
   
    private void addClonedTrees(TextureState treeTex) {
       Pyramid p = new Pyramid("Pyramid", 10, 20);
        p.setModelBound(new BoundingBox());
        p.updateModelBound();
        p.setRenderState(treeTex);
        p.setTextureCombineMode(TextureState.REPLACE);
       
        CloneCreator cc1 = new CloneCreator(p);
        cc1.addProperty("vertices");
        cc1.addProperty("normals");
        cc1.addProperty("colors");
        cc1.addProperty("texcoords");
        cc1.addProperty("indices");
        cc1.addProperty("vboinfo");
       
        for (int i = 0; i < 500; i++) {
           Spatial s1 = cc1.createCopy();
            float x = (float) Math.random() * 128 * 5;
            float z = (float) Math.random() * 128 * 5;
            s1.setLocalTranslation(new Vector3f(x, tb.getHeight(x, z)+10, z));
            rootNode.attachChild(s1);
        }
    }
}



Those "trees" are only a few polygons compared to the terrain, so the impact of replacing them with SharedMeshes is small. That's not to say it's not the right approach! So to answer your question in the other thread as well: yes it's always best to use a SharedMesh where possible. Another advantage is it saves memory. Same for shared texture states.



Remember, not any optimization will have the same effect in all situations. For example, it could be possible that this current example is fillrate limited rather than geometry limited: meaning that the GPU is limited by the speed by which it can texture the terrain (ProceduralTextureGenerator generates a lot of pretty big textures), rather than how fast it can calculate geometry. So while the calculation of geometry might be done a little quiker, it could still be waiting on texture fetches and the like. Using SharedMesh or clones doesn't make a difference there, since in fact there are no textures on them.

Thanks Llama, sounds a reasonable explanation of whats going on.



Ill have to get a profiler out and get a better understanding of whats going on under the hood.

Well, if you find a good way to profile what's going on inside a GPU, let us know  :smiley:



It's possible to profile Java code, and to trace OpenGL calls, but that's only half of the picture.

Compare the memory usage as well. There's one area that the SharedMesh will shine…

kidneybean said:


Dont know if its good - but has a trial period
http://www.gremedy.com/download.php


Looks like it's a fancy GUI for tracing OpenGL calls, plus some extra debugging features. Unfortunatly we still won't know what's going on in the GPUs, or even the videocard drivers.

If you're looking to get more performance, enable VBO on the original mesh. You might see some more advantages to having a single VBO as opposed to having 700 seperate VBOs. Anyway, the difference between the clones and sharedmeshes is so small because of the cloning method used (reusing all the buffers). However, compared to the "individual" method, you should already see good advantages in memory (escp. after creating 300 textures) and I suspect SharedMesh is a little better than the clones cause it has a bit less overhead.

The advantage of clones is that you could clone only parts of the original. For example use the same vertexbuffer but a different colorbuffer.

you might also try this all again once the lockable mesh options are in place later in the week.