[Snippet] Rects

Level 24
Joined
Mar 19, 2008
Messages
3,134
A class for manipulating rectangles.

Note: the x, y coordinates and the width and height stored inside a wxRect object may be negative and that wxRect functions do not perform any check against negative values.

Before uploading this, I've searched for similar libraries and indeed at least one has been found: Rectwraps by azlier. Even though this code highly differs from azlier's snippet and was written without any usage of his project, credits goes to azlier for uploading first library which allows easy rectangle manipulations.

Implementation based on typical Rect class from graphics application libraries. Requires Real2D which is heavly used throughout the code.

JASS:
/*****************************************************************************
*
*    Rects v2.0.1.1
*       by Bannar
*
*    Handly rectangle manipulation.
*
******************************************************************************
*
*    Requirements:
*
*       Alloc - choose whatever you like
*          e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
*
*    Optional requirement:
*
*       Real2D by Bannar
*          hiveworkshop.com/threads/snippet-real2d.249100/
*
******************************************************************************
*
*    Interface RectsForCellAction:
*
*       static method forCell takes real x, real y returns nothing
*          Performs action on specified rectangle cell.
*
*       module RectsForCellActionModule
*          Declares body for new action type.
*
*
*    Action implementation example:
*
*        | struct MyAction extends array
*        |     static method forCell takes real x, real y returns nothing
*        |         return CreateItem('crys', x, y)
*        |     endmethod
*        |
*        |     implement RectsForCellActionModule
*        | endstruct
*
******************************************************************************
*
*    struct Rect:
*
*       General:
*
*        | static method create takes real x, real y, real w, real h returns thistype
*        |    Default ctor.
*        |
*        | static method operator[] takes thistype other returns thistype
*        |    Copy ctor.
*        |
*        | method destroy takes nothing returns nothing
*        |    Default dctor.
*
*
*       Fields:
*
*        | real minX
*        | real minY
*        | real maxX
*        | real maxY
*        | real width
*        | real height
*        |
*        | readonly Coord coord
*        |    Coordinate object determining minX & minY. Requires Real2D.
*        |
*        | readonly Size size
*        |    Size object determining width & height. Requires Real2D.
*        |
*        | readonly real centerX
*        |    Center X of the rectangle.
*        |
*        | readonly real centerY
*        |    Center Y of the rectangle.
*        |
*        | readonly rect rect
*        |    Returns the underlying rect handle.
*
*
*       Methods:
*
*        | method refresh takes nothing returns nothing
*        |    Forces a refresh on the underlying rect handle.
*        |
*        | method moveTo takes real x, real y returns thistype
*        |    Center rect to specified coordinates.
*        |
*        | method empty takes nothing returns boolean
*        |    Checks whether rectangle is empty.
*        |
*        | method contains takes real x, real y returns boolean
*        |    Whether speficied value is within given rectangle.
*        |
*        | method containtsRect takes Rects r returns boolean
*        | method containsItem takes item itm returns boolean
*        | method containsUnit takes unit u returns boolean
*        | method containsDestructable takes destructable d returns boolean
*        |
*        | method inflate takes real dx, real dy returns thistype
*        |    Increases the size of the rectangle.
*        |
*        | method deflate takes real dx, real dy returns thistype
*        |    Decreases the rectangle size.
*        |
*        | method offset takes real dx, real dy returns thistype
*        |    Moves the rectangle by the specified offset.
*        |
*        | method intersect takes Rects r returns thistype
*        |    Modifies this rectangle to contain the overlapping portion of this rectangle and the one passed in as parameter.
*        |
*        | method getIntersecting takes Rects r returns thistype
*        |    Returns the overlapping portion of this rectangle and the one passed in as parameter.
*        |
*        | method intersects takes Rects r returns boolean
*        |    Returns true if this rectangle has a non-empty intersection with the rectangle rect and false otherwise.
*        |
*        | method union takes Rects r returns thistype
*        |    Modifies the rectangle to contain the bounding box of this rectangle and the one passed in as parameter.
*        |
*        | method forEach takes RectsForCellAction action, real radius returns thistype
*        |    Performs the specified action on each cell found within rectangle.
*
*
*       Methods requiring Real2D:
*
*        | static method createFromCoords takes Coord c1, Coord c2 returns thistype
*        | static method createFromCoordSize takes Coord c, Size s returns thistype
*        |    Factory ctors.
*        |
*        | method containsCoord takes Coord c returns boolean
*        | method offsetCoord takes Coord c returns thistype
*        |
*        | method inflateSize takes Size s returns thistype
*        | method deflateSize takes Size s returns thistype
*
*
*****************************************************************************/
library Rects uses Alloc optional Real2D

