# lookAt/face direction issue

Hey,

I don’t want to sound repetitive, I know there are a bunch of other lookAt() topics concerning the almost exact thing, but I’ve read through all the forum and I still haven’t got an answer for my problem. I’ve also google’d, searched the FAQ and even tried weird combinations of rotation that really surprised me. As a last resource, I’m letting the community take this unsolvable issue I’m having. I’m really new to all this (began learning JME 3 weeks ago, as for Java, it’s only been 5 months).

Here’s the thing: I’m just testing a simple way of making a Spatial (in this case a cube, real simple) follow the character (player) which in this case is another cube. The player has it’s own movement and all the good stuff. What my mind came up with is to simply look at the direction where the player is and move to that same direction it’s facing in a straight line. When the player moves, it then faces the new position of the player and move to that direction. So far so good.

The problem is with the AI (can’t really call it an AI, can I?). It doesn’t look at the player. It moves to the direction it’s pointing at, though. I’m posting the code below:

[java]package jme3test.helloworld;

import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResults;

import com.jme3.font.BitmapText;

import com.jme3.input.ChaseCamera;

import com.jme3.input.KeyInput;

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.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;

public class HelloJME3 extends SimpleApplication {

public static void main(String[] args) {

new HelloJME3().start();

}

protected Geometry player;

protected Geometry ai;

protected BitmapText guiPosition;

@Override

public void simpleInitApp() {

Box playerBox = new Box(Vector3f.ZERO, 1, 1, 1);

player = new Geometry(“Player”, playerBox);

Material playerMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

playerMaterial.setColor(“Color”, ColorRGBA.Blue);

player.setMaterial(playerMaterial);

Box aiBox = new Box(new Vector3f(10, 1, 0), 1, 1, 1);

ai = new Geometry(“AI”, aiBox);

Material aiMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

aiMaterial.setColor(“Color”, ColorRGBA.Red);

ai.setMaterial(aiMaterial);

Box staticBox = new Box(new Vector3f(-3, 0, -2), 1, 1, 1);

Geometry staticBoxGeom = new Geometry(“Static Box”, staticBox);

Material sbMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

sbMaterial.setColor(“Color”, ColorRGBA.Orange);

staticBoxGeom.setMaterial(sbMaterial);

initKeys();

initCam();

initGuiPos();

rootNode.attachChild(player);

rootNode.attachChild(ai);

rootNode.attachChild(staticBoxGeom);

}

public void initGuiPos() {

guiNode.detachAllChildren();

guiPosition = new BitmapText(guiFont, false);

guiPosition.setSize(guiPosition.getFont().getPreferredSize());

guiPosition.setLocalTranslation(settings.getWidth() - 700, 30, 0);

guiNode.attachChild(guiPosition);

}

public void initCam() {

flyCam.setEnabled(false);

ChaseCamera playerCam = new ChaseCamera(cam, player, inputManager);

playerCam.setTrailingEnabled(true);

playerCam.setSmoothMotion(true);

playerCam.setDragToRotate(false);

}

public void initKeys() {

inputManager.addListener(analogListener, new String[]{“Forward”, “Backward”, “Left”, “Right”});

}

public AnalogListener analogListener = new AnalogListener() {

public void onAnalog(String name, float value, float tpf) {

if(name.equals(“Forward”)) {

Quaternion rotation = player.getLocalRotation();

Vector3f storeVector = new Vector3f();

Vector3f moveVector = new Vector3f(0, 0, value + tpf);

rotation.mult(moveVector, storeVector);

player.move(storeVector);

}

if(name.equals(“Backward”)) {

Quaternion rotation = player.getLocalRotation();

Vector3f storeVector = new Vector3f();

Vector3f moveVector = new Vector3f(0, 0, -(value + tpf));

rotation.mult(moveVector, storeVector);

player.move(storeVector);

}

if(name.equals(“Right”)) {

player.rotate(0, -(tpf + value), 0);

}

if(name.equals(“Left”)) {

player.rotate(0, tpf + value, 0);

}

}

};

@Override

public void simpleUpdate(float tpf) {

CollisionResults collision = new CollisionResults();

Quaternion aiRotation = ai.getLocalRotation();

Vector3f aiStoreVector = new Vector3f();

Vector3f aiMoveVector = new Vector3f(0, 0, tpf*2);

guiPosition.setText(player.getLocalTranslation().toString() );

ai.getLocalRotation().lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);

if(collision.size() > 0)

System.out.println(“Ouch!”);

//if(ai.collideWith(player.getModelBound(), collision) == 0) {

aiRotation.mult(aiMoveVector, aiStoreVector);

ai.move(aiMoveVector);

//}

}[/java]

Keep in mind that I still haven’t finished the collision stuff. I’m focusing on the look at problem. Also take into account that I have used the usual [java]ai.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);[/java] and have also tried the Quaternion method. I don’t know whether I’m missing something or my IDE is bugged.

