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

This bundle is marked as approved. It works and satisfies the submission rules.
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.2b -- 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:

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

*/
//  --- 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, boolexpr filter returns nothing
static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolexpr filter returns nothing

static method EnumDestructables takes real ax, real ay, real bx, real by, real offset returns nothing

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

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, boolexpr filter 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, filter)

// 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
endif

call GroupRemoveUnit(GROUP, u)
endloop
endmethod

public static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolexpr filter returns nothing
call EnumUnitsEx(whichgroup, ax, ay, bx, by, offset, false, filter)
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, null)
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()
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()

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
end
LineSegment.enumed = nil
end)
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)
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.2b
added unit enumeration filter for vJASS
v2.2a
- Added the ability to consider unit collisions when enumeration units
Previews
Contents

# Line Segment Enumeration v2.2b (Map)

Reviews
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

Level 37
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.

#### JAKEZINC

Level 19
Very useful system especially when accompanied for lightnings!

. I hope to see future submissions using this library.

Well expect me to create a future spell using this stuff ^^.

#### Aniki

Level 13
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).

#### IcemanBo

Level 37
Definitly true. I've also chosen a wrong name. It's actually the max distance from the line. And "width" implies total width, so max wrong of 2x amount. ^^
Thanks, will fix tomorrow!

#### Aniki

Level 13
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 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
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

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 =)
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)

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)

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:

Spell Reviewer
Level 24
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)
if remaining > 0 then
//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
//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 )
endfunction``````

Last edited:

#### IcemanBo

Level 37
Aniki thanks for the tips, but I tried to make an own (more naive xD) approach. It should work correctly now, though.^^

#### Aniki

Level 13
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
endif
endif // <-- O.o?``````

#### IcemanBo

Level 37
;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.

Spell Reviewer
Level 24
With enough replies, this could turn into something very useful.

#### IcemanBo

Level 37

Btw, if you wanna really share a demo spell, you could also attach the demo map to your post above.

Spell Reviewer
Level 24

Btw, if you wanna really share a demo spell, you could also attach the demo map to your post above.

Will do, will do..
Kudos to you for making this :hand salute:

#### IcemanBo

Level 37
I stole most logics from them at first attempt + made it gui friendly and a system with all widgets. But then Aniki mentioned a bug, and logics were changed.

Spell Reviewer
Level 24
JASS:
``````set angle = angle - theta
set angle = angle*-1``````

Can be changed to

JASS:
``set angle = theta - angle``

#### Marcos_M

Level 7
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:

#### Spellbound

Level 30
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?

#### Spellbound

Level 30
Hmm, for some reason this isn't detecting terrain that have had their pathing switching off. I haven't tested with doodads but a script a friend wrote for me is doing the job, so I guess I'll pass - but thank you for the suggestion, though.

#### IcemanBo

Level 37
The script gets checks some points with defined offset from one point to an other point, and if one point is not walkable it returns false?

#### Lt_Hawkeye

Level 10
Hello, I am having a slight problem with this system, it adds the same unit twice to a group.

#### Spellbound

Level 30
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.

#### Lt_Hawkeye

Level 10
Thanks! That solved the problem. Yeah, it is happening with that function.

#### Serenity09

Level 6
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

#### IcemanBo

Level 37
Thanks! Yeh you're actually right. I'll make units align with array, too, and group can maybe be used optional, or just by the GUI snippet.

#### AGD

Level 16
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

Level 16
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:

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

*/
//  --- 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

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

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
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:

#### IcemanBo

Level 37
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

Level 16
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.

#### AGD

Level 16
Updated.

Added the ability to consider unit collisions when enumerating units, for both the vJass and the GUI Plugin.
Lua version will be added next.

#### AGD

Level 16
Updated:

Added a Lua version, as well as a GUI Plugin for Lua.

#### chopinski

Level 18
@IcemanBo @AGD There is a small mistake in the Lua version.

Lua:
``````for i = 0, BlzGroupGetSize(GROUP) - 1 do
...
end``````

you are doing:
Lua:
``````for i = 1, BlzGroupGetSize(GROUP) do
...
end``````

and this way the first unit is always ignored.

Replies
167
Views
24K
Replies
34
Views
10K
Replies
50
Views
16K
Replies
131
Views
16K
Replies
10
Views
3K