globals
    private trigger array triggers
    // iteration related globals
    private real argX = 0.0
    private real argY = 0.0
endglobals

struct RectsForCellAction extends array
    implement Alloc

    static method forCell takes real x, real y returns nothing
        return
    endmethod

    static method create takes nothing returns thistype
        local thistype this = allocate()
        set triggers[this] = CreateTrigger()
        return this
    endmethod

    method destroy takes nothing returns nothing
        call DestroyTrigger(triggers[this])
        set triggers[this] = null
        call deallocate()
    endmethod
endstruct

module RectsForCellActionModule
    private delegate RectsForCellAction parent

    private static method onInvoke takes nothing returns nothing
        call forCell(argX, argY)
    endmethod

    static method create takes nothing returns thistype
        local thistype this = RectsForCellAction.create()
        set parent = this
        call TriggerAddCondition(triggers[this], Condition(function thistype.onInvoke))
        return this
    endmethod

    method destroy takes nothing returns nothing
        call parent.destroy()
    endmethod
endmodule

struct Rects extends array
    readonly rect rect

    implement Alloc

static if not LIBRARY_Real2D then
    real minX
    real minY
    real width
    real height
else
    readonly Coord coord
    readonly Size size

    method operator width takes nothing returns real
        return size.width
    endmethod

    method operator height takes nothing returns real
        return size.height
    endmethod

    method operator minX takes nothing returns real
        return coord.x
    endmethod

    method operator minY takes nothing returns real
        return coord.y
    endmethod

    method operator width= takes real w returns nothing
        set size.width = w
    endmethod

    method operator height= takes real h returns nothing
        set size.height = h
    endmethod

    method operator minX= takes real x returns nothing
        set coord.x = x
    endmethod

    method operator minY= takes real y returns nothing
        set coord.y = y
    endmethod
endif

    method operator maxX takes nothing returns real
        return minX + width
    endmethod

    method operator maxY takes nothing returns real
        return minY + height
    endmethod

    method operator centerX takes nothing returns real
        return minX + (width / 2)
    endmethod

    method operator centerY takes nothing returns real
        return minY + (height / 2)
    endmethod

    method operator maxX= takes real x returns nothing
        set width = x - minX
    endmethod

    method operator maxY= takes real y returns nothing
        set height = y - minY
    endmethod

    method refresh takes nothing returns nothing
        call SetRect(rect, minX, minY, maxX, maxY)
    endmethod

    method moveTo takes real x, real y returns thistype
        set minX = x - (width * 0.5)
        set minY = y - (height * 0.5)
        call refresh()
        return this
    endmethod

    method empty takes nothing returns boolean
        return width <= 0 or height <= 0
    endmethod

    static method create takes real x, real y, real w, real h returns thistype
        local thistype this = allocate()

        static if LIBRARY_Real2D then
            set coord = Coord.create(x, y)
            set size = Size.create(w, h)
        else
            set minX = x
            set minY = y
            set width = w
            set height = h
        endif

        set rect = Rect(x, y, x+w, y+h)

        return this
    endmethod

    static method operator[] takes thistype other returns thistype
        return create(other.minX, other.minY, other.width, other.height)
    endmethod

    method destroy takes nothing returns nothing
        call RemoveRect(rect)
        set rect = null

        static if LIBRARY_Real2D then
            call coord.destroy()
            call size.destroy()
            set coord = 0
            set size = 0
        endif

        call deallocate()
    endmethod

    method contains takes real x, real y returns boolean
        return x >= minX and y >= minY and (y - minY) < height and (x - minX) < width
    endmethod

    method containtsRect takes Rects r returns boolean
        return contains(r.minX, r.minY) and contains(r.maxX, r.maxY)
    endmethod

    method containsItem takes item itm returns boolean
        return itm != null and not IsItemOwned(itm) and contains(GetItemX(itm), GetItemY(itm))
    endmethod

    method containsUnit takes unit u returns boolean
        return u != null and contains(GetUnitX(u), GetUnitY(u))
    endmethod

    method containsDestructable takes destructable d returns boolean
        return d != null and contains(GetDestructableX(d), GetDestructableY(d))
    endmethod

    method inflate takes real dx, real dy returns thistype
        if (-2 * dx) > width then
            set minX = centerX
            set width = 0
        else
            set minX = minX - dx
            set width = width + (2 * dx)
        endif

        if (-2 * dy) > height then
            set minY = centerY
            set height = 0
        else
            set minY = minY - dy
            set height = height + (2 * dy)
        endif

        call refresh()
        return this
    endmethod

    method deflate takes real dx, real dy returns thistype
        return inflate(-dx, -dy)
    endmethod

    method offset takes real dx, real dy returns thistype
        set minX = minX + dx
        set minY = minY + dy
        call refresh()
        return this
    endmethod

    method intersect takes Rects r returns thistype
        local real x2 = maxX
        local real y2 = maxY

        if minX < r.minX then
            set minX = r.minX
        endif
        if minY < r.minY then
            set minY = r.minY
        endif
        if x2 > r.maxX then
            set x2 = r.maxX
        endif
        if y2 > r.maxY then
            set y2 = r.maxY
        endif

        set width = x2 - minX
        set height = y2 - minY

        if width <= 0 or height <= 0 then
            set width = 0
            set height = 0
        endif

        call refresh()
        return this
    endmethod

    method getIntersecting takes Rects r returns thistype
        local real x1 = minX
        local real y1 = minY
        local real x2 = maxX
        local real y2 = maxY
        local real w
        local real h

        if minX < r.minX then
            set x1 = r.minX
        endif
        if minY < r.minY then
            set y1 = r.minY
        endif
        if x2 > r.maxX then
            set x2 = r.maxX
        endif
        if y2 > r.maxY then
            set y2 = r.maxY
        endif

        set w = x2 - minX
        set h = y2 - minY

        if w <= 0 or h <= 0 then
            set w = 0
            set h = 0
        endif

        return create(x1, y1, w, h)
    endmethod

    method intersects takes Rects r returns boolean
        local Rects tmp = getIntersecting(r)
        local boolean result

        set result = (tmp.width != 0)
        call tmp.destroy()

        return result
    endmethod

    private static method min takes real x1, real x2 returns real
        if x1 <= x2 then
            return x1
        endif
        return x2
    endmethod

    private static method max takes real x1, real x2 returns real
        if x1 >= x2 then
            return x1
        endif
        return x2
    endmethod

    method union takes Rects r returns thistype
        local real x1
        local real y1
        local real x2
        local real y2

        if width == 0 or height == 0 then
            set minX = r.minX
            set minY = r.minY
            set width = r.width
            set height = r.height
        else
            set x1 = min(minX, r.minX)
            set y1 = min(minY, r.minY)
            set x2 = max(maxX, r.maxX)
            set y2 = max(maxY, r.maxY)

            set minX = x1
            set minY = y1
            set width = x2 - x1
            set height = y2 - y1
        endif

        call refresh()
        return this
    endmethod

    method forEach takes RectsForCellAction action, real radius returns thistype
        local real x = minX
        local real y

        loop
            set y = minY
            loop
                set argX = x
                set argY = y
                call TriggerEvaluate(triggers[action])

                set y = y + radius
                exitwhen y > maxY
            endloop
            set x = x + radius
            exitwhen x > maxX
        endloop

        return this
    endmethod

static if LIBRARY_Real2D then
    static method createFromCoords takes Coord c1, Coord c2 returns thistype
        local thistype this = allocate()

        set coord = Coord.create(c1.x, c1.y)
        set size = Size.create(c2.x - c1.x, c2.y - c1.y)

        if width < 0 then
            set size.width = -width
            set coord.x = c2.x
        endif

        if height < 0 then
            set size.height = -height
            set coord.y = c2.y
        endif

        set rect = Rect(minX, minY, maxX, maxY)
        return this
    endmethod

    static method createFromCoordSize takes Coord c, Size s returns thistype
        return create(c.x, c.y, s.width, s.height)
    endmethod

    method containsCoord takes Coord c returns boolean
        return contains(c.x, c.y)
    endmethod

    method offsetCoord takes Coord c returns thistype
        return offset(c.x, c.y)
    endmethod

    method inflateSize takes Size s returns thistype
        return inflate(s.width, s.height)
    endmethod

    method deflateSize takes Size s returns thistype
        return inflate(-s.width, -s.height)
    endmethod
endif
endstruct

endlibrary
Demo:
JASS:
library RectsTest requires Rects, Real2D

struct RectsTest extends array

    static method draw_rect takes Rects rec, string lightType returns nothing
        call AddLightning(lightType, true, rec.minX, rec.minY, rec.minX, rec.maxY)
        call AddLightning(lightType, true, rec.maxX, rec.minY, rec.maxX, rec.maxY)

        call AddLightning(lightType, true, rec.minX, rec.minY, rec.maxX, rec.minY)
        call AddLightning(lightType, true, rec.minX, rec.maxY, rec.maxX, rec.maxY)
    endmethod

    static method wait takes real period returns nothing
        call TriggerSleepAction(period)
    endmethod

    static method print takes real period, string what returns nothing
        call DisplayTimedTextToPlayer(Player(0), 0, 0, period, what)
        call wait(period)
    endmethod

    static method onInit takes nothing returns nothing
        local Coord pt = Coord.create(-200, -300)
        local Size sz = Size.create(700, 800)
        local Rects rec = Rects.createFromCoordSize(pt, sz)
        local Rects rec2 = 0

        call wait(2)
        call draw_rect(rec, "CLPB")
        call print(5, "Initial rect |c000080FF(blue)|r: " + R2S(rec.minX) + " " + R2S(rec.minY) + " " + R2S(rec.maxX) + " " +R2S(rec.maxY) )

        call print(5, "Now, lets create second rect nearby and use it to intersect with our initial one.")
        set rec2 = Rects.create(300, 300, 800, 800)
        call draw_rect(rec2, "LEAS")
        call print(5, "\nOur second rect |c00FF8000(orange)|r: " + R2S(rec2.minX) + " " + R2S(rec2.minY) + " " + R2S(rec2.maxX) + " " +R2S(rec2.maxY) )

        call print(5, "Lets intersect it and see the results:")
        call rec.intersect(rec2)
        call draw_rect(rec, "AFOD")
        call print(5, "\nIntersected rect |c00FF0000(red)|r: " + R2S(rec.minX) + " " + R2S(rec.minY) + " " + R2S(rec.maxX) + " " +R2S(rec.maxY) )

        call pt.setXY(200, 200)
        set sz.width = 300

        call rec.coord.setXY(pt.x, pt.y)
        call rec.size.setWH(sz.width, sz.height)
        call rec.refresh()

        call print(5, "Time to resize our rect. New width: 300; point: (200, 200).")
        call draw_rect(rec, "DRAL")
        call print(5, "\nResized rect |c0000FF00(green)|r: " + R2S(rec.minX) + " " + R2S(rec.minY) + " " + R2S(rec.maxX) + " " +R2S(rec.maxY) )

        call print(5, "Before we finish, perform inflate method. Values: 150, 275.")
        call rec.inflate(150, 275)
        call draw_rect(rec, "HWPB")
        call print(5, "\nInflated rect |c00FFFF00(yellow)|r: " + R2S(rec.minX) + " " + R2S(rec.minY) + " " + R2S(rec.maxX) + " " +R2S(rec.maxY) )

        call print(5, "Thank you for your undivided attention.")
    endmethod

endstruct

endlibrary
 
Last edited:
Level 24
Joined
Mar 19, 2008
Messages
3,134
No longer requires Real2D - it's optional now.
x & y no longer exist. There are just minY and minX, together with width and height. Changed common functions to operators (e.g minX/Y, maxX/Y, centerX/Y).

Removed following methods:
* static method createFromSize takes Size s returns thistype
* method rect takes nothing returns rect

* method getSize takes nothing returns Size
* method getBottomLeft takes nothing returns Point
* method getBottomRight takes nothing returns Point
* method getTopLeft takes nothing returns Point
* method getTopRight takes nothing returns Point

* method setSize takes Size s returns nothing
* method setBottomLeft takes Point p returns nothing
* method setBottomRight takes Point p returns nothing
* method setTopLeft takes Point p returns nothing
* method setTopRight takes Point p returns nothing

* method moveToPoint takes Point p returns nothing

'rect' is now readonly field. Getters & setters were redundant. As of now, it's more adapted for jass environment - maxX, maxY operators has been added instead.

Note: common operations involving minX, minY, maxX, maxY, maxX etc. no longer refresh the rect handle. Modification methods such as inflate, still perform such operation, but for changes done manualy, user has to call refresh method later on to reclect the actual changes on rect handle.

Made following fields and methods implemented only when Real2D is present:
* readonly Coord coord
* readonly Size size

* static method createFromCoords takes Coord c1, Coord c2 returns thistype
* static method createFromCoordSize takes Coord c, Size s returns thistype

* method containsCoord takes Coord c returns boolean
* method offsetCoord takes Coord c returns nothing

* method inflateSize takes Size s returns thistype
* method deflateSize takes Size s returns thistype
 
Level 11
Joined
Dec 19, 2012
Messages
411
When using your Rects system, i realized that one of the method got problem :

JASS:
        method intersects takes Rects r returns boolean
            local Rects tmp = getIntersecting(this)
            local boolean result

            set result = (tmp.width <= 0)
            call tmp.destroy()

            return result
        endmethod

This method is not working properly. I noticed that the Rects r is not even being used.

Btw, I would suggest you to name this method isIntercept instead of intercepts. Just personal opinion though.
 
Level 24
Joined
Mar 19, 2008
Messages
3,134
Thank you for that report. I've been using Rects quite a lot when dealing with rect handles, though usually for simple purposes. Fixed the comparison too (getIntersecting() can never return Rect with width/height values below 0).

I would advice against changing the API now. It's been here for quite a while :)
 
Level 12
Joined
Feb 11, 2008
Messages
810
Thank you for that report. I've been using Rects quite a lot when dealing with rect handles, though usually for simple purposes. Fixed the comparison too (getIntersecting() can never return Rect with width/height values below 0).

I would advice against changing the API now. It's been here for quite a while :)

Does this system fix the blizzard issue where all rects you create are actually +32 bigger in game? so when doing a check for a unit being in an area is done it will trigger the event but wont pass the condition.
 
Level 24
Joined
Mar 19, 2008
Messages
3,134
From code you can realize that Rects is transparent to actual rect (handle type). In fact, it has 'rect' field only to allow one to retrieve blizzard native rect, which characteristics "match" its Rects equivalent.
There are no GetRect(...) function invocations. I'm also not doing any adjustments to the rect handle itself.
Should I?

When you pass to containsUnit method, it will simply verify it it's within Rects instance coords, the quadrangle.

Edit: Yey 3k post :D
 
Last edited:
Level 24
Joined
Mar 19, 2008
Messages
3,134
Yo man, thanks for this entirely unexpected suggestion.

Updated Rects:

- Added RectsForCellAction interface, comes in form of struct and module. Read documentation for more info
- Added forEach method which invokes specified action on every cell found within rectangle
- contains<X> methods now inline friendly
- Validated to be Wurst friendly, no keyword conflicts
- Improved readability and documentation

Might change Real2D from optional to requirement. Code copying is bad.

Action example (one listed in documentation):
JASS:
struct MyRectsForCellAction extends array
    static method forCell takes real x, real y returns nothing
        call CreateItem('crys', x, y)
    endmethod

    implement RectsForCellActionModule
endstruct

private function Test takes nothing returns nothing
    local Rects r = Rects.create(-200, -200, 400, 400)
    call r.forEach(MyRectsForCellAction.create(), 32)
endfunction
As you can see, this approach grants forCell method scalability, arbitrary actions can be provided. You do not have to limit yourself to SetTerrainPathable.

Demo code will be updated tomorrow - nothing wrong with it, just the naming convention sucks, plus should be found within a scope rather than library.

Edit:

Followup update:
Renamed interface action method from cellAction to forCell.
Renamed Rects::forCell method to forEach.
Method forEach now allows user to specify radius for iteration.
Methods: moveTo, offset and offsetCoord now return thistype rather than returning nothing.
 
Last edited:
Top