BulletHoleManager : a small way (not perfect) to deal with bullet hole

Hello everybody.

I was doing some stuff for a game (more something enjoy coding than a real game) and i made a class that can be usefull and that is not hard to understand (not too much at least).

I didn’t found anything like that and i read a lot of people asking for something similar.

So, here is my simple class to handle bullet holes.

[java]
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.collision.CollisionResults;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Quad;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**

  • A simple class to handle bullet holes in a simple way. It is likely not the
  • best way to do it, but it is simple and not hard to put into your application.
  • @author Bubuche
    */
    public class BulletHoleManager extends AbstractAppState
    {

private int maxTime;
private int maxHole;
private float distanceToAvoidZFight;

private Application application;
private List<BulletHole> holes;
private Vector3f temp;
private Ray ray;
private CollisionResults results;

public BulletHoleManager(Application application)
{
holes = new ArrayList<>();
this.application = application;
temp = new Vector3f();

maxTime = -1;
maxHole = -1;
// 1cm. In a "normal" world, surfaces separated with 1cm shouldn't z-fight
distanceToAvoidZFight = 0.01f;

ray = new Ray();
results = new CollisionResults();

}

public void setMaxTime(int ms)
{
this.maxTime = ms;
}

public void setNbMax(int nb)
{
this.maxHole = nb;
}

public void setDistanceToAvoidZFight(float distance)
{
this.distanceToAvoidZFight = distance;
}

public void addBulletHole(
Node node,
Material material,
Vector3f place,
Vector3f normal,
float width,
float height)
{
long time;
time = application.getTimer().getTime();
BulletHole bulletHole = new BulletHole(time, material, width, height);
temp.set(normal);
temp.normalizeLocal();

temp.multLocal(distanceToAvoidZFight);
temp.addLocal(place);
node.worldToLocal(temp, temp);

bulletHole.geom.setLocalTranslation(temp);
bulletHole.geom.setQueueBucket(Bucket.Translucent);

node.attachChild(bulletHole.geom);
temp.set(normal);
temp.addLocal(place);
bulletHole.geom.lookAt(temp, Vector3f.UNIT_Y);

testCollisionWithOtherBulletsHoles(
        bulletHole,
        place,
        normal);
holes.add(bulletHole);

}

@Override
public void update(float tpf)
{
removeTooOldHoles();
removeTooManyHoles();
}

private void removeTooOldHoles()
{
if (maxTime < 0)
{
return;
}

long time = application.getTimer().getTime();

Iterator&lt;BulletHole&gt; it = holes.iterator();
while (it.hasNext())
{
  BulletHole bh = it.next();
  if (bh.time &gt;= time + maxTime)
  {
    removeHole(bh);
    it.remove();
  }
  else
  {
    break;
  }
}

}

private void removeTooManyHoles()
{
if (maxHole < 0)
{
return;
}

while (holes.size() &gt; maxHole)
{
  removeHole(holes.remove(0));
}

}

private void removeHole(BulletHole bh)
{
bh.geom.removeFromParent();
}

private void testCollisionWithOtherBulletsHoles(
BulletHole bulletHole,
Vector3f point,
Vector3f normal)
{
Vector3f working = null;
Vector3f vert = new Vector3f();

Transform t = bulletHole.geom.getWorldTransform();

FloatBuffer buf = bulletHole.geom.getMesh().getFloatBuffer(VertexBuffer.Type.Position);
buf.rewind();

temp.set(normal);
temp.negateLocal();

ray.setDirection(normal);
//"3*" because only 2 could lead to miss some case because of approximations
ray.setLimit(1 + (3 * distanceToAvoidZFight));

/* for each corner, we check if this corner insn't inside an other
 * bullethole.
 * To get the origine of the ray, we take the (x, y, z) of the vertex from  
 * the position buffer, then we transform it's position to the world's position
 */
for (int i = 0; i &lt; 4; i++)
{
  float x, y, z;
  x = buf.get();
  y = buf.get();
  z = buf.get();

  vert.set(x, y, z);
  working = t.transformVector(vert, working);

  Vector3f origin = ray.getOrigin();
  origin.set(working);
  origin.addLocal(temp);
  ray.setOrigin(origin);

  /* We prepared the raycast. Now, we try to intersect it with all others
   * bullethole */
  for (Iterator&lt;BulletHole&gt; it = holes.iterator(); it.hasNext();)
  {
    BulletHole other = it.next();
    other.geom.collideWith(ray, results);
    if (results.size() != 0)
    {
      removeHole(other);
      it.remove();
    }
    results.clear();
  }
}

/*FIXME*/
/* NOT SURE WE HAVE TO FLIP ! CAN BE A BUG */
buf.flip();

}

private class BulletHole
{

private long time;
private Geometry geom;

public BulletHole(long time, Material material, float width, float height)
{
  Quad quad = new Quad(width, height);
  geom = new Geometry("bullet hole", quad);
  geom.setMaterial(material);
}

}
}
[/java]

By default, it enables 10 bullet hole at a time, with no life time limit. You can set a limit of time (so bullet holes will disappear by themself), by setting any positive value. You can disable the “10 bullet hole at a time” value by setting a negative value to it.

It’s NOT a perfect class for this job at all, as i think that a shader should do that. If you put a bullet hole in a corner, you’ll have an artifact with a partially floating bullet hole. I can’t adapt the form of the decal to fit the object behind it for 2 reason : this object can change it’s shape AND i have no information about the volume of your bullet hole (it’s juste an image, i don’t know if you put a hole on this image or not).

Be carefull : each bullet hole is a quad, meaning that they will be into the CollisionResults if you perform a raycast test (like i do to detect when the bullet hit :wink: )

If the object is moving (or even rolling) the bullet hole will follow it (and roll with it).

If 2 bullet holes are overlapping, the oldest will be removed (or it should be, i know that in some very specific case i’ll remove too much bullet holes - namely if you shoot on a ball, then put the resulting bullet hole near a wall then shoot on the wall, the bullet hole on the ball will be removed).

Anyway, to enable it, just add it with

getStateManager().attach(new BulletHoleManager(this));

To add a bullet hole, “just” do :

[java]
application.getStateManager().getState(BulletHoleManager.class).addBulletHole(
hitNode, //likely result.getGeometry().getParent()
bulletHoleMaterial,
result.getContactPoint(),
result.getContactNormal(),
widthOfTheBulletHole,
heightOfTheBulletHole);
[/java]

where result is a CollisionResult and the width and height are floats.

If you want to use it or even to put it into the core of jme (i don’t think it’s a good idea, however :wink: ) feel free to do it :).

4 Likes

Hello, I am now doing a 3D operation in the development of the crane, crane jib requirements can be extended and up and down rotation, crane turntable can also rotate. Are now required to simulate the real situation requiring the collision stopped, but made by boundingBox and jme3 BetterCharacterControl itself can not do. I would like to ask how to do the collision detection cable can be used?

You should ask this in its own thread or as a response to a thread that is related… instead of resurrecting a 3 year old thread that is a completely separate topic.

Thank you !
I’m just trying to figure out how to solve my problem.I have created a new topic now.