Physics Help – Bullet through paper issue [Solved]

Hi all,



Was wondering if anyone could give me a hand. I’m trying to implement a projectile system for a shooter. In the past, I’ve used ray casts, but I’d really like to try to not use them this time around, as I want the projectiles to be affected by physics.



As always, slow moving objects are easy, no problems using RigidBodyControls. However, when I try to increase the projectiles speed to something similar to what you would see in an actual gun, I get the “bullet through paper” effect. I kind of expected this, so I looked around for solutions to implement. I tried increasing the accuracy of the PhysicsSpace. No luck. I also played with the “setCcdMotionThreshold(0f)”, setting it to 0.1f I think and the “setCcdSweptSphereRadius(.5f)”, setting that to 1f to try to register a collision. Still no luck.



Is there anything else I could try? I figure worse case scenario I’ll just have to use ray casts for faster projectiles and PhysicsBodies for slower ones, but I would rather it be consistent.



-Duffy

The CCD should be a solution but it seems to be semi-broken in jbullet. I’d generally rather use rays for projectiles anyway tough. What CCD does is basically just doing a sweep test when the object moves more than “threshold” in one bullet frame.

That’s what I figured. I was hoping I missed something about a custom swept-shape or something, since I’m using a genetic algorithm to determine projectile traits, I was hoping I could just change their parameters. Oh well, ray casts aren’t hard. Thanks normen!

Well you can sweep collision shapes, similar to doing raycasts.

Oh cool. Just looked that up. In the example it’s used in the update loop. Would I want to use that method or use a physics tick listener?



Thanks again.

Both works, physics listener is ofc more in tune with actual changes in the physics space.

OK, I got the sweep test to work using a physicsTickListener. Hopefully last question before I mark this as solved. Can I get the location of where the actual collision occurred? I’m not sure what hitFraction is, is that by any chance an interpolation value between the two Transforms used in the test? The documentation only says:

getHitFraction

public float getHitFraction()
Returns:
the hitFraction

Hit fraction means when on the given path you hit something…

So if you sweep from 0,0,0 to 0,0,4 and the hit fraction is 0.5 you hit something at 0,0,2

Got it working. Thanks a ton!



:smiley:

If anybody will solve the same problem in future, there is my solution based on modified BombControl and TestBrickWall form jme3test.bullet. I know that it is probably very simple, but for some greenhorn like me it could be usefull.

I would also like if anybody can check the code if it is possible to optimize somehow, because I worry that it may be quite slow for large scenes with lot of “shootable” objects, and many active projectiles ( > 1000 )

Description:
Motion of projectile is propagated by normal PhysicsControl, but normal collision are switched off by setCollisionGroup() and setCollideWithGroups(). Instead rayCasting is used aginst a set of object in “shootables” node ( similarly to jme3test.collision.TestMousePick.java ). The ray is cast in each prePhysicsTick() of the projectile, and if there is a collision closer than dt*velocity ( = anywhere between actual position of projectile, and extrapolated position in the next physicsTick ) it will remove PhysicsControl form projectile geometry. It can also call any other action but I didnt put that into this example for simplicity. It also hadnle case when projectile does not hit anything and leave the scene and remove it. It is importaint to modify scene (like remove or translate geometry of projectile) just in “upadate()” method of BombControl otherwise there are errors and crashes. The code takes care of that.

Modified BombControl Class
[java]
package jme3test.bullet;

import com.jme3.asset.AssetManager;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import java.io.IOException;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;

/**
*

  • @author Prokop Hapala modified form normenhansen
    */
    public class BombControlProkop2 extends RigidBodyControl implements PhysicsTickListener {
    private boolean toBeKilled = false;
    private boolean wasHit = false;
    private Vector3f hitLoc = new Vector3f();
    private Spatial target;
    private float dtray;
    Ray ray = new Ray();
    CollisionResults results = new CollisionResults();
    private Vector3f vector = new Vector3f();
    private Vector3f vector2 = new Vector3f();

    public BombControlProkop2(CollisionShape shape, float mass) { super(shape, mass); }
    public BombControlProkop2(AssetManager manager, CollisionShape shape, float mass) { super(shape, mass); }
    public void setPhysicsSpace(PhysicsSpace space) {
    super.setPhysicsSpace(space);
    if (space != null) { space.addTickListener(this); }
    }

    public void rayTest( ){
    if ( (target!=null) ){
    this.getPhysicsLocation(vector);
    this.getLinearVelocity(vector2);
    float posLen2 = vector.lengthSquared();
    float vlen2 = vector2.lengthSquared();
    // remove if faster than 3000 m/s or from center more than 10000 m
    if( (vlen2 > 9000000.0f) || (posLen2 > 100000000.0f ) ){
    System.out.println( " out of Bounds -> kill " );
    kill();
    toBeKilled = true;
    }else{
    float vlen = FastMath.sqrt(vlen2);
    vector2.multLocal( 1.0f/vlen );
    ray.setOrigin(vector);
    ray.setDirection(vector2);
    float maxDist = vlen*dtray;
    target.collideWith(ray, results);
    if (results.size() > 0) {
    CollisionResult closest = results.getClosestCollision();
    float dist = closest.getDistance();
    if ( dist < maxDist ){
    target=null;
    hitLoc.set(closest.getContactPoint());
    System.out.println( " Hit “+closest.getGeometry().getName()+” “+dist+” “+hitLoc );
    wasHit = true;
    kill();
    } else {
    //System.out.println( " Hit to far “+dist+” > “+maxDist );
    }
    } else {
    //System.out.println(” rayTest : no Results”);
    }
    }
    }else{
    System.out.println( " no target -> kill " );
    kill();
    toBeKilled = true;
    }
    };

    public void prePhysicsTick(PhysicsSpace space, float f) {
    //System.out.println( " prePhysicsTick " );
    rayTest( );
    }

    public void physicsTick(PhysicsSpace space, float f) {
    //System.out.println( " physicsTick " );
    }
    @Override
    public void update(float tpf) {
    super.update(tpf);
    if(toBeKilled){ spatial.removeFromParent(); }
    if(wasHit) { spatial.setLocalTranslation(hitLoc); }
    }

    public void kill(){
    space.removeTickListener(this);
    space.remove(this);
    }

    public void setTarget( Spatial target) {
    this.target = target;
    dtray = 1.0f/60.0f; // this should be modified if you change physics accuracy
    }

    @Override
    public void read(JmeImporter im) throws IOException {
    throw new UnsupportedOperationException(“Reading not supported.”);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
    throw new UnsupportedOperationException(“Saving not supported.”);
    }
    }

[/java]

Modified test case based on TestBrickWall

[java]

package jme3test.bullet;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.scene.Mesh;
import com.jme3.shadow.BasicShadowRenderer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.scene.Node;
import com.jme3.bullet.collision.PhysicsCollisionObject;

/**
*

  • @author Prokop Hapala modified form double1984
    */
    public class TestBrickWallProkop extends SimpleApplication {
    Material mat;
    Material mat2;
    Material mat3;
    BasicShadowRenderer bsr;
    private static Mesh bullet;
    private static SphereCollisionShape bulletCollisionShape;

    private BulletAppState bulletAppState;
    Node shootables;

    public static void main(String args[]) {
    //Logger.getLogger("").setLevel(Level.OFF);
    TestBrickWallProkop f = new TestBrickWallProkop();
    f.start();
    }

    @Override
    public void simpleInitApp() {
    bulletAppState = new BulletAppState();
    bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
    stateManager.attach(bulletAppState);
    shootables = new Node(“Shootables”);
    rootNode.attachChild(shootables);
    initObjects();
    initMaterial();
    initWall();
    initFloor();
    initCrossHairs();
    this.cam.setLocation(new Vector3f(0, 6f, 6f));
    cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
    cam.setFrustumFar(1000.0f);
    flyCam.setMoveSpeed(50.0f);
    rootNode.setShadowMode(ShadowMode.Off);
    bsr = new BasicShadowRenderer(assetManager, 256);
    bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
    viewPort.addProcessor(bsr);
    registerInputs();
    }

    public void initObjects(){
    bullet = new Box(Vector3f.ZERO, 0.4f, 0.4f, 0.4f);
    bulletCollisionShape = new SphereCollisionShape(0.4f);
    }

    private PhysicsSpace getPhysicsSpace() {
    return bulletAppState.getPhysicsSpace();
    }

    public void registerInputs(){
    inputManager.addMapping(“shoot”, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(analogListener, “shoot”);
    inputManager.addMapping(“gc”, new KeyTrigger(KeyInput.KEY_X));
    inputManager.addListener(actionListener, “gc”);
    }

    private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
    if (name.equals(“shoot”) && !keyPressed) { Shoot(); }
    if (name.equals(“gc”) && !keyPressed) {
    System.gc();
    }
    }
    };

    private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf){
    if (name.equals(“shoot”) ) { Shoot(); return; }
    }
    };

    public void Shoot(){
    Geometry bulletg = new Geometry(“bullet”, bullet);
    bulletg.setMaterial(mat2);
    bulletg.setShadowMode(ShadowMode.Off);
    bulletg.setLocalTranslation(cam.getLocation());
    SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.01f);
    BombControlProkop2 bulletNode = new BombControlProkop2(assetManager, bulletCollisionShape, 1);
    bulletNode.setTarget(shootables);
    bulletNode.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_NONE);
    bulletNode.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_16);
    bulletNode.setLinearVelocity(cam.getDirection().mult(300));
    bulletg.addControl(bulletNode);
    rootNode.attachChild(bulletg);
    getPhysicsSpace().add(bulletNode);
    };

    public void initWall() {
    float bLength = 0.48f;
    float bWidth = 0.24f;
    float bHeight = 0.12f;
    Box brick = new Box(Vector3f.ZERO, bLength, bHeight, bWidth);
    brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
    float startpt = bLength / 4;
    float height = 0;
    for (int j = 0; j < 5; j++) {
    for (int i = 0; i < 4; i++) {
    Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, 0);
    addBrick(vt, brick);
    }
    startpt = -startpt;
    height += 2 * bHeight;
    }
    }

    public void initFloor() {
    Box floorBox = new Box(Vector3f.ZERO, 1000f, 0.1f, 500f);
    floorBox.scaleTextureCoordinates(new Vector2f(3, 6));

     Geometry floor = new Geometry("floor", floorBox);
     floor.setMaterial(mat3);
     floor.setShadowMode(ShadowMode.Receive);
     floor.setLocalTranslation(0, -0.1f, 0);
     floor.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)), 0));
     //this.rootNode.attachChild(floor);
     this.shootables.attachChild(floor);
     this.getPhysicsSpace().add(floor);
    

    }

    public void initMaterial() {
    mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
    TextureKey key = new TextureKey(“Textures/Terrain/BrickWall/BrickWall.jpg”);
    key.setGenerateMips(true);
    Texture tex = assetManager.loadTexture(key);
    mat.setTexture(“ColorMap”, tex);

     mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
     key2.setGenerateMips(true);
     Texture tex2 = assetManager.loadTexture(key2);
     mat2.setTexture("ColorMap", tex2);
    
     mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
     key3.setGenerateMips(true);
     Texture tex3 = assetManager.loadTexture(key3);
     tex3.setWrap(WrapMode.Repeat);
     mat3.setTexture("ColorMap", tex3);
    

    }

    public void addBrick(Vector3f ori, Box brick) {
    Geometry reBoxg = new Geometry(“brick”, brick);
    reBoxg.setMaterial(mat);
    reBoxg.setLocalTranslation(ori);
    //for geometry with sphere mesh the physics system automatically uses a sphere collision shape
    reBoxg.addControl(new RigidBodyControl(1.5f));
    reBoxg.setShadowMode(ShadowMode.CastAndReceive);
    reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f);
    //this.rootNode.attachChild(reBoxg);
    this.shootables.attachChild(reBoxg);
    this.getPhysicsSpace().add(reBoxg);
    }

    protected void initCrossHairs() {
    guiFont = assetManager.loadFont(“Interface/Fonts/Default.fnt”);
    BitmapText ch = new BitmapText(guiFont, false);
    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
    ch.setText("+"); // crosshairs
    ch.setLocalTranslation( // center
    settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
    settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiNode.attachChild(ch);
    }
    }

[/java]