I also tried to test the “ai” geometry by “looking at” the other static test cube. It doesn’t really point right (I really don’t know why). I’ve read some of the other forum topics related to lookAt() and they all say the same thing: the spatial position and the up vector. I could really have a hand on this, since I can’t seem to get what’s wrong. I would really appreciate if you could explain this as simple as possible, since, as I have already told you, I’m pretty new to jMonkeyEngine.

I appreciate any help. Since I’ll be heading to bed right now, I’ll probably have to get back to you once I get home from school.

Once again, thank you for the attention,

~Renato

I haven’t used .lookAt in a while, but why do you do that?

[java]

ai.getLocalRotation().lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);

[/java]

Simply doing this should work:

[java]

ai.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);

[/java]

It could also be a coordinates system ambiguity. So test it out using:

[java]

ai.lookAt(player.getWorldTranslation(), Vector3f.UNIT_Y);

[/java]

Yeah, mind that Quaternion.lookAt() and Spatial.lookAt() do different things. Spatial.lookAt() actually works directly and in world space, it makes the spatial (in whatever parent/child relation) look at that world location. Quaternion.lookAt() is a bit more complicated, I try to explain it here: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

Always remember that the input “up” vector has to be the correct one for this specific quaternion, so its almost always used like localRotation.lookAt(direction, localRotation.multLocal(Vector3f.UNIT_Y.clone())); This is not the case for spatial.lookAt() where the input up-vector is also in global space.

As I’ve mentioned on my original post, I did use [java]ai.lookAt(player.getWorldPosition(), Vector3f.UNIT_Y);[/java], but after I noticed it didn’t work, I started trying weird combinations like the one on the original post. Have you tried running it on your IDE? Maybe it’s just a bug on mine?

By the way, thanks for replying.

lookAt is broken in no version of joe, this certainly is a problem in your code.

Alright. I’ve “fixed” your problem. It’s a freebie, but I won’t give you an explanation. I’ll leave it to you to discover how and why.

The only thing I’ll tell you is that once the AI has caught up with the player you’ll see a “teleporting” AI box. You might wonder why and I’ll tell you. When you do a lookAt, the spatial will instantly turn. If you want to have it smooth you’ll have to do it another way. HINT: check the modifications I made.

Feel free to ask questions. I’ll answer as much as I can when I’m around.

Here’s the code:

[java]

public static void main(String[] args) {

new HelloJME3().start();

}

protected Geometry player;

protected Geometry ai;

protected BitmapText guiPosition;

@Override

public void simpleInitApp() {

Box playerBox = new Box(Vector3f.ZERO, 1, 1, 1);

player = new Geometry(“Player”, playerBox);

Material playerMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

playerMaterial.setColor(“Color”, ColorRGBA.Blue);

player.setMaterial(playerMaterial);

player.setLocalTranslation(1, 1, 1);

Box aiBox = new Box(new Vector3f(10, 1, 0), 1, 1, 1);

ai = new Geometry(“AI”, aiBox);

Material aiMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

aiMaterial.setColor(“Color”, ColorRGBA.Red);

ai.setMaterial(aiMaterial);

ai.setLocalTranslation(10, 1, 0);

Box staticBox = new Box(new Vector3f(-3, 0, -2), 1, 1, 1);

Geometry staticBoxGeom = new Geometry(“Static Box”, staticBox);

Material sbMaterial = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

sbMaterial.setColor(“Color”, ColorRGBA.Orange);

staticBoxGeom.setMaterial(sbMaterial);

initKeys();

initCam();

initGuiPos();

rootNode.attachChild(player);

rootNode.attachChild(ai);

rootNode.attachChild(staticBoxGeom);

}

public void initGuiPos() {

guiNode.detachAllChildren();

guiPosition = new BitmapText(guiFont, false);

guiPosition.setSize(guiPosition.getFont().getPreferredSize());

guiPosition.setLocalTranslation(settings.getWidth() - 700, 30, 0);

guiNode.attachChild(guiPosition);

}

public void initCam() {

flyCam.setEnabled(false);

ChaseCamera playerCam = new ChaseCamera(cam, player, inputManager);

playerCam.setTrailingEnabled(true);

playerCam.setSmoothMotion(true);

playerCam.setDragToRotate(false);

}

public void initKeys() {

inputManager.addListener(analogListener, new String[]{“Forward”, “Backward”, “Left”, “Right”});

}

public AnalogListener analogListener = new AnalogListener() {

@Override

public void onAnalog(String name, float value, float tpf) {

if (name.equals(“Forward”)) {

System.out.println("Value: " + value);

Vector3f v = new Vector3f(player.getWorldRotation().

multLocal(Vector3f.UNIT_Z.clone()));

// set new coord to move to

Vector3f newCoords = v.mult(value + tpf);

player.move(newCoords);

}

if (name.equals(“Backward”)) {

Vector3f v = new Vector3f(player.getWorldRotation().

multLocal(Vector3f.UNIT_Z.clone()));

// set new coord to move to

Vector3f newCoords = v.mult(value + tpf);

player.move(newCoords.negate());

}

if (name.equals(“Right”)) {

player.rotate(0, -(tpf + value), 0);

}

if (name.equals(“Left”)) {

player.rotate(0, tpf + value, 0);

}

}

};

@Override

public void simpleUpdate(float tpf) {

CollisionResults collision = new CollisionResults();

guiPosition.setText(player.getLocalTranslation().toString());

ai.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);

Vector3f v = new Vector3f(ai.getWorldRotation().multLocal(Vector3f.UNIT_Z.clone()));

Vector3f newCoords = v.mult(tpf);

ai.move(newCoords);

if (collision.size() > 0) {

System.out.println(“Ouch!”);

}

//if(ai.collideWith(player.getModelBound(), collision) == 0) {

// aiRotation.mult(aiMoveVector, aiStoreVector);

//}

}

[/java]

Thanks for the reply. I’m aware of the teleporting issue. I actually stumbled upon it when trying some weird lookAt modifications. Your code is of huge help. I’ve only got two questions.

1. Why not use [java]rotation.mult(moveVector, storeVector);[/java]? Is there any difference from what you’ve wrote? [java]Vector3f v = new Vector3f(ai.getWorldRotation().multLocal(Vector3f.UNIT_Z.clone()));

Vector3f newCoords = v.mult(tpf);[/java]

2. My intention is to make the AI face the player (for example: a “heat-seeking” arrow pointing to the player. It always has to have one side of it pointed to the player.) and make it move towards it (both x, z and y if necessary). Is there a way to do it with my existing code (modified to actually work by using your code of course)? I’ve taken a look at your modifications but I can’t seem to understand the last part of it, where you multiply the AI world rotation vector to its local (?) and to tpf (which I suppose makes it move by adding the vectors).

I’m sorry if all of this sounds newbie-sh, I really want to learn more about JME. Could you make it simple so I don’t have to keep asking the same thing over and over again?

~Renato

1 - First, chaining should always be used or as much as possible. During early development you can store those values like you did but once you’ve got the math figured out and it’s working as intended, using chaining. If you’re not sure what chaining is, it’s like this:

vector.mult(…).add(…).mult(…).length()… etc. You get the idea. You “chain” methods one behind the other. In recent tests I did with vector assignment, chaining proved faster by a wide margin. The test was done using 10 000 000 iterations 50 times for three sort of assignment, chained, multiple variables and field by field. Of course if your app doesn’t do this often you won’t see much of a difference, but it’s good practice to have fast code as much as you can.

The only thing that is disagreeable with chaining is it’s sometimes unclear. So, commenting a long chain is a must because if you don’t, you might end up being confused if you have to modify it later. You don’t want to redo your steps from scratch to understand a code line you did 4-5 months before…

2 - My guess is you didn’t notice, but your AI box -is- turning toward the player. It’s hard to see because all your faces are the same color, but try it using a different mesh. As for the rotation around all axis, it’s pretty much the same as how it’s done in the code I gave you, just use the proper axis. To start with, use a temporary vector/quaternion to store the result of one rotation, then add all the results from each axis together. Off the top of my head I can’t remember if addition is commutative with quaternions, I know it’s not with multiplication, so ordering might be important. Multiply quaternion x by y will give a different result than y by x!

The last line is pretty simple. It works this way:

[java]

// ai.getWorldRotation()

// Since you have to rotate a quaternion, you have to work on World Coordinates.

// .multLocal(Vector3f.UNIT_Z.clone())

// That’s the direction you’re facing, that you multiply to your local coordinates system (so it’s translated to world coords).

// So this is actually where you’ll end up looking at.

// v.mult(tpf)

// Here I could’ve used chaining. I didn’t because it’s the way you did it. Done this way mainly for visual

// reference.

// So by multiplying by tpf, we’re getting the result of where we want our spatial to look at. We could have used a speed modifier.

Vector3f v = new Vector3f(ai.getWorldRotation().multLocal(Vector3f.UNIT_Z.clone()));

Vector3f newCoords = v.mult(tpf);

// in short the line above says: Take my rotation and translate it to world coordinates and multiply it by a scalar to get the amount

// of rotation we want to achieve.

// Finally, we “move” it. Actually we should’ve rotated it, not moved it “ai.rotate(newCoords)”:

ai.move(newCoords);

[/java]

I think the above is accurate. If not I’m sure someone will correct me.

@madjack First of all, sorry for taking so long to respond. jMonkeyEngine.org was apparently down for me. I’ll try to keep all your tips in mind when creating my own code. Thank you very much!

@monkeyspotted

The site has been down for almost 24 hours. Was getting withdrawals symptoms. :s

You’re welcome. BTW, there’s a tutorial @normen made about Quaternions, vectors and game Math in general. It’s very informative and you should read it.

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies