• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Virtual Grid

Greetings.

This is just a snippet a library which grants virtual partition functionality and unique id assignment for parent Virtual Grid instances.

JASS:
library VirtualGrid requires /*
    ------------------
    */ Table        /*
    ------------------
        -> Bribe
        
        link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/
    
     -------------------
    |   VirtualGrid     |
    |     v.1.2.0       |
    |                   |
    |     - MyPad       |
     -------------------
  
     ---------------------------------------
    |                                       |
    |   A library that makes                |
    |   virtual partitioning logic          |
    |   easier to handle                    |
    |                                       |
    |   Practical use:                      |
    |       - Missile systems               |
     ---------------------------------------
   
     ---------------------------------------------------
    |                                                   |
    |   Credits:                                        |
    |       - Pinzu for suggesting                      |
    |         improvements on the capabilities          |
    |         of the library (such as instantiation     |
    |         of individual VirtualGrids                |
    |                                                   |
    |       - Overfrost for suggesting the inclusion    |
    |         of circular grids.                        |
    |                                                   |
     ---------------------------------------------------
    
     ----------------------------------------------------------------------
    |                                                                         
    |   API:
    |
    |   -------------------------------------------------------------------
    |
    |   Definition:
    |       Parent Instance -   A VirtualGrid object created via createForRect or
    |                           createForCircle
    |
    |       Child Instance  -   A VirtualGrid "id" that is bound to the Parent
    |                           Instance. They can only be accessed by
    |                           parent instances, and are not
    |                           forcibly allocated by the system.
    |
    |   -------------------------------------------------------------------
    |
    |   struct VirtualGrid extends array
    |
    |       -------------
    |       Constructors:
    |       -------------
    |       
    |       static method createFromRect(rect whichRect, integer cols, integer rows) -> VirtualGrid
    |           - Generates a unique VirtualGrid Parent Instance based on the given rect
    |             handle.
    |
    |       static method createFromCircle(real x, real y, real radius, integer cols, integer rows) -> VirtualGrid
    |           - Generates a unique VirtualGrid Parent Instance based on the given circle.
    |
    |       -----------
    |       Destructor:
    |       -----------
    |
    |       method destroy()
    |           - Destroys a VirtualGrid Parent Instance. (Child Instances cannot be destroyed this way)
    |             If attempting to destroy Child Instances, this will print an error.
    |
    |       ----------------
    |       Utility Methods:
    |       ----------------
    |
    |       ------------------------------------------------------------
    |       Note: The following methods do not accept Child Instances:
    |       ------------------------------------------------------------
    |
    |       method getGridFromPoint(real x, y) -> VirtualGrid
    |           - Retrieves a VirtualGrid Child Instance based on the coordinates.
    |           - In debug mode, this will apply bounds checking
    |             so that if out-of-bounds, it will return 0 and
    |             display an error.
    |
    |       method isPointInGrid(real x, real y, VirtualGrid subGrid) -> boolean
    |           - Checks if a point lies in the specified VirtualGrid subGrid
    |           - The note above applies here. (A Child Version of the method
    |             exists below)
    |
    |       ------------------------------------------------------------
    |       Note: The following methods accept both Parent and Child Instances.
    |       ------------------------------------------------------------
    |
    |       method operator minX
    |       method operator minY
    |       method operator maxX
    |       method operator maxY
    |           - Returns the necessary values dynamically computed at runtime.
    |           - Sub-Grids can invoke this function.
    |
    |       ------------------------------------------------------------
    |       Note: The following methods do not accept parent instances in SAFE_MODE
    |       ------------------------------------------------------------
    |
    |       method getLeftGrid() -> VirtualGrid
    |           - Returns the left grid adjacent to the requesting grid.
    |           - For leftmost grids, this will return themselves.
    |
    |       method getRightGrid() -> VirtualGrid
    |           - Returns the right grid adjacent to the requesting grid.
    |           - For rightmost grids, this will return themselves.
    |
    |       method getTopGrid() -> VirtualGrid
    |           - Returns the top grid adjacent to the requesting grid.
    |           - For topmost grids, this will return themselves.
    |
    |       method getBottomGrid() -> VirtualGrid
    |           - Returns the bottom grid adjacent to the requesting grid.
    |           - For bottom-most grids, this will return themselves.
    |
    |       ------------------------------------------------------------
    |
    |       method isPointWithinGrid(real x, real y) -> boolean
    |           - The child version of :isPointInGrid()
    |           - Does not accept Parent Instances even when not in SAFE_MODE.
    |
     ---------------------------------------------------------------------
    |
    |   globals
    |       SAFE_MODE   - A flag determining whether the system will
    |                     enforce Parent/Child instance safety when
    |                     running.
    |
    |                     This applies to methods where
    |                     Parent Instances are not accepted, and
    |                     in the checking of the provided rect handle
    |                     in constructor createFromRect, and of the
    |                     radius in constructor createFromCircle.
    |
    |       GRID_TYPE_RECT      - A unique enum variable. (Must not be 0)
    |       GRID_TYPE_CIRCLE    - A unique enum variable. (Must not be 0
    |
     ---------------------------------------------------------------------
*/
    
