How can I calculate the rotation value of a model to look at a position?


#1

how can I calculate the rotation value of a model to look at a position?
I know that is there a look At method in the spatial class. but I need to have the exact value of rotation in the Y-axis to rotate the model animated and smoothly. not in one step and fast like sp.lookat().


#2

For smooth rotations, you could still use the .lookAt() method to get the full rotation, and then you can use the slerp method to interpolate to that value each frame based on tpf

            rotateQuat = new Quaternion();
            rotateQuat.lookAt(lookAtPoint, upVar);

            Quaternion currentQuat = model.getWorldRotation();
            
            rotateQuat.slerp(currentQuat, 1 - (tpf * (rotateSpeed) ));
            
            model.setLocalRotation(rotateQuat);

Something like this - although it may vary depending on whether your model is a direct child of the rootNode, or if it has one or more parent Nodes that have been rotated


#3

why spatial.lookAt() method works correctly (it rotation is independent of any number of parents and their rotation) but I can’t use it in this way?

 public static float getLookAtPos(Node node, Vector3f position) {
        Vector3f upVector = Vector3f.UNIT_Y;
        float y = 0;

        Vector3f worldTranslation = node.getWorldTranslation();

        Vector3f compVecA = new Vector3f();
        compVecA.set(position).subtractLocal(worldTranslation);
        node.getLocalRotation().lookAt(compVecA, upVector);

        if (node.getParent() != null) {
            Quaternion rot = new Quaternion();
            rot = rot.set(node.getParent().getWorldRotation()).inverseLocal().multLocal(node.getLocalRotation());
            rot.normalizeLocal();
            y = rot.toAngleAxis(upVector);
        }

        return y;
    }

and use it like this:
head.rotate(0, getLookAtPos(head, new Vector3f(-34, 0, 1)), 0);

and the head is a Node with some child and body as parent and body is a child of the root Node. all of them may rotate in any axes. rootNode->body->head.

I need to know how much rotate the head on the Y-axis to look at a position.


#4

You have a big misunderstanding about what an “angle axis” is. This code is actually corrupting the UNIT_Y vector because it will write its result to upVector… which you’ve set to UNIT_Y.

If you want to know how far a spatial is rotated on the y axis then get the angles and grab the value… then deal with the 100 other problems you will have with this approach.

As recommended, it’s better to stay with Quaternions. Figure out what your look QUATERNION needs to be then adjust that to the spatial’s local space to interpolate.

To adjust a world rotation to spatial-local rotation, multiply it by the inverse of the parent rotation.

Quaternion localRot = spatial.getParent().getWorldRotation().inverse().mult(worldRot);

…or something like that.

Even dealing with Euler angles as you are trying to do, you will have the same problem with world space versus local space… as the local y axis is not going to be the same as the global y axis.


#5

someone can fix that code for me? I tried 3 days and I can’t do it :frowning:


#6

Welcome to the frustrating world of com.jme3.math.

Here’s what’s in my Jme3utilities library:

    /**
     * Alter the world orientation of a spatial.
     *
     * @param spatial spatial to reorient (not null)
     * @param worldOrientation desired world orientation (not null, unaffected)
     * @throws IllegalArgumentException if the spatial is a geometry with
     * ignoreTransform=true OR the parent's world orientation is not invertible
     */
    public static void setWorldOrientation(Spatial spatial,
            Quaternion worldOrientation) {
        if (worldOrientation == null) {
            throw new IllegalArgumentException("world orientation cannot be null");
        }
        if (isIgnoringTransforms(spatial)) {
            throw new IllegalArgumentException("transform ignored");
        }

        Spatial parent = spatial.getParent();
        if (parent == null) {
            spatial.setLocalRotation(worldOrientation);
        } else {
            Quaternion rotation = inverseOrientation(parent);
            rotation.multLocal(worldOrientation);
            spatial.setLocalRotation(rotation);
        }
    }

    /**
     * Test whether a spatial is a geometry with ignoreTransform=true.
     *
     * @param spatial spatial to test (unaffected)
     * @return true if the spatial ignores transforms, otherwise false
     */
    public static boolean isIgnoringTransforms(Spatial spatial) {
        boolean result = false;
        if (spatial instanceof Geometry) {
            Geometry geometry = (Geometry) spatial;
            if (geometry.isIgnoreTransform()) {
                result = true;
            }
        }

        return result;
    }

    /**
     * Construct the inverse of a spatial's world orientation, the quaternion
     * that undoes all its rotations.
     *
     * @param spatial spatial to analyze (not null, unaffected)
     * @return new instance
     * @throws IllegalArgumentException if the spatial's world orientation is
     * not invertible
     *
     */
    public static Quaternion inverseOrientation(Spatial spatial) {
        Quaternion forward = spatial.getWorldRotation();
        Quaternion result = forward.inverse();
        if (result == null) {
            throw new IllegalArgumentException("orientation not invertible");
        }

        return result;
    }

