Aligning Bullet with Camera

Hi there,



If anyone could help me with some basic how-to of aligning a capsule object (a bullet in my game) with the orientation of the camera(i.e. my bullets shouldn't be shooting out sideways)? I could just use spheres, but then I wouldn't be learning :slight_smile:

Pictures always explain what I can’t



When you create you capsule, rotate it 90

Thanks for the quick reply's as always CoreDump,



However, I think my question I think goes beyond what I guess the difficulty im having is.



Currently I move the camera around the scene, and throw my player object at the updated coordinates without it being attached to the scene. My bullets are created based on the camera's location, and normalized direction. Obviously this will be an issue if I ever want the player to rotate with where the camera looks, which is the same problem i'm having with the bullets now. I currently have no rotation for the capsule other than to make it non-vertical. If I attached the bullet to another node, when I move the other node, wont the bullet move with it?



Any suggestions to how I should be approaching this better would be appreciated too :slight_smile:



Added code for reference:



Prototype Class


package prototype;

import java.awt.event.MouseEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;

import jmetest.terrain.TestTerrain;
import com.jme.app.BaseGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.MidPointHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;


public class Prototype extends BaseGame {
   private static final Logger logger = Logger.getLogger(Prototype.class
         .getName());
   private TerrainBlock tb;
   protected Timer timer;
   // Our camera object for viewing the scene
   private Camera cam;
   // the root node of the scene graph
   private Node scene;
   //scene graph node  for player
   private Player player;
   //handles FPS input
   private InputHandler input;
   // display attributes for the window. We will keep these values
   // to allow the user to change them
   private int width, height, depth, freq;
   private boolean fullscreen;





   /**
    * Main entry point of the application
    */
   public static void main(String[] args) {
      Prototype app = new Prototype();
      // We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
      app.setConfigShowMode(ConfigShowMode.AlwaysShow, Prototype.class
            .getClassLoader().getResource(
            "jmetest/data/images/FlagRush.png"));
      app.start();
   }

   /**
    * During an update we only look for the escape button and update the timer
    * to get the framerate.
    *
    * @see com.jme.app.BaseGame#update(float)
    */
   protected void update(float interpolation) {
      // update the time to get the framerate
      timer.update();
      interpolation = timer.getTimePerFrame();
      // if escape was pressed, we exit
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
         finished = true;
      }

      float characterMinHeight = tb.getHeight(player
            .getLocalTranslation())+((BoundingBox)player.getWorldBound()).yExtent;
      if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
         player.getLocalTranslation().y = characterMinHeight+5;
      }
      Vector3f normal = null;
      //get the normal of the terrain at our current location. We then apply it to the up vector
      //of the player.
      tb.getSurfaceNormal(player.getLocalTranslation(), normal);
      if(normal != null) {
         player.rotateUpTo(normal);
      }
      /** Check for key/mouse updates. */
      input.update(timer.getTimePerFrame());
      player.setLocalTranslation(cam.getLocation());
      
      scene.updateWorldVectors();
      scene.updateWorldData(interpolation);
      scene.updateGeometricState(interpolation, true);
      scene.updateRenderState();

   }

   /**
    * draws the scene graph
    *
    * @see com.jme.app.BaseGame#render(float)
    */
   protected void render(float interpolation) {
      // Clear the screen
      display.getRenderer().clearBuffers();
      display.getRenderer().draw(scene);

   }

   /**
    * initializes the display and camera.
    *
    * @see com.jme.app.BaseGame#initSystem()
    */
   protected void initSystem() {
      // store the settings information
      width = settings.getWidth();
      height = settings.getHeight();
      depth = settings.getDepth();
      freq = settings.getFrequency();
      fullscreen = settings.isFullscreen();

      try {
         display = DisplaySystem.getDisplaySystem(settings.getRenderer());
         display.createWindow(width, height, depth, freq, fullscreen);

         cam = display.getRenderer().createCamera(width, height);
      } catch (JmeException e) {
         logger.log(Level.SEVERE, "Could not create displaySystem", e);
         System.exit(1);
      }

      // set the background to black
      display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());


      /** Get a high resolution timer for FPS updates. */
      timer = Timer.getTimer();

      display.getRenderer().setCamera(cam);

      KeyBindingManager.getKeyBindingManager().set("exit",
            KeyInput.KEY_ESCAPE);
   }
   private void buildCamera(){      
      // initialize the camera
      cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
            5000);
      Vector3f loc = new Vector3f(0.0f, 0.0f, 0.0f);
      Vector3f left = new Vector3f(-0.5f, 0.0f, 0.5f);
      Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
      Vector3f dir = new Vector3f(-0.5f, 0.0f, -0.5f);
      // Move our camera to a correct place and orientation.
      cam.setFrame(loc, left, up, dir);
      /** Signal that we've changed our camera's location/frustum. */
      cam.update();
      input = new FirstPersonHandler(cam, 50, 1);
      input.addAction( new FireMouse(scene,cam), InputHandler.DEVICE_MOUSE, MouseEvent.BUTTON1, InputHandler.AXIS_NONE, false );
      

   }

   /**
    * initializes the scene
    *
    * @see com.jme.app.BaseGame#initGame()
    */
   protected void initGame() {
      scene = new Node("Scene graph node");
      buildTerrain();
      scene.attachChild(tb);

      buildLighting();
      
      buildCamera();
      
      buildPlayer();
      
      ZBufferState zbuf = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
      zbuf.setEnabled(true);
      scene.setRenderState(zbuf);
      
      // update the scene graph for rendering
      scene.updateGeometricState(0.0f, true);
      scene.updateRenderState();
   }

   private void buildPlayer() {
      player = new Player(input);
      //player.setLocalTranslation(new Vector3f(100,0, 100));
      //scene.attachChild(player);
      player.updateWorldBound();   
      
   }

   /**
    * creates a light for the terrain.
    */
   private void buildLighting() {
      /** Set up a basic, default light. */
      DirectionalLight light = new DirectionalLight();
      light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
      light.setDirection(new Vector3f(1,-1,1));
      light.setEnabled(true);


      /** Attach the light to a lightState and the lightState to rootNode. */
      LightState lightState = display.getRenderer().createLightState();
      lightState.setEnabled(true);
      lightState.attach(light);
      scene.setRenderState(lightState);
      
   }

   /**
    * build the height map and terrain block.
    */
   private void buildTerrain() {
      // Generate a random terrain data
      MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);   // Scale the data
      Vector3f terrainScale = new Vector3f(4, .0575f, 4);
      // create a terrainblock
      tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
            heightMap.getHeightMap(), new Vector3f(0, 0, 0));

      tb.setModelBound(new BoundingBox());
      tb.updateModelBound();

      // generate a terrain texture with 3 textures
      ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
            heightMap);
      pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
            .getResource("data/images/256px-Dirttexture.jpg")), -128, 0, 128);
      pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
            .getResource("data/images/rock.jpg")), 0, 128, 255);
      pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
            .getResource("data/images/ice.jpg")), 128, 255,
            384);
      pt.createTexture(32);

      // assign the texture to the terrain
      TextureState ts = display.getRenderer().createTextureState();
      ts.setEnabled(true);
      Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
            Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true);
      ts.setTexture(t1, 0);

      //load a detail texture and set the combine modes for the two terrain textures.
      Texture t2 = TextureManager.loadTexture(
            TestTerrain.class.getClassLoader().getResource(
                  "data/images/256px-Dirttexture.jpg"),
                  Texture.MinificationFilter.Trilinear,
                  Texture.MagnificationFilter.Bilinear);

      ts.setTexture(t2, 1);
      t2.setWrap(Texture.WrapMode.Repeat);

      t1.setApply(Texture.ApplyMode.Combine);
      t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
      t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
      t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
      t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
      t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

      t2.setApply(Texture.ApplyMode.Combine);
      t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
      t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
      t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
      t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
      t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);

      tb.setRenderState(ts);
      //set the detail parameters.
      tb.setDetailTexture(1, 16);
      tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

      tb.setRenderState(ts);
   }

   /**
    * will be called if the resolution changes
    *
    * @see com.jme.app.BaseGame#reinit()
    */
   protected void reinit() {
      display.recreateWindow(width, height, depth, freq, fullscreen);
   }

   /**
    * close the window and also exit the program.
    */
   protected void quit() {
      super.quit();
      System.exit(0);
   }

   /**
    * clean up the textures.
    *
    * @see com.jme.app.BaseGame#cleanup()
    */
   protected void cleanup() {

   }
}



