Charactercontrol and drop in FPS

Hi all,



On this small game i am working on, i have 2 clear distinct objects:

Buildings

Characters



Buildings are created in the following way:

  • Load model (spatial) and apply material into a node
  • Attach node to a buildings childnode in the rootnode
  • On placement of the building a rigidbodycontrol (mesh outline) will be made which will be attached to to the node



    Characters are created in the following way:
  • Load model (spatial) and apply material into a node
  • Attach node to a character childnode in the rootnode
  • On placement of the character a CharacterControl (capsule) will be made which will be attached to to the node



    After placing like 50 buildings, there is a FPS drop of about 10 fps (300->290)

    After placing about 10 characters, the FPS is dropped to 10 fps! (300->10).



    If I remove the charactercontrol there is a similar drop in FPS as it would to buildings.

    If i attach a charactercontrol to the building the same effect happens as with the characters.



    At the current stage I have a test case with the only difference in controlset for the object.



    Any idea what this could be?



    Thanks in advance!

Maybe your character model has more polys than the buildings?

Character geometry is currently: Sphere(8, 8, 3);

Building geometry (mesh) is a blender export (way more complex).



Removing the CharacterControl results in no drop in FPS upon placing characters however this makes them unable to interact with the world.

So do you have assertions enabled? They slow down collision detection very much. Also simply consider that you are using physics and collision detection whereas with the buildings you dont.

How can I enable or disable the assertions?

You supply the “-ea” switch to your app to enable them :google:

No success :frowning: It was disabled. If I enable it the average FPS drops with 50% and problem still exists. Removing characters temporary from the phsyicspace (e.g. after they stopped moving) will keep the FPS smooth. As soon as I add them again the problem comes back.

i actually have the same problem, and im just using the HelloCollision tutorial as the base and adding in water, sky. I found the same thing, removing character control resolves the FPS problem, wierdly i didn’t have this in the may build but now im using the june 8th build but i dont think anythings changed.

A possibility that I see is that the system perhaps is way to accurate. I’m trying if that would matter.

No, dont play with the accuracy value, its good.

Is there any example with multiple (>16) CharacterControls implemented that does not have this behaviour?

No, I did a local test with several hundred controls tho back when the last changes on physics were made.

Later tonight ill create a basic app with the bare basics of this issue en post the code here so perhaps we can get a better view at the issue.

Ok, only idea I have what could have changed is the workaround @nehon added for kinematic things… They would not move when they were added while being kinematic, maybe thats provoking additional computations for the character object… But normally it should be recognized as a character object and be added to the physics space with character parameters. Another thing you might want to try is to use the native bullet implementation (see latest post), you will have to run 32bit java on windows for that though.

Example code (modified TestWalkingChar.java).



It creates the main (controllable) otis unit as used in the example and 20 low poly units.

Bullets with effects & wall have been removed to just have the terrain (cam & light) and the models.



Issue can been seen here.



pre type="java"
/


 
 Copyright © 2009-2010 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 mygame;





import com.jme3.animation.AnimChannel;


import com.jme3.animation.AnimControl;


import com.jme3.animation.AnimEventListener;


import com.jme3.animation.LoopMode;


import com.jme3.bullet.BulletAppState;


import com.jme3.app.SimpleApplication;


import com.jme3.bounding.BoundingBox;


import com.jme3.bullet.PhysicsSpace;


import com.jme3.bullet.collision.PhysicsCollisionEvent;


import com.jme3.bullet.collision.PhysicsCollisionListener;


import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;


import com.jme3.bullet.collision.shapes.SphereCollisionShape;


import com.jme3.bullet.control.CharacterControl;


import com.jme3.bullet.control.RigidBodyControl;


import com.jme3.bullet.util.CollisionShapeFactory;


import com.jme3.effect.EmitterSphereShape;


import com.jme3.effect.ParticleEmitter;


import com.jme3.effect.ParticleMesh.Type;


import com.jme3.input.ChaseCamera;


import com.jme3.input.KeyInput;


