Rotation with quaternions[solved]

Hi,

I want to rotate a box like the earth in google earth.

So, I tried:

rotQuat.fromAngles(tempAngle2 * FastMath.DEG_TO_RAD,tempAngle1 * FastMath.DEG_TO_RAD,0);
box.setLocalRotation(rotQuat);


But, the x axis rotates, if the y axis rotates. How can I rotate my box with "static" axes?

Best,
Andreas

If you just want to rotate around a specific axis with a Quaternion try using



fromAngleNormalAxis( angle, axis );



with Vector3f.UNIT_X, Vector3f.UNIT_Y or Vector3f.UNIT_Z as the given axis.



Was that what you where looking for?

Hi,

I know how to rotate around a specific axis. But, I want to rotate around two axes without an axis also is rotating. So, I also tried to use fromAngleNormalAxis two times and multiply the quaternions (multLocal). With that, I got the same result, if I rotate around an axis, another axis rotates, too.

My first axis vector should be (1,0,0) and the second one (0,1,0). These vectors should be relative to the world and not to the box. How do I do that? The problem is, with the mouse, I want to control the rotations around the x and y axis. At a specific point, the box is rotating in the wrong direction, because the rotations are local and not global.



Best,

Andreas

Hi,

I was looking for virtual trackball or arcball like this: http://viewport3d.com/trackball.htm or this: http://www.cs.sunysb.edu/~mueller/teaching/cse564/trackball.ppt. So, I modified JMESwingTest from the tutorial. With the mouse and the left mouse button pressed, you can move around the box and with the key "+" and "-", you can zoom. The graphics are in the MyImplementor class.


import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.EventQueue;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;

import org.lwjgl.opengl.DisplayMode;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.JMECanvasImplementor;
import com.jmex.awt.SimpleCanvasImpl;
import com.jmex.awt.input.AWTMouseInput;



public class JMESwingTest {
   private static final Logger logger = Logger.getLogger(JMESwingTest.class
         .getName());

   int width = 640, height = 480;

   // Swing frame
   private SwingFrame frame;

   public JMESwingTest() {
      frame = new SwingFrame();
      // center the frame
      frame.setLocationRelativeTo(null);
      // show frame
      frame.setVisible(true);
   }

   /**
    * Main Entry point...
    *
    * @param args
    *            String[]
    */
   public static void main(String[] args) {

      try {
         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      } catch (Exception e) {
         logger.logp(Level.SEVERE, JMESwingTest.class.toString(),
               "main(args)", "Exception", e);
      }
      new JMESwingTest();
   }

   // **************** SWING FRAME ****************

   // Our custom Swing frame... Nothing really special here.
   class SwingFrame extends JFrame {
      private static final long serialVersionUID = 1L;

      JPanel contentPane;
      JPanel mainPanel = new JPanel();
      Canvas comp = null;
      JButton coolButton = new JButton();
      JButton uncoolButton = new JButton();
      JPanel spPanel = new JPanel();
      JScrollPane scrollPane = new JScrollPane();
      JTree jTree1 = new JTree();
      JCheckBox scaleBox = new JCheckBox("Scale GL Image");
      JPanel colorPanel = new JPanel();
      JLabel colorLabel = new JLabel("BG Color:");
      MyImplementor impl;

      // Construct the frame
      public SwingFrame() {
         
         addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
               dispose();
            }
         });

         init();
         pack();

         // MAKE SURE YOU REPAINT SOMEHOW OR YOU WON'T SEE THE UPDATES...
         new Thread() {
            {
               setDaemon(true);
            }

            public void run() {
               while (true) {
                  comp.repaint();
                  try {
                     Thread.sleep(1);
                  } catch (InterruptedException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                  }// yield();
               }
            }
         }.start();

      }

      // Component initialization
      private void init() {
         contentPane = (JPanel) this.getContentPane();
         contentPane.setLayout(new BorderLayout());

         mainPanel.setLayout(new GridBagLayout());

         setTitle("JME - SWING INTEGRATION TEST");

         coolButton.setText("Cool Button");
         uncoolButton.setText("Uncool Button");

         colorPanel.setBackground(java.awt.Color.black);
         colorPanel.setToolTipText("Click here to change Panel BG color.");
         colorPanel.setBorder(BorderFactory.createRaisedBevelBorder());
         colorPanel.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
               final java.awt.Color color = JColorChooser.showDialog(
                     SwingFrame.this, "Choose new background color:",
                     colorPanel.getBackground());
               if (color == null)
                  return;
               colorPanel.setBackground(color);
               Callable<?> call = new Callable<Object>() {
                  public Object call() throws Exception {
                     comp.setBackground(color);
                     return null;
                  }
               };
               GameTaskQueueManager.getManager().render(call);
            }
         });

         scaleBox.setOpaque(false);
         scaleBox.setSelected(true);
         scaleBox.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
               if (comp != null)
                  doResize();
            }
         });

         spPanel.setLayout(new BorderLayout());
         contentPane.add(mainPanel, BorderLayout.WEST);
         mainPanel.add(scaleBox,
               new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
                     GridBagConstraints.CENTER,
                     GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                           5), 0, 0));
         mainPanel.add(colorLabel,
               new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
                     GridBagConstraints.CENTER,
                     GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                           5), 0, 0));
         mainPanel.add(colorPanel, new GridBagConstraints(0, 2, 1, 1, 0.0,
               0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE,
               new Insets(5, 5, 0, 5), 25, 25));
         mainPanel.add(coolButton,
               new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0,
                     GridBagConstraints.CENTER,
                     GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                           5), 0, 0));
         mainPanel.add(uncoolButton,
               new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0,
                     GridBagConstraints.CENTER,
                     GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0,
                           5), 0, 0));
         mainPanel.add(spPanel, new GridBagConstraints(0, 5, 1, 1, 1.0, 1.0,
               GridBagConstraints.CENTER, GridBagConstraints.BOTH,
               new Insets(5, 5, 0, 5), 0, 0));
         spPanel.add(scrollPane, BorderLayout.CENTER);

         scrollPane.setViewportView(jTree1);

         //


GL STUFF

         // make the canvas:
         comp = DisplaySystem.getDisplaySystem("lwjgl").createCanvas(width,
               height);

         // add a listener... if window is resized, we can do something about
         // it.
         comp.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent ce) {
               doResize();
            }
         });
         KeyInput.setProvider(KeyInput.INPUT_AWT);
         AWTMouseInput.setup(comp, false);
         GraphicsEnvironment env = GraphicsEnvironment
               .getLocalGraphicsEnvironment();
         GraphicsDevice gd = env.getDefaultScreenDevice();
         java.awt.DisplayMode dm = gd.getDisplayMode();
         
         // Important! Here is where we add the guts to the panel:
         impl = new MyImplementor(width, height);
         JMECanvas jmeCanvas = ((JMECanvas) comp);
         jmeCanvas.setImplementor(impl);
         jmeCanvas.setUpdateInput(true);
         
         //
END OF GL STUFF
         comp.setBounds(0, 0, width, height);
         contentPane.add(comp, BorderLayout.CENTER);
         Toolkit.getDefaultToolkit().getSystemEventQueue().push(
                  new EventQueue(){
                      protected void dispatchEvent(AWTEvent event) {
                                    if (event instanceof KeyEvent) {
                                          KeyEvent keyEvent = (KeyEvent) event;
                                          if (keyEvent.getID() == KeyEvent.KEY_TYPED){
                                                 if(keyEvent.getKeyChar()=='+'){
                                                    impl.scalePlus();
                            
                                                 }else if(keyEvent.getKeyChar()=='-'){
                                                    impl.scaleMinus();
                                                 }
                                               
                                                }
                                       }
                                       super.dispatchEvent(event);
                    
                                         }
                  });
         
         
      }

      protected void doResize() {
         if (scaleBox != null && scaleBox.isSelected()) {
            impl.resizeCanvas(comp.getWidth(), comp.getHeight());
         } else {
            impl.resizeCanvas(width, height);
         }

      }

      // Overridden so we can exit when window is closed
      protected void processWindowEvent(WindowEvent e) {
         super.processWindowEvent(e);
         if (e.getID() == WindowEvent.WINDOW_CLOSING) {
            System.exit(0);
         }
      }
   }

   
   
   // IMPLEMENTING THE SCENE:
      
   public class MyImplementor extends SimpleCanvasImpl {

      private Quaternion rotQuat, rotQuat2;
      private Vector3f oCamTranslation;
      private CameraNode oCamNode;
      private Box box;
      private boolean pressed = false;
      AbsoluteMouse am;
      private float scale=0;
      
      private int oldX, oldY, newX, newY;
      public MyImplementor(int width, int height) {
         super(width, height);

      }
      public void scalePlus(){
         scale-=0.05f;
         if(scale<-0.25f)scale=-0.25f;
      }
      
      public void scaleMinus(){
         scale+=0.05f;
         if(scale>3)scale=3;
      }
      
      public void simpleSetup() {
         am = new AbsoluteMouse("The Mouse", width, height);
         
         // Normal Scene setup stuff...
         rotQuat = new Quaternion();
         rotQuat2 = new Quaternion();
         
         oCamNode = new CameraNode( "Camera Node", this.getCamera());
         Vector3f max = new Vector3f(5, 5, 5);
         Vector3f min = new Vector3f(-5, -5, -5);
         box = new Box("Box", min, max);
         rootNode.attachChild(box);
         rootNode.attachChild(oCamNode);
         box.setRandomColors();
         oCamTranslation=new Vector3f(0,0,-20);
         oCamNode.setLocalTranslation(oCamTranslation);
         
         oCamNode.lookAt(box.getLocalTranslation(), new Vector3f(0,1,0));
         box.setModelBound(new BoundingBox());
         box.updateModelBound();
         rotQuat2=oCamNode.getLocalRotation();
         
         
      }

      private Vector3f trackBallMapping(int px, int py) {
         float x = (2 * (float) px) / width - 1;
         float y = (2 * (float) py) / height -1;
         float z2 = 1 - x * x - y * y;
         float z = z2 > 0 ? FastMath.sqrt(z2) : 0;
         return new Vector3f(x, y, z);
      }
      
      
      
      public void simpleUpdate() {
         
         if (MouseInput.get().isButtonDown(0)) {
            if (!pressed) {
               pressed = true;
               oldX = MouseInput.get().getXAbsolute();
               oldY = MouseInput.get().getYAbsolute();
               
            }
            newX=MouseInput.get().getXAbsolute();
            newY=MouseInput.get().getYAbsolute();
            
            Vector3f oLastVector3f = this.trackBallMapping(oldX, oldY);         
         
            Vector3f oNewVector3f = this.trackBallMapping(newX, newY);
            Vector3f oAxisVector = oLastVector3f.cross(oNewVector3f);
            Vector2f oVector2f=new Vector2f(newX-oldX,newY-oldY);
            float tAngle = oVector2f.length();
            rotQuat.fromAngleAxis(-tAngle*FastMath.DEG_TO_RAD,oAxisVector);
            rotQuat2.multLocal(rotQuat);
            oCamNode.setLocalTranslation(rotQuat2.mult(oCamTranslation).mult(FastMath.exp(scale)));
            oCamNode.setLocalRotation(rotQuat2);
            oldX=newX;
            oldY=newY;
            
         } else {
            if (pressed) {
               pressed = false;
               
            }
            oCamNode.setLocalTranslation(rotQuat2.mult(oCamTranslation).mult(FastMath.exp(scale)));
         }

      }
   }