Bullet Controller

package prototype;

import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Node;

public class BulletController extends Controller{
   /** Direciton of bullet */
   Vector3f direction;
   /** speed of bullet */
   float speed = 50;
   /** Seconds it will last before going away */
   private float lifeTime;
   private Bullet bullet;
   private Node scene;
   
   public BulletController(Bullet bullet, Vector3f direction, Node scene){
      this.bullet = bullet;
      this.direction = direction;
      this.direction.normalizeLocal();
      this.setSpeed(speed);
      this.scene = scene;
      lifeTime = 15f;
   }
   
   
   
   public void update(float time) {
      lifeTime -= time;
      /** If life is gone, remove it */
      if (lifeTime < 0) {
         scene.detachChild(bullet);
         bullet.removeController(this);
         return;
      }
      /** Move bullet */
      Vector3f bulletPos = bullet.getLocalTranslation();
      bulletPos.addLocal(direction.mult(time * speed));
      bullet.setLocalTranslation(bulletPos);
      

   }

}




Bullet Class

package prototype;

import com.jme.bounding.BoundingBox;
import com.jme.input.InputHandler;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Capsule;

public class Bullet extends Node{
   public Bullet(){
      Capsule c = new Capsule("Bullet Capsule",10,10,10,.3f,1);
      Quaternion rotQ = new Quaternion();      
      rotQ.fromAngleAxis((float) (Math.PI/2f), new Vector3f(1,0,0));
      c.setLocalRotation(rotQ);
      c.setLocalTranslation(0,9,0);
      c.setModelBound(new BoundingBox());
        c.updateModelBound();            
      this.attachChild(c);   
   }
}

