z_mirror/flib
git clone https://git.raiguard.me/z_mirror/flib
a9f2be7
trunk
1local flib_position =require ( "__flib__.position" ) 2 3--- Utilities for manipulating bounding boxes. All functions support both the shorthand and explicit syntaxes for boxes 4--- and positions, and will preserve the syntax that was passed in. Boxes are considered immutable; all functions will 5--- return new boxes. 6--- ```lua 7--- local flib_bounding_box = require("__flib__.bounding-box") 8--- ``` 9--- @class flib_bounding_box 10local flib_bounding_box ={} 11 12--- Return a new box expanded to the nearest tile edges. 13--- @param box BoundingBox 14--- @return BoundingBox 15function flib_bounding_box . ceil ( box ) 16if box . left_top then 17return { 18left_top ={ x =math . floor ( box . left_top . x ), y =math . floor ( box . left_top . y ) } , 19right_bottom ={ x =math . ceil ( box . right_bottom . x ), y =math . ceil ( box . right_bottom . y ) } , 20} 21else 22return { 23{ math . floor ( box [ 1 ][ 1 ]), math . floor ( box [ 1 ][ 2 ]) } , 24{ math . ceil ( box [ 2 ][ 1 ]), math . ceil ( box [ 2 ][ 2 ]) } , 25} 26end 27end 28 29--- Calculate the centerpoint of the box. 30--- @param box BoundingBox 31--- @return MapPosition 32function flib_bounding_box . center ( box ) 33if box . left_top then 34return { 35x =( box . left_top . x + box . right_bottom . x ) / 2 , 36y =( box . left_top . y + box . right_bottom . y ) / 2 , 37} 38else 39return { 40( box [ 1 ][ 1 ] + box [ 2 ][ 1 ]) / 2 , 41( box [ 1 ][ 2 ] + box [ 2 ][ 2 ]) / 2 , 42} 43end 44end 45 46--- Check if the first box contains the second box. 47--- @param box1 BoundingBox 48--- @param box2 BoundingBox 49--- @return boolean 50function flib_bounding_box . contains_box ( box1 , box2 ) 51local box1 =flib_bounding_box . ensure_explicit ( box1 ) 52local box2 =flib_bounding_box . ensure_explicit ( box2 ) 53 54return box1 . left_top . x <= box2 . left_top . x 55and box1 . left_top . y <= box2 . left_top . y 56and box1 . right_bottom . x >= box2 . right_bottom . x 57and box1 . right_bottom . y >= box2 . right_bottom . y 58end 59 60--- Check if the given box contains the given position. 61--- @param box BoundingBox 62--- @param pos MapPosition 63--- @return boolean 64function flib_bounding_box . contains_position ( box , pos ) 65local box =flib_bounding_box . ensure_explicit ( box ) 66local pos =flib_position . ensure_explicit ( pos ) 67return box . left_top . x <= pos . x 68and box . left_top . y <= pos . y 69and box . right_bottom . x >= pos . x 70and box . right_bottom . y >= pos . y 71end 72 73--- Return the box in explicit form. 74--- @param box BoundingBox 75--- @return BoundingBox 76function flib_bounding_box . ensure_explicit ( box ) 77return { 78left_top =flib_position . ensure_explicit ( box . left_top or box [ 1 ]), 79right_bottom =flib_position . ensure_explicit ( box . right_bottom or box [ 2 ]), 80} 81end 82 83--- Return the box in shorthand form. 84--- @param box BoundingBox 85--- @return BoundingBox 86function flib_bounding_box . ensure_short ( box ) 87return { 88flib_position . ensure_short ( box . left_top or box [ 1 ]), 89flib_position . ensure_short ( box . right_bottom or box [ 2 ]), 90} 91end 92 93--- Return a new box with initial dimensions box1, expanded to contain box2. 94--- @param box1 BoundingBox 95--- @param box2 BoundingBox 96--- @return BoundingBox 97function flib_bounding_box . expand_to_contain_box ( box1 , box2 ) 98local box2 =flib_bounding_box . ensure_explicit ( box2 ) 99 100if box1 . left_top then 101return { 102left_top ={ 103x =math . min ( box1 . left_top . x , box2 . left_top . x ), 104y =math . min ( box1 . left_top . y , box2 . left_top . y ), 105} , 106right_bottom ={ 107x =math . max ( box1 . right_bottom . x , box2 . right_bottom . x ), 108y =math . max ( box1 . right_bottom . y , box2 . right_bottom . y ), 109} , 110} 111else 112return { 113{ 114math . min ( box1 [ 1 ][ 1 ], box2 . left_top . x ), 115math . min ( box1 [ 1 ][ 2 ], box2 . left_top . y ), 116} , 117{ 118math . max ( box1 [ 2 ][ 1 ], box2 . right_bottom . x ), 119math . max ( box1 [ 2 ][ 2 ], box2 . right_bottom . y ), 120} , 121} 122end 123end 124 125--- Return a new box expanded to contain the given position. 126--- @param box BoundingBox 127--- @param pos MapPosition 128--- @return BoundingBox 129function flib_bounding_box . expand_to_contain_position ( box , pos ) 130pos =flib_position . ensure_explicit ( pos ) 131 132if box . left_top then 133return { 134left_top ={ x =math . min ( box . left_top . x , pos . x ), y =math . min ( box . left_top . y , pos . y ) } , 135right_bottom ={ x =math . max ( box . right_bottom . x , pos . x ), y =math . max ( box . right_bottom . y , pos . y ) } , 136} 137else 138return { 139{ math . min ( box [ 1 ][ 1 ], pos . x ), math . min ( box [ 1 ][ 2 ], pos . y ) } , 140{ math . max ( box [ 2 ][ 1 ], pos . x ), math . max ( box [ 2 ][ 2 ], pos . y ) } , 141} 142end 143end 144 145--- Return a new box shrunk to the nearest tile edges. 146--- @param box BoundingBox 147--- @return BoundingBox 148function flib_bounding_box . floor ( box ) 149if box . left_top then 150return { 151left_top ={ x =math . ceil ( box . left_top . x ), y =math . ceil ( box . left_top . y ) } , 152right_bottom ={ x =math . floor ( box . right_bottom . x ), y =math . floor ( box . right_bottom . y ) } , 153} 154else 155return { 156{ math . ceil ( box [ 1 ][ 1 ]), math . ceil ( box [ 1 ][ 2 ]) } , 157{ math . floor ( box [ 2 ][ 1 ]), math . floor ( box [ 2 ][ 2 ]) } , 158} 159end 160end 161 162--- Create a new box from a centerpoint and dimensions. 163--- @param center MapPosition 164--- @param width number 165--- @param height number 166--- @return BoundingBox 167function flib_bounding_box . from_dimensions ( center , width , height ) 168if center . x then 169return { 170left_top ={ x =center . x - width / 2 , y =center . y - height / 2 } , 171right_bottom ={ x =center . x + width / 2 , y =center . y + height / 2 } , 172} 173else 174return { 175{ center [ 1 ] - width / 2 , center [ 2 ] - height / 2 } , 176{ center [ 1 ] + width / 2 , center [ 2 ] + height / 2 } , 177} 178end 179end 180 181--- Create a 1x1 box from the given position, optionally snapped to the containing tile edges. 182--- @param pos MapPosition 183--- @param snap boolean? 184--- @return BoundingBox 185function flib_bounding_box . from_position ( pos , snap ) 186if snap then 187pos =flib_position . floor ( pos ) 188else 189pos =flib_position . sub ( pos , { 0.5 , 0.5 } ) 190end 191local x =pos . x or pos [ 1 ] 192local y =pos . y or pos [ 2 ] 193if pos . x then 194return { 195left_top ={ x =x , y =y } , 196right_bottom ={ x =x + 1 , y =y + 1 } , 197} 198else 199return { 200{ x , y } , 201{ x + 1 , y + 1 } , 202} 203end 204end 205 206--- Calculate the height of the box. 207--- @param box BoundingBox 208--- @return number 209function flib_bounding_box . height ( box ) 210if box . left_top then 211return box . right_bottom . y - box . left_top . y 212else 213return box [ 2 ][ 2 ] - box [ 1 ][ 2 ] 214end 215end 216 217--- Check if the first box intersects (overlaps) the second box. 218--- @param box1 BoundingBox 219--- @param box2 BoundingBox 220--- @return boolean 221function flib_bounding_box . intersects_box ( box1 , box2 ) 222local box1 =flib_bounding_box . ensure_explicit ( box1 ) 223local box2 =flib_bounding_box . ensure_explicit ( box2 ) 224return box1 . left_top . x < box2 . right_bottom . x 225and box2 . left_top . x < box1 . right_bottom . x 226and box1 . left_top . y < box2 . right_bottom . y 227and box2 . left_top . y < box1 . right_bottom . y 228end 229 230--- Return a new box with the same dimensions, moved by the given delta. 231--- @param box BoundingBox 232--- @param delta MapPosition 233--- @return BoundingBox 234function flib_bounding_box . move ( box , delta ) 235local dx =delta . x or delta [ 1 ] 236local dy =delta . y or delta [ 2 ] 237if box . left_top then 238return { 239left_top ={ x =box . left_top . x + dx , y =box . left_top . y + dy } , 240right_bottom ={ x =box . right_bottom . x + dx , y =box . right_bottom . y + dy } , 241} 242else 243return { 244{ box [ 1 ][ 1 ] + dx , box [ 1 ][ 2 ] + dy } , 245{ box [ 2 ][ 1 ] + dx , box [ 2 ][ 2 ] + dy } , 246} 247end 248end 249 250--- Return a new box with the same dimensions centered on the given position. 251--- @param box BoundingBox 252--- @param pos MapPosition 253--- @return BoundingBox 254function flib_bounding_box . recenter_on ( box , pos ) 255local height =flib_bounding_box . height ( box ) 256local width =flib_bounding_box . width ( box ) 257 258local pos_x =pos . x or pos [ 1 ] 259local pos_y =pos . y or pos [ 2 ] 260 261if box . left_top then 262return { 263left_top ={ x =pos_x - ( width / 2 ), y =pos_y - ( height / 2 ) } , 264right_bottom ={ x =pos_x + ( width / 2 ), y =pos_y + ( height / 2 ) } , 265} 266else 267return { 268{ pos_x - ( width / 2 ), pos_y - ( height / 2 ) } , 269{ pos_x + ( width / 2 ), pos_y + ( height / 2 ) } , 270} 271end 272end 273 274--- Return a new box grown or shrunk by the given delta. A positive delta will grow the box, a negative delta will 275--- shrink it. 276--- @param box BoundingBox 277--- @param delta number 278--- @return BoundingBox 279function flib_bounding_box . resize ( box , delta ) 280if box . left_top then 281return { 282left_top ={ x =box . left_top . x - delta , y =box . left_top . y - delta } , 283right_bottom ={ x =box . right_bottom . x + delta , y =box . right_bottom . y + delta } , 284} 285else 286return { 287{ box [ 1 ][ 1 ] - delta , box [ 1 ][ 2 ] - delta } , 288{ box [ 2 ][ 1 ] + delta , box [ 2 ][ 2 ] + delta } , 289} 290end 291end 292 293--- Return a new box rotated 90 degrees about its center. 294--- @param box BoundingBox 295--- @return BoundingBox 296function flib_bounding_box . rotate ( box ) 297local center =flib_bounding_box . center ( box ) 298local radius_x =flib_bounding_box . width ( box ) / 2 299local radius_y =flib_bounding_box . height ( box ) / 2 300 301if box . left_top then 302return { 303left_top ={ x =center . x - radius_y , y =center . y - radius_x } , 304right_bottom ={ x =center . x + radius_y , y =center . y + radius_x } , 305} 306else 307return { 308{ center . x - radius_y , center . y - radius_x } , 309{ center . x + radius_y , center . y + radius_x } , 310} 311end 312end 313 314--- Return a new box expanded to create a square. 315--- @param box BoundingBox 316--- @return BoundingBox 317function flib_bounding_box . square ( box ) 318local radius =math . max ( flib_bounding_box . width ( box ), flib_bounding_box . height ( box )) 319return flib_bounding_box . from_dimensions ( flib_bounding_box . center ( box ), radius , radius ) 320end 321 322--- Calculate the width of the box. 323--- @param box BoundingBox 324--- @return number 325function flib_bounding_box . width ( box ) 326if box . left_top then 327return box . right_bottom . x - box . left_top . x 328else 329return box [ 2 ][ 1 ] - box [ 1 ][ 1 ] 330end 331end 332 333return flib_bounding_box