• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] [Snippet] Field of View (IsUnitInSight + IsUnitBehind)

Level 13
Joined
May 24, 2005
Messages
609
Hey everyone,

this is a small library that provides 2 functions, one for detecting if a unit is within the field of view of another unit, and one for detecting if a unit is behind another unit (using a specified angle). For example, these functions can be used for stealth gameplay involving field of view detection, backstab abilitites, etc.

  • IsUnitInSightOfUnit( unit observer, unit target, real value )
    Checks if target is within the field of view of observer

  • IsUnitBehindUnit( unit unitToCheck, unit target, real value )
    Checks if unitToCheck is behind target (within specified behind angle)
Please make sure to read the documentation on how to properly set the function parameter.

JASS:
library FieldOfView
//*********************************************************************
//*  =================================
//*  FieldOfView 1.0.3   (by MoCo)  
//*  =================================
//*
//*  This library provides 2 functions:
//*
//*    - IsUnitInSightOfUnit(unit observer, unit target, real fieldOfView)
//*        Checks if unit 'target' is within the field of view cone of unit 'observer'
//*      
//*    - IsUnitBehindUnit(unit unitToCheck, unit target, real fieldOfView)
//*        Checks if unit 'unitToCheck' is behind unit 'target' within fieldOfView
//*
//* 
//*  Setup:
//*  ======
//*  Copy this library to your map.
//*
//*
//*  Usage:
//*  ======
//*  Use the parameter fieldOfView to set the field of view (FoV) that should be used for the detection function.
//*  The parameter needs to be set to half of the total field of view you want the unit to use.
//*  For example, if you want a unit to have a total field of view cone of 180°, you need to use a parameter value of 90
//*  
//*  Note that the natural human field of view is about 135°, so you could use a value of 67.5 here.
//*  
//*  See the FovTester script for practical examples on how to use the functions.
//*
//*   
//********************************************************************

    function IsUnitInSightOfUnit takes unit observer, unit target, real fov returns boolean
        local real face  = GetUnitFacing(observer)
        local real angle = bj_RADTODEG*Atan2(GetUnitY(target) - GetUnitY(observer), GetUnitX(target) - GetUnitX(observer))
        return not (RAbsBJ(face - angle) > fov and RAbsBJ(face - angle - 360.) > fov)
    endfunction

    function IsUnitBehindUnit takes unit unitToCheck, unit target, real fov returns boolean
        local real face  = GetUnitFacing(target)
        local real angle = bj_RADTODEG*Atan2(GetUnitY(target) - GetUnitY(unitToCheck), GetUnitX(target) - GetUnitX(unitToCheck))
        return not (RAbsBJ(face - angle) > fov and RAbsBJ(face - angle - 360.) > fov)
    endfunction

endlibrary

Code:
//*  2015-04-25: The fieldOfView parameter usage has made more intuitive
//*  2015-01-21: The external Math library is no longer needed
//*  2015-01-10: The fieldOfView parameter now is an argument and now longer a global constant

I've attached a small testmap.

If you know ways to improve the script, i.e. some way of performance optimization or better approaches for FoV detection, please let me know and I will update it.

Sidenote: If you want to implement a realistic visibility/line of sight system in your map, you should also take care of doodads, like houses, which naturally should also block line of sight. Therefore, you'd need an additional function for checking if line of sight is blocked by a doodad; One way to do this is using a pathability library and check, if a pathing blocker is between the 2 units.
 

Attachments

  • FieldOfView.jpg
    FieldOfView.jpg
    192.2 KB · Views: 484
  • FieldOfView.w3m
    22.5 KB · Views: 171
Last edited by a moderator:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
JASS:
    private constant real FIELD_OF_VIEW_FRONT  = 115.00  // Real FoV is: 180 - 2x (FIELD_OF_VIEW-90)    Using a value of 115 generates a FoV of 130   (natural FoV is about 135 degrees)
    private constant real FIELD_OF_VIEW_BEHIND = 115.00

User should be able to determine the field of view on every function call.
 
