• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[General] How can I pick units that are between point1 and point2 ?

Status
Not open for further replies.
Level 29
Joined
Sep 26, 2009
Messages
2,606
Well, point1 and point2 are nothing more than [x,y] coordinates. You can calculate the difference between them, half it by two and subtract it from one point. Then pick all units within range of the center point.

E.g. You have points A and B, to calculate the point between those two, you do the following:
A[20,25], B[30, 15]

C = A - [A - B]/2
Cx = Ax - [Ax - Bx]/2
Cx = 20 - [20 - 30]/2
Cx = 20 - (-10)/2
Cx = 20 + 5
Cx = 25

Cy = Ay - [Ay - By]/2
Cy = 25 - [25 - 15]/2
Cy = 25 - 10/2
Cy = 25 - 5
Cy = 20

C = [Cx, Cy]
C = [25, 20]

I chose simple numbers so you can see that point C is exactly in the center between those two point. It should work for all numbers.
Then you do "Pick every unit within X range of C...".
If you want the range to be exactly up to the Point1 and Point2, then you will need so save the coordinate difference you calculate above (the difference is [A-B]/2) for each coordinate, use those triangle ordinate and then calculate the hypotenuse which is the actual range.
 
Level 11
Joined
Oct 9, 2015
Messages
721
I need to pick all units in that line, I'm trying to understand Nichillus solution, I need a line of the size of about 10. I have a hero, hero is the point1, then I create a dummy unit let's say 200 away from it (that's point2), and add expiration timer to it, when it dies I want to pick every unit that is in between them and do actions (in my case cause damage)
 
Level 11
Joined
Oct 9, 2015
Messages
721
I'm actually creating 1 dummy for every 10 size making a near half a circle, that's why 10 is just fine, cause I have another dummy in the next 10 after the previous one

  • Untitled Trigger 015
    • Events
      • Unit - A unit Dies
    • Conditions
    • Actions
      • Set Loc1 = (Position of Unit[(Player number of (Owner of (Triggering unit)))])
      • Set Loc2 = (Position of (Triggering unit))
      • -------- ACTION 1 --------
      • Unit Group - Pick every unit in (Units in ((Region(Loc1, Loc2)) offset by (10.00, 10.00))) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Picked unit) Not equal to (Triggering unit)
              • (Picked unit) Not equal to Unit[(Player number of (Owner of (Triggering unit)))]
            • Then - Actions
              • Unit - Cause Unit[(Player number of (Owner of (Triggering unit)))] to damage (Picked unit), dealing 500.00 damage of attack type Spells and damage type Normal
            • Else - Actions
      • -------- ACTION 2 --------
      • Unit Group - Pick every unit in (Units in (Region(Loc1, Loc2))) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Picked unit) Not equal to (Triggering unit)
              • (Picked unit) Not equal to Unit[(Player number of (Owner of (Triggering unit)))]
            • Then - Actions
              • Unit - Cause Unit[(Player number of (Owner of (Triggering unit)))] to damage (Picked unit), dealing 500.00 damage of attack type Spells and damage type Normal
            • Else - Actions
      • Custom script: call RemoveLocation(udg_Loc1)
      • Custom script: call RemoveLocation(udg_Loc2)
Action 1 and 2 are different actions I've found to make it, but I don't know if it works, the actions are working when I create the dummy at +90 of facing of unit to +10 of facing of unit then it don't work for normal facing of unit (+0 of facing of unit) and get back to work at -10 of facing of unit, -20 and so on...
 