To obtain the worldOrientation, you’d probably use Quaternion.lookAt() but note that it’s only useful if you want to point your local Z axis at something while keeping your local Y axis pointed upward. If you want to point some other axis, or use some other criterion to resolve ambiguity, lookAt() won’t help you.


#7

I want to look at a point and get the Y value of rotation, not rotate the spatial. is there any method to do that in Jme3Utilities library?


#8

Is the rotation is entirely in the X-Z plane, or does it have other components as well?


#9

yes it is in the X-Z plane.


#10

Do you define the angle relative to some world axis? Or is it relative to some local axis of the observer?


#11

Why do you want the Y value of rotation?

…because your answer to that question will almost always lead back to us telling you that you don’t need the value. It will be nothing but trouble.


#12


#13

Is that Z axis the tank’s local +Z or the world’s +Z?


#14

I need the Y rotation to rotate the head of tank animated not fast in one step.

I need the Y value to use it in this way:

public void head_to(float deg) {

    Quaternion qua = new Quaternion().fromAngleAxis(FastMath.DEG_TO_RAD * deg, new Vector3f(0, 1, 0));
    Tween rot = SpatialTweens.rotate(head, head.getLocalRotation(), qua, 1f);
    anim.add(Tweens.smoothStep(rot));

    turning_sound.playInstance();
}

#15

the tank local z


#16

Yes, and angle is not the best way to do that… then you need to deal with wrapping, errors in the angle extraction code, etc… where you could pretty easily just “steer left if it’s left” and “steer right if it’s right” and that’s super-trivial dot-product based code. Available in any steering example.


#17

because the target is not always at left or right it may be in any angle of 360 degree in…


#18

Referring back to my example, this line of code will do that for you, and then you do not need the y value at all

Notice how tpf is used to calculate the slerp value each frame, very similar to how you would multiply a movement vector by tpf and a constant speed value each frame to make it move smoothly.


#19

If you can only turn left or right to get there… then the target is either “turn to the left” or “turn to the right”.

How would you calculate that with a y angle?

With a couple of dot products it’s simple.

Given: Spatial target, Spatial player… we want to figure out if player should turn left or right:

// Find the direction we want
Vector3f relativeDir = target.getWorldTranslation().subtract(player.getWorldTranslation()).normalizeLocal();

// Find the direction we're looking and the left vector
Vector3f fwdDir = player.getWorldTranslation().mult(Vector3f.UNIT_Z);
Vector3f leftDir = player.getWorldTranslation().mult(Vector3f.UNIT_X):

// Is it in front of us or behind us and how much?
float front = fwd.dot(relativeDir);

// Is it to the left or right and how much?
float left = leftDir.dot(relativeDir);
// left < 0, target is to the right, left > 0 target is to the left

// If it's behind us then just max out left or right
if( front < 0 ) {
    left = FastMath.sign(left);
    // Except if it's _exactly_ behind us the just pick left or right
    if( left == 0 ) {
        left = 1;
    }
}

// Now turn left/right scaled by the left value -1 = max right turn, 1 = max left turn... 0.5 = half speed left turn, etc.

#20

I believe what Paul and Ryan said is true: if you just want to aim the turret by turning it at a constant rate, you don’t need the angle, just the sign (+/-) of the angle, which can be determined by a dot product. However, I can imagine valid reasons why you might want to know the magnitude of the angle as well:

  1. you might want the turret to accelerate and decelerate as it turns
  2. you might want AI to choose between multiple targets based on their angles
  3. you might use the turn angle to provide hints to the player (“target closing at 4 o’clock”)

One simple approach to calculating the angle begins with transforming the target’s location into the turret’s local coordinates. Let targetInWorld be the target’s location in world coordinates and turretToWorld be the Transform that converts the turret’s local coordinate to world coordinates. If you were using spatials as game objects (not recommended!) you might obtain these as follows:

Vector3f targetInWorld = target.getWorldTranslation(); // alias
Transform turretToWorld = turret.getWorldTransform(); // alias

The “alias” comments are to remind you that these variables reference internal data structures that should not be modified directly.

The transformation to turret coordinates looks like this:

Vector3f targetInTurret = turretToWorld.transformInverseVector(targetInWorld, null);

And the turn angle (in radians, measured from the local +Z per your diagram) would be:

float turnAngleRadians = FastMath.atan2(x, z);

atan2() returns a value in the range (-pi, pi). If you want a positive angle (0, 2 * pi), simply add 2*pi to any negative angle:

if (turnAngleRadians < 0f) {
    turnAngleRadians += FastMath.TWO_PI;
}

To convert to radians to degrees, multiply by 180/pi:

float turnAngleDegrees = FastMath.RAD_TO_DEG * turnAngleRadians;

I hope that helps.