Turntable like motion

Hi,

I was unhappy about the translation which changed with the rotation, which is logically right, but feels wrong.

So I tested a bit to find a solution. Additionally it felt wrong, that I moved the camera, although I didn’t want it.

So here’s the “new” turntable, that works like motion in blender.
Of cause, situation can be “saved” and restored (programmatically)
Well only key support, no mouse.
Anyway - if someone is interested:

package de.schwarzrot.jme3;


import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.debug.WireBox;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.util.JmeFormatter;


/**
 * Test class to show the motion separation for a turntable, that can nick
 * toward the user and where the arrow keys always work in native direction of
 * the user.
 * .
 * Control of the sample works with cursor keys like this:
 * - cursor left/right: move the table left or right
 * - cursor up/down: moves the table towards the user or from the user away
 * - cursor left/right while holding shift: rotates the table clockwise or
 * counterclockwise
 * - cursor up/down while holding shift: rotates the table toward the user or
 * from the user away
 * + key from numpad zooms in
 * - key from numpad zooms out
 * - press T key: tell the values of the current scene through the logger
 * - press R key: restore the saved scene (have to code the values)
 *
 * @author Django Reinhard
 */
public class TestTurnTable extends SimpleApplication implements AnalogListener, ActionListener {
   /**
    * constructor that takes dimension of workarea. Dimension is taken from
    * world of cnc-machines, which means, that X-Axis is from left to right,
    * Y-Axis is from Operator to Monitor and Z-Axis is from Top to Bottom. That
    * does not match JME3 axis and axis-direction, so we have to do a mapping
    *
    * @param limits
    *           dimension of workarea. Array of floats with the sequence: xMin,
    *           xMax, yMin, yMax, zMin, zMax - but axis and axis
    *           direction/limits are taken in the sense of cnc-machines, not in
    *           jme-notion. That means we have to map axis and change direction.
    */
   public TestTurnTable(float[] limits) {
      size     = new Vector3f(limits[1] - limits[0], limits[5] - limits[4], limits[3] - limits[2]);
      baseLoc  = new Vector3f();
      rotation = new Quaternion();
      nick     = new Quaternion();

      l.log(Level.INFO, "workarea size is: " + size);

      // here's the right place to change AppSettings the way we want it to be
      AppSettings settings = new AppSettings(true);

      settings.setAudioRenderer(null);
      settings.setWidth(1000);
      settings.setWidth(1000);
      setSettings(settings);
      setPauseOnLostFocus(false);
   }


   @Override
   public void onAction(String name, boolean isPressed, float tpf) {
      if (Restore.equals(name)) {
         restoreScene();
      } else if (TellPos.equals(name)) {
         tellScene();
      } else if (RotateTrigger.equals(name)) {
         rotate = isPressed;
      } else if (MoveModelTrigger.equals(name)) {
         moveModel = isPressed;
      }
   }


   @Override
   public void onAnalog(String name, float value, float tpf) {
      if (ZoomIN.equals(name)) {
         zoomFactor -= 0.5f * zoomFactor * tpf;
         resizeView();
      } else if (ZoomOUT.equals(name)) {
         zoomFactor += 0.5f * zoomFactor * tpf;
         resizeView();
      }

      if (moveModel) {
         // model will only move up and down from workplace
         // This motion is only to compensate different workpiece height
         if (KeyUp.equals(name)) {
            modelOffset += 0.6 * zoomFactor * tpf;
            moveModel();
         } else if (KeyDown.equals(name)) {
            modelOffset -= 0.6 * zoomFactor * tpf;
            moveModel();
         }
      } else if (rotate) {
         // standard rotation is rotating the workplace around the Y-Axis
         if (KeyLeft.equals(name)) {
            rotation.fromAngleAxis(-0.001f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (KeyRight.equals(name)) {
            rotation.fromAngleAxis(0.001f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (KeyUp.equals(name)) {
            nick.fromAngleAxis(-0.001f * zoomFactor * tpf, Vector3f.UNIT_X);
            nickDesk();
         } else if (KeyDown.equals(name)) {
            nick.fromAngleAxis(0.001f * zoomFactor * tpf, Vector3f.UNIT_X);
            nickDesk();
         }
      } else {
         if (KeyLeft.equals(name)) {
            baseLoc.x -= 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyRight.equals(name)) {
            baseLoc.x += 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyUp.equals(name)) {
            baseLoc.z -= 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyDown.equals(name)) {
            baseLoc.z += 0.8f * zoomFactor * tpf;
            shiftBase();
         }
      }
   }


   @Override
   public void simpleInitApp() {
      flyCam.setEnabled(false);
      cam.setParallelProjection(true);
      Vector3f camLoc = new Vector3f(0, 2f * size.y, size.z);

      l.log(Level.INFO, "camera location: " + camLoc);

      cam.setLocation(camLoc);
      cam.lookAt(camLoc.negate(), camDir);

      l.log(Level.INFO, "camera direction: " + cam.getDirection());

      createDesk();
      createTool();
      registerInputs();
      resizeView();
   }


   protected void createDesk() {
      shiftBase = new Node("ShiftBase");
      nickBase  = new Node("NickBase");
      turnTable = new Node("TurnTable");
      modelBase = new Node("ModelBase");

      rootNode.attachChild(shiftBase);
      shiftBase.attachChild(nickBase);
      nickBase.attachChild(turnTable);
      turnTable.attachChild(modelBase);

      // the working area
      Geometry   box             = new Geometry("Box", new WireBox(size.x, size.y, size.z));
      // its our desktop, so create a nice looking surface
      Geometry   ground          = new Geometry("Ground", new Quad(size.x * 2, size.z * 2));
      Material   m               = new Material(assetManager, MATUnshaded);
      Quaternion initialRotation = new Quaternion();

      m.getAdditionalRenderState().setWireframe(true);
      m.setColor(MATColor, ColorRGBA.Green);
      box.setMaterial(m);
      box.setLocalTranslation(0, size.y, 0);

      initialRotation.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y);
      turnTable.rotate(initialRotation);
      turnTable.attachChild(box);

      m = new Material(assetManager, MATUnshaded);
      m.setTexture(MATColorMap, assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
      ground.setMaterial(m);
      ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
      ground.setLocalTranslation(-size.x, 0, size.z);
      turnTable.attachChild(ground);

      createOrigin(modelBase);
   }


   protected void createOrigin(Node parent) {
      // visual use of axis does not follow axis from jme3,
      // so create some arrows to show usage and direction of each axis
      // follow color scheme of blender (x is red, y is green and z is blue)
      Mesh     mesh = new Arrow(new Vector3f(100f, 0, 0));
      Geometry geo  = new Geometry(PrimArrow, mesh);
      Material m    = new Material(assetManager, MATUnshaded);

      m.setColor(MATColor, ColorRGBA.Red);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);

      mesh = new Arrow(new Vector3f(0, 0, -100f));
      geo  = new Geometry(PrimArrow, mesh);
      m    = new Material(assetManager, MATUnshaded);
      m.setColor(MATColor, ColorRGBA.Green);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);

      mesh = new Arrow(new Vector3f(0, 100f, 0));
      geo  = new Geometry(PrimArrow, mesh);
      m    = new Material(assetManager, MATUnshaded);
      m.setColor(MATColor, ColorRGBA.Blue);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);
   }


   protected void createTool() {
      Geometry geo = new Geometry("Tool", new Cylinder(2, 16, 10, 100, true));
      Material m   = new Material(assetManager, MATShowNormals);

      geo.setMaterial(m);
      geo.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
      geo.setLocalTranslation(0, 100, 0);

      modelBase.attachChild(geo);
   }


   protected void moveModel() {
      modelBase.setLocalTranslation(0, modelOffset, 0);
   }


   protected void nickDesk() {
      nickBase.rotate(nick);
   }


   protected void registerInputs() {
      inputManager.addMapping(ZoomIN, new KeyTrigger(KeyInput.KEY_ADD));
      inputManager.addMapping(ZoomOUT, new KeyTrigger(KeyInput.KEY_SUBTRACT));
      inputManager.addMapping(Restore, new KeyTrigger(KeyInput.KEY_R));
      inputManager.addMapping(TellPos, new KeyTrigger(KeyInput.KEY_T));
      inputManager.addMapping(KeyLeft, new KeyTrigger(KeyInput.KEY_LEFT));
      inputManager.addMapping(KeyRight, new KeyTrigger(KeyInput.KEY_RIGHT));
      inputManager.addMapping(KeyUp, new KeyTrigger(KeyInput.KEY_UP));
      inputManager.addMapping(KeyDown, new KeyTrigger(KeyInput.KEY_DOWN));
      inputManager.addMapping(MoveModelTrigger, new KeyTrigger(KeyInput.KEY_LMENU),
            new KeyTrigger(KeyInput.KEY_RMENU));
      inputManager.addMapping(RotateTrigger, new KeyTrigger(KeyInput.KEY_LSHIFT),
            new KeyTrigger(KeyInput.KEY_RSHIFT));

      inputManager.addListener(this, Restore, ZoomIN, ZoomOUT, KeyLeft, KeyRight, KeyUp, KeyDown, KeyForward,
            KeyBack, RotateTrigger, MoveModelTrigger, TellPos);
   }


   protected void resizeView() {
      float aspect = (float) cam.getWidth() / (float) cam.getHeight();

      // calculate Viewport (use big limits to avoid clipping)
      cam.setFrustum(-2000, 6000, -aspect * zoomFactor, aspect * zoomFactor, zoomFactor, -zoomFactor);
   }


   /*
    * put values in, that tellScene() printed
    */
   protected void restoreScene() {
      // key trigger is too fast, so have to limit usage to one call
      if (restored) {
         return;
      }
      restored  = true;

      //      INFORMATION TestJMEMotion 19:48:29  location: (0.0, 0.0, 879.12665)
      baseLoc.x = 0;
      baseLoc.y = 0;
      baseLoc.z = 879.12665f;
      shiftBase();

      //      INFORMATION TestJMEMotion 19:48:29      nick: (-0.0530407, 0.0, 0.0, 0.9985713)
      nick = new Quaternion(-0.0530407f, 0, 0, 0.9985713f);
      nickDesk();

      //      INFORMATION TestJMEMotion 19:48:29  rotation: (0.0, 0.38741437, 0.0, -0.92179006)
      rotation = new Quaternion(0, 0.38741437f, 0, -0.92179006f);
      rotateDesk();

      //      INFORMATION TestJMEMotion 19:48:29 elevation: 733.17413
      modelOffset = 733.17413f;
      moveModel();

      //      INFORMATION TestJMEMotion 19:48:29      zoom: 1343.7764
      zoomFactor = 1343f;
      resizeView();
   }


   protected void rotateDesk() {
      turnTable.rotate(rotation);
   }


   protected void shiftBase() {
      shiftBase.setLocalTranslation(baseLoc);
   }


   protected void tellScene() {
      l.log(Level.INFO, " location: " + shiftBase.getLocalTranslation());
      l.log(Level.INFO, "     nick: " + nickBase.getLocalRotation());
      l.log(Level.INFO, " rotation: " + turnTable.getLocalRotation());
      l.log(Level.INFO, "elevation: " + modelOffset);
      l.log(Level.INFO, "     zoom: " + zoomFactor);
   }


   public static void main(String[] args) {
      JmeFormatter formatter      = new JmeFormatter();
      Handler      consoleHandler = new ConsoleHandler();
      consoleHandler.setFormatter(formatter);

      Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
      Logger.getLogger("").addHandler(consoleHandler);
      TestTurnTable app = new TestTurnTable(
            // machine limits given from outside
            new float[] { 0, 900, 0, 1800, -500, 0 });

      app.start();
   }


   private Vector3f              size;
   private Node                  shiftBase;
   private Node                  nickBase;
   private Node                  turnTable;
   private Node                  modelBase;
   private Vector3f              baseLoc;
   private Quaternion            rotation;
   private Quaternion            nick;
   private boolean               rotate;
   private boolean               restored;
   private boolean               moveModel;
   private float                 modelOffset;
   private float                 zoomFactor       = 1800f;
   private static final Logger   l;
   private static final String   ZoomIN           = "zoomIN";
   private static final String   ZoomOUT          = "zoomOUT";
   private static final String   Restore          = "Restore";
   private static final String   TellPos          = "TellPos";
   private static final String   RotateTrigger    = "trigRotation";
   private static final String   MoveModelTrigger = "trigModelMove";
   private static final String   KeyLeft          = "kLeft";
   private static final String   KeyRight         = "kRight";
   private static final String   KeyUp            = "kUp";
   private static final String   KeyDown          = "kDown";
   private static final String   KeyForward       = "kForward";
   private static final String   KeyBack          = "kBack";
   private static final String   MATUnshaded      = "Common/MatDefs/Misc/Unshaded.j3md";
   private static final String   MATShowNormals   = "Common/MatDefs/Misc/ShowNormals.j3md";
   private static final String   MATColor         = "Color";
   private static final String   MATColorMap      = "ColorMap";
   private static final String   PrimArrow        = "Arrow";
   private static final Vector3f camDir           = new Vector3f(0, 1, 0);
   static {
      l = Logger.getLogger("Test");
   }
}

I would like to know, how can I limit the nick rotation?

Off the top of my head…

Quaternion myQuaternion = mySpatial.getLocalRotation();

// gives you an x, y and z float[3] in Radians.
float[] angles = myQuaternion.toAngles(null);

float maxRotation = FastMath.PI;

// limit the rotation of the y axis.
if (angles[1] > = maxRotation) {
    angles[1] = maxRotation;
}

myQuaternion = new Quaternion().fromAngles(angles);
mySpatial.setRotation(myQuaternion);

Maybe this github gist will be of use.

Hi,

thank you for your help.

My problem is not the limitiation of a value, but setting an absolute rotation.

From wiki I read, that Quaternion are relative rotations. And my sample predicate the same.

So I can ask a node for its rotation and get a quaternion. If I multiply two quaternions, I combine the rotations, but rotating the object will always add the quaternion to the existing rotation.

So - what am I missing?

rotate() is relative.
setLocalRotation() is not.

Quaternions are “Relative rotations” but if you start from identity then the relative rotation is absolute rotation (in this case only relative to the parent).

1 Like

Perfect! That was the missing link!
Thank you very much :slight_smile:

1 Like

Hi,

with your help I was able to complete the testapp now. With angle limitation and mousecontrol :slight_smile:

package de.schwarzrot.jme3;


import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.debug.WireBox;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.util.JmeFormatter;


/**
 * Test class to show the motion separation for a turntable, that can nick
 * toward the user and where the arrow keys always work in native direction of
 * the user.
 * .
 * Control of the sample works with cursor keys like this:
 * - cursor left/right: move the table left or right
 * - cursor up/down: moves the table towards the user or from the user away
 * - cursor left/right while holding shift: rotates the table clockwise or
 * counterclockwise
 * - cursor up/down while holding shift: rotates the table toward the user or
 * from the user away
 * - cursor up/down while holding alt: moves the model from table up or down
 * '+' key from numpad zooms in
 * '-' key from numpad zooms out
 * - press T key: tell the values of the current scene through the logger
 * - press R key: restore the saved scene (have to code the values)
 * .
 * Mouse control works like this:
 * - left mouse button - moves the table around
 * - middle mouse button - rotates the table
 * - right mouse button - moves the model up or down
 * - turning mouse wheel - zooms in or out
 *
 * @author Django Reinhard
 */
public class TestTurnTable extends SimpleApplication implements AnalogListener, ActionListener {
   /**
    * constructor that takes dimension of workarea. Dimension is taken from
    * world of cnc-machines, which means, that X-Axis is from left to right,
    * Y-Axis is from Operator to Monitor and Z-Axis is from Top to Bottom. That
    * does not match JME3 axis and axis-direction, so we have to do a mapping
    *
    * @param limits
    *           dimension of workarea. Array of floats with the sequence: xMin,
    *           xMax, yMin, yMax, zMin, zMax - but axis and axis
    *           direction/limits are taken in the sense of cnc-machines, not in
    *           jme-notion. That means we have to map axis and change direction.
    */
   public TestTurnTable(float[] limits) {
      size     = new Vector3f(limits[1] - limits[0], limits[5] - limits[4], limits[3] - limits[2]);
      baseLoc  = new Vector3f();
      rotation = new Quaternion();
      nick     = new Quaternion();

      l.log(Level.INFO, "workarea size is: " + size);

      // here's the right place to change AppSettings the way we want it to be
      AppSettings settings = new AppSettings(true);

      settings.setAudioRenderer(null);
      settings.setWidth(1000);
      settings.setWidth(1000);
      setSettings(settings);
      setPauseOnLostFocus(false);
   }