Last edited by a moderator:
Level 11
Joined
Oct 9, 2015
Messages
721
can it support a "line size" or something ? the only problem I'm having is picking units that are in a straight line in front of the hero, any other degrees are working properly, from +90 to +10, then from -10 to +90, 0 degrees are being the problem as I'm not being able to damage units in this points :(
 

Dr Super Good

Spell Reviewer
Level 65
Joined
Jan 18, 2005
Messages
27,290
You simply do nothing, since the statistical chance of a unit ever having its origin within a perfect line is as good as impossible.

Unit searches in JASS only look at unit origin, and do not factor in unit collision size. This means that a very small radius search will only return any units if their origin is in the very small search area. Expanding this definition to a line search, the results will only return units which are exactly along a line. Due to the nature of floating points this is statistically improbable.

You will need to search for units in a rotatable rectangle if you want vaguely useable results.
 
Level 11
Joined
Oct 9, 2015
Messages
721
I need to pick units between position of unit and those red dots, it begins with +90 (1) +80 (2) +70 (3) +60 (2) and so on, it works just fine ultil +10 but when it comes to +0 (normal facing of unit, without adds or subtractions on angle) the unit's aren't picked.
PS: Vertical line is the facing of the unit
I'm still trying to figure out Nichillis solution but I did not had success

https://www.dropbox.com/s/jsk739e339hvvur/example.jpg
 
Level 7
Joined
Oct 19, 2015
Messages
286
The library I linked to can also be made to consider unit collision size, so this is not a problem even if you specify a 0 line width (you can however also specify a higher width, in which case it will look for units in a rotated rectangle like Dr Super Good mentioned).

Your code is incorrect for what you're trying to do, you're not picking units in a line (or rotated rectangle) but in a non-rotated rectangle, with the two points defining two opposite corners of the rectangle. That's why angle 0 doesn't work, because the rectangle has a width of 0 in that case because the two points have the same y coordinate.
 
Level 11
Joined
Oct 9, 2015
Messages
721
I'll try to implement this library, any advice on the approach ?

I'm trying to implement the library but I can't understand what need to be done here and what does it do:
I don't quite understand jass really well

- call GetNearestPointOnSegment(Ax, Ay, Bx, By, Cx, Cy) to get the nearest point on
// the line segment AB to point C (returns a location)
// - call GetDistanceFromSegment(Ax, Ay, Bx, By, Cx, Cy) to get the distance from the
// line segment AB to a given point C
// - call GroupEnumUnitsInRangeOfSegment(whichgroup, Ax, Ay, Bx, By, distance, filter) to
// add all of the units within distance of segment AB to whichgroup, according to the filter.
// - call IsUnitInRangeOfSegment(unit, Ax, Ay, Bx, By, distance) to see if the unit givin is
// within distance of segment AB
// - xebasic is no longer required, but if avaliable, will return correct results with
// GroupEnumUnitsInRangeOfSegment() using XE_MAX_COLLISION_SIZE.

PS: Sorry for double posting, I didn't saw there was already a page 2
 
Last edited by a moderator:
Level 7
Joined
Oct 19, 2015
Messages
286
First of all, it's a vJass library so you need the NewGen editor to use it as-is, however it can also be reasonably easily modified to work with the normal editor so if you don't have NewGen I can do that for you.

The first step is to copy the library into your map, you can simply paste it into the map header. However, to keep things more organised, we usually create a new trigger for each library instead of pasting them all into the map header, so you can create a new trigger with the same name as the library, convert it to custom text and replace its content with the library code.

Then, you need a global group variable named TempGroup, which you'll use in your "pick every unit..." action.

Then, before the "Pick every unit..." action, add a custom script line with this content:
call GroupEnumUnitsInRangeOfSegmentLoc( udg_TempGroup, udg_Loc1, udg_Loc2, 10.0, null )
This will fill your group with the units in range of the segment.

As is, the library will ignore unit collision size unless you also have the xebasic library in your map, however this too can be easily changed.
 
Last edited:
Level 7
Joined
Oct 19, 2015
Messages
286
Okay, here it is. You need to paste this code into the map header (to access it, click on your map name at the top of the list of triggers), pasting it into a trigger won't work with the normal editor:
JASS:
function GroupEnumUnitsInRangeOfSegment takes group whichgroup, real Ax, real Ay, real Bx, real By, real distance, boolexpr filter returns nothing
    local real dx = Bx-Ax
    local real dy = By-Ay
    local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
    local real r = SquareRoot(dx*dx+dy*dy)/2+distance + 400.0 // replace 400.0 with the max collision size in your map
    local unit u
    call GroupClear(whichgroup)
    call GroupEnumUnitsInRange(udg_LineTempGroup, Ax+(dx/2), Ay+(dy/2), r, filter)
    loop
        set u = FirstOfGroup(udg_LineTempGroup)
        exitwhen u == null
        if L == 0 and IsUnitInRangeXY(u, Ax, Ay, distance) then // seg is actually a point so lets return the point
            call GroupAddUnit(whichgroup, u)
        else
            set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy))/(L) // get the ratio
            if r > 1 then // split if/thens so that it exists properly
                if IsUnitInRangeXY(u, Bx, By, distance) then // closests point is past seg, so return end point B
                    call GroupAddUnit(whichgroup, u)
                endif
            elseif r < 0 then
                if IsUnitInRangeXY(u, Ax, Ay, distance) then // same as B, but at A instead
                    call GroupAddUnit(whichgroup, u)
                endif
            elseif IsUnitInRangeXY(u, Ax+r*(dx), Ay+r*(dy), distance) then // In the middle of A and B so use the ratio to find the point
                call GroupAddUnit(whichgroup, u)
            endif
        endif
        call GroupRemoveUnit(udg_LineTempGroup, u)
    endloop
    set u = null    
endfunction
function GroupEnumUnitsInRangeOfSegmentLoc takes group whichgroup, location A, location B, real distance, boolexpr filter returns nothing
    call GroupEnumUnitsInRangeOfSegment(whichgroup, GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), distance, filter)
endfunction
You also need to declare another global group variable named LineTempGroup which is used by these functions internally.

I also edited my previous post, I forgot one of the arguments when I wrote that custom script line, it should be correct now.
 
Level 11
Joined
Oct 9, 2015
Messages
721
it's returning some kind of error after I've put the library on map header

EDIT: my bad forgot to add the other tempgroup, it started now, I'll try and implement it now! thanks very much, if something goes wrong I'll be back to report

Oh my god, it's working wonderfully, thank you very much for helping me out, it was really nice of you! Now one more question what is this collision thing you spoke about ?
 
Last edited by a moderator:
Level 7
Joined
Oct 19, 2015
Messages
286
Ah, I already fixed that, although it wasn't as big an issue as I thought it'd be.

Basically, when enumerating units in range of a point, the game code only considers units whose origin is in range of that point. However, area spells also affect units whose collision size is in range of the point, and these units will get highlighted when you hover over them with an area spell targeting cursor. Therefore, if we want to make a triggered area spell, doing a standard group enumeration is not acceptable since it might not affect all the highlighted units, making the spell preform differently from how players expect it to. To fix this, we need to enumerate units in a larger area and then use the IsUnitInRangeXY check which does consider unit collision size.

That's for circular area effects. In the case of this function, we have a line area of effect, so when we enumerate units in range of the centre of that line, we are already enumerating units in a much larger area than the actual line, so units that aren't standing on the line but are only touching it with their collision circle will still get picked even if we don't increase the range. That's why I said it's not such a big issue. I still increased the range of the enumeration by 400 just in case, but even if I left that at 0 the only units that would get missed would be ones that are standing at the end of the line.

Note: since you are checking multiple radial lines, there will be some overlap between them near the centre and/or some gaps between them at the edge. So some units might get picked twice (or even more times) or missed. You should add checks to your code that will make it ignore already hit units, then set the width of your line so that there will be no gaps (at least, no gaps larger than your minimum unit collision size) at the outer edge of your circle.

Ideally, you would use a slightly modified function that would support a line with different start and end width. You'd still need safety checks though since due to unit collision sizes some units would still get picked two or more times since they'd be standing on more than one line.
 
Level 7
Joined
Oct 19, 2015
Messages
286
Yes. The original library was already doing the IsUnitInRangeXY check which takes unit collision size into account, however when calculating how far away to look for units to run this check on it was relying on another library to tell it what the maximum unit collision size is and if that library wasn't present it just used 0. I changed it so that it uses the value 400.0 instead, which is larger than any building collision size, however if your spell can't hit buildings then you can reduce this to improve performance (since fewer units will need to be checked if the search range is lower).
 
Level 11
Joined
Oct 9, 2015
Messages
721
Thanks a lot man, so with the collision the detection works better ? I mean, it will detect the collision and not the center of the unit, this is better, right ? The spell itself is a simple sword swing and I create dummies whenever the animation with the sword goes

sometimes it's taking units that are very far away, as it was taking all units in line until the end of the map
 
Last edited by a moderator:
Now comes GUI:

  • Make them green
    • Events
      • Game - SomeEvent becomes equal to 1.00
    • Conditions
      • SomeRgion contains U
    • Actions
      • Set Tempp1 = (Postion of U)
      • Set Tempp2 = (position of U2)
      • Set TempAngle = angle from Tempp1 to Tempp2
      • Set TempDist = (integer(distance between Tempp1 and Tempp2) / 100))
      • Custom script: call RemoveLocation(udg_Tempp2)
      • For each (integer A) from 1 to (TempDist) do (Actions)
        • Loop - Actions -
          • Set Tempp2 = (Tempp1 offset by ((integer a) x 100) towards TempAngle
          • ---or whatever points you like --
          • Set GroupLeaky = (units within 150 of Tempp2)
          • Unit Group - Pick all units in GroupLeaky and do
            • Loop - Actions
              • Animation - set vertex coloring of (picked unit) to (0%, 100%, 0%) and 0% transparency
          • -------- remove leaks --------
          • Custom script: call RemoveLocation(udg_Tempp2)
          • Custom script: call RemoveDestroy(udg_GroupLeaky)
        • Custom script: call RemoveLocation(udg_Tempp1)

Simple is good.
 
Last edited:
Level 7
Joined
Oct 19, 2015
Messages
286
That's odd, the function I posted contains checks to make sure a unit is within the bounds of the segment. Also, it enumerates units within a certain range of the centre of the line to begin with, so it can't even get a unit that would be far away at the edge of the map. Are you sure you're passing the right values to it, maybe your point is really far away? Post your trigger.

Edit: I found your other thread about this problem and posted there.
 
Last edited:
I didn't clearly understand your trigger and what does it do, Ease, can you enlight me on it, please ? How can I adapt it to my functions ?

All you need to do is "get" the distance between your two points. Then loop the number of time needed for the distance. For example, if the distance is 1000, we divide it by 100 which equals 10. Then we loop 10 times and create circles every 100 distance. The circles are 150 so they overlap. Then we select all the units inside the circles. Now we have all the units between two points. You can narrow the circles and close the distance between them if you like. That way you get a narrower line.

I tried reading the thread but it got confusing. I don't really understand what you want to do beyond, just picking all units in a line. The GUI above will pick all units in a line. How you determine what Tempp1 and Tempp2 are is up to you.
 

Dr Super Good

Spell Reviewer
Level 65
Joined
Jan 18, 2005
Messages
27,290
So people can understand what is going on...
JASS:
set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy))/(L) // get the ratio
This is "scalar projection". It is working out the length of the orthogonal projection of A->U onto A->B.

An optimization is thrown in for later where due to L being |A->B|^2 the orthogonal projection can be computed by scalar multiplying it with A->B directly.

JASS:
            if r > 1 then // split if/thens so that it exists properly
                 if IsUnitInRangeXY(u, Bx, By, distance) then // closests point is past seg, so return end point B
                     call GroupAddUnit(whichgroup, u)
                 endif
             elseif r < 0 then
                 if IsUnitInRangeXY(u, Ax, Ay, distance) then // same as B, but at A instead
                     call GroupAddUnit(whichgroup, u)
                 endif
These two cases will result in circular ends, which may or may not be desirable. For flat ends one would need to project onto a perpendicular vector and clamp within the confines of "distance" to get the closest point and then check if the unit is within 0 of that point.

All you need to do is "get" the distance between your two points. Then loop the number of time needed for the distance. For example, if the distance is 1000, we divide it by 100 which equals 10. Then we loop 10 times and create circles every 100 distance. The circles are 150 so they overlap. Then we select all the units inside the circles. Now we have all the units between two points. You can narrow the circles and close the distance between them if you like. That way you get a narrower line.
Inefficient, inaccurate and scales badly. Also does not factor in unit collision size. Can be sufficient though and is how HotS and SC2 implement many of their skills, but they do have searches which do factor in collision size which let them do this.

I tried reading the thread but it got confusing. I don't really understand what you want to do beyond, just picking all units in a line. The GUI above will pick all units in a line. How you determine what Tempp1 and Tempp2 are is up to you.
Or you can use Anitarf cryptic JASS implementation which will do it more accurately and more efficient.
 
Well, I'm quite sure Dr. Super Good has a much better way of doing this ... if you can understand it. If not, my way works too. It ignores collision size and picks units who's center location is within range. This is either very efficient (as I posted it) or very accurate (if you set the circle every 10 distance) and less efficient. Learning Jass is a worthwhile endeavor and I should try to dig into it more, but if you are already swamped with other tasks, the GUI can get it done too.
 
Status
Not open for further replies.
Top