Limit amount of rotation


hi there, i've been making one of my object to start rotating upon key press.
when i press a key (once) the object start rotating.
so far so good, but, i wanted the object to stop rotation when i decide it.
is there's any way to setup something like:  " rotate until reaching 90deg from original state"
or a different angle too, i could need my object to rotate 60deg, or 120deg .. or any other target angle..

looking at jme-test's TestTube.java
i've found the following very interesting:


[.....]
    private Quaternion rotQuat = new Quaternion();
    private float angle = 0;
    private Vector3f axis = new Vector3f(1, 1, 0).normalizeLocal();
    private Tube t;
[.....]
    protected void simpleUpdate() {
        if (timer.getTimePerFrame() < 1) {
            angle = angle + (timer.getTimePerFrame() * 1);
            if (angle > 360) {
                angle = 0;
            }
        }
        rotQuat.fromAngleNormalAxis(angle, axis);
        t.setLocalRotation(rotQuat);
    }
[.....]



looking at TestTube.java example, i've done the same in my own project, but, i've found the float "angle" misleading.
unless i'm wrong "angle is just a timer. here the rotation will not perform until it reaches 360deg
but instead, the rotation will continue until the timer reach 360.
if i set: if (angle > 2) { ...} the rotation will happen until "angle timer = 2, which isn't a 2deg angle..

how can i setup a real angle based limit for my rotation ? (keep rotating until 90deg or 120deg whatever is reached for example)

tnx

There is a bug in the test by the looks of it.

fromAngleNormalAxis takes an angle in radians not degrees.

If using degrees (as it is obviously meant to), multiply by FastMath.DEG_TO_RAD

This will make the rotation really slow so probably make that 1 into a 10 to speed it up.



I think this is roughly what it should be:


[.....]
    private Quaternion rotQuat = new Quaternion();
    private float angle = 0;
    private Vector3f axis = new Vector3f(1, 1, 0).normalizeLocal();
    private Tube t;
[.....]
    protected void simpleUpdate() {
        if (timer.getTimePerFrame() < 1) {
            angle = angle + (timer.getTimePerFrame() * 10);
            if (angle > 360) {
                angle = 0;
            }
        }
        rotQuat.fromAngleNormalAxis(angle * FastMath.DEG_TO_RAD, axis);
        t.setLocalRotation(rotQuat);
    }
[.....]



The basic idea is fine - multiplying by the timer is just to make sure the rotation speed is not tied to your framerate.

wow that seems to work real fine,

i've just had a quick test but thanks !



now i can decide the target rotation angle and have it stops when it reaches it…

great !  :mrgreen:



any idea how to overcome the slowliness though?

i did that:

angle = angle + (timer.getTimePerFrame() * 20);

but i'm nore sure i should do it like this ?

or it's ok?

btw,  *20 is still a bill slow, *40 would be fine i guess…

Yes you can use any number like that.



It works like this:

If you do angle = angle + 10, you are saying every frame rotate the object by 10 degrees.

But that means that on a slow computer the object will rotate slowly, and on a fast computer it could rotate really fast.

timer.getTimePerFrame() gives you the time per frame as a fraction of one second.

So if you multiply by that you are now doing degrees per second instead of degrees per frame. You get the same speed of movement even with different frame rates.



ie.

angle = angle + (timer.getTimePerFrame() * degreesPerSecond);

@alric

tnx for the explanations :slight_smile:



i have it working now except for 1 thing, the rotation doesn't use the object current rotation status,

when i press my key, first the object rotation status reset to default then rotation starts and when it reaches the target angle,

instead of staying there, it finaly revert back to rotation status before rotation started.



to try to make myself clearer:

when i start my project, the object is facing west, so in init() i've put that:

b.getLocalRotation().set(new Quaternion().fromAngleAxis( - FastMath.PI/2, new Vector3f(0,1,0)) );

so the object faces now north, which is what i wanted.

i can move and all , it works fine. then i press key to start rotation and the following happens:

oject reset to facing west (original rotation status before b.getLocalRotation().set(…)),

starts a 90deg rotation towards south then when this is done,

the object revert back to rotation status before rotation began: facing north ( b.getLocalRotation().set(…) ).



how can i make my rotation to begin depending current rotation status ?



i though i could put instead of "axis" b.getLocalRotation() like:

change this:  rotQuat.fromAngleNormalAxis(angle* FastMath.DEG_TO_RAD, axis);

to this:   rotQuat.fromAngleNormalAxis(angle* FastMath.DEG_TO_RAD, b.getLocalRotation());



but because, b.getLocalRotation() return Quaternion

and Quaternion want vector as arguments.

( rotQuat.fromAngleNormalAxis(angle* FastMath.DEG_TO_RAD, axis);    axis is vector )

