Line Segment Enumeration v2.2a (vJass, Lua, GUI)

This bundle is marked as approved. It works and satisfies the submission rules.
  • Like
Reactions: Warseeker and MyPad
Info

Allows to enumerate widgets inside a line segment with an offset.
So basicly it will result in a rect, but which is also allowed to be rotated.

There is normal JASS, and also a GUI version.
For GUI, you should open the demo map, to see the triggers.

Code


JASS:
library LineSegmentEnumeration /* v2.2a -- hiveworkshop.com/threads/line-segment-enumeration-v1-1.286552/

    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Allows to enumerate widgets inside a line segment with an offset.
        So basicly it will result in a rect, but which is also allowed to be rotated.
 
 
    Mechanics
    ¯¯¯¯¯¯¯¯¯
 
        (Issue:)
 
        The problem with normal jass rects is that they aren't defined by 4 points, but only by 4 values: x/y -min/max.
        The result is that a jass rect is never rotated, so it's always paralel to the x/y axis.
 
        But when we draw a line from point A to point B we might also create a non-axix-parelel rect, and then
        we can't use the normal rect natives from jass anymore to find out if a point is inside the rect.

        (Solution:)
 
        To solve this problem the system does following:
 
        jass rect: rectangular defined by 4 values (axis paralel)
        custom rect: the real rectangular that is defined by user (not axis parelel)

        1. Create a big jass rect that is big enough so we can ensure to enum all widgets that are potentialy inside our custom rect. (Enum_Group)
           This Enum_Group will contain all wanted units, but may also contain not wanted units.

        2. Construct the custom rect following a form with the same parameters as in this image, but in 2D:
           https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Ellipsoide.svg/800px-Ellipsoide.svg.png

        3. Loop through Enum_Group and define each widget's coordinates relative to the center of the custom rect as a 2D vector

        4. Get the components of the widget's position vector on the local (rotated) x-y axis of the custom rect

        5. Check if the projected lengths (absolute value of components) of the widget's position is less than <a> and <b> as described in the
           image linked above.

*/
//  --- API ---
//! novjass

    struct LineSegment

        static constant real MAX_UNIT_COLLISION

        static method EnumUnitsEx takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolean checkCollision returns nothing
        static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing

        static method EnumDestructables takes real ax, real ay, real bx, real by, real offset returns nothing
     
        //  after enumerated destructables you have access to:
 
            static integer DestructableCounter      // starts with index "0"
            static destructable array Destructable
     
        static method EnumItems takes real ax, real ay, real bx, real by, real offset returns nothing
     
        //  after enumerated items you have access to:
 
            static integer ItemCounter      // starts with index "0"
            static destructable array Item
     
//! endnovjass
// ==== End API ====

struct LineSegment extends array

    public static constant real MAX_UNIT_COLLISION = 197.00

    private static constant rect RECT = Rect(0, 0, 0, 0)

    private static constant group GROUP = CreateGroup()

    private static real ox
    private static real oy
    private static real dx
    private static real dy
    private static real da
    private static real db
    private static real ui
    private static real uj
    private static real wdx
    private static real wdy

    private static method PrepareRect takes real ax, real ay, real bx, real by, real offset, real offsetCollision returns nothing
        local real maxX
        local real maxY
        local real minX
        local real minY

        // get center coordinates of rectangle
        set ox = 0.5*(ax + bx)
        set oy = 0.5*(ay + by)

        // get rectangle major axis as vector
        set dx = 0.5*(bx - ax)
        set dy = 0.5*(by - ay)

        // get half of rectangle length (da) and height (db)
        set da = SquareRoot(dx*dx + dy*dy)
        set db = offset

        // get unit vector of the major axis
        set ui = dx/da
        set uj = dy/da

        // Prepare the bounding Jass Rect
        set offset = offset + offsetCollision

        if ax > bx then
            set maxX = ax + offset
            set minX = bx - offset
        else
            set maxX = bx + offset
            set minX = ax - offset
        endif

        if ay > by then
            set maxY = ay + offset
            set minY = by - offset
        else
            set maxY = by + offset
            set minY = ay - offset
        endif

        call SetRect(RECT, minX, minY, maxX, maxY)
    endmethod

    private static method RotateWidgetCoordinates takes widget w returns nothing
        // distance of widget from rectangle center in vector form
        set wdx = GetWidgetX(w) - ox
        set wdy = GetWidgetY(w) - oy

        set dx = wdx*ui + wdy*uj    // get the component of above vector in the rect's major axis
        set dy = wdx*(-uj) + wdy*ui // get the component of above vector in the rect's transverse axis
    endmethod

    private static method IsWidgetInRect takes widget w returns boolean
        call RotateWidgetCoordinates(w)

        // Check if the components above are less than half the length and height of the rectangle
        // (Square them to compare absolute values)
        return dx*dx <= da*da and dy*dy <= db*db
    endmethod

    private static method IsUnitInRect takes unit u, boolean checkCollision returns boolean
        if checkCollision then
            call RotateWidgetCoordinates(u)

            // Check if the perpendicular distances of the unit from both axes of the rect are less than
            // da and db
            return IsUnitInRangeXY(u, ox - dy*uj, oy + dy*ui, RAbsBJ(da)) /*
            */ and IsUnitInRangeXY(u, ox + dx*ui, oy + dx*uj, RAbsBJ(db))
        endif

        return IsWidgetInRect(u)
    endmethod

    public static method EnumUnitsEx takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolean checkCollision returns nothing
        local unit u

        if checkCollision then
            call PrepareRect(ax, ay, bx, by, offset, MAX_UNIT_COLLISION)
        else
            call PrepareRect(ax, ay, bx, by, offset, 0.00)
        endif

        call GroupEnumUnitsInRect(GROUP, RECT, null)

        // enum through all tracked units, and check if it's inside bounds
        call GroupClear(whichgroup)
        loop
            set u = FirstOfGroup(GROUP)
            exitwhen u == null

            if IsUnitInRect(u, checkCollision) then
                call GroupAddUnit(whichgroup, u)
            endif

            call GroupRemoveUnit(GROUP, u)
        endloop
    endmethod

    public static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing
        call EnumUnitsEx(whichgroup, ax, ay, bx, by, offset, false)
    endmethod

//! textmacro LSE_WIDGET takes TYPE, NAME
    public static integer $NAME$Counter = -1
    public static $TYPE$ array $NAME$

    private static method on$NAME$Filter takes nothing returns nothing
        local $TYPE$ t = GetFilter$NAME$()

        if IsWidgetInRect(t) then
            set $NAME$Counter = $NAME$Counter + 1
            set $NAME$[$NAME$Counter] = t
        endif

        set t = null
    endmethod

    public static method Enum$NAME$s takes real ax, real ay, real bx, real by, real offset returns nothing
        call PrepareRect(ax, ay, bx, by, offset, 0.00)

        set $NAME$Counter = -1
        call Enum$NAME$sInRect(RECT, Filter(function thistype.on$NAME$Filter), null)
    endmethod
//! endtextmacro

//! runtextmacro LSE_WIDGET("destructable", "Destructable")
//! runtextmacro LSE_WIDGET("item", "Item")

endstruct
endlibrary

GUI Plugin

JASS:
library LineSegmentEnumerationGUI uses LineSegmentEnumeration /* v2.0b
*/
    private struct LineSegmentGUI extends array
 
        private static method GetUnits takes nothing returns nothing
            call LineSegment.EnumUnitsEx(udg_LSE_Group, GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset, udg_LSE_UnitCollisionFlag)
        endmethod

        private static method GetDestructables takes nothing returns nothing
            call LineSegment.EnumDestructables(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
            set udg_LSE_DestructableCounter = -1
            loop
                exitwhen LineSegment.DestructableCounter < 0
                set udg_LSE_DestructableCounter = udg_LSE_DestructableCounter + 1
                set udg_LSE_Destructable[udg_LSE_DestructableCounter] = LineSegment.Destructable[LineSegment.DestructableCounter]
                set LineSegment.DestructableCounter = LineSegment.DestructableCounter - 1
            endloop
        endmethod
   
        private static method GetItems takes nothing returns nothing
            call LineSegment.EnumItems(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
            set udg_LSE_ItemCounter = -1
            loop
                exitwhen LineSegment.ItemCounter < 0
                set udg_LSE_ItemCounter = udg_LSE_ItemCounter + 1
                set udg_LSE_Item[udg_LSE_ItemCounter] = LineSegment.Item[LineSegment.ItemCounter]
                set LineSegment.ItemCounter = LineSegment.ItemCounter - 1
            endloop
        endmethod

        private static method onInit takes nothing returns nothing
            set udg_LSE_GetUnits = CreateTrigger()
            set udg_LSE_GetDestructables = CreateTrigger()
            set udg_LSE_GetItems = CreateTrigger()
            call TriggerAddAction(udg_LSE_GetUnits, function thistype.GetUnits)
            call TriggerAddAction(udg_LSE_GetDestructables, function thistype.GetDestructables)
            call TriggerAddAction(udg_LSE_GetItems, function thistype.GetItems)
        endmethod
   
    endstruct

endlibrary



Lua:
--[[ LineSegmentEnumeration v2.2a

    API:

        function LineSegment.EnumUnits(number: ax, number: ay, number: bx, number: by, number: offset, boolean: checkCollision)
        function LineSegment.EnumDestructables(number: ax, number: ay, number: bx, number: by, number: offset)
        function LineSegment.EnumItems(number: ax, number: ay, number: bx, number: by, number: offset)
            - returns the enumerated widgets as a table

]]--
LineSegment = {}
do
    local RECT = Rect(0, 0, 0, 0)
    local GROUP = CreateGroup()

    local ox
    local oy
    local dx
    local dy
    local da
    local db
    local ui
    local uj
    local wdx
    local wdy
    local uui
    local uuj

    local function prepare_rect(ax, ay, bx, by, offset)
        local x_max
        local x_min
        local y_max
        local y_min

        -- get center coordinates of rectangle
        ox, oy = 0.5*(ax + bx), 0.5*(ay + by)

        -- get rectangle major axis as vector
        dx, dy = 0.5*(bx - ax), 0.5*(by - ay)

        -- get half of rectangle length (da) and height (db)
        da, db = math.sqrt(dx*dx + dy*dy), offset

        -- get unit vector of the major axis
        ui, uj = dx/da, dy/da

        -- prepare bounding rect
        if ax > bx then
            x_min, x_max = bx - offset, ax + offset
        else
            x_min, x_max = ax - offset, bx + offset
        end

        if ay > by then
            y_min, y_max = by - offset, ay + offset
        else
            y_min, y_max = ay - offset, by + offset
        end

        SetRect(RECT, x_min, y_min, x_max, y_max)
    end

    local function rect_contains_widget(w, offset)
        wdx, wdy = GetWidgetX(w) - ox, GetWidgetY(w) - oy
        dx, dy = wdx*ui + wdy*uj, wdx*(-uj) + wdy*ui
        da, db = da + offset, db + offset

        return dx*dx <= da*da and dy*dy <= db*db
    end

    local function widget_filter(w, offset)
        if rect_contains_widget(w, offset) then
            table.insert(LineSegment.enumed, w)
        end
    end

    function LineSegment.EnumUnits(ax, ay, bx, by, offset, checkCollision)
        prepare_rect(ax, ay, bx, by, offset)
        GroupEnumUnitsInRect(GROUP, RECT)

        local enumed = {}
        LineSegment.enumed = enumed

        for i = 1, BlzGroupGetSize(GROUP) do
            local u = BlzGroupUnitAt(GROUP, i)
            widget_filter(u, checkCollision and BlzGetUnitCollisionSize(u) or 0.)
        end

        return enumed
    end

    function LineSegment.EnumDestructables(ax, ay, bx, by, offset)
        prepare_rect(ax, ay, bx, by, offset)

        local enumed = {}
        LineSegment.enumed = enumed

        EnumDestructablesInRect(RECT, Filter(function ()
            widget_filter(GetFilterDestructable(), 0.)
        end))

        return enumed
    end

    function LineSegment.EnumItems(ax, ay, bx, by, offset)
        prepare_rect(ax, ay, bx, by, offset)

        local enumed = {}
        LineSegment.enumed = enumed

        EnumItemsInRect(RECT, Filter(function ()
            widget_filter(GetFilterItem(), 0.)
        end))

        return enumed
    end

end

GUI Plugin

Lua:
onTriggerInit(function ()
    udg_LSE_GetUnits = CreateTrigger()
    udg_LSE_GetDestructables = CreateTrigger()
    udg_LSE_GetItems = CreateTrigger()

    TriggerAddAction(udg_LSE_GetUnits, function ()
        LineSegment.EnumUnits(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset, udg_LSE_UnitCollisionFlag)
        for i = 1, #LineSegment.enumed do
            GroupAddUnit(udg_LSE_Group, LineSegment.enumed[i])
        end
        LineSegment.enumed = nil
    end)
    TriggerAddAction(udg_LSE_GetDestructables, function ()
        LineSegment.EnumDestructables(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
        udg_LSE_DestructableCounter = 0
        for i = 1, #LineSegment.enumed do
            udg_LSE_DestructableCounter = udg_LSE_DestructableCounter + 1
            udg_LSE_Destructable[udg_LSE_DestructableCounter] = LineSegment.enumed[i]
        end
        LineSegment.enumed = nil
    end)
    TriggerAddAction(udg_LSE_GetItems, function ()
        LineSegment.EnumItems(GetLocationX(udg_LSE_Loc_1), GetLocationY(udg_LSE_Loc_1), GetLocationX(udg_LSE_Loc_2), GetLocationY(udg_LSE_Loc_2), udg_LSE_Offset)
        udg_LSE_ItemCounter = 0
        for i = 1, #LineSegment.enumed do
            udg_LSE_ItemCounter = udg_LSE_ItemCounter + 1
            udg_LSE_Item[udg_LSE_ItemCounter] = LineSegment.enumed[i]
        end
        LineSegment.enumed = nil
    end)
end)



  • LSE GUI How to Use
    • Ereignisse
    • Bedingungen
    • Aktionen
      • -------- ----------------------------------------------------------- --------
      • -------- Step 1 - Define endpoints and offset tolerance of line. --------
      • -------- ----------------------------------------------------------- --------
      • Set LSE_Loc_1 = (Random point in (Playable map area))
      • Set LSE_Loc_2 = (Random point in (Playable map area))
      • Set LSE_Offset = 100.00
      • Set LSE_UnitCollisionFlag = false
      • -------- ----------------------------------------------------------- --------
      • -------- Step 2 Enumeration --------
      • -------- ----------------------------------------------------------- --------
      • -------- 2.1 Units --------
      • -------- ----------------------------------------------------------- --------
      • Trigger - Run LSE_GetUnits (ignoring conditions)
      • -------- ----------------------------------------------------------- --------
      • -------- Now you can work with LSE_group. --------
      • -------- ----------------------------------------------------------- --------
      • Unit Group - Pick every unit in LSE_Group and do (Actions)
        • Loop - Actions
          • -------- ----------------------------------------------------------- --------
          • -------- Do some actions here for example. --------
          • -------- ----------------------------------------------------------- --------
      • -------- ----------------------------------------------------------- --------
      • -------- Attention -- do never destroy LSE_Group. --------
      • -------- ----------------------------------------------------------- --------
      • -------- 2.2 Destructables --------
      • -------- ----------------------------------------------------------- --------
      • Trigger - Run LSE_GetDestructables (ignoring conditions)
      • -------- ----------------------------------------------------------- --------
      • -------- Loop from 0 to LSE_DestructableCounter --------
      • -------- ----------------------------------------------------------- --------
      • For each (Integer A) from 0 to LSE_DestructableCounter, do (Actions)
        • Loop - Actions
          • -------- ----------------------------------------------------------- --------
          • -------- Use LSE_Destructable[Integer A] to refer to current destructable. --------
          • -------- ----------------------------------------------------------- --------
      • -------- ----------------------------------------------------------- --------
      • -------- 2.3 Items --------
      • -------- ----------------------------------------------------------- --------
      • Trigger - Run LSE_GetItems (ignoring conditions)
      • -------- ----------------------------------------------------------- --------
      • -------- Loop from 0 to LSE_ItemCounter --------
      • -------- ----------------------------------------------------------- --------
      • For each (Integer A) from 0 to LSE_ItemCounter, do (Actions)
        • Loop - Actions
          • -------- ----------------------------------------------------------- --------
          • -------- Use LSE_Item[Integer A] to refer to current item. --------
          • -------- ----------------------------------------------------------- --------
      • -------- ----------------------------------------------------------- --------
      • -------- To prevent leaks. --------
      • -------- ----------------------------------------------------------- --------
      • Custom script: call RemoveLocation(udg_LSE_Loc_1)
      • Custom script: call RemoveLocation(udg_LSE_Loc_2)


How To Import A Spell/System


v2.2a
- Added the ability to consider unit collisions when enumeration units
Previews
Contents

Line Segment Enumeration (Lua) v2.2a (Map)

Line Segment Enumeration .v1.1 (Map)

Line Segment Enumeration v2.2a (Map)

Reviews
KILLCIDE
A simple mechanic, yet the math behind it all is beyond my scope of knowledge! A great thank you to Ammorth and Anitarf for the small work such as this they do. We can of course cannot forget to thank the IcemanBo for applying it into a GUI-Friendly...

KILLCIDE

Arena Moderator
Level 35
Joined
Jul 22, 2015
Messages
3,487
A simple mechanic, yet the math behind it all is beyond my scope of knowledge! A great thank you to Ammorth and Anitarf for the small work such as this they do.

We can of course cannot forget to thank the IcemanBo for applying it into a GUI-Friendly and vanilla editor compatible system with extremely easy to use API. The code looks good as usual, and the demo map is superb (; Personally, I would use another global group instead of declaring local group g all the time.

Approved with a 4/5 rating. I hope to see future submissions using this library.
 
Level 12
Joined
Nov 7, 2014
Messages
569
Have you tried:
  • Actions
    • -------- not important, just for visuals --------
    • Custom script: call MoveLightningEx(udg_Lightning, true, GetUnitX(udg_Unit1), GetUnitY(udg_Unit1), 75.00, GetUnitX(udg_Unit2), GetUnitY(udg_Unit2), 75.00)
    • -------- --------
    • -------- IMPORTANT STUFF --------
    • Set LSE_Loc_1 = (Position of Unit1)
    • Set LSE_Loc_2 = (Position of Unit2)
    • Set LSE_Width = 500.00
i.e setting udg_LSE_Width to 500 for example and move a knight towards a chicken, the chicken dies when the knight is 500 units or less away from it.

Another example would be if we use the LSE_GroupEnumUnitsInRangeOfSegment function to get all the units between a caster unit and a target location say 600.0 away (when casting a point target spell), what would happen is that units that are behind the caster and less than 600.0 units away would also be picked (also units that are 600.0 away from the target point in the direction that the caster is facing would also be picked).
 
Level 12
Joined
Nov 7, 2014
Messages
569
vJass version:

JASS:
library EnumInRangeOfLineSegment

globals
    private constant real max_collision_size = 48.0
endglobals


static if DEBUG_MODE then
private function panic takes string s returns nothing
    call BJDebugMsg("|cffFF0000" + s + "|r")
    if 1 / 0 == 1 then
    endif
endfunction
endif

globals
    private group gg = CreateGroup()
endglobals
function GroupEnumUnitsInRangeOfLineSegment takes group g, real ax, real ay, real bx, real by, real line_half_width returns nothing
    local real abx
    local real aby
    local real ab_len_sq
    local real radius
    local real t
    local unit u

static if DEBUG_MODE then
    if ax == bx and ay == by then
        call panic("[GroupEnumUnitsInRangeOfLineSegment] error: " + "expected a line segment, got point (" + R2S(ax) + ", " + R2S(ay) + ")")
    endif
endif

    set abx = bx - ax
    set aby = by - ay
    set ab_len_sq = abx*abx + aby*aby

    set radius = 0.5 * SquareRoot(abx*abx + aby*aby) + line_half_width + max_collision_size
    call GroupEnumUnitsInRange(gg, ax + (0.5 * abx), ay + (0.5 * aby), radius, null)
    loop
        set u = FirstOfGroup(gg)
        exitwhen u == null
        call GroupRemoveUnit(gg, u)

        set t = ((GetUnitX(u) - ax)*abx + (GetUnitY(u) - ay)*aby) / ab_len_sq
        if 0.0 <= t and t <= 1.0 and IsUnitInRangeXY(u, ax + t*abx, ay + t*aby, line_half_width) then
            call GroupAddUnit(g, u)
        endif
    endloop
endfunction

globals
    destructable array ls_destructables // 1 based array
    integer ls_destructables_count = 0

    private constant real tau = 2.0 * (355.0 / 113.0)
    private rect rr = Rect(0.0, 0.0, 0.0, 0.0)
    private real pax
    private real pay
    private real pbx
    private real pby
    private real pline_half_width
endglobals

private function enum_destructables takes nothing returns nothing
    local destructable d = GetEnumDestructable()
    local real dx = GetWidgetX(d)
    local real dy = GetWidgetY(d)
    local real abx = pbx - pax
    local real aby = pby - pay
    local real ab_len_sq = abx*abx + aby*aby
    local real t = ((dx - pax)*abx + (dy - pay)*aby) / ab_len_sq
    local real px
    local real py
    local real dist_sq

    if 0.0 <= t and t <= 1.0 then
        set px = pax + t*abx
        set py = pay + t*aby
        set dist_sq = (dx - px)*(dx - px) + (dy - py)*(dy - py)
        if dist_sq <= pline_half_width*pline_half_width then
            set ls_destructables_count = ls_destructables_count + 1
            set ls_destructables[ls_destructables_count] = d
        endif
    endif

    set d = null
endfunction

function EnumDestructablesInRangeOfLineSegment takes real ax, real ay, real bx, real by, real line_half_width returns nothing
    local real hw
    local real minx
    local real miny
    local real maxx
    local real maxy
    local real d
    local real ang
    local integer quadrant

static if DEBUG_MODE then
    if ax == bx and ay == by then
        call panic("[EnumDestructablesInRangeOfLineSegment] error: " + "expected a line segment, got point (" + R2S(ax) + ", " + R2S(ay) + ")")
    endif
endif

    loop
        exitwhen ls_destructables_count <= 0
        set ls_destructables[ls_destructables_count] = null
        set ls_destructables_count = ls_destructables_count - 1
    endloop

    set hw = line_half_width
    set d = Atan2(by - ay, bx - ax)

    set ang = d
    if ang < 0.0 then
        set ang = ang + tau
    endif
    set quadrant = R2I(ang / (tau/4.0))

    // there's probably an easier way to find the 2 points but I don't know it =)
    if quadrant == 0 then
        set minx = ax + hw * Cos(d + tau/4.0)
        set miny = ay + hw * Sin(d - tau/4.0)
        set maxx = bx + hw * Cos(d - tau/4.0)
        set maxy = by + hw * Sin(d + tau/4.0)

    elseif quadrant == 1 then
        set minx = bx + hw * Cos(d + tau/4.0)
        set miny = ay + hw * Sin(d + tau/4.0)
        set maxx = ax + hw * Cos(d - tau/4.0)
        set maxy = by + hw * Sin(d - tau/4.0)

    elseif quadrant == 2 then
        set minx = bx + hw * Cos(d - tau/4.0)
        set miny = by + hw * Sin(d + tau/4.0)
        set maxx = ax + hw * Cos(d + tau/4.0)
        set maxy = ay + hw * Sin(d - tau/4.0)

    else // if quadrant == 3 then
        set minx = ax + hw * Cos(d - tau/4.0)
        set miny = by + hw * Sin(d - tau/4.0)
        set maxx = bx + hw * Cos(d + tau/4.0)
        set maxy = ay + hw * Sin(d + tau/4.0)

    endif
    call SetRect(rr, minx, miny, maxx, maxy)

    set pax = ax
    set pay = ay
    set pbx = bx
    set pby = by
    set pline_half_width = hw
    call EnumDestructablesInRect(rr, null, function enum_destructables)
endfunction

endlibrary

Edit: changed this:
JASS:
    set hw = line_half_width
    set minx = RMinBJ(ax, bx) - hw
    set miny = RMinBJ(ay, by) - hw
    set maxx = RMaxBJ(ax, bx) + hw
    set maxy = RMaxBJ(ay, by) + hw
    call SetRect(rr, minx, miny, maxx, maxy)

to this:
JASS:
    set hw = line_half_width
    set d = Atan2(by - ay, bx - ax)
    set minx = ax + hw * Cos(d + tau/4.0)
    set miny = ay + hw * Sin(d - tau/4.0)
    set maxx = bx + hw * Cos(d - tau/4.0)
    set maxy = by + hw * Sin(d + tau/4.0)
    call SetRect(rr, minx, miny, maxx, maxy)

which I think is a better fit.

Edit2: now should work for all quadrants =)
 
Last edited:

MyPad

Spell Reviewer
Level 18
Joined
May 9, 2014
Messages
1,575
JASS:
//Written in pseudo vJass
//Thanks to IcemanBo for the LSE system

//Globals section is a vjass feature, since it uses a preprocessor to generate these variables.

globals
    constant hashtable SPELL_HASH = InitHashtable()
    unit enum_e
endglobals

constant function Radius takes nothing returns real
    return 45.
endfunction

function Timer_Move_Unit_Actions takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local real remaining = 1 - LoadReal(SPELL_HASH, GetHandleId(t), 1)
    local unit caster = LoadUnitHandle(SPELL_HASH, GetHandleId(t), 0)
    local real getFacing = LoadReal(SPELL_HASH, GetHandleId(t), 2)
    local real c_x = GetUnitX(caster)
    local real c_y = GetUnitY(caster)
    local group ggwp = CreateGroup()
    local group getGroup = LoadGroupHandle(SPELL_HASH, GetHandleId(t), 3)
    //Initializing our variables retrieved from the timer that expired.
    if remaining > 0 then
        call LSE_GroupEnumUnitsInRangeOfSegment(ggwp, c_x, c_y, c_x + Radius() * Cos(getFacing), c_y + Radius() * Sin(getFacing), Radius())
        //call LSE_GroupEnumUnitsInRangeOfSegment(udg_LSE_Group, c_x, c_y, c_x + Radius() * Cos(getFacing), c_y + Radius() * Sin(getFacing), Radius())
        //Normally, this would return a usable global variable, but it is too late to incorporate it now.
        loop
            set enum_e = FirstOfGroup(ggwp)
            exitwhen enum_e == null
            if not IsUnitInGroup(enum_e, getGroup) and IsUnitEnemy(enum_e, GetOwningPlayer(caster)) and GetWidgetLife(enum_e) > 0.405 then
                //This checks whether or not the unit was previously enumerated and included.
                //The GetWidgetLife native gets the life of the unit and checks  whether or not its' health is greater than 0.405.
                //If it is, it will proceed to the next line since it is alive.
                call UnitDamageTarget(caster, enum_e, 75, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                //action here
                call GroupAddUnit(getGroup, enum_e)
                //Includes the enumerated unit if it hasn't been included already.
            endif
            call GroupRemoveUnit(ggwp, enum_e)
            //FirstOfGroup loops use up all of the units in a group, remmoving them so as not to cause an infinite loop.
        endloop
        call SetUnitX(caster, c_x + Radius() * Cos(getFacing))
        call SetUnitY(caster, c_y + Radius() * Sin(getFacing))
        //call SetUnitPositionLoc()
        //Think of this as SetUnitPositionLoc without the location handle, which means that you don't need to call RemoveLocation()
        //with the benefit of not needing to null the real values.
        call SaveReal(SPELL_HASH, GetHandleId(t), 1, LoadReal(SPELL_HASH, GetHandleId(t), 1) + 0.03125)
        //This updates the passage of time so as not to cause an infinite loop.
        //Q: How would this create an infinite loop?
        //A: The timer will always repeat itself, not stopping until it is manually paused.
        //  This creates a problem for us, since we can't access it directly but through
        //  this function only. Thus, we include this variable to account for the passage of
        //  time.
        //
        //     Going back to the if-then statement, the statement remaining > 0 checks
        //  if the time elapsed is not yet 1 since the variable in question is stated as:
        //  remaining = 1 - LoadReal(SPELL_HASH, GetHandleId(t), 1)
        //  If it is false, it then proceeds to the else portion of the code.
    else
        call FlushChildHashtable(SPELL_HASH, GetHandleId(t))
        //This removes all data associated with the expiring timer
        call PauseTimer(t)
        call DestroyTimer(t)
        //Why do we pause the timer? It could be that the timer is still running and it would create a bug if we destroyed it right away.
        //As always, destroying leaks in this case, the timer, is a must. (unless you have a library that recycles them instead)
        call DestroyGroup(getGroup)
        //The group is no longer used at this point, which means that we have a leak. Destroying it would (minimize) remove the leak.
    endif
    call DestroyGroup(ggwp)
    //This group is a dummy group, only used for enumeration. However, since that it would end up always being created, we
    //must destroy it so as not to cause leaks.
    set ggwp = null
    set getGroup = null
    set caster = null
    set t = null
endfunction

function Trig_Move_Unit_Actions takes nothing returns boolean
    local unit caster = GetTriggerUnit()
    local real c_x = GetSpellTargetX()
    local real c_y = GetSpellTargetY()
    local timer t
    if GetSpellAbilityId() == 'A000' then
        //This checks whether or not our ability being cast is correct.
        set t = CreateTimer()
        //This creates a timer handle, which we can use to execute a loop
        call SaveUnitHandle(SPELL_HASH, GetHandleId(t), 0, caster)
        call SaveReal(SPELL_HASH, GetHandleId(t), 1, 0.)
        call SaveReal(SPELL_HASH, GetHandleId(t), 2, Atan2(c_y - GetUnitY(caster), c_x - GetUnitX(caster)))
        call SaveGroupHandle(SPELL_HASH, GetHandleId(t), 3, CreateGroup())
        //Messy syntax, but this saves our data in the hashtable SPELL_HASH.
        call TimerStart(t, 0.03125, true, function Timer_Move_Unit_Actions)
        //This starts the timer with a loop.
        //    Q: Why timers when you could just use TriggerSleepAction?
        //    A: TriggerSleepAction is infamous for its inaccuracy in displaying the exact amount of time being displaced.
        //    Now for longer wait times, this would become negligible, but for shorter frequencies, it become annoying
        //    specially if you want to do some sort of gliding.
        set t = null
    endif
    set caster = null
    return false
endfunction

//===========================================================================
function InitTrig_Move_Unit takes nothing returns nothing
    set gg_trg_Move_Unit = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Move_Unit, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( gg_trg_Move_Unit, function Trig_Move_Unit_Actions )
    call UnitAddAbility(gg_unit_hwt3_0057, 'A000')
endfunction
 
Last edited:
Level 12
Joined
Nov 7, 2014
Messages
569
It should work correctly now, though.^^
I think it does.

There's this strange check in LineSegment.EnumUnits though:
JASS:
if GetOwningPlayer(u) != GetLocalPlayer() then // <-- o.O?
    set x = GetUnitX(u)
    set y = GetUnitY(u)

    set x_new = (x-bx)*thetaCos - (y-by)*thetaSin + bx
    set y_new = (x-bx)*thetaSin + (y-by)*thetaCos + by

    if x_new >= minX and x_new <= maxX and y_new >= minY and y_new <= maxY then
        call GroupAddUnit(whichgroup, u)
    endif 
endif // <-- O.o?
 
;D

Yes, that was from test code, to not enum my own units, thanks mate^^. Will update in an hour or so:).
I figured it is a bit sad that I don't provide function to rotate a rect/or point, or just check if a point is inside one rotatet rect. :(
But if wanting such functions one should probably just use polygon, because they will really create/destroy data, and with this one you can just instantly enumerate without any storage. So it's maybe ok, just for this function.
 
Level 7
Joined
Jan 23, 2011
Messages
350
Test map is not working for me. Or should i use the v1.1?

edit: I am using Newgen, and have no error on the vJASS trigg, the demo triggers works, but the system triggers don't
edit2: Fixed.
Apparently, struct method onInit ran before the "Global Initializations" function, and when that function runs, each trigger variable get "resetted" to "CreateTrigger()", losing the reference to the already created triggers.
So i inserted a Timer on that onInit method, hopefully you can find a better solution
 
Last edited:
I'm fiddling around with an energy wall system can can be turned on and off like the laser fence from Tiberium Sun and I'm using Line Segment Enum to check for any buildings in the way, but I also need to find a way to switch the pathing between each wall node on and off, but also to verify if the pathing between is unobstructed (so as to prevent energy walls from forming through walls. up and down cliffs, through doodads, etc). Would it be at all possible to do that using Line Segment as basis?
 
Is that happening with LineSegment.EnumUnits(whichGroup, ...)? Because the issue might be because that method actually runs a GroupClear(whichGroup), so if you're trying to enum multiple units in a line using the same group, it'll remove the previous selection. You'll have to store the contents of whichGroup to a secondary one.
 
Level 5
Joined
Mar 7, 2011
Messages
121
super useful snippet, wish i'd seen this earlier

im back and forth on using different types of collections to return enumed units vs items and destructables. i might prefer just all arrays, but then i think the one-off unit groups are worth it for gui
 

AGD

AGD

Level 13
Joined
Mar 29, 2016
Messages
677
I think it would be useful if there is a function for unit enumeration that considers unit collisions too. Currently, the user has to offset the parameters, and do additional filtering in the filled-up group to achieve this result.

Maybe something like
JASS:
public static method EnumUnitsEx takes group g, real ax, real ay, real bx, real by, real offset, real unitCollisionFactor returns nothing
then the existing method could just become
JASS:
public static method EnumUnits takes group g, real ax, real ay, real bx, real by, real offset returns nothing
    call EnumUnitsEx(g, ax, ay, bx, by, offset, 0.00)
endmethod
 

AGD

AGD

Level 13
Joined
Mar 29, 2016
Messages
677
Btw, the current version can also be shortened and use a slightly more optimized algorithm:
JASS:
library LineSegmentEnumeration /* v2.1c (unofficial) -- hiveworkshop.com/threads/line-segment-enumeration-v1-1.286552/

    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Allows to enumerate widgets inside a line segment with an offset.
        So basicly it will result in a rect, but which is also allowed to be rotated.
     
     
    Mechanics
    ¯¯¯¯¯¯¯¯¯
     
        (Issue:)
     
        The problem with normal jass rects is that they aren't defined by 4 points, but only by 4 values: x/y -min/max.
        The result is that a jass rect is never rotated, so it's always paralel to the x/y axis.
     
        But when we draw a line from point A to point B we might also create a non-axix-parelel rect, and then
        we can't use the normal rect natives from jass anymore to find out if a point is inside the rect.

        (Solution:)
     
        To solve this problem the system does following:
     
        jass rect: rectangular defined by 4 values (axis paralel)
        custom rect: the real rectangular that is defined by user (not axis parelel)

        1. Create a big jass rect that is big enough so we can ensure to enum all widgets that are potentialy inside our custom rect. (Enum_Group)
           This Enum_Group will contain all wanted units, but may also contain not wanted units.

        2. Construct the custom rect following a form with the same parameters as in this image, but in 2D:
           https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Ellipsoide.svg/800px-Ellipsoide.svg.png

        3. Loop through Enum_Group and define each widget's coordinates relative to the center of the custom rect as a 2D vector

        4. Get the components of the widget's position vector on the local (rotated) x-y axis of the custom rect

        5. Check if the projected lengths (absolute value of components) of the widget's position is less than <a> and <b> as described in the
           image linked above.

*/
//  --- API ---
//! novjass

    struct LineSegment

        static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing

        static method EnumDestructables takes real ax, real ay, real bx, real by, real offset returns nothing
         
        //  after enumerated destructables you have access to:
     
            static integer DestructableCounter      // starts with index "0"
            static destructable array Destructable
         
        static method EnumItems takes real ax, real ay, real bx, real by, real offset returns nothing
         
        //  after enumerated items you have access to:
     
            static integer ItemCounter      // starts with index "0"
            static destructable array Item
         
//! endnovjass
// ==== End API ====

struct LineSegment extends array

    private static constant rect RECT = Rect(0, 0, 0, 0)

    private static constant group GROUP = CreateGroup()

    private static real ox
    private static real oy
    private static real dx
    private static real dy
    private static real da
    private static real db
    private static real ui
    private static real uj
    private static real wdx
    private static real wdy

    private static method PrepareRect takes real ax, real ay, real bx, real by, real offset returns nothing
        local real maxX
        local real maxY
        local real minX
        local real minY

        // get center coordinates of rectangle
        set ox = 0.5*(ax + bx)
        set oy = 0.5*(ay + by)

        // get rectangle major axis as vector
        set dx = 0.5*(bx - ax)
        set dy = 0.5*(by - ay)

        // get half of rectangle length (da) and height (db)
        set da = SquareRoot(dx*dx + dy*dy)
        set db = offset

        // get unit vector of the major axis
        set ui = dx/da
        set uj = dy/da

        // Enum all potential units
        if ax > bx then
            set maxX = ax + offset
            set minX = bx - offset
        else
            set maxX = bx + offset
            set minX = ax - offset
        endif

        if ay > by then
            set maxY = ay + offset
            set minY = by - offset
        else
            set maxY = by + offset
            set minY = ay - offset
        endif

        call SetRect(RECT, minX, minY, maxX, maxY)
    endmethod

    private static method IsWidgetInRect takes widget w returns boolean
        // distance of coordinate from rectangle center in vector form
        set wdx = GetWidgetX(w) - ox
        set wdy = GetWidgetY(w) - oy

        set dx = wdx*ui + wdy*uj    // get the component of above vector in the rect's major axis
        set dy = wdx*(-uj) + wdy*ui // get the component of above vector in the rect's transverse axis

        // Check if the components above are less than half the length and height of the rectangle
        // (Square them to compare absolute values)
        return dx*dx <= da*da and dy*dy <= db*db
    endmethod

    public static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset returns nothing
        local unit u

        call PrepareRect(ax, ay, bx, by, offset)
        call GroupEnumUnitsInRect(GROUP, RECT, null)

        // enum through all tracked units, and check if it's inside bounds

        call GroupClear(whichgroup)
        loop
            set u = FirstOfGroup(GROUP)
            exitwhen u == null

            if IsWidgetInRect(u) then
                call GroupAddUnit(whichgroup, u)
            endif

            call GroupRemoveUnit(GROUP, u)
        endloop
    endmethod

//! textmacro LSE_WIDGET takes TYPE, NAME
    public static integer $NAME$Counter = -1
    public static $TYPE$ array $NAME$

    private static method on$NAME$Filter takes nothing returns nothing
        local $TYPE$ t = GetFilter$NAME$()

        if IsWidgetInRect(t) then
            set $NAME$Counter = $NAME$Counter + 1
            set $NAME$[$NAME$Counter] = t
        endif

        set t = null
    endmethod

    public static method Enum$NAME$s takes real ax, real ay, real bx, real by, real offset returns nothing
        call PrepareRect(ax, ay, bx, by, offset)

        set $NAME$Counter = -1
        call Enum$NAME$sInRect(RECT, Filter(function thistype.on$NAME$Filter), null)
    endmethod
//! endtextmacro

//! runtextmacro LSE_WIDGET("destructable", "Destructable")
//! runtextmacro LSE_WIDGET("item", "Item")

endstruct
endlibrary
 
Last edited:
I think it would be useful if there is a function for unit enumeration that considers unit collisions too. Currently, the user has to offset the parameters, and do additional filtering in the filled-up group to achieve this result.
It would be EnumUnitsEx(g, ax, ay, bx, by, 100, 100) instead of EnumUnitsEx(g, ax, ay, bx, by, 200)? What extra filtering is needed?
Thank you for the suggestions, though I guess I won't do updates, or at least not for performance optimizations alike.
 

AGD

AGD

Level 13
Joined
Mar 29, 2016
Messages
677
It would be EnumUnitsEx(g, ax, ay, bx, by, 100, 100) instead of EnumUnitsEx(g, ax, ay, bx, by, 200)? What extra filtering is needed?
I guess the last argument in my example is not always applicable, so yeah it can just be EnumUnitsEx(g, ax, ay, bx, by, 200).

What extra filtering is needed?
To consider unit collisions, current you have to add MAX_UNIT_COLLISION to the value you pass for offset. Then you have to filter the units in the group again based on their distance from the line since they have varying collision sizes. Similar to what is usually done when enumerating units in a circle while considering unit collisions which is:
JASS:
call GroupEnumUnitsInRange(g, x, y, radius + MAX_UNIT_COLLISION, null)
loop
    ...
    if IsUnitInRangeXY(u, x, y, radius) then
       // Do Stuffs
    endif
endloop
But since in a line segment, the additional check is not just a simple IsUnitInRangeXY() but needs additional calculations and most of which are already done inside the present EnumUnits method, so I just thought it would be inconvenient to do redundant maths in the user's side.

Thank you for the suggestions, though I guess I won't do updates, or at least not for performance optimizations alike.
Alright, I understand.
 
Top