globals
    private constant boolean SAFE_MODE              = false
    
    private constant integer GRID_TYPE_RECT         = 1
    private constant integer GRID_TYPE_CIRCLE       = 2
endglobals
static if DEBUG_MODE then
private function Error takes string consoleName, string msg, string errorType returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "|cffff0000>> " + consoleName + "|r -> " + msg + " |cffff0000" + errorType + "|r")
endfunction
endif
private module Init
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule
struct VirtualGrid extends array
    private static thistype array allocNodes
    private static Table allocTable
    
    private integer gridType
    private integer size
    private boolean isHead
    
    //  For rects
    readonly rect quad
    readonly real quadCellX
    readonly real quadCellY
    readonly integer quadRows
    readonly integer quadCols
    
    //  For circles
    readonly real centerX
    readonly real centerY
    readonly real radius
    readonly real radStep
    readonly integer centerRows     //  The angular partitions
    readonly integer centerCols     //  The concentric partitions
    
    method destroy takes nothing returns nothing
        if not this.isHead then
            debug call Error("thistype:destroy", "Cannot destroy Child Instance or a deallocated Parent Instance", "InvalidObjectException")
            return
        endif
        set this.isHead = false
        
        if this.gridType == GRID_TYPE_RECT then
            set this.quad       = null
            set this.quadCellX  = 0.
            set this.quadCellY  = 0.
        else    // if this.gridType == GRID_TYPE_CIRCLE then
            set this.centerX    = 0.
            set this.centerY    = 0.
            set this.radius     = 0.
            set this.centerCols = 0
            set this.centerRows = 0
        endif
        
        set allocTable[this.size].integer[integer(this)] = allocTable[this.size].integer[0]
        set allocTable[this.size].integer[0]             = integer(this)
        
        set this.size   = 0
    endmethod
    //  Core constructor
    private static method create takes integer size returns thistype
        local thistype object = 0
        
        if size <= 0 then
            return object
        endif
        
        if not allocTable.integer.has(size) then
            set allocTable[size]    = Table.create()
        endif
        
        set object = thistype(allocTable[size].integer[0])
        if allocTable[size].integer[object] == 0 then
            //  No deallocated instance to be reallocated
            set object                      = thistype(allocTable.integer[0] + 1)
            set allocTable.integer[0]       = integer(object) + size
            
            set allocNodes[0]               = thistype(integer(allocNodes[0]) + 1)
            set allocNodes[allocNodes[0]]   = object
        else
            set allocTable[size].integer[0]     = allocTable[size].integer[integer(object)]
            call allocTable[size].integer.remove(integer(object))        
        endif
        set object.isHead   = true
        set object.size     = size
        return object
    endmethod
    
    //  Members generated to not waste locals.
    private static thistype array extras        
    private static real array vars
    
    private method getHeader takes nothing returns nothing
        set extras[0] = 0
        set extras[1] = thistype(allocNodes[0])
        set extras[2] = thistype(((extras[0]) + (extras[1]))/2)
        set extras[3] = 0
        loop
            if ((this) < (allocNodes[extras[2]])) then
                set extras[1] = allocNodes[extras[2]]
            else
                set extras[0] = allocNodes[extras[2]]
            endif
            set extras[3] = extras[2]
            set extras[2] = thistype(((extras[0]) + (extras[1]))/2)
            
            exitwhen extras[3] == extras[2]
        endloop
    endmethod
    
    private method minimax takes nothing returns nothing
        set vars[0] = I2R(ModuloInteger((this) - (extras[2]) - 1, extras[2].centerRows) + 1)*extras[2].radStep
        set vars[1] = I2R(R2I((extras[2].size + (extras[2]) - (this))/I2R(extras[2].centerRows)))
    endmethod
    
    method operator minX takes nothing returns real
        if this.gridType == GRID_TYPE_RECT then
            return GetRectMinX(this.quad)
        elseif this.gridType == GRID_TYPE_CIRCLE then
            return extras[2].centerX - extras[2].radius
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            return GetRectMinX(extras[2].quad) + (ModuloInteger(((this) - (extras[2])) - 1, extras[2].quadCols)+1)*extras[2].quadCellX
        endif
        call this.minimax()
        if (vars[0] > bj_PI/2) and (3*bj_PI/2 > vars[0]) then
            if vars[0] <= bj_PI then
                return extras[2].centerX + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Cos(vars[0])
            endif
            return extras[2].centerX + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Cos(vars[0] - extras[2].radStep)
        endif
        if (vars[0] <= bj_PI/2) then
            return extras[2].centerX + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Cos(vars[0])
        endif
        return extras[2].centerX + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Cos(vars[0] - extras[2].radStep)
    endmethod
    
    method operator maxX takes nothing returns real
        if this.gridType == GRID_TYPE_RECT then
            return GetRectMaxX(this.quad)
        elseif this.gridType == GRID_TYPE_CIRCLE then
            return extras[2].centerX + extras[2].radius            
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            return GetRectMinX(extras[2].quad) + (ModuloInteger(((this) - (extras[2])) - 1, extras[2].quadCols)+2)*extras[2].quadCellX
        endif
        call this.minimax()
        if (vars[0] > bj_PI/2) and (3*bj_PI/2 > vars[0]) then
            if vars[0] <= bj_PI then
                return extras[2].centerX + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Cos(vars[0] - extras[2].radStep)
            endif
            return extras[2].centerX + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Cos(vars[0])
        endif
        if (vars[0] <= bj_PI/2) then
            return extras[2].centerX + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Cos(vars[0] - extras[2].radStep)
        endif
        return extras[2].centerX + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Cos(vars[0])
    endmethod
    
    method operator minY takes nothing returns real
        if this.gridType == GRID_TYPE_RECT then
            return GetRectMinY(this.quad)
        elseif this.gridType == GRID_TYPE_CIRCLE then
            return extras[2].centerY - extras[2].radius
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            return GetRectMaxY(extras[2].quad) - (((this) - (extras[2]))/extras[2].quadRows)*extras[2].quadCellY
        endif
        call this.minimax()
        if (vars[0] <= bj_PI) then
            if (vars[0] <= bj_PI/2) then
                return extras[2].centerY + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Sin(vars[0] - extras[2].radStep)
            endif
            return extras[2].centerY + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Sin(vars[0])
        endif
        if (vars[0] <= 3*bj_PI/2) then
            return extras[2].centerY + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Sin(vars[0])
        endif
        return extras[2].centerY + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Sin(vars[0] - extras[2].radStep)
    endmethod
    method operator maxY takes nothing returns real
        if this.gridType == GRID_TYPE_RECT then
            return GetRectMaxY(this.quad)
        elseif this.gridType == GRID_TYPE_CIRCLE then
            return extras[2].centerY + extras[2].radius
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            return GetRectMaxY(extras[2].quad) - ((((this) - (extras[2]))/extras[2].quadRows) - 1)*extras[2].quadCellY
        endif
        call this.minimax()
        if (vars[0] <= bj_PI) then
            if (vars[0] <= bj_PI/2) then
                return extras[2].centerY + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Sin(vars[0])
            endif
            return extras[2].centerY + (extras[2].radius*((vars[1]+1)/I2R(extras[2].centerCols)))*Sin(vars[0] - extras[2].radStep)
        endif
        if (vars[0] <= 3*bj_PI/2) then
            return extras[2].centerY + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Sin(vars[0] - extras[2].radStep)
        endif
        return extras[2].centerY + (extras[2].radius*((vars[1])/I2R(extras[2].centerCols)))*Sin(vars[0])
    endmethod
    
    method getRightGrid takes nothing returns thistype
        static if SAFE_MODE then
            if this.isHead then
                debug call Error("thistype:getRightGrid", "Parent Instance was provided (this) (" + I2S(this) + ")", "(InvalidObjectException)")
                return this
            endif
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            if ModuloInteger((this) - (extras[2]) + 1, extras[2].quadCols) == 0 then
                //  Rightmost instance
                debug call Error("thistype:getRightGrid", "Instance provided is already situated at the rightmost section", "(OutOfBoundsException)")
                return this
            endif
            return thistype((this) + 1)
        endif
        if ModuloInteger((this) - (extras[2]) - 1, extras[2].centerRows) == 0 then
            return thistype((this) + extras[2].centerRows - 1)
        endif
        return thistype((this) - 1)
    endmethod
    
    method getLeftGrid takes nothing returns thistype
        static if SAFE_MODE then
            if this.isHead then
                debug call Error("thistype:getLeftGrid", "Parent Instance was provided (this) (" + I2S(this) + ")", "(InvalidObjectException)")
                return this
            endif
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            if ModuloInteger((this) - (extras[2]) - 1, extras[2].quadCols) == 0 then
                //  Leftmost instance
                debug call Error("thistype:getLeftGrid", "Instance provided is already situated at the leftmost section", "(OutOfBoundsException)")
                return this
            endif
            return thistype((this) - 1)
        endif
        if ModuloInteger((this) - (extras[2]) + 1, extras[2].centerRows) == 0 then
            return thistype((this) - extras[2].centerRows + 1)
        endif
        return thistype((this) + 1)    
    endmethod
    
    method getTopGrid takes nothing returns thistype
        static if SAFE_MODE then
            if this.isHead then
                debug call Error("thistype:getTopGrid", "Parent Instance was provided (this) (" + I2S(this) + ")", "(InvalidObjectException)")
                return this
            endif
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            if ((this) - (extras[2]))/extras[2].quadRows <= 0 then
                //  Topmost instance
                debug call Error("thistype:getTopGrid", "Instance provided is already situated at the topmost section", "(OutOfBoundsException)")
                return this
            endif
            return thistype((this) - extras[2].quadRows)
        endif
        if R2I(I2R((extras[2]) + extras[2].size - (this))/I2R(extras[2].centerRows)) + 1 >= extras[2].centerCols then
            return this
        endif
        return thistype((this) - extras[2].centerRows)
    endmethod
    
    method getBottomGrid takes nothing returns thistype
        static if SAFE_MODE then
            if this.isHead then
                debug call Error("thistype:getBottomGrid", "Parent Instance was provided (this) (" + I2S(this) + ")", "(InvalidObjectException)")
                return this
            endif
        endif
        call this.getHeader()
        if extras[2].gridType == GRID_TYPE_RECT then
            if ((this) - (extras[2]))/extras[2].quadRows <= 0 then
                //  Topmost instance
                debug call Error("thistype:getBottomGrid", "Instance provided is already situated at the bottommost section", "(OutOfBoundsException)")
                return this
            endif
            return thistype((this) - extras[2].quadRows)
        endif
        if R2I(I2R((this) - (extras[2]))/I2R(extras[2].centerRows)) + 1 >= extras[2].centerCols then
            return this
        endif
        return thistype((this) + extras[2].centerRows)
    endmethod
    
    method getGridFromPoint takes real x, real y returns thistype
        if not this.isHead then
            debug call Error("thistype:getGridFromPoint", "Child Instance was provided (this) (" + I2S(this) + ")", "(InvalidObjectException)")
            return 0
        endif
        if this.gridType == GRID_TYPE_RECT then
            debug if x < this.minX or x > this.maxX then
                debug call Error("thistype:getGridFromPoint", "Point out of bounds of rect (this) (" + I2S(this) + ")", "(OutOfBoundsError)")
                debug return 0
            debug endif
            return thistype((this) + R2I((x - this.minX)/this.quadCellX) + 1 + R2I((this.maxY - y)/this.quadCellY)*this.quadRows)
        endif
        
        set vars[0] = Atan2(y - this.centerY, x - this.centerX)
        
        set vars[1] = (y - this.centerY)*(y - this.centerY) + (x - this.centerX)*(x - this.centerX)
        set vars[2] = 1./I2R(this.centerCols)
        set vars[3] = vars[2]
        set vars[4] = this.radius*this.radius
        
        if vars[0] < 0 then
            set vars[0] = vars[0] + 2*bj_PI
        endif
        loop
            exitwhen vars[3]*vars[3]*vars[4] > vars[1]
            set vars[3] = vars[3] + vars[2]
        endloop
        if vars[3] > 1 then
            debug call Error("thistype:getGridFromPoint", "Point out of bounds for circle (this) (" + I2S(this) + ")", "(OutOfBoundsError)")
            return 0
        endif
        set vars[3] = vars[3]*this.centerCols
        return thistype((this) + R2I(vars[0]/this.radStep) + 1 + (this.centerCols - R2I(vars[3]))*this.centerRows)
    endmethod
    
    method isPointInGrid takes real x, real y, thistype subGrid returns boolean
        if not this.isHead then
            debug call Error("thistype:isPointInGrid", "This method cannot be accessed by Child Instances.", "(InvalidObjectException)")
            return false
        endif
        return this.getGridFromPoint(x, y) == subGrid
    endmethod
    
    method isPointWithinGrid takes real x, real y returns boolean
        if this.isHead then
            debug call Error("thistype:isPointWithinGrid", "This method cannot be accessed by Parent Instances.", "(InvalidObjectException)")
            return false
        endif
        call this.getHeader()
        return extras[2].getGridFromPoint(x, y) == this
    endmethod
    
    static method createFromRect takes rect whichRect, integer cols, integer rows returns thistype
        local thistype object = 0
        static if SAFE_MODE then
            if whichRect == null then
                debug call Error("thistype::createFromRect", "parameter whichRect (in line 507) has no value", "(NullHandleException)")
                return object
            endif
        endif
        
        set object = thistype.create(cols*rows)
        //  In debug mode, this will point out that the grid you're making has invalid parameters
        debug if object == 0 then
            debug call Error("thistype::createFromRect", "The resulting number of grids requested is less than 0.", "(GridCountError)")
        debug endif
        
        if object != 0 then
            //  Ensure that invalid reads are not generated at runtime.
            set object.gridType     = GRID_TYPE_RECT
            set object.quad         = whichRect
            set object.quadRows     = rows
            set object.quadCols     = cols
            set object.quadCellX    = (GetRectMaxX(whichRect) - GetRectMinX(whichRect))/cols
            set object.quadCellY    = (GetRectMaxY(whichRect) - GetRectMinY(whichRect))/rows
        endif        
        return object
    endmethod
    static method createFromCircle takes real x, real y, real radius, integer cols, integer rows returns thistype
        local thistype object = 0
        static if SAFE_MODE then
            if radius <= 0. then
                debug call Error("thistype::createFromCircle", "parameter radius (in line 535) is invalid", "(ExtraneousInputException)")
                return object
            endif
        endif
        
        set object = thistype.create(cols*rows)
        //  In debug mode, this will point out that the grid you're making has invalid parameters
        debug if object == 0 then
            debug call Error("thistype::createFromCircle", "The resulting number of grids requested is less than 0.", "(GridCountError)")
        debug endif
        
        if object != 0 then
            //  Ensure that invalid reads are not generated at runtime.
            set object.gridType     = GRID_TYPE_CIRCLE
            set object.centerX      = x
            set object.centerY      = y
            set object.radius       = radius
            set object.centerRows   = rows
            set object.centerCols   = cols
            set object.radStep      = (2*bj_PI)/rows
        endif  
        return object
    endmethod
    
    private static method init takes nothing returns nothing
        set thistype.allocTable        = Table.create()
    endmethod
    
    implement Init
endstruct
endlibrary


Version 1.1


JASS:
library VirtualGrid requires /*

    ------------------------------
    */ optional WorldBounds     /*
    ------------------------------
        -> Nestharus
      
        link: https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
      
     -------------------
    |   VirtualGrid     |
    |     - MyPad       |
     -------------------
 
     ---------------------------------------
    |                                       |
    |   A simple snippet that makes         |
    |   collision detection easier          |
    |                                       |
    |   Practical use:                      |
    |       - Missile systems               |
     ---------------------------------------
   
     ----------------------------------------------------------------------
    |                                                                         
    |   API:
    |
    |   -------------------------------------------------------------------
    |
    |   struct VirtualGrid extends array
    |       static method get(real x, y) -> VirtualGrid
    |           - Retrieves a QuadTree instance based on the coordinates
    |           - In debug mode, this will apply bounds checking
    |             so that if out-of-bounds, it will return 0.
    |
    |       method isPointIn(real x, real y) -> boolean
    |           - Checks if a point lies in the specified VirtualGrid
    |
    |       method operator minX
    |       method operator minY
    |       method operator maxX
    |       method operator maxY
    |           - Returns the necessary values dynamically computed at runtime
    |
     ---------------------------------------------------------------------
*/

globals
    private constant integer PARTITIONS = 4
    //  Will behave as constant, but value is determined at runtime
    private integer INSTANCE_SIZE       = 0
endglobals

private module Init
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule

private struct World extends array
    readonly static real minX   = 0.
    readonly static real minY   = 0.
    readonly static real maxX   = 0.
    readonly static real maxY   = 0.
 
    private static method init takes nothing returns nothing
    static if LIBRARY_WorldBounds then
        set .minX   = WorldBounds.minX
        set .minY   = WorldBounds.minY     
        set .maxX   = WorldBounds.maxX
        set .maxY   = WorldBounds.maxY
    else
        local rect world = GetWorldBounds()
      
        set .minX   = GetRectMinX(world)
        set .minY   = GetRectMinY(world)
        set .maxX   = GetRectMaxX(world)
        set .maxY   = GetRectMaxY(world)
      
        call RemoveRect(world)
        set world   = null
    endif
    endmethod
 
    implement Init
endstruct

struct VirtualGrid extends array
    private static real cellSizeX   = 0.
    private static real cellSizeY   = 0.
 
    static method get takes real x, real y returns thistype
        local thistype result = 0
      
        debug if not (((x > World.maxX) or (x < World.minX)) or ((y > World.maxY) or (y < World.minY))) then
            set result = thistype((R2I((x - World.minX)/cellSizeX) + 1) + INSTANCE_SIZE*(R2I((World.maxY - y)/cellSizeY)))
        debug endif
        return result
    endmethod
    method isPointIn takes real x, real y returns boolean
        return thistype.get(x, y) == this
    endmethod
 
    method operator minX takes nothing returns real
        local integer i = ModuloInteger(integer(this) - 1, INSTANCE_SIZE)
        return .cellSizeX*i + World.minX
    endmethod
 
    method operator maxX takes nothing returns real
        local integer i = ModuloInteger(integer(this) - 1, INSTANCE_SIZE)
        return .cellSizeX*(i + 1) + World.minX
    endmethod
 
    method operator minY takes nothing returns real
        local integer i = ModuloInteger(integer(this)/INSTANCE_SIZE, INSTANCE_SIZE)
        return World.maxY - .cellSizeY*(i + 1)
    endmethod
 
    method operator maxY takes nothing returns real
        local integer i = ModuloInteger(integer(this)/INSTANCE_SIZE, INSTANCE_SIZE)
        return World.maxY - .cellSizeY*i
    endmethod

    private static method init takes nothing returns nothing
        local integer i = 1
      
        set .cellSizeX      = World.maxX - World.minX
        set .cellSizeY      = World.maxY - World.minY
        set INSTANCE_SIZE   = 1
      
        loop
            exitwhen i > PARTITIONS
          
            set .cellSizeX  = .cellSizeX/2
            set .cellSizeY  = .cellSizeY/2
            set INSTANCE_SIZE   = INSTANCE_SIZE*2
          
            set i = i + 1
        endloop
    endmethod
 
    implement Init