i cannot seem to be allowed to put b.getLocalrotation() here instead of axis



or am i completely wrong, this wouldn't work anyway?

how could i make my rotation starts from object's current rotation status?



tnx






There's a few ways you could do that. Probably the simplest for many games is just to store the current rotation of your <whatever>. So then you just add that to the angle you are rotating by.



Another way is instead of

b.getLocalRotation().set(rotQuat);

use


b.getLocalRotation().multLocal(rotQuat);



This will rotate it from where it already is, so you would want to use the same angle every time or it will get faster and faster. The problem with this way is that you don't know what the angle is right now.

Another option is slerp. This lets you specify a start and end rotation, and how far between them you are.


b.getLocalRotation().slerp(startQuat, endQuat, 0.5f);


Would put you half way between the start and end.

thanks!

i tryed the b.getLocalRotation().multLocal(rotQuat);

but couldn't find a way to make the rotation stop… it kept rotating again and again.

and for reducing the speed i had to use:

angle = angle + (timer.getTimePerFrame() * 0.001f);

if there was a way to make it stop at a defined target angle that would be cool



about slerp,

b.getLocalRotation().slerp(startQuat, endQuat, 0.5f);

how would you setup startQuat and endQuat to, in order to make a 90deg rotation ? (or other angles for example?)



i tryed this: (among other failled attempts)

Quaternion startQuat=b.getLocalRotation();

Quaternion endQuat=b.getLocalRotation().set(new Quaternion().fromAngleAxis( - FastMath.PI/2, new Vector3f(0,1,0)) );



b is my box object. i tryed to put it's current rotation status in startQuat

and then in endQuat an attempt at doing  90deg  …



it didn't seemed to work, it fact it did doing at all… no rotation

i must had it wrong.

could you advice some way of setting up thoses 2 quaternion? (startQuat and endQuat) … what should i put in there?





tnx

I think you just need to change that third parameter. It is how far through the movement you are. 0 is just started, 1 is finished, 0.5 is half way etc. With slerp you update that instead of keeping track of the angle.



Try something like this to get the initial values (only done once, at the start of the turn):



initialRotation = b.getLocalRotation();
turn90.fromAngleAxis(90 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y);
eventualRotation = initialRotation.mult(turn90);
float turnAmount = 0;



Then something like this each update:


if(timer.getTimePerFrame() < 1f) turnAmount += timer.getTimePerFrame(); else turnAmount = 0;
b.getLocalRotation().slerp(initialRotation, eventualRotation, turnAmount);



I haven't tried that but it should be about right.

i tryed like this:




    private Quaternion initialRotation = new Quaternion();
    private Quaternion turn90 = new Quaternion();
    private Quaternion eventualRotation = new Quaternion();
    private float turnAmount = 0;

[.....]

            if (KeyBindingManager.getKeyBindingManager().isValidCommand("rotateRight", false))  {
                rotateRight=true;
                initialRotation = b.getLocalRotation();
                turn90.fromAngleAxis(90 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y);
                eventualRotation = initialRotation.mult(turn90);               
            }
            if (rotateRight){
                rotateLeft=false;
                if(timer.getTimePerFrame() < 1f) turnAmount += timer.getTimePerFrame(); else turnAmount = 0;
                b.getLocalRotation().slerp(initialRotation, eventualRotation, turnAmount);
            }



it works well but only 3 or 4 times.. the 5th time i press the key it goes bad.
it starts spinning like mad, or just go outside the boundaries of the level..

how i can tell when a rotation has finished so i can turn my flag off ?

i need to set rotateRight=false; at some point…



also i see speed increase each time i rotate…

maybe it's because i still have the rotateRight flag which is never turned off?



tnx

i tried a few things and this worked for me:

  • when you press A or D, set a variable toRotate to 90 and isRotationg to true
  • in the update cycle, check if you are rotating, if yes, calculate the amount of degree you rotate in this update cycle
  • subtract this from the toRotate value
  • create a new quaternion with this amount negative or positive depending on the direction and apply it to your char



    the good thing about this, is its easy to see when the rotation is ended



            // left rotation code
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("rotateLeft", false)) {
                if (isRotating) {
                    System.out.println("ignoring keypress while rotating");
                } else {
                    isRotating = true;
                    toRotate = ROTATION_STEP;  // 90 or 45
                    dir = Direction.LEFT;
                }
            }




            if (isRotating) {
                float value = 0;
                // look in which direction we turn, and calculate
                // how much we should turn in this update cycle
                if (dir == Direction.RIGHT) {
                    value = rotationSpeed*tpf*-1;
                    toRotate += value;
                } else {
                    value = rotationSpeed*tpf;
                    toRotate -= value;
                }
                System.out.println("Degrees toRotate: " +toRotate);
               
                // have we finished rotating ?
                if (toRotate <= 0) {
                    // rotation has reached its end
                    isRotating = false;
                } else {
                    // not yet reached the end position, continue rotation
                    // create a Quaternion which represents the Rotation
                    // for this Cycle
                    tmpAngles[0] = 0;                            // X
                    tmpAngles[1] = value * FastMath.DEG_TO_RAD;  // Y
                    tmpAngles[2] = 0;                            // Z
                    tmpRotationQuat.fromAngles(tmpAngles);
                   
                    // multiply the players current rotation with the rotation
                    // we want to do in this update cycle
                    playerNode.getLocalRotation().multLocal(tmpRotationQuat);
                }
            }



