It's still not evident because the head angle is not the same as the real-world angle between the character and the object it's looking at, the character's facing direction needs to be taken into account, and we also need to take care not to break the character's neck by looking completely backwards, for example.
Since this is an interesting problem, I tried to write a small program to make a character always look at a target object. Here is the result. You can test it with any human model (I used one from the Dark GDK samples collection) if you know the number of the head limb.
This works in most cases, but it can produce wrong head direction with certain angles, probably because of stepping over 360 degrees which is always difficult to handle.
#include "DarkGDK.h"
const int idModel = 1;
const int idTarget = 2;
const int idHead = 12;
const int idTexImage = 1;
const float limitAngle = 85; // how much the model can turn its head sideways
char* ModelName = "L-Knight-Static.x";
char* TextureName = "knight.dds";
#define DIK_A 0x1E
#define DIK_D 0x20
void DarkGDK (void)
{
dbSyncOn();
dbSyncRate(60);
dbAutoCamOff();
dbLoadObject(ModelName, idModel);
dbLoadImage(TextureName, idTexImage);
dbTextureObject(idModel, idTexImage);
dbScaleObject(idModel, 200, 200, 200);
// To make the model face away from us as default rotation
dbRotateObject(idModel, 0, 180, 0);
dbFixObjectPivot(idModel);
dbMakeObjectCube(idTarget, 0.5);
dbPositionObject(idTarget, -5, 0, 0);
dbPositionCamera(0, 5, -6);
dbPointCamera(0, 0, 0);
char Buffer[256];
float modelDir = 0;
while (LoopGDK())
{
// Control the target with the arrow keys
if (dbRightKey()) dbMoveObjectRight(idTarget, 0.1);
if (dbLeftKey()) dbMoveObjectLeft(idTarget, 0.1);
if (dbUpKey()) dbMoveObject(idTarget, 0.1);
if (dbDownKey()) dbMoveObject(idTarget, -0.1);
// Turn the model with the A and D keys
if (dbKeyState(DIK_D)) {
modelDir += 0.6;
if (modelDir >= 360) modelDir -= 360;
dbYRotateObject(idModel, modelDir);
}
if (dbKeyState(DIK_A)) {
modelDir -= 0.6;
if (modelDir < 0) modelDir += 360;
dbYRotateObject(idModel, modelDir);
}
// Calculate the current angle from the model towards the target
float worldAngle = dbWrapValue(dbATANFULL(dbObjectPositionX(idTarget) - dbObjectPositionX(idModel), dbObjectPositionZ(idTarget) - dbObjectPositionZ(idModel)));
sprintf(Buffer, "angle to target = %.1f model direction = %.1f", worldAngle, modelDir);
dbText(0, 0, Buffer);
// Determine the required head angle to look at the target
float headAngle = worldAngle - modelDir;
if (headAngle > 180) headAngle -= 360;
sprintf(Buffer, "head angle = %.1f", headAngle);
dbText(0, 12, Buffer);
// Limit the angle and rotate the head limb
if (headAngle < -limitAngle) headAngle = -limitAngle;
if (headAngle > limitAngle) headAngle = limitAngle;
dbRotateLimb(idModel, idHead, 0, headAngle, 0, 1);
dbSync();
}
}