Best,
Andreas

Hi,

with the current program, the rotations around the global x and y axes are limited to the radius of the "sphere". But, if the sphere radius is big enough, you also have the described rotations outside the application window. Here is the modification of my trackBallMapping method:



private Vector3f trackBallMapping(int px, int py) {
         float x = ((float) px)-(float)width/2;
         float y = ((float) py)-(float)height/2;
         float z2 = width*height*1000 - x * x - y * y;
         float z = z2 > 0 ? FastMath.sqrt(z2) : 0;
         return new Vector3f(x, y, z);
      }



Best,
Andreas

Hi,

I was asked how to rotate the object and not the camera. Here is my jme 2.0 solution:



import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import javax.swing.ImageIcon;

import javax.swing.WindowConstants;
import javax.swing.SwingUtilities;


import com.jme.bounding.BoundingBox;
import com.jme.input.AbsoluteMouse;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.CameraNode;
import com.jme.scene.shape.Box;
import com.jme.system.DisplaySystem;
import com.jme.system.canvas.JMECanvas;
import com.jme.system.canvas.SimpleCanvasImpl;
import com.jme.system.lwjgl.LWJGLSystemProvider;
import com.jmex.awt.input.AWTMouseInput;
import com.jmex.awt.lwjgl.LWJGLAWTCanvasConstructor;

public class RotatingCube2 extends javax.swing.JFrame {
   /**
    *
    */
   private static final long serialVersionUID = 1L;
   private Canvas canvas1;
   int width = 640, height = 480;
   MyImplementor impl;