Level 17
Joined
Jul 17, 2011
Messages
1,864
this is actually the first time ive seen someone implement this correctly here, also you added scientific insight(
JASS:
Example 1# - Natural human field of view (~135°):
//*    If you want to set up natural human FoV in your map, 
//*    use a value of 112.5 for fieldOfView ( because 180 - 2*(112.5-90) = 135 ).)

to state how your functions should work so i really dont see a reason why this should not be approved.. i mean its really simple but its done right
 
Level 13
Joined
May 24, 2005
Messages
609
I thought it is faster using the function of the library instead of using a BJ, but I'm not sure on this? If it is not faster, the Rabs function of the library would be pointless.

But I also think there should be such kind of a math resource on hiveworkshop (whatever the actual name will be).

And I think it requires a dedicated thread for discussion. Probably, there are also more functions that could be included in such kind of library than the ones used in this snippet.
 
Level 13
Joined
May 24, 2005
Messages
609
If this is the case, I could remove the math library from this snippet and just replace RAbs with RAbsBJ. Still I guess I would love to do a banchmark here, comparing both variants to please my scientific interest. Can anyone recommend a good way to do jass code speed tests?
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
basic logic for comparision:
If you have user-made function that replaces BJ, then check the contents of the user-made function(you could post it here too), and if they are totally equal, they are equal in speed.

If your function does less work(less computation, fewer function calls, fewer if dispatches), it is faster, otherwise it is slower.

If your user-made function is meant to replace Native directly, it will be always slower.

Also yes there are micro-micro-piko-fento-optimizations like shortening names so the interpreter runs faster, but that is so small that I call it that way :D

So if your RAbs function does less computation than:

JASS:
function RAbsBJ takes real a returns real
    if (a >= 0) then
        return a
    else
        return -a
    endif
endfunction

than your function is faster :D But I cant think of any normal version that would be faster
 
Level 13
Joined
May 24, 2005
Messages
609
Alright then.

I've just updated the script so that it uses the BJ and thus does not need the math library anymore.

As I'm curious about speed, I could also eliminate the BJ function calls completely by doing something like the following:

JASS:
function IsUnitInSightOfUnit takes unit observer, unit target, real fov returns boolean
    local real face = GetUnitFacing(observer)
    local real angle = bj_RADTODEG*Atan2(GetUnitY(observer)-GetUnitY(target), GetUnitX(observer)-GetUnitX(target))
    local real r1 = face-angle
    local real r2 = r1-360
    
    // we do not want to use the RabsBJ function so we do a manual conversion here
    if r1 < 0 then   
        set r1 = -r1
    endif
    if r2 < 0 then
        set r2 = -r2
    endif
    
    return r1 > FIELD_OF_VIEW and r2 > FIELD_OF_VIEW
endfunction

I guess this variant would be faster than using the BJ's, right? (at the cost of 2 more local variables ofc)
 
Last edited:
Level 13
Joined
May 24, 2005
Messages
609
Yeah, I see. So I think for a general resource, the RAbsBJ seems to be fine here.

Personally, I have a huge interest in speed optimization because I'm working on a project were I need to run the function quite a lot of times within short time periods so it's worth to optimize performance. ;)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
your thread of execution will crash far before you will notice any lag, unless you are doing group enumerations and creating groups and destroying them 100 times per second(personal experience :D), so no need to go too heavy into micro-optimizations
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
what? He even stated it in documentation, with the formula used, and someone on first page said that it is the correct way to calculate field of view. But I agree that there could be some way of providing raw value, instead of having to calculate it out of the formula(when I know what FOV I want, but dont know the arugment to formula)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Yepp, I will add a formula to the documentation so that users know exactly how to calculate the parameter value they should use for the actual FOV degree they want to implement.

Instead of adding it to the documentation, how if the function will automatically calculate it for user? It's much better than having to calculate manually. ;)
 
Level 13
Joined
May 24, 2005
Messages
609
I don't know. I have consciously decided to not include it because of speed reaons, but of course it won't be a problem to include the calculation in the function. It's just a tradeoff intuitivity vs. speed. My personal vote goes to speed, but I would be fine with intuitivity as well. ;)
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
The IsUnitInSight function is buggy with some angles.
1. You calculate the angle wrong(target to observer, instead of observer to target), with the result being 180 degrees off. For this reason it was necessary to use 180-fov instead of fov.
2. When standing to the left of a unit and testing fov while looking down, the unit on the left got included to visible units.

Also, I needed facing to be customizable, so I added it as a parameter.
Here's a fixed version.

JASS:
function IsUnitInSightOfUnit takes unit observer, unit target, real face, real fov returns boolean
    local real angle = bj_RADTODEG*Atan2(GetUnitY(target)-GetUnitY(observer), GetUnitX(target)-GetUnitX(observer))
    // You may enable this or a similiar message if you want to track particular calculation values.
    // debug call BJDebugMsg( "T Facing: " + R2S(face) + " Angle: " + R2S(angle) + " RAbs(face-angle)+ " + R2S(RAbsBJ(face-angle)) + " Rabs(face-angle-360): "+R2S(RAbsBJ(face-angle-360))  )
   
    return not(RAbsBJ(face-angle) > fov and RAbsBJ(face-angle-360) > fov)
endfunction
 
Level 13
Joined
May 24, 2005
Messages
609
Hi Xonok, can you provide a test map that shows how the old system is buggy with some angles so I can reproduce the issue you've mentioned?
Also, do you have used the parameter correctly? (as discussed before, the fov param does not represent the real physical fov)
I was pretty sure that the function is working properly with all angles, especially if you have a look at the test map I've provided with the script.

I guess with the function above, the fov parameter needs to be set to half of the total fov degreese, so for example, for a total fov of 90°, I'd have to use a parameter value of 45, right?
Thus, chances are that your approach is a bit more intuitive, so it might be better. However, I'm not quite sold that the original function does any wrong calculations.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Hi Xonok, can you provide a test map that shows how the old system is buggy with some angles so I can reproduce the issue you've mentioned?
Also, do you have used the parameter correctly? (as discussed before, the fov param does not represent the real physical fov)
I was pretty sure that the function is working properly with all angles, especially if you have a look at the test map I've provided with the script.

I guess with the function above, the fov parameter needs to be set to half of the total fov degreese, so for example, for a total fov of 90°, I'd have to use a parameter value of 45, right?
Thus, chances are that your approach is a bit more intuitive, so it might be better. However, I'm not quite sold that the original function does any wrong calculations.

The original function still considers fov from target to observer, which will create a 180-degree offset unless the calculations fix it elsewhere(unintuitive if it's so imo).

I didn't check the original map, because I assumed things would work intuitively.
I'm afraid I can't produce it atm, because I didn't make any subversions of my map before fixing this. I don't know why exactly the function broke with those angles

And yes, the way I used is fov from middle to one edge.
It could be reasonable to have fov mean the full field of view as well. I suppose that's purely a question of API.
 
Level 13
Joined
May 24, 2005
Messages
609
Thanks for your input.

Alright. Well, the original calculation wasn't wrong, but the parameter needed to be set correctly which was not very intuitive and comfortable as it required some prior calculations.

So I've just updated the library so it uses the more intuitive approach; now the field of view parameter represents half of the total field of view cone in degrees.

I'm not sure if facing should be a parameter for the generic resource though.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I need the facing to be a parameter, because I used this for a spell.
As a generic resource it would actually be ideal to provide everything through parameters.
Essentially checking if something is in field of view is equivalent to checking if it's in a triangle.
However, having it use units has so far been fine for me.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Seems to work fine. I'm gonna approve it soon.

IsUnitBehindUnit(unit unitToCheck, unit target, real fieldOfView)
in the code unitToCheck is labeled as attacker.
function IsUnitBehindUnit takes unit attacker, unit target, real fov returns boolean

You could use a better indentation for the two functions. Add a tab to it, makes it look cleaner

JASS:
library Example
    
    // Good
    function MyFunction takes nothing returns nothing
    endfunction

// Bad 
function MyFunction takes nothing returns nothing
endfunction

endlibrary

Don't write the change log into the snippet, just in this forum thread.

// ... if you want a unit ... an unit
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
an goes with a, e, i, o, u(for some reason, in english by pronounciation, not how you write the word), so I think there should be an, but I think that is a useless to even mention, I can bet you my resources have 10x worse typos/grammar failures
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
an goes with a, e, i, o, u(for some reason, in english by pronounciation, not how you write the word), so I think there should be an, but I think that is a useless to even mention, I can bet you my resources have 10x worse typos/grammar failures

No. You should use "a" because You pronounce it as Yoo-nit(which starts as a consonant). This case is also the same as Honest(such as "an Honest Person"). a and an actually is based on the pronunciation characters in some cases.
 
Level 4
Joined
Feb 4, 2013
Messages
43
Hey can u make it GUi cause i hate compiling again again in jnjp and switching we to see how it works...
it is a very very useful system.
backstab can be done easily but to detect wether unit is line of sight is dificult we can check if unit is masked or not but other than tat no clue...
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I think that is really unprofessional(is it even allowed in this scale??), editing other's people code in such a degree, what if he likes the changelog in his resource(I use it too, and I like it), and also maybe he likes his API listings in different style.

You should've asked him to change it. He isnt even inactive(Last activity: yesterday)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I think that is really unprofessional(is it even allowed in this scale??), editing other's people code in such a degree, what if he likes the changelog in his resource(I use it too, and I like it), and also maybe he likes his API listings in different style.
1.) To make things clear. I added a tab to both functions, so they have a proper identation.

2.) Possibly slightly more radical was romving the change log from the description, putting it outside the library.

3.) The API says:
//function IsUnitBehindUnit takes unit unitToCheck, unit target, real fieldOfView returns boolean
inside the code it was:
function IsUnitBehindUnit takes unit attacker, unit target, real fov returns boolean
What I did was changing attacker into unitToCheck.

I would never manipulate actual code of a submission. However I might erase grammar mistakes if I see them.
I pointed those things out 26 days ago, so bringing user activity into account is not a valid argument.

I you feel this case should be discussed in serious debate, take it to my superior.
 
Level 13
Joined
Nov 7, 2014
Messages
571
You could get rid of the RAbsBJs and return early, also the parameter order for
JASS:
function IsUnitInSightOfUnit takes unit observer, unit target, real fov returns boolean
seems wrong? Shouldn't it be:
JASS:
function IsUnitInSightOfUnit takes unit target, unit observer, real fov returns boolean

And of course the fov parameter being a half angle seems... I don't like it.


In my opinion the following are better names for the functions and the parameter order seems easier to remember because the fov parameter always follows that of the unit that has it.
JASS:
library FieldOfView

function can_observer_see_target takes unit observer, real observer_fov, unit target returns boolean
    local real observer_fov_2  = 0.5 * observer_fov
    local real observer_facing = GetUnitFacing(observer)
    local real angle           = bj_RADTODEG * Atan2(GetUnitY(target) - GetUnitY(observer), GetUnitX(target) - GetUnitX(observer))

    local real a
    local real b

    set a = observer_facing - angle
    if a < 0 then
        set a = -a
    endif
    if a < observer_fov_2 then
        return true
    endif

    set b = observer_facing - angle - 360
    if b < 0 then
        set b = -b
    endif
    if b < observer_fov_2 then
        return true
    endif

    return false
endfunction

function is_attacker_behind_target takes unit attacker, unit target, real target_fov returns boolean
    local real target_fov_2  = 0.5 * target_fov
    local real target_facing = GetUnitFacing(target)
    local real angle         = bj_RADTODEG * Atan2(GetUnitY(attacker) - GetUnitY(target), GetUnitX(attacker) - GetUnitX(target))

    local real a
    local real b

    set a = target_facing - angle
    if a < 0 then
        set a = -a
    endif
    if a < target_fov_2 then
        return false
    endif

    set b = target_facing - angle - 360
    if b < 0 then
        set b = -b
    endif
    if b < target_fov_2 then
        return false
    endif

    return true
endfunction

endlibrary

Otherwise gj! =)

Edit:
It seems the IsUnitBehindUnit is incorrect:
JASS:
    function IsUnitBehindUnit takes unit attacker, unit target, real fov returns boolean
        local real face  = GetUnitFacing(target)
        local real angle = bj_RADTODEG*Atan2(GetUnitY(target) - GetUnitY(attacker), GetUnitX(target) - GetUnitX(attacker))
        return not (RAbsBJ(face - angle) > fov and RAbsBJ(face - angle - 360.) > fov)
    endfunction

The call to 'Atan2' has the wrong order of parameters and on the return line the 'not' should not be there.

Fixed:
JASS:
function IsUnitBehindUnit takes unit attacker, unit target, real fov returns boolean
    local real face  = GetUnitFacing(target)
    local real angle = bj_RADTODEG*Atan2(GetUnitY(attacker) - GetUnitY(target), GetUnitX(attacker) - GetUnitX(target))
    return RAbsBJ(face - angle) > fov and RAbsBJ(face - angle - 360.) > fov
endfunction
 
Last edited:
Top