[vJASS] [System] Polygon

Level 22
Joined
Feb 6, 2014
Messages
2,468
This resource allows you to create a Polygon. A Polygon has all the functionality (and more) a rect has; it can detect when a unit enters/leaves the Polygon.. It can enumerate/pick all items, destructables, units inside the Polygon (with filter option). It can instantly change position just like a rect. Moreover, Polygons can have slanted sides, concave and convex interior angle, can contain more than a hundred sides/vertices and can be geometrically transformed (e.g. scale, rotate).

JASS:
library Polygon /* 
                            Polygon v1.42
                               by Flux

    This system allows you to create 2D Polygons. Notable features
    include:
        - Enumerating units, items, destructables inside the Polygon.
        - Register a trigger/code when a unit enter/leaves the Polygon.
        - Geometric transformations (e.g. scale, rotate).
        - Determining whether a Polygon intersects to a certain other Polygon.
   
    LIMITATION:
        Event Responses has a maximum delay of TIMEOUT (0.03125 second)
        Sample:
        http://www.hiveworkshop.com/forums/pastebin_data/scr8y9/delay.jpg
       
    */ requires /*
       (nothing)
       
    */ optional Table /*
        If not found, this system will create a hashtable instead. Hashtables are limited
        to 255 per map.
       
    Credits:
        Bribe    - Table
        IcemanBo - For introducing me the line intersection algorithm used.
    */

    //! novjass
    *******************************************************************
    *****************************  API:  ******************************  
    *******************************************************************
   
    ----------------------------------
    struct Polygon
    ----------------------------------
   
    //Geometric Properties
        readonly Vertex vertex
        readonly real area
    //Bounding Box
        readonly real xMin
        readonly real xMax
        readonly real yMin
        readonly real yMax
    //Centroid
        readonly real centerX  
        readonly real centerY  
       
        readonly Polygon.unit
            //Event Response, the unit that causes the trigger to run.
       
        readonly Polygon.object
            //Event Response, the Polygon which causes the trigger to run.

        static method create takes nothing returns thistype
            //Create a Polygon with no configured vertices.
       
        static method intersect takes Polygon p1, Polygon p2 returns boolean
            //Returns true if the Polygon input arguments intersects.
       
        method addVertex takes real x, real y returns nothing
            //Add a Vertex to a Polygon.
       
        method finalize takes nothing returns nothing
            //Tells the system that the Polygon no longer has a Vertex to be added.
       
        method clear takes nothing returns nothing
            //Remove all the vertices of a Polygon. Use this.addVertex to add vertices again
            //with this.finalize after the last vertex.
            //Reset the referenced value for transform and transformEx.
       
        method triggerRegisterEnter takes trigger trig returns nothing
            //Register a trigger when a unit enters the Polygon. 
            //The Polygon equivalent of TriggerRegisterEnterRectSimple.
             
        method triggerRegisterLeave takes trigger trig returns nothing
            //Register a trigger when a unit leaves the Polygon. 
            //The Polygon equivalent of TriggerRegisterLeaveRectSimple.
       
        method registerEnter takes code c return nothing
            //Registers a code to run when a unit enters the Polygon
       
        method registerLeave takes code c returns nothing
            //Registers a code to run when a unit leavess the Polygon
       
        method enumUnits takes group whichGroup returns nothing
            //Pick all units inside the Polygon and add them to whichGroup
            //The Polygon equivalent of GroupEnumUnitsInRect
           
        method enumDestructables takes code actionFunc returns nothing
            //Picks all the destructable inside the Polygon.
            //Polygon equivalent of EnumDestructablesInRect.
           
        method enumItems takes code actionFunc returns nothing
            //Picks all the item inside the Polygon. 
            //The Polygon equivalent to EnumItemsInRect.
       
        method isPointInside takes real x, real y returns boolean
            //Returns true if x and y is inside the Polygon
       
        method isWidgetInside takes widget w returns boolean
            //Returns true if widget w is inside the Polygon
       
        method update takes nothing returns nothing
            //Immediately update the Polygon without waiting for the next timer tick.
            //Used for recursion safety. See Polygon Recursion Safety Test Demo
       
        method destroy takes nothing returns nothing
            //Destroy the Polygon.
       
        method move takes real x, real y returns nothing
            //Move the centroid of the Polygon to a new position.
            //Polygon equivalent of MoveRectTo.
       
        method transformEx takes real scale, real angle, real x, real y returns nothing
            //Changes the scaling size of the Polygon and Rotation of a Polygon with reference
            //to a pivot point. Rotation angle is in radian.
            //Input scale and angle are referenced to the original size and angle.
       
        method transform takes real scale, real angle returns nothing
            //Changes the scaling size of the Polygon and Rotation of a Polygon with reference
            //to the Centroid of the Polygon. Rotation angle is in radian.
             
    //! endnovjass

    //=========================== CONFIGURATION ================================
    globals
        private constant real TIMEOUT = 0.03125000  //Monitoring Timeout
        private constant real MARGIN = 128.0        //Maximum collision size
    endglobals
    //======================== END CONFIGURATION ===============================
   
    private struct TriggerNode
       
        readonly trigger trg
        readonly boolean enter  //trigger runs when a unit enters?
        readonly boolean leave  //trigger runs when a unit leaves?
       
        readonly thistype next
        readonly thistype prev
       
        method destroy takes nothing returns nothing
           //Remove from the list
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set this.enter = false
            set this.leave = false
            set this.trg = null
            call this.deallocate()
        endmethod
       
        static method head takes nothing returns thistype
            local thistype this = thistype.allocate()
            set this.next = 0
            set this.prev = 0
            return this
        endmethod
       
        static method add takes trigger t, thistype head, boolean enter returns nothing
            local thistype this = thistype.allocate()
            if enter then
                set this.enter = true
            else
                set this.leave = true
            endif
            set this.trg = t
            set this.next = head.next
            set this.prev = head
            set this.next.prev = this
            set this.prev.next = this
        endmethod
       
    endstruct
   
    struct Vertex
        real x
        real y
       
        readonly thistype next
        readonly thistype prev
       
        method destroy takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
            call this.deallocate()
        endmethod
       
        static method head takes nothing returns thistype
            local thistype this = thistype.allocate()
            set this.next = 0
            set this.prev = 0
            return this
        endmethod
       
        static method add takes thistype head, real x, real y returns nothing
            local thistype this = thistype.allocate()
            set this.x = x
            set this.y = y
            set this.next = head.next
            set this.prev = head
            set this.next.prev = this
            set this.prev.next = this
        endmethod
       
    endstruct
   
    struct Polygon
       
        //Polygon Geometric Properties
        readonly Vertex v   //head node of the Vertex stack
        readonly real xMin
        readonly real xMax
        readonly real yMin
        readonly real yMax
       
        readonly static unit unit
        readonly static thistype object

        private thistype next        //next node in the monitoring list
        private thistype prev        //previous node in the monitoring list
        private real temp_centerX    //for caching x-centroid
        private real temp_centerY    //for caching y-centroid
        private real temp_area       //for caching polygon area
        private real temp_scale      //referenced scale
        private real temp_rotate     //referenced angle of rotation
        private boolean monitored    //is Polygon periodically monitored?
        private boolean refreshRect  //is the Polygon going to refresh this.enumRect upon enumerating widgets?
        private region reg           //region for TriggerRegisterEnterRegion
        private rect enterRect       //rect for RegionAddRect, when a unit enters this rect, start monitoring
        private rect enumRect        //rect for GroupEnumUnitsInRect since 'rect enterRect' can't detect it all
        private trigger trg          //trigger that will run when a unit enters the Polygon's rect
        private group g              //units that is already inside the Polygon
        private TriggerNode trgHead  //for the group of triggers that will run when unit enters/leaves the Polygon
        private trigger codeTrgEnter
        private trigger codeTrgLeave
       
       
        private static timer t = CreateTimer()          
        private static group tempGroup = CreateGroup()
        private static group insideGroup = CreateGroup()
        private static integer enumType
       
        private static constant real NO_VALUE = 1000000000.0
        private static constant integer ENUM_UNIT = 1
        private static constant integer ENUM_ITEM = 2
        private static constant integer ENUM_DESTRUCTABLE = 3
       
        static if LIBRARY_Table then
            private static Table tb
        else
            private static hashtable hash = InitHashtable()
        endif
       
        //Remove from the monitored list
        private method stopMonitor takes nothing returns nothing
            set this.monitored = false
            //Remove from the Monitor List
            set this.prev.next = this.next
            set this.next.prev = this.prev
            if thistype(0).next == 0 then
                call PauseTimer(t)
            endif
        endmethod
       
        //Insert in the monitored list
        private method startMonitor takes nothing returns nothing
            set this.monitored = true
            //Insert in the monitor list
            set this.next = 0
            set this.prev = thistype(0).prev
            set this.prev.next = this
            set thistype(0).prev = this
            if this.prev == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
            endif
        endmethod
       
        method clear takes nothing returns nothing
            local Vertex vtx = this.v.next
            loop
                exitwhen vtx == 0
                call vtx.destroy()
                set vtx = this.v.next
            endloop
            set this.temp_scale = 0
        endmethod
       
        method destroy takes nothing returns nothing
            local TriggerNode node
            //Remove from the monitoring list
            if this.monitored then
                call this.stopMonitor()
            endif
            //Destroy the Vertices
            call this.clear()
            call this.v.destroy()
            //Remove Monitoring Handles
            if this.trgHead != 0 then
                //Destroy List of triggers that will execute
                set node = this.trgHead.next
                loop
                    exitwhen node == 0
                    call node.destroy()
                    set node = this.trgHead.next
                endloop
                call DestroyTrigger(this.trg)
                call DestroyGroup(this.g)
                call RemoveRect(this.enterRect)
                call RemoveRegion(this.reg)
                set this.trg = null
                set this.codeTrgEnter = null
                set this.codeTrgLeave = null
                set this.g = null
                set this.enterRect = null
                set this.reg = null
                call this.trgHead.destroy()
            endif
            if this.enumRect != null then
                call RemoveRect(this.enumRect)
                set this.enumRect = null
            endif
            //Recycle Instance
            call this.deallocate()
        endmethod
       
        //Reset handles involved in monitoring of the Polygon
        private method reset takes nothing returns nothing
            call DestroyTrigger(this.trg)
            call RemoveRegion(this.reg)
            set this.trg = CreateTrigger()
            set this.reg = CreateRegion() 
            call RegionAddRect(this.reg, this.enterRect)
            static if LIBRARY_Table then
                set tb[GetHandleId(this.trg)] = this
            else
                call SaveInteger(hash, GetHandleId(this.trg), 0, this)
            endif
            call TriggerAddCondition(this.trg, Condition(function thistype.onRectEnter))
            call TriggerRegisterEnterRegion(this.trg, reg, null)
            call this.update()
            if not this.monitored then
                call this.startMonitor()
            endif
        endmethod
       
        method finalize takes nothing returns nothing
            local Vertex vtx = this.v.next
            set this.xMax = vtx.x
            set this.xMin = vtx.x
            set this.yMax = vtx.y
            set this.yMin = vtx.y
            loop
                exitwhen vtx == 0
                if vtx.x > this.xMax then
                    set this.xMax = vtx.x
                elseif vtx.x < this.xMin then
                    set this.xMin = vtx.x
                endif
                if vtx.y > this.yMax then
                    set this.yMax = vtx.y
                elseif vtx.y < this.yMin then
                    set this.yMin = vtx.y
                endif
                set vtx = vtx.next
            endloop
            //reset temp values
            set this.temp_centerX = NO_VALUE
            set this.temp_centerY = NO_VALUE
            set this.temp_area = NO_VALUE
            if this.trgHead != 0 then
                call SetRect(this.enterRect, this.xMin, this.yMin, this.xMax, this.yMax)
                call SetRect(this.enumRect, this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
                call this.reset()
            else
                set this.refreshRect = true
            endif
        endmethod
       
        method isPointInside takes real x, real y returns boolean  
            local boolean b = false
            local Vertex v1 = this.v.next
            local Vertex v2
            //Check if the point is within the bounding box first
            if x >= this.xMin and x <= this.xMax and y >= this.yMin and y <= this.yMax then
                //loop through all the edges
                loop
                    exitwhen v1 == 0
                    set v2 = v1.next
                    if v2 == 0 then
                        set v2 = this.v.next
                    endif
                    //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 >= v1.y) != (y >= v2.y) and (x < (v2.x - v1.x)*(y - v1.y)/(v2.y - v1.y) + v1.x) then
                        set b = not b
                    endif
                    set v1 = v1.next
                endloop
                return b
            endif
            return false
        endmethod
       
        //Will be inlined anyway
        method isWidgetInside takes widget w returns boolean
            return this.isPointInside(GetWidgetX(w), GetWidgetY(w))
        endmethod
       
        private method centroidCalc takes nothing returns nothing
            local Vertex v1 = this.v.next
            local Vertex v2
            local real area = 0
            local real temp
            set this.temp_centerX = 0
            set this.temp_centerY = 0
            loop
                exitwhen v1 == 0
                set v2 = v1.next
                if v2 == 0 then
                    set v2 = this.v.next
                endif
                set temp = (v1.x*v2.y - v2.x*v1.y)
                set area = area + temp
                set this.temp_centerX = this.temp_centerX + (v1.x + v2.x)*temp
                set this.temp_centerY = this.temp_centerY + (v1.y + v2.y)*temp
                set v1 = v1.next
            endloop
            if area != 0 then
                set area = area/2
                set this.temp_centerX = this.temp_centerX/(6*area)
                set this.temp_centerY = this.temp_centerY/(6*area)
                if area < 0 then
                    set this.temp_area = -area
                else
                    set this.temp_area = area
                endif
            endif
        endmethod
       
        method operator centerX takes nothing returns real
            if this.temp_centerX == NO_VALUE then
                call this.centroidCalc()
            endif
            return this.temp_centerX
        endmethod
       
        method operator centerY takes nothing returns real
            if this.temp_centerY == NO_VALUE then
                call this.centroidCalc()
            endif
            return this.temp_centerY
        endmethod
       
        method operator area takes nothing returns real
            if this.temp_area == NO_VALUE then
                call this.centroidCalc()
            endif
            return this.temp_area
        endmethod
       
        method operator vertex takes nothing returns Vertex
            return this.v.next
        endmethod
       
        private static method groupCheck takes nothing returns nothing
            local thistype this = thistype.object
            local unit u = GetEnumUnit()
            local TriggerNode node
            if not this.isPointInside(GetUnitX(u), GetUnitY(u)) then
                call GroupRemoveUnit(this.g, u)
                set this.unit = u
                set node = this.trgHead.next
                //Run all the triggers in the list
                loop
                    exitwhen node == 0
                    if node.leave and IsTriggerEnabled(node.trg) then
                        if TriggerEvaluate(node.trg) then
                            call TriggerExecute(node.trg)
                        endif
                    endif
                    set node = node.next    
                endloop
            endif
            set this.unit = null
            set u = null
        endmethod
       
        //Detects when a unit enters/leaves the Polygon
        method update takes nothing returns nothing
            local integer i = 0
            local TriggerNode node
            local unit u
            //Pick all units within the bounding box
            call GroupEnumUnitsInRect(tempGroup, this.enumRect, null)
            loop
                set u = FirstOfGroup(tempGroup)
                exitwhen u == null
                call GroupRemoveUnit(tempGroup, u)
                set i = i + 1
                if this.isPointInside(GetUnitX(u), GetUnitY(u)) then
                    //insideGroup has all units inside the Qud
                    call GroupAddUnit(insideGroup, u)
                endif
            endloop
            //-------------------------------------
            //   Detect units leaving the Polygon
            //-------------------------------------
            set thistype.object = this
            call ForGroup(this.g, function thistype.groupCheck)
            //-------------------------------------
            //   Detect units entering the Polygon
            //-------------------------------------
            loop
                set u = FirstOfGroup(insideGroup)
                exitwhen u == null
                call GroupRemoveUnit(insideGroup, u)
                if not IsUnitInGroup(u, this.g) then
                    call GroupAddUnit(this.g, u)
                    set this.unit = u
                    set node = this.trgHead.next
                    //Run all the triggers in the list
                    loop
                        exitwhen node == 0
                        if node.enter and IsTriggerEnabled(node.trg) then
                            if TriggerEvaluate(node.trg) then
                                call TriggerExecute(node.trg)
                            endif
                        endif
                        set node = node.next
                    endloop
                endif
            endloop
            set this.unit = null
            set thistype.object = 0
            if i == 0 then
                call this.stopMonitor()
            endif
            set u = null
        endmethod

        //Pick all Polygons being monitored
        private static method pickAll takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                call this.update()
                set this = this.next
            endloop
        endmethod
       
        //When a unit enters trigger-registed Polygon's this.enterRect, periodically monitor it
        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 Polygon is currently not being monitored, monitor it
            if not this.monitored then
                call this.startMonitor()
            endif
            return false
        endmethod
       
        private method triggerRegister takes trigger trig, boolean enter returns nothing
            //If it is the first time a trigger is registered
            if this.trgHead == 0 then
                set this.enterRect = Rect(this.xMin, this.yMin, this.xMax, this.yMax)
                set this.enumRect = Rect(this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
                set this.trgHead = TriggerNode.head()
                set this.trg = CreateTrigger()
                set this.g = CreateGroup()  
                call TriggerAddCondition(this.trg, Condition(function thistype.onRectEnter))
                static if LIBRARY_Table then
                    set tb[GetHandleId(this.trg)] = this
                else
                    call SaveInteger(hash, GetHandleId(this.trg), 0, this)
                endif
                set this.reg = CreateRegion()
                call RegionAddRect(this.reg, this.enterRect)
                call TriggerRegisterEnterRegion(this.trg, reg, null)
            endif
            call TriggerNode.add(trig, this.trgHead, enter)
            if not this.monitored then
                call this.startMonitor()
            endif
            call this.update()
        endmethod
       
        method triggerRegisterEnter takes trigger trig returns nothing
            call this.triggerRegister(trig, true)
        endmethod
       
        method triggerRegisterLeave takes trigger trig returns nothing
            call this.triggerRegister(trig, false)
        endmethod
       
        method registerEnter takes code c returns nothing
            if this.codeTrgEnter == null then
                set this.codeTrgEnter = CreateTrigger()
                call this.triggerRegisterEnter(this.codeTrgEnter)
            endif
            call TriggerAddCondition(this.codeTrgEnter, Condition(c))
        endmethod
       
        method registerLeave takes code c returns nothing
            if this.codeTrgLeave == null then
                set this.codeTrgLeave = CreateTrigger()
                call this.triggerRegisterLeave(this.codeTrgLeave)
            endif
            call TriggerAddCondition(this.codeTrgLeave, Condition(c))
        endmethod
       
        method move takes real x, real y returns nothing
            local real prevX = this.centerX
            local real prevY = this.centerY
            local real dx = x - prevX
            local real dy = y - prevY
            local Vertex vtx = this.v.next
            //Reposition vertices by the offset
            loop
                exitwhen vtx == 0
                set vtx.x = vtx.x + dx
                set vtx.y = vtx.y + dy
                set vtx = vtx.next
            endloop
            set this.xMin = this.xMin + dx
            set this.xMax = this.xMax + dx
            set this.yMin = this.yMin + dy
            set this.yMax = this.yMax + dy
            set this.temp_centerX = x
            set this.temp_centerY = y
            if this.trgHead != 0 then
                call MoveRectTo(this.enterRect, x, y)
                call MoveRectTo(this.enumRect, x, y)
                call this.reset()
            else
                set this.refreshRect = true
            endif
        endmethod
       
        method transformEx takes real scaling, real rotation, real x, real y returns nothing
            local Vertex vtx = this.v.next
            local real scaleFactor
            local real rotateFactor
            local real angle
            local real dist
            if scaling <= 0 then
                set scaling = 1
            endif
            //If scale and rotate are not referenced yet
            if this.temp_scale == 0 then
                set scaleFactor = scaling
                set rotateFactor = rotation
            else
                set scaleFactor = scaling/.temp_scale
                set rotateFactor = rotation - this.temp_rotate
            endif
            set this.temp_scale = scaling
            set this.temp_rotate = rotation
            loop
                exitwhen vtx == 0
                set dist = scaleFactor*SquareRoot((vtx.x - x)*(vtx.x - x) + (vtx.y - y)*(vtx.y - y))
                set angle = rotateFactor + Atan2(vtx.y - y, vtx.x - x)
                set vtx.x = x + dist*Cos(angle)
                set vtx.y = y + dist*Sin(angle)
                set vtx = vtx.next
            endloop
            call this.finalize()
        endmethod
       
        method transform takes real scaling, real rotation returns nothing
            call this.transformEx(scaling, rotation, this.centerX, this.centerY)
        endmethod
       
        method addVertex takes real x, real y returns nothing
            call Vertex.add(this.v, x, y)
        endmethod
       
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            //Important initilizations
            set this.v = Vertex.head()
            set this.monitored = false
            set this.trgHead = 0
            set this.temp_scale = 0
            return this
        endmethod
       
        private static method ccw takes real x1, real y1, real x2, real y2, real x3, real y3 returns boolean
            return (y3 - y1)*(x2 - x1) > (y2 - y1)*(x3 - x1)
        endmethod
       
        private static method intersectPoints takes real x1, real y1, real x2, real y2, real x3, real y3, real x4, real y4 returns boolean
            return thistype.ccw(x1, y1, x3, y3, x4, y4) != thistype.ccw(x2, y2, x3, y3, x4, y4) and thistype.ccw(x1, y1, x2, y2, x3, y3) != thistype.ccw(x1, y1, x2, y2, x4, y4)
        endmethod
       
        //Currently using the "naive" O(n^2) approach but works for concave and convex and is optimized.
        //It checks every edge combination if the edges intersects.
        static method intersect takes thistype p1, thistype p2 returns boolean
            local Vertex v1a = p1.v.next
            local Vertex v2a
            local Vertex v1b
            local Vertex v2b
            //First apply Polygon bounding box intersection
            if p1.xMin < p2.xMax and p2.xMin < p1.xMax and p1.yMin < p2.yMax and p2.yMin < p1.yMax then
                //Pick each edge of p1 and compare if it intersects p2's edge
                loop
                    exitwhen v1a == 0
                    set v1b = v1a.next
                    if v1b == 0 then
                        set v1b = p1.v.next
                    endif
                    set v2a =p2.v.next 
                    loop
                        exitwhen v2a == 0
                        set v2b = v2a.next
                        if v2b == 0 then
                            set v2b = p2.v.next
                        endif
                        if thistype.intersectPoints(v1a.x, v1a.y, v1b.x, v1b.y, v2a.x, v2a.y, v2b.x, v2b.y) then
                            return true
                        endif
                        set v2a = v2a.next
                    endloop
                    set v1a = v1a.next
                endloop
            endif
            return false
        endmethod
       
        //*************************************************************
        //******************* ENUMERATION METHODS *********************
        //*************************************************************
       
        private static thistype filterTemp
       
        private static method filterItems takes nothing returns boolean
            return thistype.filterTemp.isWidgetInside(GetFilterItem())
        endmethod
       
        private static method filterDestructables takes nothing returns boolean
            return thistype.filterTemp.isWidgetInside(GetFilterDestructable())
        endmethod
       
        private static method filterUnits takes nothing returns boolean
            return thistype.filterTemp.isWidgetInside(GetFilterUnit())
        endmethod
       
        method enumItems takes code actionFunc returns nothing
            if this.enumRect == null then
                set this.enumRect = Rect(this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
            elseif this.refreshRect then
                call SetRect(this.enumRect, this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
                set this.refreshRect = false
            endif
            set thistype.filterTemp = this
            call EnumItemsInRect(this.enumRect, Filter(function thistype.filterItems), actionFunc)
        endmethod
       
        method enumDestructables takes code actionFunc returns nothing
            if this.enumRect == null then
                set this.enumRect = Rect(this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
            elseif this.refreshRect then
                call SetRect(this.enumRect, this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
                set this.refreshRect = false
            endif
            set thistype.filterTemp = this
            call EnumDestructablesInRect(this.enumRect, Filter(function thistype.filterDestructables), actionFunc)
        endmethod
       
        method enumUnits takes group whichGroup returns nothing
            if this.enumRect == null then
                set this.enumRect = Rect(this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
            elseif this.refreshRect then
                call SetRect(this.enumRect, this.xMin - MARGIN, this.yMin - MARGIN, this.xMax + MARGIN, this.yMax + MARGIN)
                set this.refreshRect = false
            endif
            set thistype.filterTemp = this
            call GroupEnumUnitsInRect(whichGroup, this.enumRect, Filter(function thistype.filterUnits))
        endmethod
       
        //*************************************************************
       
        static if LIBRARY_Table then
            private static method onInit takes nothing returns nothing
                set tb = Table.create()
            endmethod
        endif
   
    endstruct
   
endlibrary


Add-on which contains more useful features but is not considered as a core functionality.

JASS:
library PolygonUtils requires Polygon/* v1.20
       
            by Flux
 
    Contains additional functionalities to Polygon.
 
    */
 
    //! novjass
    *******************************************************************
    *****************************  API:  ******************************
    *******************************************************************
 
        function SetToRectangeLine takes Polygon p, real x1, real y1, real x2, real y2, real width, real offset returns nothing
            - Change an existing Polygon into a Rectangle connecting from (x1, y1) to (x2, y2).
   
        function RectangleFromLine takes real x1, real y1, real x2, real y2, real width, real offset returns Polygon
            - Create a rectangle connecting from (x1, y1) to (x2, y2).
   
        function SetToRegularPolygon takes Polygon p, real x, real y, integer sides, real distance returns nothing
            - Change an existing Polygon into a Regular Polygon with center at (x, y).
       
        function RegularPolygon takes real x, real y, integer sides, real distance returns Polygon
            - Create a Regular Polygon with center at (x, y).The input real distance is the distance from center to the vertices.
       
        function SetToStar takes Polygon p, real x, real y, integer point, real pointDistance, real nearDistance returns nothing
            - Change an existing Polygon into a Star-shaped (non-intersecting) Polygon with center at (x, y).
   
        function Star takes real x, real y, integer point, real pointDistance, real nearDistance returns Polygon
            - Create a Star-shaped (non-intersecting) Polygon with center at (x, y).
   
        function AddLightningToPolygon takes string lightningCode, Polygon p returns nothing
            - Add a lightning effect on the edges of Polygon p.
   
        function MoveLightningFromPolygon takes Polygon p returns nothing
            - Updates the lightning effect on the edges of Polygon p.
   
        function RemoveLightningFromPolygon takes Polygon p returns nothing
            - Remove the lightning effect attached on the edges of Polygon p

   
    //! endnovjass
 
    function SetToRectangleLine takes Polygon p, real x1, real y1, real x2, real y2, real width, real offset returns nothing
        local real angle = Atan2(y2-y1, x2-x1)
        local real dx1 = width*Cos(angle + bj_PI/2)
        local real dx2 = width*Cos(angle - bj_PI/2)
        local real dy1 = width*Sin(angle + bj_PI/2)
        local real dy2 = width*Sin(angle - bj_PI/2)
        local real offx = offset*Cos(angle)
        local real offy = offset*Sin(angle)
        call p.clear()
        call p.addVertex(x1 + dx1 - offx, y1 + dy1 - offy)
        call p.addVertex(x1 + dx2 - offx, y1 + dy2 - offy)
        call p.addVertex(x2 + dx2 + offx, y2 + dy2 + offy)
        call p.addVertex(x2 + dx1 + offx, y2 + dy1 + offy)
        call p.finalize()
    endfunction
 
    function RectangleLine takes real x1, real y1, real x2, real y2, real width, real offset returns Polygon
        local Polygon p = Polygon.create()
        call SetToRectangleLine(p, x1, y1, x2, y2, width, offset)
        return p
    endfunction
 
    function SetToRegularPolygon takes Polygon p, real x, real y, integer sides, real distance returns nothing
        local real angle = 0
        local real i
        if sides > 2 then
            set i = 2*bj_PI/sides
            call p.clear()
            //The loop where the vertices are projected from center
            loop
                exitwhen angle >= 2*bj_PI
                call p.addVertex(x + distance*Cos(angle), y + distance*Sin(angle))
                set angle = angle + i
            endloop
            call p.finalize()
        endif
    endfunction
 
    function RegularPolygon takes real x, real y, integer sides, real distance returns Polygon
        local Polygon p = Polygon.create()
        call SetToRegularPolygon(p, x, y, sides, distance)
        return p
    endfunction
 
    function SetToStar takes Polygon p, real x, real y, integer point, real pointDistance, real nearDistance returns nothing
        local real angle = 0
        local real i = bj_PI/point
        local boolean b = true
        if point > 1 then
            set i = bj_PI/point
            call p.clear()
            //The loop where the vertices are projected from center
            loop
                exitwhen angle >= 2*bj_PI
                if b then
                    call p.addVertex(x + pointDistance*Cos(angle), y + pointDistance*Sin(angle))
                else
                    call p.addVertex(x + nearDistance*Cos(angle), y + nearDistance*Sin(angle))
                endif
                set b = not b
                set angle = angle + i
            endloop
            call p.finalize()
        endif
    endfunction
 
    function Star takes real x, real y, integer point, real pointDistance, real nearDistance returns Polygon
        local Polygon p = Polygon.create()
        call SetToStar(p, x, y, point, pointDistance, nearDistance)
        return p
    endfunction
 
    globals
        private hashtable hash = InitHashtable()
    endglobals
 
    function RemoveLightningFromPolygon takes Polygon p returns nothing
        local Vertex v1 = p.vertex
        local integer i = 0
        loop
            exitwhen v1 == 0
            call DestroyLightning(LoadLightningHandle(hash, p, i))
            set i = i + 1
            set v1 = v1.next
        endloop
        call FlushChildHashtable(hash, p)
    endfunction
 
    function MoveLightningFromPolygon takes Polygon p returns nothing
        local Vertex v1 = p.vertex
        local Vertex v2
        local integer i = 0
        loop
            exitwhen v1 == 0
            set v2 = v1.next
            if v2 == 0 then
                set v2 = p.v.next
            endif
            if HaveSavedHandle(hash, p, i) then
                call MoveLightning(LoadLightningHandle(hash, p, i), false, v1.x, v1.y, v2.x, v2.y)
            endif
            set i = i + 1
            set v1 = v1.next
        endloop
    endfunction

 
    function AddLightningToPolygon takes string lightningCode, Polygon p returns nothing
        local Vertex v1 = p.vertex
        local Vertex v2
        local integer i = 0
        loop
            exitwhen v1 == 0
            set v2 = v1.next
            if v2 == 0 then
                set v2 = p.v.next
            endif
            call SaveLightningHandle(hash, p, i, AddLightning(lightningCode, false, v1.x, v1.y, v2.x, v2.y))
            set i = i + 1
            set v1 = v1.next
        endloop
    endfunction
 
endlibrary



v1.0 - [24 Feb 2016]
- Initial Release

v1.10 - [26 Feb 2016]
- Fixed a major bug where the system is unable to identify the correct triggering unit.
- Added Recursion safety feature and demo.
- Removed unused struct members.
- Correctly positioned method stopMonitor to make it a direct function call.

v1.20 - [21 March 2016]
- Fixed forgotten index recycling of Quad instances.
- Fixed a bug where leaving unit is undetected when the unit instantly move at a very far location.
- Included retrieving the Quad's centerX, centerY.
- Added method setPosition which reposition the vertices of the Quad.
- Added method move which reposition the centerX and centerY of the Quad.
- Added method transform which changes the size and rotation of the Quad.
- Optimized scripting, removing redundant/repeating codes at a cost of a function call.

v1.21 - [22 March 2016]
- Optimized method setPosition now uses SetRect instead of re-creating the rects.
- Optimized method transform now uses SetRect instead of re-creating the rects.
- Optimized Quad creation, creating rects only when it has an event.
- Added static method createFromLine which creates Quad from two points and a specified width.

v1.30 - [27 March 2016]
- Changes the name from Quadrilateral to Polygon. It now supports any concave/convex polygon. However, all functions involving centroids will not work on self-intersecting polygons.
- Completely rewrote a large portion of the code, vertices are now represented by struct Vertex.
- Significant changes in the API. Event response is now Polygon.unit.
- Added method transformEx which includes the pivot point in the input argument.
- Created CommonPolygons as an add-on which contains useful wrapper functions for creating common polygons.

v1.31 - [27 March 2016]
- Fixed a bug where it will not be able to detect units exactly horizontal to a vertex.
- Destroying a Polygon no longer destroy the triggers attached to it.
- Added method destroyTriggers which destroy all triggers attached to the Polygon.
- Added event response Polygon.struct.
- Added more functions in CommonPolygons add-on.
- Fixed documentation errors.

v1.32 - [30 March 2016]
- Fixed unable to enumerate items, destructables and units on non trigger-registered Polygons.
- Correctly positioned method startMonitor to make it a direct function call.
- Added method operator area to struct Polygon.
- Added method operator vertex that returns the last added vertex of a Polygon.
- Added an offset option in SetToRectangleLine and RectangleLine (from CommonPolygons)

v1.33 - [22 April 2016]
- Added static method intersect which takes two Polygons as input and returns true if the two Polygons intersect.
- Changed name from CommonPolygons to PolygonUtils.
- Included Lightning Effects API in PolygonUtils.
- Changed method transformEx and transform, now referenced to the original size and angle. Therefore using this.transform(1, 0) will make the Polygon returns to the original size and rotation angle. Using this.clear() redefines the referenced value.

v1.40 - [21 October 2016]
- Renamed Polygon.struct to Polygon.object.
- Made Polygon.object and Polygon.unit readonly.
- Removed the filter parameters in the enumeration methods.
- Added method registerEnter and method registerLeave which requires a code parameter instead of a trigger.
- Removed method destroyTriggers.
- Improved Polygon edge intersection algorithm.

v1.41 - [2 November 2016]
- Fixed a bug where disabled trigger still evaluates.

v1.42 - [3 November 2016]
- Fixed a bug where units/items/destructables outside Polygon gets enumerated.



Technical Questions:

What kind of Polygons are supported?

Any concave/convex Polygon which does not self-intersect.

Does a Polygon have a number of sides limit?

No, a Polygon can support any number of sides as long as there are index for the vertex to be allocated. Using only 1 Polygon at a time can support 8190 sides. Using only two Polygons at a time can support 4094 sides each. But if the first Polyon only uses 4 sides, the second Polygon can support up to 8185 sides.

What is a Polygon's Centroid?

The Centroid is the average position of all points in the Polygon. Informally, it is the point at which a 2D cutout of the Polygon could be perfectly balanced on the tip of a pin.

Is this resource lightweight?

Yes. Test Result:
- FPS drops by ~10 when there are 50 4-sided Polygons existing (and monitored through triggerRegister) at the same time.
- FPS drops by ~20 when there are 60 4-sided Polygons existing (and monitored through triggerRegister) at the same time.
- FPS drops by ~30 when there are 70 4-sided Polygons existing (and monitored through triggerRegister) at the same time.

 

Attachments

  • Polygon v1.42.w3x
    59.2 KB · Views: 145
Last edited:
Hey, this seems quite useful.

Some quick thoughts:

Is a individual trigger list necessary?
A quad could have only one binded trigger.
The trick would be that the user can bind multiple (for example) boolexpr to the quad,
and they all will be registered to the trigger. Then, when the one trigger is evaluted, it will
also evalute all binded boolexpression automatically.

Is there a reason why next/prev are static members for quad?
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Only thing I noticed is that stopMonitor is misplaced, its called from destroy, which is above it, which means it can potentially generate trigger eval/exec.

For the suggestions, could rotating the quadrilateral be possible?(I have almost no knowledge in this kind of field :D)

Also it would appear the UnitInQuad event is not recursively safe(if I move unit into quadrilateral while inside callback from moving unit to quadrilateral it will do some magic), if it is, feel free to correct me
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Hey, this seems quite useful.

Some quick thoughts:

Is a individual trigger list necessary?
A quad could have only one binded trigger.
The trick would be that the user can bind multiple (for example) boolexpr to the quad,
and they all will be registered to the trigger. Then, when the one trigger is evaluted, it will
also evalute all binded boolexpression automatically.

Is there a reason why next/prev are static members for quad?

Ahh didn't know that trick but I'm trying to emulate rect's API and triggers can be disabled. The static next/prev are for picking all Quads being monitored.

Only thing I noticed is that stopMonitor is misplaced, its called from destroy, which is above it, which means it can potentially generate trigger eval/exec.

For the suggestions, could rotating the quadrilateral be possible?(I have almost no knowledge in this kind of field :D)

Also it would appear the UnitInQuad event is not recursively safe(if I move unit into quadrilateral while inside callback from moving unit to quadrilateral it will do some magic), if it is, feel free to correct me
Yeah, I misplaced that one. Nice find.

Rotating the Quad as a feature is good idea, I'll add it.

I'm not getting what you mean. Can you elaborate?


How about you use an Event lib? (RIP Nestharus Event ??-2015)

Also, could have been much better if it is, at first, just a Quad struct, then another struct for events.

Sigh, Me Polygon Lib :(
What's the advantage of using it? I don't use one. And I also prefer if my resource has minimal requirements.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
lets say I have this code

JASS:
globals
    boolean b = true
endglobals

function myF ...
    if not b then
        return
    endif
    set b = false
    call BJDebugMsg(GetUnitName(GetUnitInQuad()))
    call PositionAnotherUnitToQuad()
    call BJDebugMsg(GetUnitName(GetUnitInQuad()))
    set b = true
endfunction

Now obviously this is not 100% compilable, since I was pulling the functions from memory, but if you do this, the second call to print the name of the unit will probably either print the unit I moved into some quad in the function MoveAnotherUnitToQuad, or it will print null, hence it is not recursion safe.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
@edo I see what you mean. I'm guessing it will print the new unit who entered though I haven't tested it. If that's the case, it can easily be solved by local variables.

EDIT: You're right, it is not recursively safe due to the delay (0.0312500 second). That means if I copy something like that^, the trigger will run twice.
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,468
Update, changes:
- Fixed a major bug where the system is unable to identify the correct triggering unit.
- Added Recursion safety feature and demo.
- Removed unused struct members.
- Correctly positioned method stopMonitor to make it a direct function call.

Is my workaround for the recursion safety acceptable??
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Updated,
Changes:
- Fixed forgotten index recycling of Quad instances.
- Fixed a bug where leaving unit is undetected when the unit instantly move at a very far location.
- Included retrieving the Quad's centerX, centerY.
- Added method setPosition which reposition the vertices of the Quad.
- Added method move which reposition the centerX and centerY of the Quad.
- Added method transform which changes the size and rotation of the Quad.
- Optimized scripting, removing redundant/repeating codes at a cost of a function call.

@looking_for_help
I didn't include the ordering in the documentation because it seems obvious in my opinion how the vertices are ordered.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
@Bannar I like those resources you suggested, however I'be already written the rects in non-struct format so I think I'll just leave it as is instead of re-writing it. The benefit would be readability but it will cost 2 required resources which is a huge con for me.

@Aniki, Good idea, I'll include a performance test.

@Moderator/Reviewer Do you think I should use array members (specifically the vertices) to simplify the code a bit? That would limit the number of active instances to around 2k which is still huge.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Huge update. It is now a Polygon system.
Changes:
- Changes the name from Quadrilateral to Polygon. It now supports any concave/convex polygon. However, all functions involving centroids will not work on self-intersecting polygons.
- Completely rewrote a large portion of the code, vertices are now represented by struct Vertex.
- Significant changes in the API. Event response is now Polygon.unit.
- Added method transformEx which includes the pivot point in the input argument.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Saw a bug so I had to quickly update this. I thoroughly checked and tested the code and couldn't find anything that would cause another quick update. I think it's ready.
Latest Changes:
- Fixed a bug where it will not be able to detect units exactly horizontal to a vertex.
- Destroying a Polygon no longer destroy the triggers attached to it.
- Added method destroyTriggers which destroy all triggers attached to the Polygon.
- Added event response Polygon.struct.
- Added more functions in CommonPolygons add-on.
- Fixed documentation errors.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
- Destroying a Polygon no longer destroy the triggers attached to it.
- Added method destroyTriggers which destroy all triggers attached to the Polygon.

Whats the reasoning behind this? I understand the second bullet point, but why not automatically remove triggers to polygon when we are destroying the polygon anyways?
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Whats the reasoning behind this? I understand the second bullet point, but why not automatically remove triggers to polygon when we are destroying the polygon anyways?

So that you can only have 1 trigger for all Polygons you will ever create. Check the Demo about RegularPolygon and StarPolygon.

The Map Sample contains a lot of demo so I highly advise checking it out.
 
Level 30
Joined
Jul 10, 2007
Messages
6,307
An event lib or something like Trigger from Spells section would increase performance by quite a bit. You might be able to even double the number of instances you can run.

You can use a simple thing like a simple trigger with conditions registered to it. Alternatively you could use a full Trigger lib that allows users to register triggers, conditions, whatever to the lib.

The problem with the Trigger lib for you is that it has a lot of dependencies and is a bit too complex to embed into your code. The single trigger is simple enough to embed.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
This looks complete enough to me, but I am no expert when it comes to N-polygon shapes :D something like triangle and rectangle would do, but when you get to any shaped polygons, its beyond me quite a bit, so someone with more knowledge in this field could step in
 
That's an extremly useful resource! The demonstration code is also awesome.

Will take another attempt or so to read more code logics, but it actually looks very good.

Why on method destroyTriggers not the destroy method is called? And in the triggernode destroy method, the trigger member should be nulled.

JASS:
thistype struct
unit unit
^Could you change the naming?

JASS:
        method enumItems takes boolexpr filter, code actionFunc returns nothing
            local boolexpr tempFilter = filter
            if filter == null then
                set tempFilter = Filter(function thistype.alwaysTrue)
            endif
            if .enumRect == null then
                set .enumRect = Rect(.xMin - MARGIN, .yMin - MARGIN, .xMax + MARGIN, .yMax + MARGIN)
            elseif .refreshRect then
                call SetRect(.enumRect, .xMin - MARGIN, .yMin - MARGIN, .xMax + MARGIN, .yMax + MARGIN)
                set .refreshRect = false
            endif
            set .struct = this
            set enumType = ENUM_ITEM
            call EnumItemsInRect(.enumRect, And(tempFilter, Filter(function thistype.enumFilter)), actionFunc)
        endmethod
... and for method enumDestructables takes boolexpr filter, code actionFunc returns nothing

^Why not allow null filters? Seems like needless hustle without benefits.
The And function does create a new boolexpr each call. Must be removed after usage.
I see you use boolexpr and code as function arguments to mimic native function structure, and so you take usage of the And.
But if you don't want to take the hustle with it, it would be also totaly okay imo, if you just define code argument and let the custom filter to users within it.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
That's an extremly useful resource! The demonstration code is also awesome.
Thanks!

Why on method destroyTriggers not the destroy method is called?
JASS:
      method destroy takes nothing returns nothing
           - Destroy the Polygon.
      
       method destroyTriggers takes nothing returns nothing
           - Destroy all trigger attached to the Polygon.
The purpose of destroyTriggers is to destroy all triggers attached/registered to the polygon but still maintaining the polygon.

And in the triggernode destroy method, the trigger member should be nulled.
Does it matter?

JASS:
thistype struct
unit unit
^Could you change the naming?
Yes, what do you suggest?

JASS:
        method enumItems takes boolexpr filter, code actionFunc returns nothing
            local boolexpr tempFilter = filter
            if filter == null then
                set tempFilter = Filter(function thistype.alwaysTrue)
            endif
            if .enumRect == null then
                set .enumRect = Rect(.xMin - MARGIN, .yMin - MARGIN, .xMax + MARGIN, .yMax + MARGIN)
            elseif .refreshRect then
                call SetRect(.enumRect, .xMin - MARGIN, .yMin - MARGIN, .xMax + MARGIN, .yMax + MARGIN)
                set .refreshRect = false
            endif
            set .struct = this
            set enumType = ENUM_ITEM
            call EnumItemsInRect(.enumRect, And(tempFilter, Filter(function thistype.enumFilter)), actionFunc)
        endmethod
... and for method enumDestructables takes boolexpr filter, code actionFunc returns nothing

^Why not allow null filters? Seems like needless hustle without benefits.
Err, null filters are allowed. It's this part right here
JASS:
            if filter == null then
                set tempFilter = Filter(function thistype.alwaysTrue)
            endif


The And function does create a new boolexpr each call. Must be removed after usage.
Right, I missed that one. Good find.

I see you use boolexpr and code as function arguments to mimic native function structure, and so you take usage of the And.
But if you don't want to take the hustle with it, it would be also totaly okay imo, if you just define code argument and let the custom filter to users within it.
Hmm, yeah you're right. My mindset when I was creating this was to mimic (as you mentioned) the Rect functionalities but now I realized, most of the time, the boolexpr argument would be null and it is easier for users to implement their own custom filter than using boolexpr.
 
Err, null filters are allowed. It's this part right here
Yes, right, null expr is technically allowed, but will be changed to "always true". It could stay just null I meant.

Yes, what do you suggest?
for "struct" maybe just something "polygon" would do hm... I find "struct" really bad. ;D

"unit", in comparison to "struct" is much better ^^, and honestly I don't know a short good replacement. :(
And now as I think about it, it will be only used with ".unit" or so -- so it wont be ever highlighted, so it would be actually fine I guess.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
I forgot about this one, sorry, but before I update it, let me get your opinion IcemanBo.
Do you think I should just remove the trigger list and just use a simpler API, e.g. method registerEnter and method registerLeave which both takes code arguments instead of trigger arguments. Or maybe I should keep method triggerRegisterEnter and method triggerRegisterLeave but add those I mentioned earlier?
I'm planning to rename Polygon.struct to Polygon.object, is the name reasonable? Or maybe Polygon.get()?
 
object/instance/polygon anything you will take is fine I guess.

I think it sounds like a good idea to register code for register methods. Maybe both (trigger and code) is not needed, I personaly would probably make only one way for registering.

Btw, to something above, why trigger should not be nulled?

And you anyways know, no need to be sorry for being late ^^ I forgot myself about it and there are anyways also other resources that can be dealt with meanwhile. ^^

I didn't really get behind all used maths, but I guess I would need to make such a system myself to fully understand it.
But so it looks good, and also the demo tests worked fine for me. So from my side it's approveable when you confirm with your wanted updates.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
object/instance/polygon anything you will take is fine I guess.
I'll go with object. Polygon.object seems better compared to Polygon.instance especially to Polygon.polygon.

I think it sounds like a good idea to register code for register methods. Maybe both (trigger and code) is not needed, I personaly would probably make only one way for registering.
Ok, I'll ditch the trigger list then and just use a single static trigger and all codes registered will simply be triggerconditions.

Btw, to something above, why trigger should not be nulled?
What part?

I didn't really get behind all used maths, but I guess I would need to make such a system myself to fully understand it.
But so it looks good, and also the demo tests worked fine for me. So from my side it's approveable when you confirm with your wanted updates.
Yep, the math is kinda bloody, in Polygon intersection, I used a naive approach by checking each possible edge combination if they intersect. I feel like there's a better algorithm for it, but I can't think of any so for now, I'll stick with the naive approach (which also works).
I'll update this and fix all the issues you've pointed.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Updated!
v1.40 - [21 October 2016]
- Renamed Polygon.struct to Polygon.object.
- Made Polygon.object and Polygon.unit readonly.
- Removed the filter parameters in the enumeration methods.
- Added method registerEnter and method registerLeave which requires a code parameter instead of a trigger.
- Removed method destroyTriggers.
- Improved Polygon edge intersection algorithm.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Yeah, but while working on my DDS, I realized I needed to implement a Trigger Node List in my DDS to avoid infinite recursion so I applied the same concept here by making both API available (code and trigger parameters).

But I forgot to put "IsTriggerEnabled" before the trigger evaluations so it still needs another update. What's your opinion on the matter?
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Maybe something like this:
When a unit enters the Polygon, actions would be to move the Polygon to another random unit's location outside the polygon. Because the Polygon is moved to a random unit's location, the random unit chosen will run the entered event again thus infinite recursion.
 
Last edited:
I'm not sure the polygon should fire "unit enters" event multiple times for a unit that is already inside, but only if it comes from outside.

When the user uses a counter for example in his code for how often the unit "enters" a certain polygon, he might have wrong results if the polygon is slightly moving around, but the unit does never leave it actually.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
Could you give a little code example of the infinite recursion?

When a unit enters the Polygon, actions would be to move the Polygon to another random unit's location outside the polygon. Because the Polygon is moved to a random unit's location that is not inside the polygon, the random unit chosen will run the entered event again thus infinite recursion.

So method registerTriggerEnter/Leave stays?
 

Submission:
[System] Polygon v1.41

Date:
2 November 16

Status:
Approved
Note:


A very useful system in my eyes. It allows you to work with polygons.
Checking for intersections, making enumerations, binding enter events, and some more functionality.
Writing these kind of functions are always bothersome to write on one's own, but can
be often required if one aims cool effects and features.
If it was 3D, it would probably cover all demands one could ever have,
but practicaly seen is the 2D usage mostly needed and of course a bit easier to use. Approved.
 
Last edited:

AGD

AGD

Level 13
Joined
Mar 29, 2016
Messages
678
If I understood correctly, methods enumItem, enumUnit, etc. will enumerate all things within the Polygon's bounding rect which is a rectangle and therefore, not all enumerated things are actually inside the Polygon. So I think some check after the enumeration like this is needed:
JASS:
        private static method check takes nothing returns nothing
            local unit u = GetEnumUnit()
            if not isWidgetInside(u) then
                call GroupRemoveUnit(currentGroup, u)
            endif
            set u = null
        endmethod

        method enumUnits takes group whichGroup returns nothing
            //...
            call GroupEnumUnitsInRect(whichGroup, this.enumRect, null)
            set currentGroup = whichGroup
            call ForGroup(whichGroup, function thistype.check)
        endmethod

EDIT:
Lol, just realized how ridiculous my proposed way of checking is after I saw your update XD.
 
Last edited:
Top