   /**
   * Auto-generated main method to display this JFrame
   */
   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            RotatingCube2 inst = new RotatingCube2();
            inst.setLocationRelativeTo(null);
            inst.setVisible(true);
         }
      });
   }
   
   public RotatingCube2() {
      super();
      initOpenGl();
      initGUI();
      
      Toolkit.getDefaultToolkit().getSystemEventQueue().push(
               new EventQueue(){
                   protected void dispatchEvent(AWTEvent event) {
                                if (event instanceof KeyEvent) {
                                      KeyEvent keyEvent = (KeyEvent) event;
                                      if (keyEvent.getID() == KeyEvent.KEY_TYPED){
                                            if(keyEvent.getKeyChar()=='+'){
                                                impl.scalePlus();
                        
                                             }else if(keyEvent.getKeyChar()=='-'){
                                                impl.scaleMinus();
                                             }
                                          }
                                    }
                                   super.dispatchEvent(event);
                 
                                      }
               });
   }
   
   private void initOpenGl(){
      DisplaySystem display = DisplaySystem.getDisplaySystem(LWJGLSystemProvider.LWJGL_SYSTEM_IDENTIFIER);
       display.registerCanvasConstructor("AWT", LWJGLAWTCanvasConstructor.class);
       canvas1 = (Canvas)display.createCanvas(width, height);
      //
      // add a listener... if window is resized, we can do something about
      // it.
      canvas1.addComponentListener(new ComponentAdapter() {
         public void componentResized(ComponentEvent ce) {
            doResize();
         }
      });
      KeyInput.setProvider(KeyInput.INPUT_AWT);
      AWTMouseInput.setup(canvas1, false);
      // Important! Here is where we add the guts to the panel:
      impl = new MyImplementor(width, height);
      JMECanvas jmeCanvas = ((JMECanvas) canvas1);
      jmeCanvas.setImplementor(impl);
      jmeCanvas.setUpdateInput(true);
      
      
   }
   
   private void initGUI() {
      try {
         setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
         this.setIconImage(new ImageIcon(getClass().getClassLoader().getResource("stern_icon.png")).getImage());
         this.setTitle("Mouse Cube");
         {
            getContentPane().add(canvas1, BorderLayout.CENTER);
            canvas1.setBounds(0,0,width,height);
         }
         pack();      
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   
   protected void doResize() {
         impl.resizeCanvas(canvas1.getWidth(), canvas1.getHeight());
      

   }

   // Overridden so we can exit when window is closed
   protected void processWindowEvent(WindowEvent e) {
      super.processWindowEvent(e);
      if (e.getID() == WindowEvent.WINDOW_CLOSING) {
         System.exit(0);
      }
   }
   
   // IMPLEMENTING THE SCENE:
   
   public class MyImplementor extends SimpleCanvasImpl {

      private Quaternion rotQuat;
      private CameraNode oCamNode;
      private Box box;
      private boolean pressed = false;
      AbsoluteMouse am;
      private float scale=0;
      
      
      private int oldX, oldY, newX, newY;
      public MyImplementor(int width, int height) {
         super(width, height);
      }
      public void scalePlus(){
         scale-=0.05f;
         if(scale<-0.25f)scale=-0.25f;
      }
      
      public void scaleMinus(){
         scale+=0.05f;
         if(scale>3)scale=3;
      }
      
      public void simpleSetup() {
         am = new AbsoluteMouse("The Mouse", width, height);
         DisplaySystem.getDisplaySystem().getRenderer().setBackgroundColor( ColorRGBA.white );
         // Normal Scene setup stuff...
         rotQuat = new Quaternion();
         
         
         //oCamNode = new CameraNode( "Camera Node", this.getCamera());
         Vector3f max = new Vector3f(5, 5, 5);
         Vector3f min = new Vector3f(-5, -5, -5);
         box = new Box("Box", min, max);
         rootNode.attachChild(box);
         rootNode.attachChild(oCamNode);
         box.setRandomColors();
         //oCamTranslation=new Vector3f(0,0,-23f);
         //oCamNode.setLocalTranslation(oCamTranslation);
         
         //oCamNode.lookAt(box.getLocalTranslation(), new Vector3f(0,1,0));
         box.setModelBound(new BoundingBox());
         box.updateModelBound();
         
         
         
      }

      private Vector3f trackBallMapping(int px, int py) {
         float x = ((float) px)-(float)width/2f;
         float y = ((float)height/2f-(float) py);
         float z2 = width*height*1000f - x * x - y * y;
         float z = z2 > 0 ? FastMath.sqrt(z2) : 0;
         return new Vector3f(x, y, z);
      }
      
      
      
      public void simpleUpdate() {
         
         if (MouseInput.get().isButtonDown(0)) {
            if (!pressed) {
               pressed = true;
               oldX = MouseInput.get().getXAbsolute();
               oldY = MouseInput.get().getYAbsolute();
               
            }
            newX=MouseInput.get().getXAbsolute();
            newY=MouseInput.get().getYAbsolute();
            
            Vector3f oLastVector3f = this.trackBallMapping(oldX, oldY);         
         
            Vector3f oNewVector3f = this.trackBallMapping(newX, newY);
            Vector3f oAxisVector = oLastVector3f.cross(oNewVector3f);
            Vector2f oVector2f=new Vector2f(newX-oldX,newY-oldY);
            float tAngle = oVector2f.length();
            rotQuat.fromAngleAxis(tAngle*FastMath.DEG_TO_RAD,oAxisVector);
            this.box.setLocalRotation(rotQuat.mult(this.box.getLocalRotation()));
            oldX=newX;
            oldY=newY;
            
         } else {
            pressed = false;
         }
                        this.box.setLocalScale(1/FastMath.exp(scale));
      }
   }
}



Have joy,
Andreas

Hi Andreas,



I moved my message to a new topic cos I noticed that this topic was tagged as [solved] and I thought nobody would read it because of that. :roll:



Thanks a lot, it works perfectly! :smiley: