AngleBetweenLocations issue

I cannot solve this myself. I am coding a special ability where I need to find out all objects behind targetted object which lies on the same line as from targetted object and player.

Ie, the task is this:

My premise was that all I need to do is to check difference between angle of PC and target and target and object. If the difference is within say 10.0 then it lies on that line.

However I cannot get this to work (it is not working correctly in all angles), it seems the major problem is that the VectorToAngle and custom function in x0_i0_position GetAngleBetweenLocations

// This returns the angle between two locations
float GetAngleBetweenLocations(location lOne, location lTwo)
{
vector vPos1 = GetPositionFromLocation(lOne);
vector vPos2 = GetPositionFromLocation(lTwo);
float fDist = GetDistanceBetweenLocations(lOne, lTwo);

float fChangeX = IntToFloat(abs(FloatToInt(vPos1.x - vPos2.x)));

float fAngle = acos(fChangeX / fDist);
return fAngle;

}

is not returning expected values. I made some testing and found out that this function is not working properly - or at least not within my expectations. I tried various solutions from uncle google but none of those gave me the values I believe I do need.

Here is image describing the actual and expected behavior:

Note: returned values from this functions are identical no matter whether I check distance between point and middle or middle and point.

Anyone knows how to solve this, be it function which will give me values I expect or different method of calculation?

