Terrain won't receive ray cast from click, but will from middle of camera

Howdy, monkeys! I’m having a problem with click shooting, which I stole the code from this tutorial… I can shoot anything BUT the terrain if I click on it… However, with just a normal raycast from the camera (with the camera’s direction) I can shoot everything (including the terrain)…

Sooo, does anyone know why that is, and if I can have it shoot everything (including terrain)? (don’t worry, I remembered to have it collide with the rootNode both times :smiley: ) Thanks!

I wrote up a test case (combo of TestTerrainAdvanced and HelloPicking). Right click to send a ray from the click, press space to just send a ray from the middle of the camera… Try to shoot both the shootables and the terrain with both methods:

[java]
package test;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bounding.BoundingBox;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
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.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
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;

/**

  • Uses the terrain’s lighting texture with normal maps and lights.

  • @author bowens
    */
    public class TerrainWontClickShootTestCase extends SimpleApplication {

    private TerrainQuad terrain;
    Material matTerrain;
    Material matWire;
    boolean wireframe = false;
    private Geometry mark;
    private float dirtScale = 16;
    private float darkRockScale = 32;
    private float pinkRockScale = 32;
    private float riverRockScale = 80;
    private float grassScale = 32;
    private float brickScale = 128;
    private float roadScale = 200;
    private Node shootables;

    public static void main(String[] args) {
    TerrainWontClickShootTestCase app = new TerrainWontClickShootTestCase();
    app.start();
    }

    @Override
    public void initialize() {
    super.initialize();

    }

    @Override
    public void simpleInitApp() {
    setupKeys();
    initMark();
    shootables = new Node(“Shootables”);
    rootNode.attachChild(shootables);
    shootables.attachChild(makeCube(“a Dragon”, -2f, 0f, 1f));
    shootables.attachChild(makeCube(“a tin can”, 1f, -2f, 0f));
    shootables.attachChild(makeCube(“the Sheriff”, 0f, 1f, -2f));
    shootables.attachChild(makeCube(“the Deputy”, 1f, 0f, -4f));

      // First, we load up our textures and the heightmap texture for the terrain
    
      // TERRAIN TEXTURE material
      matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
      matTerrain.setBoolean("useTriPlanarMapping", false);
      matTerrain.setFloat("Shininess", 0.0f);
    
      // ALPHA map (for splat textures)
      matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png"));
      matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png"));
      // this material also supports 'AlphaMap_2', so you can get up to 12 diffuse textures
      
      // HEIGHTMAP image (for the terrain heightmap)
      TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false);
      Texture heightMapImage = assetManager.loadTexture(hmKey);
      
      // DIRT texture, Diffuse textures 0 to 3 use the first AlphaMap
      Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
      dirt.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap", dirt);
      matTerrain.setFloat("DiffuseMap_0_scale", dirtScale);
      
      // DARK ROCK texture
      Texture darkRock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
      darkRock.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_1", darkRock);
      matTerrain.setFloat("DiffuseMap_1_scale", darkRockScale);
      
      // PINK ROCK texture
      Texture pinkRock = assetManager.loadTexture("Textures/Terrain/Rock/Rock.PNG");
      pinkRock.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_2", pinkRock);
      matTerrain.setFloat("DiffuseMap_2_scale", pinkRockScale);
      
      // RIVER ROCK texture, this texture will use the next alphaMap: AlphaMap_1
      Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
      riverRock.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_3", riverRock);
      matTerrain.setFloat("DiffuseMap_3_scale", riverRockScale);
      
      // GRASS texture
      Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
      grass.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_4", grass);
      matTerrain.setFloat("DiffuseMap_4_scale", grassScale);
    
      // BRICK texture
      Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg");
      brick.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_5", brick);
      matTerrain.setFloat("DiffuseMap_5_scale", brickScale);
      
      // ROAD texture
      Texture road = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
      road.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("DiffuseMap_6", road);
      matTerrain.setFloat("DiffuseMap_6_scale", roadScale);
    
      
      // diffuse textures 0 to 3 use AlphaMap
      // diffuse textures 4 to 7 use AlphaMap_1
      // diffuse textures 8 to 11 use AlphaMap_2
    
      
      // NORMAL MAPS
      Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
      normalMapDirt.setWrap(WrapMode.Repeat);
      Texture normalMapPinkRock = assetManager.loadTexture("Textures/Terrain/Rock/Rock_normal.png");
      normalMapPinkRock.setWrap(WrapMode.Repeat);
      Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
      normalMapGrass.setWrap(WrapMode.Repeat);
      Texture normalMapRoad = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
      normalMapRoad.setWrap(WrapMode.Repeat);
      matTerrain.setTexture("NormalMap", normalMapDirt);
      matTerrain.setTexture("NormalMap_1", normalMapPinkRock);
      matTerrain.setTexture("NormalMap_2", normalMapPinkRock);
      matTerrain.setTexture("NormalMap_4", normalMapGrass);
      matTerrain.setTexture("NormalMap_6", normalMapRoad);
    
      
      // WIREFRAME material (used to debug the terrain, only useful for this test case)
      matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
      matWire.getAdditionalRenderState().setWireframe(true);
      matWire.setColor("Color", ColorRGBA.Green);
      
      createSky();
    
      // CREATE HEIGHTMAP
      AbstractHeightMap heightmap = null;
      try {
          heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f);
          heightmap.load();
          heightmap.smooth(0.9f, 1);
    
      } catch (Exception e) {
          e.printStackTrace();
      }
    
      /*
    
  • Here we create the actual terrain. The tiles will be 65x65, and the total size of the

  • terrain will be 513x513. It uses the heightmap we created to generate the height values.
    /
    /
    *

  • Optimal terrain patch size is 65 (64x64).

  • The total size is up to you. At 1025 it ran fine for me (200+FPS), however at

  • size=2049 it got really slow. But that is a jump from 2 million to 8 million triangles…
    */
    terrain = new TerrainQuad(“terrain”, 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
    TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
    control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
    terrain.addControl(control);
    terrain.setMaterial(matTerrain);
    terrain.setModelBound(new BoundingBox());
    terrain.updateModelBound();
    terrain.setLocalTranslation(0, -100, 0);
    terrain.setLocalScale(1f, 1f, 1f);
    rootNode.attachChild(terrain);

      DirectionalLight light = new DirectionalLight();
      light.setDirection((new Vector3f(-0.1f, -0.1f, -0.1f)).normalize());
      rootNode.addLight(light);
    
      cam.setLocation(new Vector3f(0, 10, -10));
      cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
      flyCam.setMoveSpeed(100);
      flyCam.setDragToRotate(true);
    

    }

    private void setupKeys() {
    inputManager.addMapping(“wireframe”, new KeyTrigger(KeyInput.KEY_T));
    inputManager.addListener(actionListener, “wireframe”);
    inputManager.addMapping(“shoot”, new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addListener(actionListener, “shoot”);
    inputManager.addMapping(“clickShoot”, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
    inputManager.addListener(actionListener, “clickShoot”);
    }
    private ActionListener actionListener = new ActionListener() {

      public void onAction(String name, boolean pressed, float tpf) {
          if (name.equals("wireframe") && !pressed) {
              wireframe = !wireframe;
              if (!wireframe) {
                  terrain.setMaterial(matWire);
              } else {
                  terrain.setMaterial(matTerrain);
              }
          }else if(name.equals("shoot")){
              if(pressed){
                  // 1. Reset results list.
                  CollisionResults results = new CollisionResults();
                  // 2. Aim the ray from cam loc to cam direction.
                  Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                  // 3. Collect intersections between Ray and Shootables in results list.
                  rootNode.collideWith(ray, results);
                  // 4. Print the results
                  System.out.println("----- Collisions? " + results.size() + "-----");
                  for (int i = 0; i < results.size(); i++) {
                    // For each hit, we know distance, impact point, name of geometry.
                    float dist = results.getCollision(i).getDistance();
                    Vector3f pt = results.getCollision(i).getContactPoint();
                    String hit = results.getCollision(i).getGeometry().getName();
                    System.out.println("* Collision #" + i);
                    System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");
                  }
                  // 5. Use the results (we mark the hit object)
                  if (results.size() > 0) {
                    // The closest collision point is what was truly hit:
                    CollisionResult closest = results.getClosestCollision();
                    // Let's interact - we mark the hit with a red dot.
                    mark.setLocalTranslation(closest.getContactPoint());
                    rootNode.attachChild(mark);
                  } else {
                    // No hits? Then remove the red mark.
                    rootNode.detachChild(mark);
                  }
              }
          }else if(name.equals("clickShoot")){
              if(pressed){
                  CollisionResults results = new CollisionResults();
                  // 2. Aim the ray from cam loc to cam direction.
                  Vector2f click2d = inputManager.getCursorPosition();
                  Vector3f click3d = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
                  Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
                  // Aim the ray from the clicked spot forwards.
                  Ray ray = new Ray(click3d, dir);
                  // 3. Collect intersections between Ray and Shootables in results list.
                  rootNode.collideWith(ray, results);
                  // 4. Print the results
                  System.out.println("----- Collisions? " + results.size() + "-----");
                  for (int i = 0; i < results.size(); i++) {
                    // For each hit, we know distance, impact point, name of geometry.
                    float dist = results.getCollision(i).getDistance();
                    Vector3f pt = results.getCollision(i).getContactPoint();
                    String hit = results.getCollision(i).getGeometry().getName();
                    System.out.println("* Collision #" + i);
                    System.out.println("  You shot " + hit + " at " + pt + ", " + dist + " wu away.");
                  }
                  // 5. Use the results (we mark the hit object)
                  if (results.size() > 0) {
                    // The closest collision point is what was truly hit:
                    CollisionResult closest = results.getClosestCollision();
                    // Let's interact - we mark the hit with a red dot.
                    mark.setLocalTranslation(closest.getContactPoint());
                    rootNode.attachChild(mark);
                  } else {
                    // No hits? Then remove the red mark.
                    rootNode.detachChild(mark);
                  }
              }
          }
      }
    

    };

    private void createSky() {
    Texture west = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_west.jpg”);
    Texture east = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_east.jpg”);
    Texture north = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_north.jpg”);
    Texture south = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_south.jpg”);
    Texture up = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_up.jpg”);
    Texture down = assetManager.loadTexture(“Textures/Sky/Lagoon/lagoon_down.jpg”);

      Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
      sky.setLocalTranslation(new Vector3f(1000,1000,1000));
      rootNode.attachChild(sky);
    

    }

    protected void initMark() {
    Sphere sphere = new Sphere(30, 30, 0.2f);
    mark = new Geometry(“BOOM!”, sphere);
    Material mark_mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
    mark_mat.setColor(“Color”, ColorRGBA.Red);
    mark.setMaterial(mark_mat);
    }
    protected Geometry makeCube(String name, float x, float y, float z) {
    Box box = new Box(1, 1, 1);
    Geometry cube = new Geometry(name, box);
    cube.setLocalTranslation(x, y, z);
    Material mat1 = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
    mat1.setColor(“Color”, ColorRGBA.randomColor());
    cube.setMaterial(mat1);
    return cube;
    }
    }
    [/java]

What version of jME are you using?

Make sure your direction ray is pointing in the correct direction for “clickshoot”, you can also try normalizing the ray.

1 Like

As hinted…
In this:
Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
// Aim the ray from the clicked spot forwards.
Ray ray = new Ray(click3d, dir);

dir is not a direction vector as it’s length is probably much bigger than one unit. Direction vectors must be length 1.

1 Like

Dangg, thought it was normalized… So it turns out it that normalizing fixed it; man I feel like such an idiot… Anyways, can somebody update the tutorial then with that vector normalized please?

EDIT: well, I have no idea where I copied my code from, but the code in the tutorial has it normalized O.O really wonder what happened with my ctrl-c and ctrl-v… Thanks guys!