I adjusted test intersection in jme tests for weapons test to use a cylinder and fixed the orientation of the bullet to point down the the camera direction.



protected void simpleUpdate() {
        
        
        mouseBinding.addCommand("firebullet", MouseButtonBinding.LEFT_BUTTON);
        
        if( mouseBinding.isValidCommand("firebullet", true) && (double)(timer.getTimeInSeconds() - lastFire) > 0.1500000001D)
        {
            lastFire = timer.getTimeInSeconds();
          
            logger.info("BANG");
            /** Create bullet */
             Cylinder bullet = new Cylinder("bullet"+ numBullets++, 2, 16, 0.01f, 2, true);
            System.out.println(numBullets);
            bullet.setModelBound(new BoundingSphere());
            bullet.updateModelBound();
            /** Move bullet to the camera location */
            bullet.setLocalTranslation(new Vector3f(cam.getLocation()));
            bullet.setRenderState(bulletMaterial);
            //BULLET ROTATION
            Quaternion newRot = new Quaternion();
            newRot.lookAt(new Vector3f(cam.getDirection().x,cam.getDirection().y,cam.getDirection().z),  Vector3f.UNIT_Y);  
            bullet.getLocalRotation().set(newRot);
            
            /**
             * Update the new world locaion for the bullet before I add a
             * controller
             */
            bullet.updateGeometricState(0, true);
            /**
             * Add a movement controller to the bullet going in the camera's
             * direction
             */
            
                                    
            bullet.addController(new BulletMover(bullet,  new Vector3f(cam.getDirection().x,cam.getDirection().y,cam.getDirection().z)));
            
            rootNode.attachChild(bullet);
            bullet.updateRenderState();
            /** Signal our sound to play laser during rendering */
            targetSound.setWorldPosition(cam.getLocation());
            targetSound.play();
            
            if(numBullets == 30)
            {
                numBullets =0;
                reload = timer.getTimeInSeconds()+4;
                lastFire = reload;
            }
              
        }
    }



Brilliant :slight_smile: thanks.