GetClosestUnit(s) v1.3.1finds the unit, or group of units closest to a point faster than any existing alternative
Changelog
v1.3.1
removed unnecessary GroupClear calls
v1.3
GetClosestUnit function is now smart
GetClosestUnitAlt: a new function that resembles the old and simple GetClosestUnit function (since it can be faster than the smart version in some rare cases)
GetClosestUnit and GetClosestUnitInRange now use ForGroup() instead of And() making them a lot faster
v1.2b
apparently boolexprs created with And() leak every time they are created, these leaks have now been removed from GCU
v1.2
added a description and comments
replaced bj_globals with own private globals (again from HINDYhat's request)
v1.1
fixed a few bugs in GetClosestUnit(s)InGroup and also replaced a few global groups with local ones (thx HINDYhat)
//------------------------------------------------------------------------------ // // This library contains the following functions, each of which return the // unit that is closest to given coordinates and passes a specified filter: // // - GetClosestUnit(x, y, filter) // - GetClosestUnitAlt(x, y, filter) // - GetClosestUnitInRange(x, y, radius, filter) // - GetClosestUnitInGroup(x, y, g) // // Included are also the following functions, each of which return a // group consisting of the units closest to given coordinates that // pass a specified filter: // // - GetClosestUnits(x, y, n, filter) // - GetClosestUnitsInRange(x, y, radius, n, filter) // - GetClosestUnitsInGroup(x, y, g, n) // // Important: // // - flying heights and the height of the ground aren't taken into account, // only the distance in 2D // // - these functions can't be used in the boolexpr's passed to them // // - all of the functions in this library go through many units to find the // closest one, meaning that the functions' performance is highly dependant // on the amount of units they have to go through // //------------------------------------------------------------------------------ library GCU
privatefunction Enum takesnothingreturnsnothing localunit u = GetEnumUnit() localreal dx = GetUnitX(u) - CenterX localreal dy = GetUnitY(u) - CenterY localreal d = (dx*dx + dy*dy) / 10000. if d < CurrentDistance then set CurrentDistance = d set CurrentPick = u endif set u = null endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from all units on the map that // pass the filter and do not have the locust ability. // function GetClosestUnit takesreal x, real y, boolexpr filter returnsunit localreal r = 800. loop call GroupEnumUnitsInRange(AnyGroup, x, y, r, filter) exitwhen FirstOfGroup(AnyGroup) != null if r >= 3200. then call GroupEnumUnitsInRect(AnyGroup, bj_mapInitialPlayableArea, filter) exitwhentrue endif set r = 2.00 * r endloop set CurrentPick = null set CenterX = x set CenterY = y set CurrentDistance = 100000 call ForGroup(AnyGroup, function Enum) return CurrentPick endfunction
//========================================================================== // Does the same as above. Faster when there are no units that pass the // filter in a 3200 radius, but at other times slower, and most likely a // lot slower. How much faster is somewhat directly proportional to the // amount of units that do not pass the filter inside that 3200 radius. // function GetClosestUnitAlt takesreal x, real y, boolexpr filter returnsunit set CurrentPick = null set CenterX = x set CenterY = y set CurrentDistance = 100000 call GroupEnumUnitsInRect(AnyGroup, bj_mapInitialPlayableArea, filter) call ForGroup(AnyGroup, function Enum) return CurrentPick endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from all units in the specified // radius, that pass the filter and do not have the locust ability. // function GetClosestUnitInRange takesreal x, real y, real radius, boolexpr filter returnsunit set CurrentPick = null set CenterX = x set CenterY = y set CurrentDistance = 100000 call GroupEnumUnitsInRange(AnyGroup, x, y, radius, filter) call ForGroup(AnyGroup, function Enum) return CurrentPick endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from whichGroup. Unlike the // other versions this one considers locusted units also. // function GetClosestUnitInGroup takesreal x, real y, group whichGroup returnsunit set CurrentPick = null set CenterX = x set CenterY = y set CurrentDistance = 100000 call ForGroup(whichGroup, function Enum) return CurrentPick endfunction
//========================================================================== // The following three functions do the same as the preceding ones, with // // the exception that they return groups consisting of the n closest // // units instead of the closest unit // //==========================================================================
globals privategroup ResultGroup endglobals
function GetClosestUnits takesreal x, real y, integer n, boolexpr filter returnsgroup call GroupEnumUnitsInRect(AnyGroup, bj_mapInitialPlayableArea, filter) set ResultGroup = CreateGroup() set CenterX = x set CenterY = y loop exitwhen n == 0 set CurrentPick = null set CurrentDistance = 100000 call ForGroup(AnyGroup, function Enum) exitwhen CurrentPick == null call GroupRemoveUnit(AnyGroup, CurrentPick) call GroupAddUnit(ResultGroup, CurrentPick) set n = n - 1 endloop return ResultGroup endfunction
function GetClosestUnitsInRange takesreal x, real y, real radius, integer n, boolexpr filter returnsgroup call GroupEnumUnitsInRange(AnyGroup, x, y, radius, filter) set ResultGroup = CreateGroup() set CenterX = x set CenterY = y loop exitwhen n == 0 set CurrentPick = null set CurrentDistance = 100000 call ForGroup(AnyGroup, function Enum) exitwhen CurrentPick == null call GroupRemoveUnit(AnyGroup, CurrentPick) call GroupAddUnit(ResultGroup, CurrentPick) set n = n - 1 endloop return ResultGroup endfunction
function GetClosestUnitsInGroup takesreal x, real y, group whichGroup, integer n returnsgroup call GroupClear(AnyGroup) call ForGroup(whichGroup, function AnyGroupAddGroupEnum) set ResultGroup = CreateGroup() set CenterX = x set CenterY = y loop exitwhen n == 0 set CurrentPick = null set CurrentDistance = 100000 call ForGroup(AnyGroup, function Enum) exitwhen CurrentPick == null call GroupRemoveUnit(AnyGroup, CurrentPick) call GroupAddUnit(ResultGroup, CurrentPick) set n = n - 1 endloop return ResultGroup endfunction
endlibrary// End of GetClosestUnit //==============================================================================a
The code above requires JassHelper to work, whereas the code below doesn't
//------------------------------------------------------------------------------ // // Contents: // // This library contains the following functions, each of which return the // unit that is closest to given coordinates and passes a specified filter: // // - GetClosestUnit(x, y, filter) // - GetClosestUnitAlt(x, y, filter) // - GetClosestUnitInRange(x, y, radius, filter) // - GetClosestUnitInGroup(x, y, g) // // Included are also the following functions, each of which return a // group consisting of the units closest to given coordinates that // pass a specified filter: // // - GetClosestUnits(x, y, n, filter) // - GetClosestUnitsInRange(x, y, radius, n, filter) // - GetClosestUnitsInGroup(x, y, g, n) // // Notes: // // - flying heights and the height of the ground aren't taken into // account, only the distance in 2D // // - these functions can't be used in the boolexpr's passed to them // // - all of the functions in this library go through many units to find // the closest one, meaning that the functions' performance is highly // dependant on the amount of units they have to go through // // - this without vJass version is as efficient as the vJass version // // Implementing Instructions: // // - either paste this system into your map's header or inside any // trigger that has been converted to custom text, that has been // created before any other triggers in which you wish to use these // functions! // // - press Ctrl+B to open the variable editor and create the following // variables leaving their initial values to the default ones // // Type Name // Unit GCU_CurrentPick // Real GCU_CurrentX // Real GCU_CurrentY // Real GCU_CurrentDistance // Unit Group GCU_AnyGroup // Unit Group GCU_ResultingGroup // // - congrats, you are done! // //------------------------------------------------------------------------------
function GCU_Enum takesnothingreturnsnothing localunit u = GetEnumUnit() localreal dx = GetUnitX(u) - udg_GCU_CurrentX localreal dy = GetUnitY(u) - udg_GCU_CurrentY localreal d = (dx*dx + dy*dy) / 10000. if d < udg_GCU_CurrentDistance then set udg_GCU_CurrentDistance = d set udg_GCU_CurrentPick = u endif set u = null endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from all units on the map that // pass the filter and do not have the locust ability. // function GetClosestUnit takesreal x, real y, boolexpr filter returnsunit localreal r = 800. loop call GroupEnumUnitsInRange(udg_GCU_AnyGroup, x, y, r, filter) exitwhen FirstOfGroup(udg_GCU_AnyGroup) != null if r >= 3200. then call GroupEnumUnitsInRect(udg_GCU_AnyGroup, bj_mapInitialPlayableArea, filter) exitwhentrue endif set r = 2.00 * r endloop set udg_GCU_CurrentPick = null set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y set udg_GCU_CurrentDistance = 100000 call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) return udg_GCU_CurrentPick endfunction
//========================================================================== // Does the same as above. Faster when there are no units that pass the // filter in a 3200 radius, but at other times slower, and most likely a // lot slower. How much faster is somewhat directly proportional to the // amount of units that do not pass the filter inside that 3200 radius. // function GetClosestUnitAlt takesreal x, real y, boolexpr filter returnsunit set udg_GCU_CurrentPick = null set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y set udg_GCU_CurrentDistance = 100000 call GroupEnumUnitsInRect(udg_GCU_AnyGroup, bj_mapInitialPlayableArea, filter) call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) return udg_GCU_CurrentPick endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from all units in the specified // radius, that pass the filter and do not have the locust ability. // function GetClosestUnitInRange takesreal x, real y, real radius, boolexpr filter returnsunit set udg_GCU_CurrentPick = null set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y set udg_GCU_CurrentDistance = 100000 call GroupEnumUnitsInRange(udg_GCU_AnyGroup, x, y, radius, filter) call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) return udg_GCU_CurrentPick endfunction
//========================================================================== // Finds the unit that is closest to (x, y) from whichGroup. Unlike the // other versions this one considers locusted units also. // function GetClosestUnitInGroup takesreal x, real y, group whichGroup returnsunit set udg_GCU_CurrentPick = null set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y set udg_GCU_CurrentDistance = 100000 call ForGroup(whichGroup, function GCU_Enum) return udg_GCU_CurrentPick endfunction
//========================================================================== // The following three functions do the same as the preceding ones, with // // the exception that they return groups consisting of the n closest // // units instead of the closest unit // //==========================================================================
function GetClosestUnits takesreal x, real y, integer n, boolexpr filter returnsgroup call GroupEnumUnitsInRect(udg_GCU_AnyGroup, bj_mapInitialPlayableArea, filter) set udg_GCU_ResultingGroup = CreateGroup() set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y loop exitwhen n == 0 set udg_GCU_CurrentPick = null set udg_GCU_CurrentDistance = 100000 call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) exitwhen udg_GCU_CurrentPick == null call GroupRemoveUnit(udg_GCU_AnyGroup, udg_GCU_CurrentPick) call GroupAddUnit(udg_GCU_ResultingGroup, udg_GCU_CurrentPick) set n = n - 1 endloop return udg_GCU_ResultingGroup endfunction
function GetClosestUnitsInRange takesreal x, real y, real radius, integer n, boolexpr filter returnsgroup call GroupEnumUnitsInRange(udg_GCU_AnyGroup, x, y, radius, filter) set udg_GCU_ResultingGroup = CreateGroup() set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y loop exitwhen n == 0 set udg_GCU_CurrentPick = null set udg_GCU_CurrentDistance = 100000 call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) exitwhen udg_GCU_CurrentPick == null call GroupRemoveUnit(udg_GCU_AnyGroup, udg_GCU_CurrentPick) call GroupAddUnit(udg_GCU_ResultingGroup, udg_GCU_CurrentPick) set n = n - 1 endloop return udg_GCU_ResultingGroup endfunction
function GCU_AnyGroupAddGroupEnum takesnothingreturnsnothing call GroupAddUnit(udg_GCU_AnyGroup, GetEnumUnit()) endfunction
function GetClosestUnitsInGroup takesreal x, real y, group whichGroup, integer n returnsgroup call GroupClear(udg_GCU_AnyGroup) call ForGroup(whichGroup, function GCU_AnyGroupAddGroupEnum) set udg_GCU_ResultingGroup = CreateGroup() set udg_GCU_CurrentX = x set udg_GCU_CurrentY = y loop exitwhen n == 0 set udg_GCU_CurrentPick = null set udg_GCU_CurrentDistance = 100000 call ForGroup(udg_GCU_AnyGroup, function GCU_Enum) exitwhen udg_GCU_CurrentPick == null call GroupRemoveUnit(udg_GCU_AnyGroup, udg_GCU_CurrentPick) call GroupAddUnit(udg_GCU_ResultingGroup, udg_GCU_CurrentPick) set n = n - 1 endloop return udg_GCU_ResultingGroup endfunction
// End of GetClosestUnit //==============================================================================
Last edited by DiscipleOfLife; 06-28-2008 at 11:34 AM..
Basically, if you give them a radius and coordinates, it finds the unit closest to the coordinates matching certain conditions (including being in that radius), although some assume radius is infinite.
I'll give these the standard checkup once I return from vacation, Monday night or Tuesday afternoon.
Found a bug. In the GetClosestUnitInGroup function:
call ForGroup( bj_groupAddGroupDest, function Enum2)
Should be:
call ForGroup( whichGroup, function Enum2)
Other than that, why are you using global groups everywhere?
Damn I feel stupid right now. Fixed that bug now and removed 2 unneccessary globals uses. Thx & +rep.
Right now the bj_groupRemoveGroupDest global is used because it is returned in the functions it is in. The G global is just an empty group that is never filled with anything since the filter I use with it returns always false. (Using a null group instead doesn't work.)
In GetClosestUnitsInGroup I use bj_groupAddGroupDest because the GroupAddGroupEnum function fills it automaticly.
Now I have actually tested all of them -.-... I thought I had but seems like I skipped the Group versions :(. Pls forgive me.
I understand the use of bj_groupAddGroupDest, but I don't understand the use of bj_groupRemoveGroupDest... You should use a local to avoid double frees or just randomness with globals.
Maybe you should add some documentation (mostly considering locusted units and enums) or something about each function : some people don't understand very easily. Extra documentation is always nice.
If you fix those two issues, I think this is good for approval. Might ask another mod though first.
Maybe you should add some documentation (mostly considering locusted units and enums) or something about each function : some people don't understand very easily. Extra documentation is always nice.
Updated to 1.2 which now includes documentation.
Quote:
Originally Posted by HINDYhat
I understand the use of bj_groupAddGroupDest, but I don't understand the use of bj_groupRemoveGroupDest... You should use a local to avoid double frees or just randomness with globals.
bj_groupRemoveGroupDest, which I know replaced with a private global 'resultingGroup' in v1.2, is a global because if it was local I would have to null it, but I cant null and return it at the same time.
Example failure:
function A takesnothingreturnsgroup localgroup g = CreateGroup() // ...actions... set g = null return g // == return null endfunction
The new version uses 'G' instead of bj_groupAddGroupDest, so there shouldn't be any possible conflicts regarding bj_globals anymore.
Why would you have to null it...? If the user is calling that function, then the local is being passed to another use, so the nulling falls in the hands of the user (like it always should). Really, use of a global group here is completely unnecessary, but I'll try to get a second opinion to strengthen my cause.
Ahhh, I asked Earth-Fury for help and he said that you were right. It seems that you DO have to null local handles, even if they are the returned value. Sounds great.