- Joined
- Feb 6, 2014
- Messages
- 2,466
Have you ever wanted to create diagonal rects or irregularly shaped rects? Then this is the resource for you.
It functions like a rect, you can detect when a unit enters/leaves the quad. You can enumerate items/destructables/units inside the quad (with filter option) all in struct format.
Big thanks to Aniki for helping with the algorithm in determining if a point is inside the quadrilateral.
It functions like a rect, you can detect when a unit enters/leaves the quad. You can enumerate items/destructables/units inside the quad (with filter option) all in struct format.
JASS:
library Quadrilateral /* v0.1
by Flux
[url]http://www.hiveworkshop.com/forums/members/flux/[/url]
This system allows you to create Quadrilaterals which is almost
the same as rects, except that quadrilaterals are not limited
to only having horizontal and vertical sides. This system provides
the same extensive API of rect but in struct format.
ALGORITHM:
The system uses a quick ray casting algorithm to determine if
the point is inside the quadrilateral. The system periodically
monitors all Quads which have units within its bounding box.
When no units is present within its bounding box, its monitoring
is stop and will only resume its monitoring when a unit enters
its bounding box.
LIMITATION:
Event Responses has a maximum delay of TIMEOUT (0.03125 second)
Sample:
[url]http://www.hiveworkshop.com/forums/pastebin_data/scr8y9/delay.jpg[/url]
optionally requires:
- Table by Bribe
Credits:
Bribe - Table
*/
//! novjass
************************** API: *********************************
function GetTriggerUnitInQuad takes nothing returns unit
- Event Response, the unit that causes the trigger to run
----------------------------------
struct Quad
----------------------------------
//Quadrilateral Geometric Properties
Vertices
readonly real array x[0,1,2,3]
readonly real array y[0,1,2,3]
Bounding Box
readonly real xMin
readonly real xMax
readonly real yMin
readonly real yMax
Centroid
readonly real centerX <- not yet implemented
readonly real centerY <- not yet implemented
Area
readonly real area <- not yet implemented
static method create takes real ax, real ay, real bx, real by, real cx, real cy, real dx, real dy returns thistype
- Create the Quadrilateral
method triggerRegisterEnter takes trigger trig returns nothing
- Register a trigger when a unit enters the Quad.
The Quad equivalent of TriggerRegisterEnterRectSimple.
method triggerRegisterLeave takes trigger trig returns nothing
- Register a trigger when a unit leaves the Quad.
The Quad equivalent of TriggerRegisterLeaveRectSimple.
method enumUnits takes group whichGroup, boolexpr filter returns nothing
- Pick all units inside the Quad and add them to whichGroup
The Quad equivalent of GroupEnumUnitsInRect
method enumDestructables takes boolexpr filter, code actionFunc returns nothing
- Picks all the destructable inside the Quad.
Quad equivalent of EnumDestructablesInRect.
method enumItems takes boolexpr filter, code actionFunc returns nothing
- Picks all the item inside the Quad.
The Quad equivalent to EnumItemsInRect.
method isPointInside takes real x, real y returns boolean
- Returns true if x and y is inside the Quad
method isUnitInside takes real x, real y returns boolean
- Returns true if x and y is inside the Quad
method destroy takes nothing returns nothing
- Destroy a Quad
******** Plan to add: *******
method move takes real x, real y returns nothing
- Move the centroid of the Quad to a new position.
Quad equivalent of MoveRectTo.
suggest more here...
//! endnovjass
globals
//Monitoring Timeout
private constant real TIMEOUT = 0.03125000
private constant real MARGIN = 30.0
endglobals
private keyword TrgList
private struct TrgList
readonly trigger trg
readonly boolean enter //trigger runs when a unit enters?
readonly boolean leave //trigger runs when a unit leaves?
private Quad q
thistype next
thistype prev
method destroy takes nothing returns nothing
if .trg != null then
call DestroyTrigger(.trg)
set .trg = null
endif
//Remove from the list
set .next.prev = .prev
set .prev.next = .next
set .enter = false
set .leave = false
call .deallocate()
endmethod
static method allocateHead takes nothing returns thistype
local thistype this = .allocate()
set .next = 0
set .prev = 0
return this
endmethod
static method addToList takes trigger t, Quad q, boolean enter returns thistype
local thistype this = .allocate()
if enter then
set .enter = true
else
set .leave = true
endif
set .trg = t
set .next = q.head.next
set .prev = q.head
return this
endmethod
endstruct
globals
private unit triggeringUnit
private constant integer ENUM_UNIT = 1
private constant integer ENUM_ITEM = 2
private constant integer ENUM_DESTRUCTABLE = 3
endglobals
struct Quad
//Quadrilateral Geometric Properties
readonly real array x[4] //x of the vertices
readonly real array y[4] //y of the vertices
readonly real xMin
readonly real xMax
readonly real yMin
readonly real yMax
readonly real centerX
readonly real centerY
readonly real area
private boolean monitored
private region reg //region for TriggerRegisterEnterRegion
private rect r //rect for RegionAddRect, when a unit enters this rect, start monitoring
private rect rMargin //rect for GroupEnumUnitsInRect since 'rect r' can't detect it all
private trigger trg //trigger that will run when a unit enters the Quad's rect
private integer count //number of units inside the quad
private group g //Units periodically monitored inside rect r
readonly TrgList head //group of triggers that will run when unit enters/leaves the Quad
private static integer array next
private static integer array prev
private static timer t = CreateTimer()
private static group tempGroup = CreateGroup()
private static group triggeringGroup = CreateGroup()
private static thistype globalThis
private static integer enumType
static if LIBRARY_Table then
private static Table tb
else
private static hashtable hash = InitHashtable()
endif
method destroy takes nothing returns nothing
local TrgList node
local TrgList nextNode
if .monitored then
call .stopMonitor()
endif
call DestroyTrigger(.trg)
set .trg = null
//Destroy List of triggers that will execute
set node = .head
loop
exitwhen node == 0
set nextNode = node.next
call node.destroy()
set node = nextNode
endloop
call .head.destroy()
call DestroyGroup(.g)
set .g = null
call RemoveRect(.r)
call RemoveRect(.rMargin)
call RemoveRegion(.reg)
set .r = null
set .rMargin = null
set .reg = null
endmethod
private method minAndMax takes nothing returns nothing
local integer i = 1
set .xMin = .x[0]
set .xMax = .x[0]
set .yMin = .y[0]
set .yMax = .y[0]
loop
if .x[i] < .xMin then
set .xMin = .x[i]
elseif .x[i] > .xMax then
set .xMax = .x[i]
endif
if .y[i] < .yMin then
set .yMin = .y[i]
elseif .y[i] > .yMax then
set .yMax = .y[i]
endif
exitwhen i == 3
set i = i + 1
endloop
endmethod
method isPointInside takes real x, real y returns boolean
local integer i
local integer j
local boolean b
//Check if the point is within the bounding box first
if x >= .xMin and x <= .xMax and y >= .yMin and y <= .yMax then
//loop through all the edges
set i = 0
set j = 3 //number of vertices - 1
set b = false
loop
//if the y is within the y's of the edge and if the x is on the left side of the edge
if (y >= .y[i]) != (y >= .y[j]) and ( x < (.x[j] - .x[i])*(y - .y[i])/(.y[j] - .y[i]) + .x[i]) then
set b = not b
endif
exitwhen i == 3 //number of vertices - 1
set j = i
set i = i + 1
endloop
return b
endif
return false
endmethod
method isWidgetInside takes widget w returns boolean
return isPointInside(GetWidgetX(w), GetWidgetY(w))
endmethod
//Core of the system, it checks if the number of units inside the
//Quad changes, if it does, apply appropriate actions such as
//executing triggers and setting the correct GetTriggerUnitInQuad
private method check takes nothing returns nothing
local integer rectCount = 0
local integer quadCount = 0
local unit u
local integer diff
local TrgList node
call GroupEnumUnitsInRect(tempGroup, .rMargin, null)
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
call GroupRemoveUnit(tempGroup, u)
set rectCount = rectCount + 1
if .isPointInside(GetUnitX(u), GetUnitY(u)) then
set quadCount = quadCount + 1
call GroupAddUnit(triggeringGroup, u)
endif
endloop
//quadCount = newCount, .count = previousCount
if quadCount > .count then
set diff = quadCount - .count
loop
set u = FirstOfGroup(triggeringGroup)
exitwhen diff == 0 or u == null
call GroupRemoveUnit(triggeringGroup, u)
set triggeringUnit = u
set node = .head
//Run all the triggers in the list
loop
exitwhen node == 0
if node.enter then
if TriggerEvaluate(node.trg) then
call TriggerExecute(node.trg)
endif
endif
set node = node.next
endloop
set diff = diff - 1
endloop
elseif quadCount < .count then
set diff = .count - quadCount
loop
set u = FirstOfGroup(triggeringGroup)
exitwhen diff == 0 or u == null
call GroupRemoveUnit(triggeringGroup, u)
set triggeringUnit = u
set node = .head
//Run all the triggers in the list
loop
exitwhen node == 0
if node.leave then
if TriggerEvaluate(node.trg) then
call TriggerExecute(node.trg)
endif
endif
set node = node.next
endloop
set diff = diff - 1
endloop
endif
set .count = quadCount
if rectCount == 0 then
call .stopMonitor()
endif
set u = null
endmethod
//pick all Quads being monitored
private static method pickAll takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen this == 0
call .check()
set this = next[this]
endloop
endmethod
private method stopMonitor takes nothing returns nothing
set .monitored = false
//Remove from the Monitor List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
if next[0] == 0 then
call PauseTimer(t)
endif
endmethod
private method startMonitor takes nothing returns nothing
set .monitored = true
//Insert in the monitor list
set next[this] = 0
set prev[this] = prev[0]
set next[prev[this]] = this
set prev[0] = this
if prev[this] == 0 then
call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
endif
endmethod
private static method onRectEnter takes nothing returns boolean
static if LIBRARY_Table then
local thistype this = tb[GetHandleId(GetTriggeringTrigger())]
else
local thistype this = LoadInteger(hash, GetHandleId(GetTriggeringTrigger()), 0)
endif
//If the Quad is currently not being monitored, monitor it
if not .monitored then
call .startMonitor()
endif
return false
endmethod
method triggerRegisterEnter takes trigger trig returns nothing
local TrgList tl = TrgList.addToList(trig, this, true)
set .head.next.prev = tl
set .head.next = tl
if not .monitored then
call .startMonitor()
endif
endmethod
method triggerRegisterLeave takes trigger trig returns nothing
local TrgList tl = TrgList.addToList(trig, this, false)
set .head.next.prev = tl
set .head.next = tl
if not .monitored then
call .startMonitor()
endif
endmethod
method move takes real x, real y returns nothing
endmethod
private static method alwaysTrue takes nothing returns boolean
return true
endmethod
private static method enumFilter takes nothing returns boolean
local thistype this = globalThis
if enumType == ENUM_ITEM then
return .isPointInside(GetItemX(GetFilterItem()), GetItemY(GetFilterItem()))
elseif enumType == ENUM_DESTRUCTABLE then
return .isPointInside(GetDestructableX(GetFilterDestructable()), GetDestructableY(GetFilterDestructable()))
else
return .isPointInside(GetUnitX(GetFilterUnit()), GetUnitY(GetFilterUnit()))
endif
endmethod
method enumItems takes boolexpr filter, code actionFunc returns nothing
local boolexpr tempFilter = filter
if filter == null then
set tempFilter = Filter(function thistype.alwaysTrue)
endif
set globalThis = this
set enumType = ENUM_ITEM
call EnumItemsInRect(.rMargin, And(tempFilter, Filter(function thistype.enumFilter)), actionFunc)
endmethod
method enumDestructables takes boolexpr filter, code actionFunc returns nothing
local boolexpr tempFilter = filter
if filter == null then
set tempFilter = Filter(function thistype.alwaysTrue)
endif
set globalThis = this
set enumType = ENUM_DESTRUCTABLE
call EnumDestructablesInRect(.rMargin, And(tempFilter, Filter(function thistype.enumFilter)), actionFunc)
endmethod
method enumUnits takes group whichGroup, boolexpr filter returns nothing
local boolexpr tempFilter = filter
if filter == null then
set tempFilter = Filter(function thistype.alwaysTrue)
endif
set globalThis = this
set enumType = ENUM_UNIT
call GroupEnumUnitsInRect(whichGroup, .rMargin, And(tempFilter, Filter(function thistype.enumFilter)))
endmethod
static method create takes real ax, real ay, real bx, real by, real cx, real cy, real dx, real dy returns thistype
local thistype this = allocate()
set .x[0] = ax
set .x[1] = bx
set .x[2] = cx
set .x[3] = dx
set .y[0] = ay
set .y[1] = by
set .y[2] = cy
set .y[3] = dy
//Calculate the Min and Max of x and y
call .minAndMax()
call .head.destroy()
set .r = Rect(.xMin, .yMin, .xMax, .yMax)
set .rMargin = Rect(.xMin - MARGIN, .yMin - MARGIN, .xMax + MARGIN, .yMax + MARGIN)
set .head = TrgList.allocateHead()
set .trg = CreateTrigger()
call TriggerAddCondition(.trg, Condition(function thistype.onRectEnter))
static if LIBRARY_Table then
set tb[GetHandleId(.trg)] = this
else
call SaveInteger(hash, GetHandleId(.trg), 0, this)
endif
set .reg = CreateRegion()
call RegionAddRect(.reg, .r)
call TriggerRegisterEnterRegion(.trg, reg, null)
return this
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set tb = Table.create()
endif
endmethod
endstruct
//-------------- WRAPPER FUNCTIONS ------------------
//JASSHelper says this won't be a problem
function GetTriggerUnitInQuad takes nothing returns unit
return triggeringUnit
endfunction
endlibrary
Big thanks to Aniki for helping with the algorithm in determining if a point is inside the quadrilateral.
Attachments
Last edited: