Skip to main content

Looking around with the mouse

4 replies [Last post]
richteabiscuit
Offline
Joined: 2012-03-29
Points: 0

I'm trying to create a user interphase in java3D in which I move with the keyboard and look around with the mouse, i.e. like a first person shooter. I've got the keyboard behaviour to work quite well but i've been looking without success for an example code for a custom behaviour extended from mouseBehaviour (which is I believe what I need to extend to create this), can anyone point me towards either an example code for looking around with the mouse or failing that any custom code extending mouseBehaviour.

On a related point, simple universe has (as I understand it) only one transform group above the viewing platform, will I need to abandon simple universe to get simultaneous translation and rotatation of the viewing platform?

(I have searched these forums and found several topics on this subject but they all contained dead links (understandable given their age))

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
richteabiscuit
Offline
Joined: 2012-03-29
Points: 0

#delete

interactivemesh
Offline
Joined: 2006-06-07
Points: 0

"look around with the mouse"
- com.sun.j3d.utils.behaviors.vp.OrbitBehavior
- OrbitBehaviorInterim : SimpleUniverse Navigation 2, http://www.interactivemesh.org/testspace/orbitbehavior.html

"simultaneous translation and rotatation of the viewing platform"
- SimpleUniverse(Canvas3D canvas, int numTransforms) creates the ViewingPlatform with the specified number of TransformGroups
- simpleUniverse.getViewingPlatform().getMultiTransformGroup()

August

richteabiscuit
Offline
Joined: 2012-03-29
Points: 0

This isn't exactly what I wanted, since as I understand it orbit behavior changes the position and angle of the viewing platform so you are always looking at the same point. Like walking around a piece of art. This isn't what I want. I want it to be as if you're moving your head('s angle) with the mouse. However, this has the source code so will be useful to see how a mouse driven behaviour is put together.

Thanks for the multiple transform grouped simple universe!

richteabiscuit
Offline
Joined: 2012-03-29
Points: 0

In the end i abandoned behaviours (although i'm sure it would be possible and probably better to do it that way) and instead used a technique i'm more familiar with, KeyListener and MouseMotionListener. I created a simple universe with 3 transform groups above the viewing platform, one for rotation up and down, one for rotation left and right and one for translation. The code for this is below for anyone else searching for the same thing.

In this code the mouse is used to look. The 'a' button is used to move in the direction of negative x. No other keyboard buttons are included. Its just to give the idea. If this was for a real application the mouse should be made invisible and the keys should move left right forward and back relative to the camera position

In the scene there is a color cube to give a static reference point and several balls moving about (mostly because they make the scene interesting).

WARNING! The mouse is forced to stay in the centre of the screen, TO RELEASE THE MOUSE PRESS ESCAPE

/**
*
* @author Richard Tingle
*/


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.awt.Robot;
import com.sun.j3d.utils.geometry.ColorCube;


public class Main extends JFrame implements KeyListener, MouseMotionListener {


    //used for reseting the mouse to the centre
    private Robot robot = null;
   
    // The center of the component (to reset the mouse to)
    private Point center;
   
    // robot doesn't centre perfectly (why!!), this is what it actually centres to, is set on first mouse centering )
    private Point trueCenter;
    private int trueCenterInitialisedState=-1; //-1=uninitialised, 0=about to be initialised, 1=initialised
   
    private boolean mouseSlaved; //used to slave and release mouse
   
    public static final int FRAMEHEIGHT=500;
    public static final int FRAMEWIDTH=600;
   
   
    private static final float timeslice=0.01f;//seconds
   
    //box boundarys
    public static final float XMIN=-5f; //left
    public static final float  XMAX=5f; //right
    public static final float  YMIN=-5f; //down
    public static final float YMAX=5f; //up
    public static final float ZMIN=-25f; //away from screen
    public static final float ZMAX=-20f;   //towards the screen
    public static final float MAXSPEED=1f; //the maximum speed balls are created with
   
    private int noOfBalls=50;
    private Ball balls[]=new Ball[noOfBalls];

    private TransformGroup viewingTGPos;
    private TransformGroup viewingTGRotX;
    private TransformGroup viewingTGRotY;
   
   
    private double curX;
    private double currentRotationX;
    private double currentRotationY;
   
    private static final double RADIANSPERPIXEL=1.0/1000.0; // .0 needed or else is calculated with integer maths)
   
    public Main() {

       this.setSize(FRAMEWIDTH, FRAMEHEIGHT);
       setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      
      
       try {
            robot = new Robot();
       } catch( Exception e ) {
           System.out.println("Robot unavailable, bad!");
       }
       center=new Point(this.getBounds().width/2,this.getBounds().height/2);
       mouseSlaved=true; //pressing the escape key releases the mouse


      
       setLayout(new BorderLayout());
       GraphicsConfiguration config =
          SimpleUniverse.getPreferredConfiguration();
       Canvas3D c = new Canvas3D(config);
       add("Center", c);
       c.addKeyListener(this);
       c.addMouseMotionListener(this);


       // Create a simple scene and attach it to the virtual universe

       BranchGroup scene = createSceneGraph();
       SimpleUniverse su = new SimpleUniverse(c,3); //3 TransformGroups above viewing platform, one for translation, one for rotation in x, one for rotation in y
       //su.getViewingPlatform().setNominalViewingTransform();
       su.addBranchGraph(scene);
       UpdatingThread updatingThread=new UpdatingThread(balls,25);
      
       //order of viewing platforms is important, as rotations are around the co-ordinate (0,0), not around the centre of the viewing platform);
       viewingTGPos=su.getViewingPlatform().getMultiTransformGroup().getTransformGroup(0);
       viewingTGRotY=su.getViewingPlatform().getMultiTransformGroup().getTransformGroup(1);
       viewingTGRotX=su.getViewingPlatform().getMultiTransformGroup().getTransformGroup(2);
   
       this.setVisible(true); //MUST come after all the java3D stuff, or else screen will be grey
   
    }

   
    public BranchGroup createSceneGraph() {

       // Create the root of the branch graph

       BranchGroup objRoot = new BranchGroup();
      
       //add a color cube as a static reference point
       TransformGroup colCubetransformGr=new TransformGroup();
       Transform3D colCubetransform =new Transform3D();
       colCubetransform.setTranslation(new Vector3d(0,0,-10));
       colCubetransformGr.setTransform(colCubetransform);
       colCubetransformGr.addChild(new ColorCube());
      
       objRoot.addChild(colCubetransformGr);
      
       //add some moving balls for interest (updating thread adjusts each of these balls transform group, moving them)
       for(int i=0;i<noOfBalls;i++){
           balls[i]=new Ball(0.5f*(XMIN+XMAX),0.5f*(YMIN+YMAX),0.5f*(YMIN+YMAX),MAXSPEED,Math.abs(XMIN-XMAX),0.5f);

           objRoot.addChild(balls[i].getTransformGroup());
       }
      
      
       //add some lights to light the balls (color cube 'lights' itself)
       BoundingSphere bounds =
          new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000.0);
       Color3f light1Color = new Color3f(1.0f, 0.0f, 0.2f);
       Vector3f light1Direction = new Vector3f(4.0f, -7.0f, -12.0f);
       DirectionalLight light1
          = new DirectionalLight(light1Color, light1Direction);
       light1.setInfluencingBounds(bounds);
       objRoot.addChild(light1);
      
       // Set up the ambient light
       Color3f ambientColor = new Color3f(1.0f, 1.0f, 1.0f);
       AmbientLight ambientLightNode = new AmbientLight(ambientColor);
       ambientLightNode.setInfluencingBounds(bounds);
       objRoot.addChild(ambientLightNode);

       return objRoot;

    }

    @Override
    public void mouseMoved(MouseEvent e){
        double xRel;
        double yRel;
       
        //first entry, mouse moved from starting pos. to anywhere (anywhere returned)
        //second entry mouse moved by robot to centre (centre returned, RECORDED AS TRUE CENTER)
        //third entry onwards normal operation
        if (trueCenter==null){
            xRel=0;
            yRel=0;
            if (trueCenterInitialisedState==-1){
                trueCenterInitialisedState=0;
                centerMouse();
            }else{
                trueCenterInitialisedState=1;
                trueCenter=new Point(e.getX(),e.getY());
            }
           
        }else{
            if  (mouseSlaved==true){
                xRel=e.getX()-trueCenter.x;
                yRel=e.getY()-trueCenter.y;      
                centerMouse();
            }else{ //if mouse is unslaved then don't move the display
                xRel=0;
                yRel=0;   
            }
        }
       
        //yRel and xRel refer to SCREEN x and y, currentRotationX/Y relates to WORLD x/y
        currentRotationX=currentRotationX+yRel* RADIANSPERPIXEL;
        currentRotationY=currentRotationY+xRel* RADIANSPERPIXEL;

        //upDownControl
       
        Transform3D rotationX=new Transform3D();
        rotationX.rotX(currentRotationX);
     
        viewingTGRotX.setTransform(rotationX);
       
        //leftright Control
        Transform3D rotationY=new Transform3D();

        rotationY.rotY(currentRotationY);
       
        viewingTGRotY.setTransform(rotationY);
       
       
       
    }
   
    @Override
    public void mouseDragged(MouseEvent e){
       
       
       
    }
   
    @Override
    public void keyPressed(KeyEvent e) {
       
        char pressedChar=e.getKeyChar();
        if(e.getKeyChar()==KeyEvent.VK_ESCAPE) { //escape key
            mouseSlaved=false;
        }
       
       
       
        if(pressedChar=='a') { //'a' key
            System.out.println(""+curX);
            Transform3D existingTransform= new Transform3D();
            viewingTGPos.getTransform(existingTransform);
            curX=curX+0.5;
            existingTransform.setTranslation(new Vector3d(-curX,0,0));
            viewingTGPos.setTransform(existingTransform);
        }
       
       
       //Invoked when a key has been pressed.

    }

    public void keyReleased(KeyEvent e){

       // Invoked when a key has been released.

    }

    public void keyTyped(KeyEvent e){

       //Invoked when a key has been typed.

    }

    public void actionPerformed(ActionEvent e ) {



    }
   
    private void centerMouse() {
    if( robot != null && this.isShowing() ) {
    // Because the convertPointToScreen method
    // changes the object, make a copy!
      Point copy = new Point( center.x, center.y );
      SwingUtilities.convertPointToScreen( copy, this );
      robot.mouseMove( copy.x, copy.y );
    }
  }
   
   

    public static void main(String[] args) {

       System.out.println("Program Started");
       Main bb = new Main();
       //bb.addKeyListener(bb);
       //MainFrame mf = new MainFrame(bb, 1000, 600);  

    }

}

Ball class, controlling the behaviour of the floating balls

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.geometry.Sphere;
import java.util.Random;


public class Ball {
   
    private final static float COLLIDECOOLDOWN=0.1f; //to prevent double collisions a collision can only occure at most every 0.1 seconds
   
    //positions
    private float x;
    private float y;
    private float z;
    //velocitys
    private float xV;
    private float yV;
    private float zV;
    private float diameter;
    TransformGroup objTrans;
    float mass=1; //default mass, mass is needed for collision formulas
    float timeSinceCollide=1000; //collidable is used to prevent double collisions on a single move cycle
   
    Ball(float x, float y, float z, float xV, float yV, float zV, float diameter){
        this.x=x;
        this.y=y;
        this.z=z;
        this.xV=xV;
        this.yV=yV;
        this.zV=zV;
        this.diameter=diameter;
       
        objTrans = new TransformGroup();
        objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        Sphere sphere = new Sphere(diameter);
        Transform3D pos1 = new Transform3D();
        pos1.setTranslation(new Vector3f(0.0f,0.0f,0.0f));
        objTrans.setTransform(pos1);
        objTrans.addChild(sphere);
    }
   
    Ball(float x, float y, float z,float maxSpeed, float radiusOfCreation, float diameter ){
        Random rnd=new Random();
        this.x=x+rnd.nextFloat()*2.0f*radiusOfCreation-radiusOfCreation;
        this.y=y+rnd.nextFloat()*2.0f*radiusOfCreation-radiusOfCreation;
        this.z=z+rnd.nextFloat()*2.0f*radiusOfCreation-radiusOfCreation;
        this.xV=rnd.nextFloat()*2.0f*maxSpeed-maxSpeed;
        this.yV=rnd.nextFloat()*2.0f*maxSpeed-maxSpeed;
        this.zV=rnd.nextFloat()*2.0f*maxSpeed-maxSpeed;
        this.diameter=diameter;
       
        objTrans = new TransformGroup();
        objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        Sphere sphere = new Sphere(diameter);
        Transform3D pos1 = new Transform3D();
        pos1.setTranslation(new Vector3f(x,y,z));
        objTrans.setTransform(pos1);
        objTrans.addChild(sphere);
       
    }
   
    public TransformGroup getTransformGroup(){
          
        return objTrans;

    }
   