   @Override
   public void onAction(String name, boolean isPressed, float tpf) {
      if (Restore.equals(name)) {
         restoreScene();
      } else if (TellPos.equals(name)) {
         tellScene();
      } else if (RotateTrigger.equals(name)) {
         rotate = isPressed;
      } else if (ToggleTranslate.equals(name)) {
         translate = isPressed;
      } else if (MoveModelTrigger.equals(name)) {
         moveModel = isPressed;
      }
   }


   /*
    * mouse control needs different motion-factors than keys, so this looks
    * pretty ugly.
    * But this is, what I feel "natural" :)
    */
   @Override
   public void onAnalog(String name, float value, float tpf) {
      if (ZoomIN.equals(name)) {
         zoomFactor -= 0.5f * zoomFactor * tpf;
         resizeView();
      } else if (ZoomOUT.equals(name)) {
         zoomFactor += 0.5f * zoomFactor * tpf;
         resizeView();
      } else if (MouseZoomIN.equals(name)) {
         zoomFactor -= 30f * zoomFactor * tpf;
         resizeView();
      } else if (MouseZoomOUT.equals(name)) {
         zoomFactor += 30f * zoomFactor * tpf;
         resizeView();
      }

      if (moveModel) {
         // model will only move up and down from workplace
         // This motion is only to compensate different workpiece height
         if (KeyUp.equals(name)) {
            modelOffset += 0.6 * zoomFactor * tpf;
            moveModel();
         } else if (KeyDown.equals(name)) {
            modelOffset -= 0.6 * zoomFactor * tpf;
            moveModel();
         } else if (MouseMoveUp.equals(name)) {
            modelOffset -= 4 * zoomFactor * tpf;
            moveModel();
         } else if (MouseMoveDown.equals(name)) {
            modelOffset += 4 * zoomFactor * tpf;
            moveModel();
         }
      } else if (rotate) {
         if (KeyLeft.equals(name)) {
            rotation.fromAngleAxis(-0.001f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (KeyRight.equals(name)) {
            rotation.fromAngleAxis(0.001f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (KeyUp.equals(name)) {
            ensureNickLimits(-0.001f * zoomFactor * tpf);
         } else if (KeyDown.equals(name)) {
            ensureNickLimits(0.001f * zoomFactor * tpf);
         } else if (MouseMoveLeft.equals(name)) {
            rotation.fromAngleAxis(0.006f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (MouseMoveRight.equals(name)) {
            rotation.fromAngleAxis(-0.006f * zoomFactor * tpf, Vector3f.UNIT_Y);
            rotateDesk();
         } else if (MouseMoveUp.equals(name)) {
            ensureNickLimits(0.005f * zoomFactor * tpf);
         } else if (MouseMoveDown.equals(name)) {
            ensureNickLimits(-0.005f * zoomFactor * tpf);
         }
      } else if (translate) {
         if (MouseMoveLeft.equals(name)) {
            baseLoc.x += 5f * zoomFactor * tpf;
            shiftBase();
         } else if (MouseMoveRight.equals(name)) {
            baseLoc.x -= 5f * zoomFactor * tpf;
            shiftBase();
         } else if (MouseMoveUp.equals(name)) {
            baseLoc.z += 8f * zoomFactor * tpf;
            shiftBase();
         } else if (MouseMoveDown.equals(name)) {
            baseLoc.z -= 8f * zoomFactor * tpf;
            shiftBase();
         }
      } else {
         if (KeyLeft.equals(name)) {
            baseLoc.x -= 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyRight.equals(name)) {
            baseLoc.x += 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyUp.equals(name)) {
            baseLoc.z -= 0.8f * zoomFactor * tpf;
            shiftBase();
         } else if (KeyDown.equals(name)) {
            baseLoc.z += 0.8f * zoomFactor * tpf;
            shiftBase();
         }
      }
   }


   @Override
   public void simpleInitApp() {
      flyCam.setEnabled(false);
      cam.setParallelProjection(true);
      Vector3f camLoc = new Vector3f(0, 2f * size.y, size.z);

      l.log(Level.INFO, "camera location: " + camLoc);

      cam.setLocation(camLoc);
      cam.lookAt(camLoc.negate(), camDir);

      l.log(Level.INFO, "camera direction: " + cam.getDirection());

      createDesk();
      createTool();
      registerInputs();
      resizeView();
   }


   protected void createDesk() {
      shiftBase = new Node("ShiftBase");
      nickBase  = new Node("NickBase");
      turnTable = new Node("TurnTable");
      modelBase = new Node("ModelBase");

      rootNode.attachChild(shiftBase);
      shiftBase.attachChild(nickBase);
      nickBase.attachChild(turnTable);
      turnTable.attachChild(modelBase);

      // the working area
      Geometry   box             = new Geometry("Box", new WireBox(size.x, size.y, size.z));
      // its our desktop, so create a nice looking surface
      Geometry   ground          = new Geometry("Ground", new Quad(size.x * 2, size.z * 2));
      Material   m               = new Material(assetManager, MATUnshaded);
      Quaternion initialRotation = new Quaternion();

      m.getAdditionalRenderState().setWireframe(true);
      m.setColor(MATColor, ColorRGBA.Green);
      box.setMaterial(m);
      box.setLocalTranslation(0, size.y, 0);

      initialRotation.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y);
      turnTable.rotate(initialRotation);
      turnTable.attachChild(box);

      m = new Material(assetManager, MATUnshaded);
      m.setTexture(MATColorMap, assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
      ground.setMaterial(m);
      ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
      ground.setLocalTranslation(-size.x, 0, size.z);
      turnTable.attachChild(ground);

      createOrigin(modelBase);
   }


   protected void createOrigin(Node parent) {
      // visual use of axis does not follow axis from jme3,
      // so create some arrows to show usage and direction of each axis
      // follow color scheme of blender (x is red, y is green and z is blue)
      Mesh     mesh = new Arrow(new Vector3f(100f, 0, 0));
      Geometry geo  = new Geometry(PrimArrow, mesh);
      Material m    = new Material(assetManager, MATUnshaded);

      m.setColor(MATColor, ColorRGBA.Red);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);

      mesh = new Arrow(new Vector3f(0, 0, -100f));
      geo  = new Geometry(PrimArrow, mesh);
      m    = new Material(assetManager, MATUnshaded);
      m.setColor(MATColor, ColorRGBA.Green);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);

      mesh = new Arrow(new Vector3f(0, 100f, 0));
      geo  = new Geometry(PrimArrow, mesh);
      m    = new Material(assetManager, MATUnshaded);
      m.setColor(MATColor, ColorRGBA.Blue);
      m.getAdditionalRenderState().setLineWidth(3);
      geo.setMaterial(m);
      parent.attachChild(geo);
   }


   protected void createTool() {
      Geometry geo = new Geometry("Tool", new Cylinder(2, 16, 10, 100, true));
      Material m   = new Material(assetManager, MATShowNormals);

      geo.setMaterial(m);
      geo.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
      geo.setLocalTranslation(0, 100, 0);

      modelBase.attachChild(geo);
   }


   protected void ensureNickLimits(float delta) {
      l.log(Level.INFO, "change nick angle by delta: " + delta);
      nickAngle += delta;

      if (nickAngle < nickMinLimit)
         nickAngle = nickMinLimit;
      if (nickAngle > nickMaxLimit)
         nickAngle = nickMaxLimit;
      l.log(Level.INFO, "nick angle (limited) is now: " + nickAngle);

      nick.fromAngleAxis(nickAngle, Vector3f.UNIT_X);
      l.log(Level.INFO, "nick converted to Quaternion: " + nick);
      nickBase.setLocalRotation(nick);
   }


   protected void moveModel() {
      modelBase.setLocalTranslation(0, modelOffset, 0);
   }


   protected void registerInputs() {
      inputManager.addMapping(ToggleTranslate, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
      inputManager.addMapping(MouseMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));
      inputManager.addMapping(MouseMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));
      inputManager.addMapping(MouseMoveUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
      inputManager.addMapping(MouseMoveDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
      inputManager.addMapping(MouseZoomIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
      inputManager.addMapping(MouseZoomOUT, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
      inputManager.addMapping(ZoomIN, new KeyTrigger(KeyInput.KEY_ADD));
      inputManager.addMapping(ZoomOUT, new KeyTrigger(KeyInput.KEY_SUBTRACT));
      inputManager.addMapping(Restore, new KeyTrigger(KeyInput.KEY_R));
      inputManager.addMapping(TellPos, new KeyTrigger(KeyInput.KEY_T));
      inputManager.addMapping(KeyLeft, new KeyTrigger(KeyInput.KEY_LEFT));
      inputManager.addMapping(KeyRight, new KeyTrigger(KeyInput.KEY_RIGHT));
      inputManager.addMapping(KeyUp, new KeyTrigger(KeyInput.KEY_UP));
      inputManager.addMapping(KeyDown, new KeyTrigger(KeyInput.KEY_DOWN));
      inputManager.addMapping(MoveModelTrigger, new KeyTrigger(KeyInput.KEY_LMENU),
            new KeyTrigger(KeyInput.KEY_RMENU), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
      inputManager.addMapping(RotateTrigger, new KeyTrigger(KeyInput.KEY_LSHIFT),
            new KeyTrigger(KeyInput.KEY_RSHIFT), new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));

      inputManager.addListener(this, Restore, ZoomIN, ZoomOUT, MouseZoomIN, MouseZoomOUT, KeyLeft, KeyRight,
            KeyUp, KeyDown, KeyForward, KeyBack, RotateTrigger, MoveModelTrigger, TellPos, ToggleTranslate,
            MouseMoveRight, MouseMoveLeft, MouseMoveUp, MouseMoveDown);
   }


   protected void resizeView() {
      float aspect = (float) cam.getWidth() / (float) cam.getHeight();

      // calculate Viewport (use big limits to avoid clipping)
      cam.setFrustum(-2000, 6000, -aspect * zoomFactor, aspect * zoomFactor, zoomFactor, -zoomFactor);
   }


   /*
    * put values in, that tellScene() printed
    */
   protected void restoreScene() {
      // key trigger is too fast, so have to limit usage to one call
      if (restored) {
         return;
      }
      restored  = true;

      //      INFORMATION TestTurnTable 20:06:51  location: (0.0, 0.0, 1005.16516)
      baseLoc.x = 0;
      baseLoc.y = 0;
      baseLoc.z = 1005.165f;
      shiftBase();

      //    INFORMATION TestTurnTable 20:06:51      nick: -0.047284026
      nickAngle = -0.0472f;
      ensureNickLimits(0);      // nick always uses absolute rotation

      //    INFORMATION TestTurnTable 20:06:51  rotation: (0.0, 0.3626218, 0.0, -0.93195355)
      rotation = new Quaternion(0, 0.3626218f, 0, -0.93195355f);
      // rotateDesk uses relative rotation, so don't use it for restore
      //      rotateDesk();
      // instead use absolute rotation
      turnTable.setLocalRotation(rotation);

      //    INFORMATION TestTurnTable 20:06:51 elevation: 33.33061
      modelOffset = 33.33061f;
      moveModel();

      //    INFORMATION TestTurnTable 20:06:51      zoom: 1592.0188
      zoomFactor = 1592f;
      resizeView();
   }


   protected void rotateDesk() {
      turnTable.rotate(rotation);
   }


   protected void shiftBase() {
      shiftBase.setLocalTranslation(baseLoc);
   }


   protected void tellScene() {
      l.log(Level.INFO, " location: " + shiftBase.getLocalTranslation());
      l.log(Level.INFO, "     nick: " + nickAngle);
      l.log(Level.INFO, " rotation: " + turnTable.getLocalRotation());
      l.log(Level.INFO, "elevation: " + modelOffset);
      l.log(Level.INFO, "     zoom: " + zoomFactor);
   }


   public static void main(String[] args) {
      JmeFormatter formatter      = new JmeFormatter();
      Handler      consoleHandler = new ConsoleHandler();
      consoleHandler.setFormatter(formatter);

      Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
      Logger.getLogger("").addHandler(consoleHandler);
      TestTurnTable app = new TestTurnTable(
            // machine limits given from outside
            new float[] { 0, 900, 0, 1800, -500, 0 });

      app.start();
   }


   private Vector3f              size;
   private Node                  shiftBase;
   private Node                  nickBase;
   private Node                  turnTable;
   private Node                  modelBase;
   private Vector3f              baseLoc;
   private Quaternion            rotation;
   private Quaternion            nick;
   private boolean               rotate;
   private boolean               restored;
   private boolean               translate;
   private boolean               moveModel;
   private float                 modelOffset;
   private float                 nickAngle;
   private float                 zoomFactor       = 1800f;
   private static final float    nickMaxLimit     = 0.5f;
   private static final float    nickMinLimit     = -0.5f;
   private static final Logger   l;
   private static final String   ZoomIN           = "zoomIN";
   private static final String   ZoomOUT          = "zoomOUT";
   private static final String   Restore          = "Restore";
   private static final String   TellPos          = "TellPos";
   private static final String   RotateTrigger    = "trigRotation";
   private static final String   MoveModelTrigger = "trigModelMove";
   private static final String   KeyLeft          = "kLeft";
   private static final String   KeyRight         = "kRight";
   private static final String   KeyUp            = "kUp";
   private static final String   KeyDown          = "kDown";
   private static final String   KeyForward       = "kForward";
   private static final String   KeyBack          = "kBack";
   private static final String   ToggleTranslate  = "MToglTrans";
   private static final String   MouseZoomIN      = "MZoomIN";
   private static final String   MouseZoomOUT     = "MZoomOUT";
   private static final String   MouseMoveRight   = "MMRight";
   private static final String   MouseMoveLeft    = "MMLeft";
   private static final String   MouseMoveUp      = "MMUp";
   private static final String   MouseMoveDown    = "MMDown";
   private static final String   MATUnshaded      = "Common/MatDefs/Misc/Unshaded.j3md";
   private static final String   MATShowNormals   = "Common/MatDefs/Misc/ShowNormals.j3md";
   private static final String   MATColor         = "Color";
   private static final String   MATColorMap      = "ColorMap";
   private static final String   PrimArrow        = "Arrow";
   private static final Vector3f camDir           = new Vector3f(0, 1, 0);
   static {
      l = Logger.getLogger("Test");
   }
}
1 Like

Awesome. You can use switches if you want instead of the if ladder. Slightly more elegant, just remember to break after each case.

Thank you.

I wasn’t aware, that switch for strings works in java. Nice to know :slight_smile:
Same converted to switch uses about double the space (thanks to formatter of eclipse :wink: ), but its easier readable and generates code more efficient.