1. Fill your cup and take your pick among the maps best suited for this year's Hive Cup. The 6th Melee Mapping Contest Poll is up!
    Dismiss Notice
  2. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  3. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] [System] Polygon

Discussion in 'JASS Resources' started by Flux, Feb 23, 2016.

  1. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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).

    Code (vJASS):

    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.

    Code (vJASS):

    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
     


    Changelog


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

    What kind of Polygons are supported?
    Answer

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

    Does a Polygon have a number of sides limit?
    Answer

    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?
    Answer

    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?
    Answer

    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.

     

    Attached Files:

    Last edited: Nov 3, 2016
  2. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,525
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    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?
     
  3. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    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
     
  4. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,842
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    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 :(
     
  5. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.

    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?


    What's the advantage of using it? I don't use one. And I also prefer if my resource has minimal requirements.
     
  6. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    lets say I have this code

    Code (vJASS):

    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.
     
  7. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    @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: Feb 25, 2016
  8. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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??
     
  9. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    973
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Could you include some documentation how the vertices are ordered relativly to each other? Maybe something like an ASCII picture:

    Code (vJASS):

    //   p1----p2
    //   |      |
    //   |      |
    //   p3----p4
     
     
  10. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Ok, I'll add that to the to do list.

    Code (Text):

    (ax,ay)----------(bx,by)
    |                      |
    |                      |
    |                      |
    |                      |
    (dx,dy)----------(cx,cy)
     
     
  11. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,915
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Awesomeness overload..

    I wanna create Wildstar skillshots with this :3
     
  12. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.
     
  13. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,099
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Seems like this could make use of Real2D and/or Rects. Both were made to ease handling area-oriented abilities when creating more complicated effects.
     
  14. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Ok, I'll take a look at it.
     
  15. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    Have you tried making more than 1 Quad? What's a "reasonable number" of Quads to have, 10, 50, 100, at most at the same time after which the fps just drops noticeably?
     
  16. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    @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.
     
  17. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,099
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    Sure man! I might write requirement-"heavy" version for you if I find some free time.
     
  18. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.
     
  19. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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.
     
  20. edo494

    edo494

    Joined:
    Apr 16, 2012
    Messages:
    3,846
    Resources:
    5
    Spells:
    1
    JASS:
    4
    Resources:
    5
    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?