Picking

From JPCT
Revision as of 15:40, 28 September 2016 by Admin (Talk | contribs)

Jump to: navigation, search

Picking

Picking in jPCT can be implemented in two ways. One of these ways is usually a bit faster but doesn't work for compiled objects, while the other one works for all objects but might be a bit slower.


The recommended way

This works with all renderers and all objects, but depending on the scene it might by a bit slower. It's the only one that is available in jPCT-AE. Unlike the latter approach, this is actually a kind of collision detection, which is why it triggers collision events too. Just like with the approach below, you have to make your objects pickable. However, because it's a collision detection, this works different. Instead of using setSelectable(...), you have to use setCollisionMode(...). For example:

obj.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);

Like above, you need your 2D picking coordinates. With them, you need a direction vector in world space. This is simple:

SimpleVector dir=Interact2D.reproject2D3DWS(camera, frameBuffer, x, y).normalize();

Armed with this vector, you can now go to World and do

Object[] res=world.calcMinDistanceAndObject3D(camera.getPosition(), dir, 10000 /*or whatever*/);

The result is an Object[]-array with the Float-distance to the picked object in the first slot and the picked Object3D in the second. If nothing has been hit, the result will be [COLLISION_NONE, null].

An example

Here an example that uses the software renderer to make an object follow the mouse:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JFrame;

import com.threed.jpct.*;
import com.threed.jpct.util.Light;


public class MouseFollowDemo
  extends JFrame
  implements MouseMotionListener, MouseListener
{

  private static final long serialVersionUID = 1L;
  private Graphics g = null;
  private FrameBuffer fb = null;
  private World world = null;
  private Object3D plane = null;
  private Object3D ramp = null;
  private Object3D player = null;
  private Object3D cube2 = null;
  private Object3D sphere = null;

  private int mouseX = 320;
  private int mouseY = 240;


  public MouseFollowDemo()
  {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
    setSize(640, 480);
    setResizable(false);
    setLocationRelativeTo(null);
    setVisible(true);
    addMouseMotionListener(this);
    addMouseListener(this);
    g = getGraphics();
  }


  @Override
  public void mouseMoved(MouseEvent m)
  {
    mouseX = m.getX();
    mouseY = m.getY();
  }


  @Override
  public void mouseDragged(MouseEvent m)
  {
  //
  }


  @Override
  public void mouseClicked(MouseEvent e)
  {
  //
  }


  @Override
  public void mouseEntered(MouseEvent e)
  {
  //
  }


  @Override
  public void mouseExited(MouseEvent e)
  {
  //
  }


  @Override
  public void mousePressed(MouseEvent e)
  {
  //
  }


  @Override
  public void mouseReleased(MouseEvent e)
  {
  //
  }


  private void initStuff()
  {
    fb = new FrameBuffer(640, 480, FrameBuffer.SAMPLINGMODE_NORMAL);
    world = new World();
    fb.enableRenderer(IRenderer.RENDERER_SOFTWARE);

    ramp = Primitives.getCube(20);
    ramp.setAdditionalColor(Color.RED);
    plane = Primitives.getPlane(20, 10);
    plane.setAdditionalColor(Color.GREEN);
    sphere = Primitives.getSphere(30);
    sphere.setAdditionalColor(Color.CYAN);
    sphere.translate(-50, 10, 50);
    cube2 = Primitives.getCube(20);
    cube2.setAdditionalColor(Color.ORANGE);
    cube2.translate(60, -20, 60);

    plane.rotateX((float) Math.PI / 2f);
    ramp.rotateX((float) Math.PI / 2f);

    player = Primitives.getCone(3);
    player.rotateX((float) Math.PI / 2f);
    player.rotateMesh();
    player.clearRotation();

    plane.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
    ramp.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
    sphere.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
    cube2.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);

    cube2.setBillboarding(true);

    world.addObject(plane);
    world.addObject(ramp);
    world.addObject(sphere);
    world.addObject(cube2);
    world.addObject(player);

    player.translate(-50, -10, -50);

    Light light = new Light(world);
    light.setPosition(new SimpleVector(0, -80, 0));
    light.setIntensity(40, 25, 22);

    world.setAmbientLight(10, 10, 10);
    world.buildAllObjects();
  }


  private void relocate()
  {
    SimpleVector pos = getWorldPosition();
    if (pos != null)
    {
      player.clearTranslation();
      player.translate(pos);
    }
  }


  private SimpleVector getWorldPosition()
  {
    SimpleVector pos = null;
    SimpleVector ray = Interact2D.reproject2D3DWS(world.getCamera(), fb, mouseX, mouseY);
    if (ray != null)
    {
      SimpleVector norm = ray.normalize(); // Just to be sure...

      float f = world.calcMinDistance(world.getCamera().getPosition(), norm, 1000);
      if (f != Object3D.COLLISION_NONE)
      {
        SimpleVector offset = new SimpleVector(norm);
        norm.scalarMul(f);
        norm = norm.calcSub(offset);
        pos = new SimpleVector(norm);
        pos.add(world.getCamera().getPosition());
      }
    }
    return pos;
  }


  private void doIt()
    throws Exception
  {
    Camera cam = world.getCamera();
    cam.moveCamera(Camera.CAMERA_MOVEOUT, 100);
    cam.moveCamera(Camera.CAMERA_MOVEUP, 160);
    cam.lookAt(plane.getTransformedCenter());

    while (true)
    {
      relocate();
      fb.clear();
      world.renderScene(fb);
      world.draw(fb);
      fb.update();
      fb.display(g);
      Thread.sleep(10);
    }
  }


  public static void main(String[] args)
    throws Exception
  {
    MouseFollowDemo cd = new MouseFollowDemo();
    cd.initStuff();
    cd.doIt();
  }
}


The old fashioned way

As said, this doesn't work on compiled objects. If you are using the software renderer only or the hardware renderer in hybrid mode (i.e. without compiled objects), it's save to use though. To use this way, you have to make the objects in questions "selectable". You can do this be calling

obj.setSelectable(Object3D.MOUSE_SELECTABLE);

on your objects. Then, you render the scene and get your picking coordinates from your input device in screen space. Most likely mouse coordinates or similar. Then you do this:

SimpleVector dir=Interact2D.reproject2D3D(camera, frameBuffer, x, y).normalize();
int[] res=Interact2D.pickPolygon(world.getVisibilityList(), dir);

In res, you'll find the object- and the polygon number (in that order), if the picking actually picked something. If not, res is null. Your picked Object3D is now

Object3D picked=world.getObject(res[0]);

Please note that there are two variants of the pickPolygon-methods. The simple one (see above) makes unselectable objects act as a block to the picking ray, i.e. even if an object isn't selectable, it will still block the ray so that no object behind that object can be picked.