tho whole thing:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.CollisionTree;
import com.jme.bounding.CollisionTreeManager;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.intersection.CollisionData;
import com.jme.intersection.CollisionResults;
import com.jme.intersection.TriangleCollisionResults;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.util.export.binary.BinaryImporter;
import com.jmex.model.converters.FormatConverter;
import com.jmex.model.converters.ObjToJme;

public class HelloModelLoading extends SimpleGame {

    private final float ROTATION_STEP = 45;
   
    private Node playerNode = new Node("player Node");
    private Spatial map;
    private CollisionResults results;
    private CollisionData oldData;

    // player run speed
    private float playerSpeed = 3f;
    // player rotation speed
    private float rotationSpeed = 100f;
   
    private Vector3f lastPosition;

    // temporary float array to store rotation angles
    private float[] tmpAngles = new float[3];
    // temporary quaternion used to rotate
    private Quaternion tmpRotationQuat = new Quaternion();
    // are we rotationg currently ?
    private boolean isRotating = false;
    // degrees to rotate
    private float toRotate = 0;
   
    private enum Direction {
        RIGHT,
        LEFT;
    }
    Direction dir;
   
    public static void main(String[] args) {
        HelloModelLoading app = new HelloModelLoading();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        Logger.getLogger("").setLevel(Level.SEVERE);
        app.start();
    }

   
    protected void simpleInitGame() { 
        display.setTitle("test dungeon");
        CollisionTreeManager.getInstance().setTreeType(CollisionTree.Type.AABB);
        results = new TriangleCollisionResults();     

        URL folder= HelloModelLoading.class.getClassLoader().getResource("data/");
        URL model = HelloModelLoading.class.getClassLoader().getResource("data/test01.obj");
        FormatConverter converter=new ObjToJme();
        converter.setProperty("mtllib", folder);
        converter.setProperty("texdir", folder);

        ByteArrayOutputStream BO=new ByteArrayOutputStream();
       
        try {
            //mapNode = new Node("map node");
            converter.convert(model.openStream(), BO);
            map=(Spatial) BinaryImporter.getInstance().load(new ByteArrayInputStream(BO.toByteArray()));
            map.setLocalScale(.1f);
            map.setModelBound(new BoundingBox());
            map.updateModelBound();

            //player = new Cylinder("box", 10, 25, 1, 1,true);
            //player.setLocalRotation( new Quaternion().fromAngleAxis( - FastMath.PI/2, new Vector3f(1,0,0)) );
            Spatial player = new Box("box", new Vector3f(0,5,0), 1, 1 ,1);
            player.setLocalScale(0.1f);
            player.setModelBound(new BoundingBox());
            player.updateModelBound();
           
            playerNode.getLocalRotation().set(new Quaternion().fromAngleAxis( - FastMath.PI/2, new Vector3f(0,1,0)) );
            playerNode.setLocalTranslation(0, 0.1f, 0);
            playerNode.attachChild(player);
           
            // attach player and map to the scene
            rootNode.attachChild(playerNode);
            rootNode.attachChild(map);

        } catch (Exception e) {   // Just in case anything happens
            System.out.println("Damn exceptions! O_o n" + e);
            e.printStackTrace();
            System.exit(0);
        }

        KeyBindingManager.getKeyBindingManager().set("moveFwd", KeyInput.KEY_W);
        KeyBindingManager.getKeyBindingManager().set("moveBwd", KeyInput.KEY_S);
        KeyBindingManager.getKeyBindingManager().set("rotateLeft", KeyInput.KEY_A);
        KeyBindingManager.getKeyBindingManager().set("rotateRight", KeyInput.KEY_D);
        KeyBindingManager.getKeyBindingManager().set("strafeLeft", KeyInput.KEY_Q);
        KeyBindingManager.getKeyBindingManager().set("strafeRight", KeyInput.KEY_E);
        KeyBindingManager.getKeyBindingManager().set("return", KeyInput.KEY_RETURN);

        // create a Camera Node and attach it to the player node
        // move the camera a bit behind and above the player
        CameraNode camNode = new CameraNode("", cam);
        playerNode.attachChild(camNode);
        camNode.setLocalTranslation(0, 0.7f, -0.5f);
       
        lastPosition = new Vector3f();
        //disable mouselook:
        //input = new InputHandler();
       
        //prevent "look through walls glitch
        cam.setFrustumPerspective(45.0f, (float) display.getWidth() / (float) display.getHeight(), 0.002f, 350);
    }