    public void updateGraphics(){
         Transform3D trans = new Transform3D();
         trans.setTranslation(new Vector3f(x,y,z));
         objTrans.setTransform(trans);
    }
   
    public void move(float timeslice){
        x=x+xV*timeslice;
        y=y+yV*timeslice;
        z=z+zV*timeslice;

        timeSinceCollide=timeSinceCollide+timeslice;
        updateGraphics();
    }
   
    public void boundaryCheck(float minX, float maxX,float minY, float maxY,float minZ, float maxZ){
        if (x>maxX){xV=-Math.abs(xV);}
        if (x<minX){xV=+Math.abs(xV);}
       
        if (y>maxY){yV=-Math.abs(yV);}
        if (y<minY){yV=+Math.abs(yV);}
       
        if (z>maxZ){zV=-Math.abs(zV);}
        if (z<minZ){zV=+Math.abs(zV);}
    }
   
    public static void collide(Ball ballA, Ball ballB){
        //this function both tests for collisions and if appropriate collides them
       
        //the distance between the centroids
        float distance =(float)(Math.sqrt(Math.pow(ballA.x-ballB.x,2))+Math.pow(ballA.y-ballB.y,2)+Math.pow(ballA.z-ballB.z,2));
       
        if (distance<(0.5*ballA.diameter+0.5*ballB.diameter) ){
            if (ballA.timeSinceCollide>COLLIDECOOLDOWN && ballB.timeSinceCollide>COLLIDECOOLDOWN){
                ballA.timeSinceCollide=0;
                ballB.timeSinceCollide=0;

                //ballA.xV=(ballA.xV*(ballA.mass-ballB.mass)+2*ballB.mass*ballB.xV)/(ballA.mass + ballB.mass);
                //ballA.yV=(ballA.yV*(ballA.mass-ballB.mass)+2*ballB.mass*ballB.yV)/(ballA.mass + ballB.mass);
                //ballA.zV=(ballA.zV*(ballA.mass-ballB.mass)+2*ballB.mass*ballB.zV)/(ballA.mass + ballB.mass);

                //ballB.xV=(ballB.xV*(ballB.mass-ballA.mass)+2*ballA.mass*ballA.xV)/(ballB.mass + ballA.mass);
                //ballB.yV=(ballB.yV*(ballB.mass-ballA.mass)+2*ballA.mass*ballA.yV)/(ballB.mass + ballA.mass);
                //ballB.zV=(ballB.zV*(ballB.mass-ballA.mass)+2*ballA.mass*ballA.zV)/(ballB.mass + ballA.mass);
               
                ballA.xV=-ballA.xV;
                ballA.yV=-ballA.yV;
                ballA.zV=-ballA.zV;
                ballB.xV=-ballB.xV;
                ballB.yV=-ballB.yV;
                ballB.zV=-ballB.zV;
               
               
            }else{
                if (ballA.timeSinceCollide!=COLLIDECOOLDOWN){ //repeated collision (this tests for multiple collisions in the same update
                   //jiggle free
                    Random rnd=new Random();
                    ballA.xV=ballA.xV+rnd.nextFloat()*0.01f;
                    ballA.yV=ballA.yV+rnd.nextFloat()*0.01f;
                    ballA.zV=ballA.zV+rnd.nextFloat()*0.01f;
     
                }
               
            }

           
        }
       
       
       
    }
}

UpdatingThread, thread creating requests to the ball class to move at a set frame rate

class UpdatingThread implements Runnable {

    Thread t;
    Ball balls[];
    int FPS;
   
    UpdatingThread(Ball balls[], int FPS) {

        this.balls=balls;
        t=new Thread(this,"Drawing Thread");
        t.start();
        this.FPS=FPS;
    }
       
    public void run() {
        long startTime;
        long usedTime;
       

       
        while(true){
           
            updateStuff();
           
            try {
                Thread.sleep((int)1000/FPS);
            } catch(InterruptedException e){
                System.out.println("Drawing Tread Interupted");
            }
                   
        }
    }
           
   
    private void updateStuff() {
            for(int i=0;i<balls.length;i++){
                balls[i].boundaryCheck(Main.XMIN,Main.XMAX, Main.YMIN, Main.YMAX, Main.ZMIN, Main.ZMAX);
                balls[i].move((float)1.0/FPS);
           
                for(int j=0;j<balls.length;j++){
                    if (i!=j){
                        Ball.collide(balls[i], balls[j]);
                    }
                }
               
               
            }
           
           
           
    }
   

   
}