• 🏆 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!

Line Segments library edit

Status
Not open for further replies.
Level 18
Joined
Nov 21, 2012
Messages
835
Thanks to Anitarf post I found this library:
http://www.wc3c.net/showthread.php?t=101405


JASS:
// ==================================================================================
// LineSegment - by Ammorth, Feb 13, 2010 - rev5
// 
// This script, and myself, can be found at wc3c.net
//
// Used to determine certain things about segments, involving units and geometry.
//
// Requirements:
//   - vJass
//   - optional: xebasic - by Vexorian ( [url]http://www.wc3c.net/showthread.php?t=101150[/url] )
//     will use XE_MAX_COLLISION_SIZE if xebasic is present, otherwise it will ignore it.
//
// Installation:
//   - Create a new trigger called LineSegments and convert it to custom text
//   - Copy the code to the new trigger, replacing everything
//
// How To Use:
//   - 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.
//
// Notes:
//   - All functions have a location version wrappers incase you would rather pass locations
//     They are named as follows:
//         > GetNearestPointOnSegmentLoc()
//         > GetDistanceFromSegmentLoc()
//         > GroupEnumUnitsInRangeOfSegmentLoc()
//         > IsUnitInRangeOfSegmentLoc()
//
// ==================================================================================
library LineSegment requires optional xebasic
globals
    private group udg_LineTempGroup = CreateGroup()
endglobals

// with optionals we can add/improve features if other libraries are present
// this should in-line with an optimizer, since the static if makes it constant
private constant function xebasic_wrapper takes nothing returns real
    static if LIBRARY_xebasic then
        return XE_MAX_COLLISION_SIZE
    else
        return 0.0
    endif
endfunction

function GetNearestPointOnSegment takes real Ax, real Ay, real Bx, real By, real Cx, real Cy returns location
    local real r
    // could use a point struct here if you really wanted, instead of a location.
    local real dx = Bx-Ax
    local real dy = By-Ay
    local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
    if L == 0 then // seg is actually a point so lets return the point
        return Location(Ax, Ay)
    endif
    set r = ((Cx-Ax)*(dx) + (Cy-Ay)*(dy))/(L) // get the ratio
    if r > 1 then // closests point is past seg, so return end point B
        return Location(Bx, By)
    elseif r < 0 then // same as B, but at A instead
        return Location(Ax, Ay)
    endif // In the middle of A and B so use the ratio to find the point
    return Location(Ax+r*(dx), Ay+r*(dy))
endfunction
function GetNearestPointOnSegmentLoc takes location A, location B, location C returns location
    return GetNearestPointOnSegment(GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), GetLocationX(C), GetLocationY(C))
endfunction


function GetDistanceFromSegment takes real Ax, real Ay, real Bx, real By, real Cx, real Cy returns real
    local real r
    local real dx = Bx-Ax
    local real dy = By-Ay
    local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
    if L == 0 then // seg is actually a point so lets return the distance to the point
        return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
    endif
    set r = ((Cx-Ax)*(dx) + (Cy-Ay)*(dy))/(L) // get the ratio
    if r > 1 then // closests point is past seg, so return distance to point B
        return SquareRoot((Cx-Bx)*(Cx-Bx)+(Cy-By)*(Cy-By))
    elseif r < 0 then // same as B, but at A instead
        return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
    endif // In the middle of A and B so use the ratio to find the point
    set Ax = Ax+r*(dx)
    set Ay = Ay+r*(dy)
    return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
endfunction
function GetDistanceFromSegmentLoc takes location A, location B, location C returns real
    return GetDistanceFromSegment(GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), GetLocationX(C), GetLocationY(C))
endfunction

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 + xebasic_wrapper() // double-purpose for r
    local unit u
    call GroupClear(udg_LineTempGroup)
    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

function IsUnitInRangeOfSegment takes unit u, real Ax, real Ay, real Bx, real By, real distance returns boolean
    local real r
    local real dx = Bx-Ax
    local real dy = By-Ay
    local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
    if L == 0 then // seg is actually a point so lets return the point
        return IsUnitInRangeXY(u, Ax, Ay, distance)
    endif
    set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy))/(L) // get the ratio
    if r > 1 then // closests point is past seg, so return end point B
        return IsUnitInRangeXY(u, Bx, By, distance)
    elseif r < 0 then // same as B, but at A instead
        return IsUnitInRangeXY(u, Ax, Ay, distance)
    endif // In the middle of A and B so use the ratio to find the point
    return IsUnitInRangeXY(u, Ax+r*(dx), Ay+r*(dy), distance)
endfunction
function IsUnitInRangeOfSegmentLoc takes unit u, location A, location B, real distance returns boolean
    return IsUnitInRangeOfSegment(u, GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), distance)
endfunction

endlibrary


I have read thread on wc3c.net but I don't fully undestand math behind.
I really need only one function from this library
function GroupEnumUnitsInRangeOfSegment takes group whichgroup, real Ax, real Ay, real Bx, real By, real distance, boolexpr filter returns nothing
I won't need "optional: xebasic". Additionaly I need one more function parameter (boolean) for setting green area to be enumerated or not.
Something like boolean addGreenCurve.
So it may look like
function GroupEnumUnitsInRangeOfSegment takes group whichgroup, real Ax, real Ay, real Bx, real By, real distance, boolean addGreenCurve, boolexpr filter returns nothing
Let's assume that "start point" on image is Ax, Ay
It is better if all function will work on coordinates (not on locations)
I know Ammorth send some solutions on wc3c but I'm not feeling strong enough to edit this lib myself.
223791-albums8824-picture107790.jpg
 
Level 7
Joined
Oct 19, 2015
Messages
286
Try if this works:
JASS:
globals
    group udg_LineTempGroup = CreateGroup()
endglobals

function GroupEnumUnitsInRangeOfSegmentEx_EdgeCheck takes unit u, real x, real y, real rx, real ry returns boolean
    local real r
    set x = x - rx
    set y = y - ry
    set rx = rx*2.0
    set ry = ry*2.0
    if rx==0 and ry==0 then
        return IsUnitInRangeXY(u, x, y, 0.0)
    endif
    set r = ((GetUnitX(u) - x)*rx + (GetUnitY(u) - y)*ry) / (rx*rx + ry*ry)
    if r > 1 then
        return IsUnitInRangeXY(u, (x+rx), (y+ry), 0.0)
    elseif r < 0 then
        return IsUnitInRangeXY(u, x, y, 0.0)
    endif
    return IsUnitInRangeXY(u, x + r*rx, y + r*ry, 0.0)
endfunction

function GroupEnumUnitsInRangeOfSegmentEx takes group whichgroup, real Ax, real Ay, real Bx, real By, real Adistance, real Bdistance, boolean Acircle, boolean Bcircle, boolexpr filter returns nothing
    local real maxCollisionSize = 197.0
    local real dx = Bx-Ax
    local real dy = By-Ay
    local real L2 = ((dx)*(dx) + (dy)*(dy)) // Square of length (vector scalar product with itself)
    local real L = SquareRoot(L2)
    local real r = L/2 + RMaxBJ( Adistance, Bdistance ) + maxCollisionSize
    local unit u
    local real ex
    local real ey
    call GroupClear(udg_LineTempGroup)
    call GroupEnumUnitsInRange(udg_LineTempGroup, Ax+(dx/2), Ay+(dy/2), r, filter)
    loop
        set u = FirstOfGroup(udg_LineTempGroup)
        exitwhen u == null
        if L2 == 0 and IsUnitInRangeXY(u, Ax, Ay, Adistance) then // seg is actually a point so lets return the point
            call GroupAddUnit(whichgroup, u) // we ignore Acircle and Bcircle in this case
        else
            set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy)) / (L2) // get the ratio
            if r > 1 then //unit is past end of segment
                if Bcircle then
                    if IsUnitInRangeXY(u, Bx, By, Bdistance) then // closests point is past seg, so return end point B
                        call GroupAddUnit(whichgroup, u)
                    endif
                else
                    // check a perpendicular segment at the endpoint
                    if GroupEnumUnitsInRangeOfSegmentEx_EdgeCheck( u, Bx, By, -dy/L*Bdistance, dx/L*Bdistance ) then
                        call GroupAddUnit(whichgroup, u)
                    endif
                endif
            elseif r < 0 then //unit is past start of segment
                if Acircle then
                    if IsUnitInRangeXY(u, Ax, Ay, Adistance) then // same as B, but at A instead
                        call GroupAddUnit(whichgroup, u)
                    endif
                else
                    if GroupEnumUnitsInRangeOfSegmentEx_EdgeCheck( u, Ax, Ay, -dy/L*Adistance, dx/L*Adistance ) then
                        call GroupAddUnit(whichgroup, u)
                    endif
                endif
            elseif IsUnitInRangeXY(u, Ax+r*(dx), Ay+r*(dy), Adistance*(1.0-r) + Bdistance*r) 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
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
Thank you,
can you please describe
function GroupEnumUnitsInRangeOfSegmentEx takes group whichgroup, real Ax, real Ay, real Bx, real By, real Adistance, real Bdistance, boolean Acircle, boolean Bcircle, boolexpr filter returns nothing
what is Adistance & Bdistance ?

I used in tests call GroupEnumUnitsInRangeOfSegmentEx(udg_ug, x, y, Tx, Ty, 150.00, 150.00, false, false, filter)
and sometimes (on some angles) units behind caster are enumerated, which I don't want to.
 
Level 7
Joined
Oct 19, 2015
Messages
286
The Adistance and Bdistance are the same value as the old distance, meaning half the width of the segment, however each value corresponds to one end of the segment. This way, you can define segments that have different width at the start and at the end, with linear interpolation in between.

Do the units that are getting picked intersect the segment with their collision size? That way, they could get picked even if their centre is behind the caster, as long as a part of their collision circle is in front. If you don't want this, edit the GroupEnumUnitsInRangeOfSegmentEx_EdgeCheck function to always return false.
 
Level 18
Joined
Nov 21, 2012
Messages
835
pls look at this example
223791-albums8824-picture107861.jpg


im using this small code to test
JASS:
function Filtr1 takes nothing returns boolean
    return not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) and GetUnitTypeId(GetFilterUnit()) != 'Otch'
endfunction

function Trig_lineTest_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'AOsh'
endfunction
//----------------------------------------------------------
function Life200 takes nothing returns nothing
    local unit u = GetEnumUnit()
    call SetWidgetLife(u, GetWidgetLife(u)-100.00)
    set u=null
endfunction

function Trig_lineTest_Actions takes nothing returns nothing
    local real x = GetUnitX(GetTriggerUnit())
    local real y = GetUnitY(GetTriggerUnit())
    local real Tx = GetSpellTargetX()
    local real Ty = GetSpellTargetY()
    local boolexpr filter = Condition(function Filtr1)

    call GroupClear(udg_ug)
    call GroupEnumUnitsInRangeOfSegmentEx(udg_ug, x, y, Tx, Ty, 200.00, 200.00, false, false, filter)
    //call GroupEnumUnitsInRangeOfSegment(udg_ug, x, y, Tx, Ty, 150.00, filter)
    call ForGroup(udg_ug, function Life200)

    call DestroyBoolExpr(filter)
    set filter=null
endfunction

//===========================================================================
function InitTrig_lineTest takes nothing returns nothing
    set gg_trg_lineTest = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_lineTest, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_lineTest, Condition( function Trig_lineTest_Conditions ) )
    call TriggerAddAction( gg_trg_lineTest, function Trig_lineTest_Actions )
endfunction
 
Level 7
Joined
Oct 19, 2015
Messages
286
Thanks for the detailed error report. I found the mistake in the code and corrected it in my first post. My earlier suggestion of simply making the secondary function always return false would have also prevented the error, since the cause of the problem were incorrect parameters being passed to that function.
 
Level 18
Joined
Nov 21, 2012
Messages
835
Thanks, Anitarf! I can confirm now your code works OK.
Im going to use function in this form in most cases
call GroupEnumUnitsInRangeOfSegmentEx(udg_ug, x, y, Tx, Ty, 100.00, 100.00, false, true, filter)
Also lowered local real maxCollisionSize = 64.0 //197 since I plan to use function on units, not on buildings.

It would be great addition to spell section if you could upload your modifed code here with description for other users.
 
Status
Not open for further replies.
Top