endstruct

endlibrary




v.1.2 - Overhauled VirtualGrid. Now comes with additional functionality and dynamic
Virtual Grid object creation.
v.1.1 - Changed QuadTree to VirtualGrid because of the slight difference in data structure and
purpose.
v.1.0 - Release​
 
Last edited:
GIven a point, a VirtualGrid instance can be obtained. From there, the VirtualGrid instance can be treated as an object, where data attachment, attribute attachment can be made possible.

Unlike [vJASS] - [Snippet] TileDefinition, this does not have a specific function for getting tiles, nor does this intend to have any other functionality other than the exposed VirtualGrid struct. In other words, it is a helper snippet, where it does nothing alone, but would be helpful for systems that detect collision.

EDIT:

The number of grids is equivalent to 4^(PARTITIONS). I might have to include that in the documentation later on.
 
A specific function for getting tile does exist very well, I believe:
JASS:
static method get takes real x, real y returns thistype
        local thistype result = 0
   
        debug if not (((x > World.maxX) or (x < World.minX)) or ((y > World.maxY) or (y < World.minY))) then
            set result = thistype((R2I((x - World.minX)/cellSizeX) + 1) + INSTANCE_SIZE*(R2I((World.maxY - y)/cellSizeY)))
        debug endif
        return result
    endmethod

How can it attach data, that TileDefinition would not let you? Here you would use "struct instance id" as key for data, in TileDefinition the "tilde id", which is both an integer. Maybe there is something I don't get.
 
Level 6
Joined
Jan 9, 2019
Messages
102
GIven a point, a VirtualGrid instance can be obtained. From there, the VirtualGrid instance can be treated as an object, where data attachment, attribute attachment can be made possible.
I know this clearer than crystals.

The number of grids is equivalent to 4^(PARTITIONS).
Oh yeah I missed this.

JASS:
    method operator minX takes nothing returns real
        local integer i = ModuloInteger(integer(this) - 1, INSTANCE_SIZE)
        return .cellSizeX*i + World.minX
    endmethod
Make this 1 line, I see 0 point in making the local except for laziness. Break 1 line into 2 by using comment instead if you want 2 lines. Of course this goes along with the others.

Still, dividing the whole map into 256 cells for what? TileDefinition makes 128x128 cells which have clearer uses. But random-sized cells for what really? For a custom spell eh? You know I'm just curious for an example here.
 
A specific function for getting tile does exist very well, I believe:
JASS:
static method get takes real x, real y returns thistype
        local thistype result = 0
  
        debug if not (((x > World.maxX) or (x < World.minX)) or ((y > World.maxY) or (y < World.minY))) then
            set result = thistype((R2I((x - World.minX)/cellSizeX) + 1) + INSTANCE_SIZE*(R2I((World.maxY - y)/cellSizeY)))
        debug endif
        return result
    endmethod

How can it attach data, that TileDefinition would not let you? Here you would use "struct instance id" as key for data, in TileDefinition the "tilde id", which is both an integer. Maybe there is something I don't get.

It does data attachment exactly how it has been described. The "struct instance id" is mostly for proper object handling, and can be easily casted via integer(id)

As for the specific function, I was referring to the snippet itself (this snippet).

method operator minX takes nothing returns real
local integer i = ModuloInteger(integer(this) - 1, INSTANCE_SIZE)
return .cellSizeX*i + World.minX
endmethod

Make this 1 line, I see 0 point in making the local except for laziness. Break 1 line into 2 by using comment instead if you want 2 lines. Of course this goes along with the others.

It was done to ensure a clearer flow of arithmetic logic, and that the methods perform as intended. This will be optimized in a later version.

Still, dividing the whole map into 256 cells for what? TileDefinition makes 128x128 cells which have clearer uses. But random-sized cells for what really? For a custom spell eh? You know I'm just curious for an example here.

I made this in order to code a part of this: (Optimize Missile Collision Detection)

Missile Rework

The code therein is a work in progress, and I hope this will suffice as an example.
 
A specific function for getting tile does exist very well, I believe:
JASS:
static method get takes real x, real y returns thistype
        local thistype result = 0
  
        debug if not (((x > World.maxX) or (x < World.minX)) or ((y > World.maxY) or (y < World.minY))) then
            set result = thistype((R2I((x - World.minX)/cellSizeX) + 1) + INSTANCE_SIZE*(R2I((World.maxY - y)/cellSizeY)))
        debug endif
        return result
    endmethod

How can it attach data, that TileDefinition would not let you? Here you would use "struct instance id" as key for data, in TileDefinition the "tilde id", which is both an integer. Maybe there is something I don't get.

It does data attachment exactly how it has been described. The "struct instance id" is mostly for proper object handling, and can be easily casted via integer(id)

As for the specific function, I was referring to the snippet itself (this snippet).

(I did not notice the GetTileId function in TileDefinition, so I thought TileDefinition had more to do with tile type and not a unique type id, thus leading to this)

method operator minX takes nothing returns real
local integer i = ModuloInteger(integer(this) - 1, INSTANCE_SIZE)
return .cellSizeX*i + World.minX
endmethod

Make this 1 line, I see 0 point in making the local except for laziness. Break 1 line into 2 by using comment instead if you want 2 lines. Of course this goes along with the others.

It was done to ensure a clearer flow of arithmetic logic, and that the methods perform as intended. This will be optimized in a later version.

Still, dividing the whole map into 256 cells for what? TileDefinition makes 128x128 cells which have clearer uses. But random-sized cells for what really? For a custom spell eh? You know I'm just curious for an example here.

It actually depends on the user, where the partitions can be changed. However, I do not recommend values above 15. (4^16 == 2^32 == xP)
I made this in order to code a part of this: (Optimize Missile Collision Detection)

Missile Rework

The code therein is a work in progress, and I hope this will suffice as an example.
 
It does data attachment exactly how it has been described. The "struct instance id" is mostly for proper object handling, and can be easily casted via
integer(id)


As for the specific function, I was referring to the snippet itself (this snippet).

(I did not notice the GetTileId function in TileDefinition, so I thought TileDefinition had more to do with tile type and not a unique type id, thus leading to this)
Why do you always write so bloated, in other words, no difference.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I see no point in being able to just partition the entire map. I think it should work for any rect and partition dimension.

You'd save the partition map to the rect handle id or the "World handle" in your base case you've provided.

So something like create(someRect, 14, 8) would partition a rect into 14 columns and 8 rows. And for the use case you have right now, you could allocate a static one for the entire world. You could of course have another creation method for the fixed size 16x16 or what you were refering.

This could then be used for things like distributing units evenly in a rect, something i very much would like to be able to do.
 
Last edited:
I see no point in being able to just partition the entire map. I think it should work for any rect and partition dimension.

You'd save the partition map to the rect handle id or the "World handle" in your base case you've provided.

So something like create(someRect, 14, 8) would partition a rect into 14 columns and 8 rows. And for the use case you have right now, you could allocate a static one for the entire world. You could of course have another creation method for the fixed size 16x16 or what you were refering.

This could then be used for things like distributing units evenly in a rect, something i very much would like to be able to do.

Those are actually nice suggestions. I didn't think of approaching the snippet in this way, but I'll see what I can come up with. For now, it might take a while before I can get around implementing those. :thumbs_up:

Good to see a clearer use. Though I'd prefer partitioning a circle instead for that matter, making web-like cells. It's not that hard, just use angle/radius instead of x/y and you got your web, nice and easy.

I will try to implement this as well. (The mainly tricky ones are the ones that have to do with getting the minimum and maximum x and y of a particular circular grid, as a result of standardization.)

Why do you always write so bloated, in other words, no difference.

I learn the purpose of a snippet best when I write my version of it (which ends up being quite similar to the version it was inspired by, on occasion), but that is soon to change with the next version.

 
Last edited:
Finally got around to updating this:

Changelog:

  • Method names were changed, along with their attributes.
    • static method get changed to method getGridFromPoint
    • method isPointIn changed to method isPointInGrid.
    • method isPointIn also has a new child version, method isPointWithinGrid.
  • Removed WorldBounds as an optional requirement, instead requiring Table.
  • Introduced six new methods:
    • method getRightGrid
    • method getLeftGrid
    • method getTopGrid
    • method getBottomGrid
    • static method createFromRect
    • static method createFromCircle
  • Added Credits section in the documentation.
  • Documentation has been updated to reflect the new version.
 