    protected void simpleUpdate() {
        //make collision box invisible
        //player.setCullHint(CullHint.Always);

        //collision stuff
        results.clear();           
        playerNode.findCollisions(rootNode, results);

        if (results.getNumber() <= 0) {
            System.out.printf("don't cross the dungeon limits dammitn");
            System.exit(0);
        }
        oldData = results.getCollisionData(0);

        if (oldData.getTargetTris().size() == 0) {

            //store player last known working location
            lastPosition.set(playerNode.getLocalTranslation());

            // the boolean at the end is for keypress repeat
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("moveFwd", true)) {
                playerNode.getLocalTranslation().x += cam.getDirection().x * playerSpeed * timer.getTimePerFrame();
                playerNode.getLocalTranslation().z += cam.getDirection().z * playerSpeed * timer.getTimePerFrame();
            }
           
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("moveBwd", true)) {
                playerNode.getLocalTranslation().x -= cam.getDirection().x * playerSpeed * timer.getTimePerFrame();
                playerNode.getLocalTranslation().z -= cam.getDirection().z * playerSpeed * timer.getTimePerFrame();
            }
          
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("strafeLeft", true)) {     
                Vector3f sideways = cam.getLeft();
                playerNode.getLocalTranslation().x += sideways.x * playerSpeed * timer.getTimePerFrame();
                playerNode.getLocalTranslation().z += sideways.z * playerSpeed * timer.getTimePerFrame();

            }
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("strafeRight", true))  {
                Vector3f sideways = cam.getLeft();
                playerNode.getLocalTranslation().x -= sideways.x * playerSpeed * timer.getTimePerFrame();
                playerNode.getLocalTranslation().z -= sideways.z * playerSpeed * timer.getTimePerFrame();
            }     
          
            // left rotation code
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("rotateLeft", false)) {
                if (isRotating) {
                    System.out.println("ignoring keypress while rotating");
                } else {
                    isRotating = true;
                    toRotate = ROTATION_STEP;
                    dir = Direction.LEFT;
                    System.out.println("Target Direction set to: " +dir
                                       +"toRoate set to" +ROTATION_STEP);
                }
            }
           
            // right rotation code
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("rotateRight", false))  {
                if (isRotating) {
                    System.out.println("ignoring keypress while rotating");
                } else {
                    isRotating = true;
                    toRotate = ROTATION_STEP;
                    dir = Direction.RIGHT;
                    System.out.println("Target Direction set to: " +dir
                                       +"toRoate set to" +ROTATION_STEP);
                }
            }

            // check if we should turn
            if (isRotating) {
                float value = 0;
                // look in which direction we turn, and calculate
                // how much we should turn in this update cycle
                if (dir == Direction.RIGHT) {
                    value = rotationSpeed*tpf*-1;
                    toRotate += value;
                } else {
                    value = rotationSpeed*tpf;
                    toRotate -= value;
                }
                System.out.println("Degrees toRotate: " +toRotate);
               
                // have we finished rotating ?
                if (toRotate <= 0) {
                    // rotation has reached its end
                    isRotating = false;
                } else {
                    // not yet reached the end position, continue rotation
                    // create a Quaternion which represents the Rotation
                    tmpAngles[0] = 0;                            // X
                    tmpAngles[1] = value * FastMath.DEG_TO_RAD;  // Y
                    tmpAngles[2] = 0;                            // Z
                    tmpRotationQuat.fromAngles(tmpAngles);
                   
                    // multiply the players current rotation with the rotation
                    // we want to do in this update cycle
                    playerNode.getLocalRotation().multLocal(tmpRotationQuat);
                }
            }
           
            if (KeyBindingManager.getKeyBindingManager().isValidCommand("return", true)) {
                //report current angle
                playerNode.getLocalRotation().toAngles(tmpAngles);
                float currentAngle = tmpAngles[1] * FastMath.RAD_TO_DEG;
                System.out.println("current angle: " +currentAngle);
            }
        }
       
        if (oldData.getTargetTris().size() > 0) {
            System.out.println("Collission - reset position");
            playerNode.getLocalTranslation().set(lastPosition);
        }
    }
}