import com.jme3.input.controls.ActionListener;


import com.jme3.input.controls.KeyTrigger;


import com.jme3.light.DirectionalLight;


import com.jme3.material.Material;


import com.jme3.math.ColorRGBA;


import com.jme3.math.FastMath;


import com.jme3.math.Quaternion;


import com.jme3.math.Vector2f;


import com.jme3.math.Vector3f;


import com.jme3.post.FilterPostProcessor;


import com.jme3.post.filters.BloomFilter;


import com.jme3.renderer.Camera;


import com.jme3.renderer.queue.RenderQueue.ShadowMode;


import com.jme3.scene.Geometry;


import com.jme3.scene.Node;


import com.jme3.scene.Spatial;


import com.jme3.scene.shape.Box;


import com.jme3.scene.shape.Sphere;


import com.jme3.scene.shape.Sphere.TextureMode;


import com.jme3.terrain.geomipmap.TerrainLodControl;


import com.jme3.terrain.geomipmap.TerrainQuad;


import com.jme3.terrain.heightmap.AbstractHeightMap;


import com.jme3.terrain.heightmap.ImageBasedHeightMap;


import com.jme3.texture.Texture;


import com.jme3.texture.Texture.WrapMode;


import com.jme3.util.SkyFactory;


import java.util.ArrayList;


import java.util.List;


import jme3tools.converters.ImageToAwt;





/**


 


 
 @author normenhansen


 /


public class Main extends SimpleApplication implements ActionListener, AnimEventListener {





    private BulletAppState bulletAppState;


    //character


    CharacterControl character;


    Node model;


    //temp vectors


    Vector3f walkDirection = new Vector3f();


    //terrain


    TerrainQuad terrain;


    RigidBodyControl terrainPhysicsNode;


    //Materials


    Material matRock;


    Material matWire;


    Material matBullet;


    //animation


    AnimChannel animationChannel;


    AnimChannel shootingChannel;


    AnimControl animationControl;


    float airTime = 0;


    //camera


    boolean left = false, right = false, up = false, down = false;


    ChaseCamera chaseCam;


    //bullet


    Sphere bullet;


    SphereCollisionShape bulletCollisionShape;


    //explosion


    ParticleEmitter effect;


    //brick wall


    Box brick;


    float bLength = 0.8f;


    float bWidth = 0.4f;


    float bHeight = 0.4f;


    FilterPostProcessor fpp;





    public static void main(String[] args) {


        Main app = new Main();


        app.start();


    }





    @Override


    public void simpleInitApp() {


        bulletAppState = new BulletAppState();


        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);


        stateManager.attach(bulletAppState);


        setupKeys();


        createLight();


        createSky();


        createTerrain();


        createCharacter();


        for (int number = 1; number <= 20; number++) {


            createNewCharacter(number);


        }


        


        setupChaseCamera();


        setupAnimationController();


        setupFilter();


    }





    private void setupFilter() {


        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);


        BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects);


        fpp.addFilter(bloom);


        viewPort.addProcessor(fpp);


    }





    private PhysicsSpace getPhysicsSpace() {


        return bulletAppState.getPhysicsSpace();


    }





    private void setupKeys() {


        inputManager.addMapping(“wireframe”, new KeyTrigger(KeyInput.KEY_T));


        inputManager.addListener(this, “wireframe”);


        inputManager.addMapping(“CharLeft”, new KeyTrigger(KeyInput.KEY_A));


        inputManager.addMapping(“CharRight”, new KeyTrigger(KeyInput.KEY_D));


        inputManager.addMapping(“CharUp”, new KeyTrigger(KeyInput.KEY_W));


        inputManager.addMapping(“CharDown”, new KeyTrigger(KeyInput.KEY_S));


        inputManager.addMapping(“CharSpace”, new KeyTrigger(KeyInput.KEY_RETURN));


        inputManager.addMapping(“CharShoot”, new KeyTrigger(KeyInput.KEY_SPACE));


        inputManager.addListener(this, “CharLeft”);


        inputManager.addListener(this, “CharRight”);


        inputManager.addListener(this, “CharUp”);


        inputManager.addListener(this, “CharDown”);


        inputManager.addListener(this, “CharSpace”);


        inputManager.addListener(this, “CharShoot”);


    }





    private void createLight() {


        Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal();


        DirectionalLight dl = new DirectionalLight();


        dl.setDirection(direction);


        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));


        rootNode.addLight(dl);


    }





    private void createSky() {


        rootNode.attachChild(SkyFactory.createSky(assetManager, “Textures/Sky/Bright/BrightSky.dds”, false));


    }





    private void createTerrain() {


        matRock = new Material(assetManager, “Common/MatDefs/Terrain/TerrainLighting.j3md”);


        matRock.setBoolean(“useTriPlanarMapping”, false);


        matRock.setBoolean(“WardIso”, true);


        matRock.setTexture(“AlphaMap”, assetManager.loadTexture(“Textures/Terrain/splat/alphamap.png”));


        Texture heightMapImage = assetManager.loadTexture(“Textures/Terrain/splat/mountains512.png”);


        Texture grass = assetManager.loadTexture(“Textures/Terrain/splat/grass.jpg”);


        grass.setWrap(WrapMode.Repeat);


        matRock.setTexture(“DiffuseMap”, grass);


        matRock.setFloat(“DiffuseMap_0_scale”, 64);


        Texture dirt = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);


        dirt.setWrap(WrapMode.Repeat);


        matRock.setTexture(“DiffuseMap_1”, dirt);


        matRock.setFloat(“DiffuseMap_1_scale”, 16);


        Texture rock = assetManager.loadTexture(“Textures/Terrain/splat/road.jpg”);


        rock.setWrap(WrapMode.Repeat);


        matRock.setTexture(“DiffuseMap_2”, rock);


        matRock.setFloat(“DiffuseMap_2_scale”, 128);


        Texture normalMap0 = assetManager.loadTexture(“Textures/Terrain/splat/grass_normal.png”);


        normalMap0.setWrap(WrapMode.Repeat);


        Texture normalMap1 = assetManager.loadTexture(“Textures/Terrain/splat/dirt_normal.png”);


        normalMap1.setWrap(WrapMode.Repeat);


        Texture normalMap2 = assetManager.loadTexture(“Textures/Terrain/splat/road_normal.png”);


        normalMap2.setWrap(WrapMode.Repeat);


        matRock.setTexture(“NormalMap”, normalMap0);


        matRock.setTexture(“NormalMap_1”, normalMap2);


        matRock.setTexture(“NormalMap_2”, normalMap2);


        matWire = new Material(assetManager, “Common/MatDefs/Misc/WireColor.j3md”);


        matWire.setColor(“Color”, ColorRGBA.Green);





        AbstractHeightMap heightmap = null;


        try {


            heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f);


            heightmap.load();





        } catch (Exception e) {


            e.printStackTrace();


        }





        terrain = new TerrainQuad(“terrain”, 65, 513, heightmap.getHeightMap());


        List<Camera> cameras = new ArrayList<Camera>();


        cameras.add(getCamera());


        TerrainLodControl control = new TerrainLodControl(terrain, cameras);


        terrain.addControl(control);


        terrain.setMaterial(matRock);


        terrain.setModelBound(new BoundingBox());


        terrain.updateModelBound();


        terrain.setLocalScale(new Vector3f(2, 2, 2));





        terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0);


        terrain.addControl(terrainPhysicsNode);


        rootNode.attachChild(terrain);


        getPhysicsSpace().add(terrainPhysicsNode);


    }





    private void createCharacter() {


        CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 2f);


        character = new CharacterControl(capsule, 0.01f);


        model = (Node) assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);


        model.setLocalScale(0.5f);


        model.addControl(character);


        character.setPhysicsLocation(new Vector3f(-140, 10, -10));


        rootNode.attachChild(model);


        getPhysicsSpace().add(character);


    }


    


    private void createNewCharacter(int i) {


        CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 2f);


        CharacterControl character = new CharacterControl(capsule, 0.01f);


        Sphere s = new Sphere(4, 4, 3);


        Geometry model = new Geometry(“BallBeast”);


        model.setMesh(s);


        Material mat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);


        ColorRGBA alphaGreen = new ColorRGBA(0.2f,0.2f,0.5f,1f);


        mat.setColor(“Color”, alphaGreen);


        model.setMaterial(mat);


        model.addControl(character);


        character.setPhysicsLocation(new Vector3f((-140 +(i
5)), (10+(i)), -10));


        rootNode.attachChild(model);


        getPhysicsSpace().add(character);


    }





    private void setupChaseCamera() {


        flyCam.setEnabled(false);


        chaseCam = new ChaseCamera(cam, model, inputManager);


    }





    private void setupAnimationController() {


        animationControl = model.getControl(AnimControl.class);


        animationControl.addListener(this);


        animationChannel = animationControl.createChannel();


        shootingChannel = animationControl.createChannel();


        shootingChannel.addBone(animationControl.getSkeleton().getBone(“uparm.right”));


        shootingChannel.addBone(animationControl.getSkeleton().getBone(“arm.right”));


        shootingChannel.addBone(animationControl.getSkeleton().getBone(“hand.right”));


    }





    @Override


    public void simpleUpdate(float tpf) {


        Vector3f camDir = cam.getDirection().clone().multLocal(0.2f);


        Vector3f camLeft = cam.getLeft().clone().multLocal(0.2f);


        camDir.y = 0;


        camLeft.y = 0;


        walkDirection.set(0, 0, 0);


        if (left) {


            walkDirection.addLocal(camLeft);


        }


        if (right) {


            walkDirection.addLocal(camLeft.negate());


        }


        if (up) {


            walkDirection.addLocal(camDir);


        }


        if (down) {


            walkDirection.addLocal(camDir.negate());


        }


        if (!character.onGround()) {


            airTime = airTime + tpf;


        } else {


            airTime = 0;


        }


        if (walkDirection.length() == 0) {


            if (!“stand”.equals(animationChannel.getAnimationName())) {


                animationChannel.setAnim(“stand”, 1f);


            }


        } else {


            character.setViewDirection(walkDirection);


            if (airTime > .3f) {


                if (!“stand”.equals(animationChannel.getAnimationName())) {


                    animationChannel.setAnim(“stand”);


                }


            } else if (!“Walk”.equals(animationChannel.getAnimationName())) {


                animationChannel.setAnim(“Walk”, 0.7f);


            }


        }


        character.setWalkDirection(walkDirection);


    }





    public void onAction(String binding, boolean value, float tpf) {


        if (binding.equals(“CharLeft”)) {


            if (value) {


                left = true;


            } else {


                left = false;


            }


        } else if (binding.equals(“CharRight”)) {


            if (value) {


                right = true;


            } else {


                right = false;


            }


        } else if (binding.equals(“CharUp”)) {


            if (value) {


                up = true;


            } else {


                up = false;


            }


        } else if (binding.equals(“CharDown”)) {


            if (value) {


                down = true;


            } else {


                down = false;


            }


        } else if (binding.equals(“CharSpace”)) {


            character.jump();


        } else if (binding.equals(“CharShoot”) && !value) {


            //createInactiveCharacter();


        }


    }





    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {


        if (channel == shootingChannel) {


            channel.setAnim(“stand”);


        }


    }





    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {


    }


}



/pre

Anyone else has the same result with the code? Or rather, anyone that has not :).

Ok, I have a small (yet unwanted) fix for the problem.

If you change the radius in

CapsuleCollisionShape capsule = new CapsuleCollisionShape(1.5f, 1f);


to very small values like
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.1f, 1f);


There is no drop in FPS anymore. However this results in strange behaviour of the characters (bouncing on the gound).



Edit: More unwanted behaviour: Characters fall though the ground on unexpected moments.