Level 6
Joined
Jan 9, 2019
Messages
102
I'm no math expert, but are the webs still grids? Afaik grids are rectangles.

But anyway, I had 0 intention of suggesting the web stuff, seriously. It was more of a response to what Pinzu said. So you should just remove all the circular stuffs eh? Unless you can say its various applications more than what Pinzu said.

On a similar matter, one single cone is more useful. Have serious application in spells, and it should be implemented similar to rects, minAngle, maxAngle, minRadius, maxRadius. So the cone is more flexible. But this must belong on another lib if you want to make one.
---

For grids, getRightGrid n friends should just be merged into one method that gets another grid by offset. And if it offsets to a grid outside its bounds, it returns a 'negative grid'. Another way to handle out-of-bound grids is to put it back from the opposite end of it, so if a grid is (4x3) and has top-left cell as (0,0), a (-1,-2) cell is the same as (3,1).

Haven't read the others, waiting the lib become more precise before I can get into evaluating all of it.
 
I'm no math expert, but are the webs still grids? Afaik grids are rectangles.

In this library, it should be understood that webs are still grids, just of a different grid type.

But anyway, I had 0 intention of suggesting the web stuff, seriously. It was more of a response to what Pinzu said.

I know, and I thought it would be a good idea to include it, as a challenge. However, I'll clear that up in the credits.

On a similar matter, one single cone is more useful. Have serious application in spells, and it should be implemented similar to rects, minAngle, maxAngle, minRadius, maxRadius. So the cone is more flexible. But this must belong on another lib if you want to make one.
---

Since the cone is already encapsulated within the VirtualGrid struct via circle webs/grids, for GRID_TYPE_CIRCLE parent instances, the least I can do is provide a way to move the associated circle object, and add said methods.

For grids, getRightGrid n friends should just be merged into one method that gets another grid by offset. And if it offsets to a grid outside its bounds, it returns a 'negative grid'. Another way to handle out-of-bound grids is to put it back from the opposite end of it, so if a grid is (4x3) and has top-left cell as (0,0), a (-1,-2) cell is the same as (3,1).

Haven't read the others, waiting the lib become more precise before I can get into evaluating all of it.

Hmmm, I may expose such a method, but I'll keep those methods above, even if just as wrappers to such a new method.
 
Level 6
Joined
Jan 9, 2019
Messages
102
I thought it would be a good idea to include it, as a challenge.
This "challenge" is not supposed to be here, not at all. You can challenge yourself enough by making another lib that handle cones, very thoroughly at it.

Keeping the struct/fields/methods as simple as possible is a way to make the struct easier to grasp, and remember. And I'm sure getting adjacent cells won't see too much use, except for looping through all cells, which something like getNextCell would fit better. Then the struct can have both looped-up offset and out-of-bound offset handlers at the same time, like getClosedOffset and getOpenOffset. Top/right/down/left is simply bizarre in modern mathematics world lol. I mean, getRightCell(getRightCell(getTopCell())) is genius and getRightCell() is useful, but getOffsettedCell(3, 1) is quite stupid. Hahah.
 
This "challenge" is not supposed to be here, not at all. You can challenge yourself enough by making another lib that handle cones, very thoroughly at it.

Correct. That is something I had intended to place upon myself, inasmuch as the apparent simplicity of the snippet demanded further development.

Keeping the struct/fields/methods as simple as possible is a way to make the struct easier to grasp, and remember. And I'm sure getting adjacent cells won't see too much use, except for looping through all cells, which something like getNextCell would fit better. Then the struct can have both looped-up offset and out-of-bound offset handlers at the same time, like getClosedOffset and getOpenOffset.

That was the original guiding principle of this snippet, at its' initial stages. Yes, the usage of adjacent cells wouldn't be quite useful, except in very niche cases, where an enum function in a rect or circle can be made.

That said, a direct way to get the next Child Grid is to simply increment the id of the instance by 1, only returning back to the next id of its' parent if it's id is a maximum. The previous grid can easily be obtained in a similar manner.

Circular Virtual Grids may be taken out from the library in the next update. They will be placed in a separate library, which might include cone logic as well.

but
getOffsettedCell(3, 1)
is quite stupid.

:xxd: I would rather think of it as .getGridByOffset.[/QUOTE]
 
Last edited:
That is incorrect. The grid is the array of cells, a grid contains cells, the lib handles grids and their cells. Therefore it's getCellByOffset. It's even better named as getCell, can be used by both the grid or its cells.

Hmm, very well. Being so stuck-up with the name blinds one to what can be clear to see for others.

 
Last edited:
Top