I know that Lightning Bolt spell does basically what I am trying to achieve via GetFirst/NextObjectInShape(SHAPE_SPELLCYLINDER but I am not sure how to use this either - the range shouldn’t be limited and the script is not called from spell.

Because this function is crap, like the lot of vanilla “library” functions.

To calculate a planar angle you need 2 vectors: start (v1) and end (v2) or 3 points: v1, v2 and o, which is typically the origin ([0, 0, 0]). v1 and v2 grow from o, forming a triangle. If o is not at the origin, you need to do some shifting. The angle is then returned as an unsigned real number between 0 and 180, which means that you get the same value if you switch v1 and v2. The function you posted returns angle between v1 and x axis, with tip of the angle located at v2. It is not generic. And it is why you only get values between 0 and 90.

See image for comparison (correct on the left).

angles

In my scripts I use a function that returns signed angle between vectors (-180 to 180) so you also get the direction, i.e. to determine whether the target is to the left or right of source. When working with creatures, v1 is often the facing vector of the source (i.e. PC) and v2 is difference between location of source and target (facing vector towards the target from source).

In short: dot product to get the angle, cross product to get the sign.

Script below does the calculation and runs a short demo. This code is robust.

//::////////////////////////////////////////////////////////////////////////////
//:: By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
//::////////////////////////////////////////////////////////////////////////////


// By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
// =============================================================================
// Calculates signed angle (-180 to 180) between vSource and vTarget within XY
// plane (ignoring Z components). This can be interpreted as by how many degrees
// vSource must be rotated around origin ([0, 0, 0]) and in which direction:
// negative - clockwise, positive - counter-clockwise to obtain vTarget (right
// hand rule). 0.0 is returned if either of the vectors has length 0.
// =============================================================================
// Parameters:
// * vSource: start vector
// * vTarget: end vector
// =============================================================================
// Return value:
// * 0.0 - vTarget points exactly in the same direction as vSource
// * 180.0 - vTarget points exactly in the opposite direction to vSource
// * between 0.0 and +180.0 - vTarget points somewhere to the left of vSource
// * between 0.0 and -180.0 - vTarget points somewhere to the right of vSource
float NWSH_GetSignedAngleBetweenVectors(vector vSource, vector vTarget);
float NWSH_GetSignedAngleBetweenVectors(vector vSource, vector vTarget)
{
    float fAngle;

    // normalize vSource
    vSource = VectorNormalize(vSource);
    // normalize vTarget
    vTarget = VectorNormalize(vTarget);

    // return 0 if vSource or vTarget has zero length
    if(VectorMagnitude(vSource) == 0.0 || VectorMagnitude(vTarget) == 0.0)
    {
        return 0.0;
    }

    // calculate vector dot product to determine the angle
    fAngle = acos(vTarget.x * vSource.x + vTarget.y * vSource.y);

    // calculate vector cross product to determine the sign
    if(vTarget.x * vSource.y - vTarget.y * vSource.x > 0.0)
    {
        return -fAngle;
    }
    else
    {
        return fAngle;
    }
}


// By NWShacker, 2022-01-11, licence: CC BY-SA 4.0
// =============================================================================
// Uses NWSH_GetSignedAngleBetweenVectors() to obtain signed angle (-180 to 180)
// describing position of lTarget within XY plane in respect to lSource. If
// lTarget and lSource are in different areas this function returns 0.0.
// =============================================================================
// Parameters:
// * lSource: start location
// * lTarget: end location
// * iUseFacing:
//   * TRUE: calculate angle between facing vector of lSource and vector from
//     lSource to lTarget (both vectors starting from lSource)
//   * FALSE: calculate angle between vectors pointing to lSource and lTarget
//     (both vectors starting from origin, [0, 0, 0])
// =============================================================================
// Return value:
//      +135   +90   +45
//          \   |   /
//           \  |  /
// +/-180 -- lSource == facing of lSource => 0
//           /  |  \
//          /   |   \
//      -135   -90   -45
float NWSH_GetSignedAngleBetweenLocations(location lSource, location lTarget, int iUseFacing=FALSE);
float NWSH_GetSignedAngleBetweenLocations(location lSource, location lTarget, int iUseFacing=FALSE)
{
    vector vSource;
    vector vTarget;
    float fAngle;

    // check if lTarget and lSource are valid and in the same area
    if(GetAreaFromLocation(lTarget) != GetAreaFromLocation(lSource) ||
        !GetIsObjectValid(GetAreaFromLocation(lTarget)))
    {
        return 0.0;
    }

    if(iUseFacing)
    {
        // if iUseFacing is TRUE, calculate angle relative to lSource,
        // between vector of its facing and vector towards lTarget
        vSource = AngleToVector(GetFacingFromLocation(lSource));
        vTarget = GetPositionFromLocation(lTarget) - GetPositionFromLocation(lSource);
    }
    else
    {
        // otherwise calculate angle between vectors of positions
        // of lSource and lTarget attached at the origin
        vSource = GetPositionFromLocation(lSource);
        vTarget = GetPositionFromLocation(lTarget);
    }

    return NWSH_GetSignedAngleBetweenVectors(vSource, vTarget);
}


// =============================================================================
// DEMO: iterates all objects in the area of the caller and makes them speak
// their relative angle in respect to the caller.
// =============================================================================
void main()
{
    object oObject;
    float fAngle;

    oObject = GetFirstObjectInArea();

    while(GetIsObjectValid(oObject))
    {
        fAngle = NWSH_GetSignedAngleBetweenLocations(
            GetLocation(OBJECT_SELF),
            GetLocation(oObject),
            TRUE);
        AssignCommand(oObject, SpeakString(FloatToString(fAngle, 0, 1)));
        oObject = GetNextObjectInArea();
    }
}

Your case: vPC is PC’s position (o), vTrgt is position of primary target (v1), vHit is position of the secondary target (v2). So you simply check if:

fabs(NWSH_GetSignedAngleBetweenVectors(vTrgt-vPC, vHit-vPC)) <= 10.0
1 Like

You expect North to be at 0°, but it’s usually East which is at 0°. So you’ll need to deduct 90° from the result given by GetAngleBetweenLocations().

Also, fChangeX is always positive (since you use the abs() function), so acos() will always return a value between 0° and 90°

But even after removing abs(), you only consider the difference in the X coordinates, which means your result will be limited to the [0° ; 180°] range: you’ll need to also check the sign of the fChangeY to determine if you need to add 0° or 180° to the result.

1 Like

Yes, that is also true. I know about that and for my project it is not a problem, it doesn’t matter for me whether 0 is at north or east but good that you mentioned it.

It is getting close, but it is not working correctly. I mean, I only tried to check angle of position of my character at various places towards center of area, so perhaps it might work in an actual situation I need to use this, but I noticed the distance is affecting the outcome - which in my opinion should not.

function NWSH_GetSignedAngleBetweenVectors is returning completely different values than the location one, i wasn’t able to get values over 50 at least untill I moved further from the center. EDIT: or is this just private function for the latter? If so nevermind.

NWSH_GetSignedAngleBetweenLocations is much more precise, but distance is still affecting outcome. If i position myself to direct west from center of the area I am using as source I get ~160.0 at close proximity. If I move backwards towards further west using backstepping is is increasing. Hmm actually that makes sense, will try it applly into the real script and let you know.

Yes, because my NWSH_GetSignedAngleBetweenLocations is not just a pass-through to NWSH_GetSignedAngleBetweenVectors. If you want that, just call

NWSH_GetSignedAngleBetweenVectors(
    GetPositionFromLocation(lSource),
    GetPositionFromLocation(lTarget))

NWSH_GetSignedAngleBetweenLocations also uses facing of lSource. My fault by not writing about that in the doc, I’ll fix it.

If you run the full script you’ll see that the result is affected by your PC turning around and moving. This is why you’re experiencing what you wrote about in the last paragraph. The code is also correct: two creatures on the same line from you will always yield the same angle, regardless of their distance (vectors are normalized).

EDIT: I updated it. You can use iUseFacing to control the behavior of the location function.

angles

  • rectangles simulate the area.
  • on the left is NWSH_GetSignedAngleBetweenVectors.
  • on the right is NWSH_GetSignedAngleBetweenLocations with iUseFacing=TRUE; with FALSE it becomes like the one on the left.

If you want something different than [0, 0, 0] on the left, just subtract it from vSource and vTarget, like I did in the previous post at “Your case”.

So it seems it works, thanks @NWShacker ! Now, any idea how to avoid the issue described here Problem with beams - #3 by Shadooow ? :smiley:

Sorry, no idea. Perhaps there is some delay introduced with applying “lots” of VFX beams to creatures? Have you tried replicating the issue with just placeables or applying similar number of VFX to the creature?

Not yet, I need to make some test module under 1.69 first I guess. I know it worked the way I wanted when I was coding it. Will update the thread after I do that.