ianmackenzie / elm-geometry-prerelease / BoundingBox2d

A BoundingBox2d is a rectangular box in 2D defined by its minimum and maximum X and Y values. It is possible to generate bounding boxes for most geometric objects; for example, Triangle2d.boundingBox takes a Triangle2d and returns a BoundingBox2d that contains that triangle. There are several use cases where it is more efficient to deal with the bounding box of an object than the object itself, such as:


type alias BoundingBox2d =
Geometry.Types.BoundingBox2d

Constructors

fromExtrema : { minX : Basics.Float, maxX : Basics.Float, minY : Basics.Float, maxY : Basics.Float } -> BoundingBox2d

Construct a bounding box from its minimum and maximum X and Y values:

exampleBox =
    BoundingBox2d.fromExtrema
        { minX = 3
        , maxX = 8
        , minY = 2
        , maxY = 6
        }

If the minimum and maximum values are provided in the wrong order (for example if minX > maxX), then they will be swapped so that the resulting bounding box is valid.

singleton : Point2d -> BoundingBox2d

Construct a zero-width bounding box containing a single point.

point =
    Point2d.fromCoordinates ( 2, 3 )

BoundingBox2d.singleton point
--> BoundingBox2d.fromExtrema
-->     { minX = 2
-->     , maxX = 2
-->     , minY = 3
-->     , maxY = 3
-->     }

from : Point2d -> Point2d -> BoundingBox2d

Construct a bounding box with the two given points as two of its corners. The points can be given in any order and don't have to represent the 'primary' diagonal of the bounding box.

firstPoint =
    Point2d.fromCoordinates ( 2, 3 )

secondPoint =
    Point2d.fromCoordinates ( -1, 5 )

BoundingBox2d.from firstPoint secondPoint
--> BoundingBox2d.fromExtrema
-->     { minX = -1
-->     , maxX = 2
-->     , minY = 3
-->     , maxY = 5
-->     }

hull : BoundingBox2d -> BoundingBox2d -> BoundingBox2d

Build a bounding box that contains both given bounding boxes.

firstBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 4
        , minY = 2
        , maxY = 3
        }

secondBox =
    BoundingBox2d.fromExtrema
        { minX = -2
        , maxX = 2
        , minY = 4
        , maxY = 5
        }

BoundingBox2d.hull firstBox secondBox
--> BoundingBox2d.fromExtrema
-->     { minX = -2
-->     , maxX = 4
-->     , minY = 2
-->     , maxY = 5
-->     }

intersection : BoundingBox2d -> BoundingBox2d -> Maybe BoundingBox2d

Attempt to build a bounding box that contains all points common to both given bounding boxes. If the given boxes do not intersect, returns Nothing.

firstBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 4
        , minY = 2
        , maxY = 3
        }

secondBox =
    BoundingBox2d.fromExtrema
        { minX = 2
        , maxX = 5
        , minY = 1
        , maxY = 4
        }

thirdBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 4
        , minY = 4
        , maxY = 5
        }

BoundingBox2d.intersection firstBox secondBox
--> Just
-->     (BoundingBox2d.fromExtrema
-->         { minX = 2
-->         , maxX = 4
-->         , minY = 2
-->         , maxY = 3
-->         }
-->     )

BoundingBox2d.intersection firstBox thirdBox
--> Nothing

If two boxes just touch along an edge or at a corner, they are still considered to have an intersection, even though that intersection will have zero area (at least one of its dimensions will be zero):

firstBox =
    BoundingBox2d.fromExtrema
        { minX = 0
        , maxX = 1
        , minY = 0
        , maxY = 2
        }

secondBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 2
        , minY = 1
        , maxY = 3
        }

BoundingBox2d.intersection firstBox secondBox
--> Just
-->     (BoundingBox2d.fromExtrema
-->         { minX = 1
-->         , maxX = 1
-->         , minY = 1
-->         , maxY = 2
-->         }
-->     )

aggregate : List BoundingBox2d -> Maybe BoundingBox2d

Construct a bounding box containing all bounding boxes in the given list. If the list is empty, returns Nothing.

singletonBox =
    BoundingBox2d.singleton
        (Point2d.fromCoordinates ( 1, 3 ))

BoundingBox2d.aggregate [ exampleBox, singletonBox ]
--> Just
-->     (BoundingBox2d.fromExtrema
-->         { minX = 1,
-->         , maxX = 8
-->         , minY = 2
-->         , maxY = 6
-->         }
-->     )

BoundingBox2d.aggregate [ exampleBox ]
--> Just exampleBox

BoundingBox2d.aggregate []
--> Nothing

If you have exactly two bounding boxes, you can use BoundingBox2d.hull instead (which returns a BoundingBox2d instead of a Maybe BoundingBox2d).

containingPoints : List Point2d -> Maybe BoundingBox2d

Construct a bounding box containing all points in the given list. If the list is empty, returns Nothing.

BoundingBox2d.containingPoints
    [ Point2d.fromCoordinates ( 2, 3 )
    , Point2d.fromCoordinates ( -1, 5 )
    , Point2d.fromCoordinates ( 6, 4 )
    ]
--> Just <|
-->     BoundingBox2d.fromExtrema
-->         { minX = -1
-->         , maxX = 6
-->         , minY = 3
-->         , maxY = 5
-->         }

BoundingBox2d.containingPoints []
--> Nothing

Properties

extrema : BoundingBox2d -> { minX : Basics.Float, maxX : Basics.Float, minY : Basics.Float, maxY : Basics.Float }

Get the minimum and maximum X and Y values of a bounding box in a single record.

BoundingBox2d.extrema exampleBox
--> { minX = 3
--> , maxX = 8
--> , minY = 2
--> , maxY = 6
--> }

Can be useful when combined with record destructuring, for example

{ minX, maxX, minY, maxY } =
    BoundingBox2d.extrema exampleBox


--> minX = 3
--> maxX = 8
--> minY = 2
--> maxY = 6

minX : BoundingBox2d -> Basics.Float

Get the minimum X value of a bounding box.

BoundingBox2d.minX exampleBox
--> 3

maxX : BoundingBox2d -> Basics.Float

Get the maximum X value of a bounding box.

BoundingBox2d.maxX exampleBox
--> 8

minY : BoundingBox2d -> Basics.Float

Get the minimum Y value of a bounding box.

BoundingBox2d.minY exampleBox
--> 2

maxY : BoundingBox2d -> Basics.Float

Get the maximum Y value of a bounding box.

BoundingBox2d.maxY exampleBox
--> 6

dimensions : BoundingBox2d -> ( Basics.Float, Basics.Float )

Get the X and Y dimensions (width and height) of a bounding box.

( width, height ) =
    BoundingBox2d.dimensions exampleBox


--> width = 5
--> height = 4

midX : BoundingBox2d -> Basics.Float

Get the median X value of a bounding box.

BoundingBox2d.midX exampleBox
--> 5.5

midY : BoundingBox2d -> Basics.Float

Get the median Y value of a bounding box.

BoundingBox2d.midY exampleBox
--> 4

centerPoint : BoundingBox2d -> Point2d

Get the point at the center of a bounding box.

BoundingBox2d.centerPoint exampleBox
--> Point2d.fromCoordinates ( 5.5, 4 )

centroid : BoundingBox2d -> Point2d

DEPRECATED: Alias for centerPoint, will be removed in the next major release. Use centerPoint instead.

Queries

contains : Point2d -> BoundingBox2d -> Basics.Bool

Check if a bounding box contains a particular point.

point =
    Point2d.fromCoordinates ( 4, 3 )

BoundingBox2d.contains point exampleBox
--> True

BoundingBox2d.contains Point2d.origin exampleBox
--> False

isContainedIn : BoundingBox2d -> BoundingBox2d -> Basics.Bool

Test if the second given bounding box is fully contained within the first (is a subset of it).

outerBox =
    BoundingBox2d.fromExtrema
        { minX = 0
        , maxX = 10
        , minY = 0
        , maxY = 10
        }

innerBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 5
        , minY = 3
        , maxY = 9
        }

overlappingBox =
    BoundingBox2d.fromExtrema
        { minX = 1
        , maxX = 5
        , minY = 3
        , maxY = 12
        }

BoundingBox2d.isContainedIn outerBox innerBox
--> True

BoundingBox2d.isContainedIn outerBox overlappingBox
--> False

intersects : BoundingBox2d -> BoundingBox2d -> Basics.Bool

Test if two boxes touch or overlap at all (have any points in common);

BoundingBox2d.intersects firstBox secondBox

is equivalent to

BoundingBox2d.intersection firstBox secondBox
    /= Nothing

but is more efficient.

firstBox =
    BoundingBox2d.fromExtrema
        { minX = 0
        , maxX = 3
        , minY = 0
        , maxY = 2
        }

secondBox =
    BoundingBox2d.fromExtrema
        { minX = 0
        , maxX = 3
        , minY = 1
        , maxY = 4
        }

thirdBox =
    BoundingBox2d.fromExtrema
        { minX = 0
        , maxX = 3
        , minY = 4
        , maxY = 5
        }

BoundingBox2d.intersects firstBox secondBox
--> True

BoundingBox2d.intersects firstBox thirdBox
--> False

overlappingBy : Basics.Order -> Basics.Float -> BoundingBox2d -> BoundingBox2d -> Basics.Bool

Check if one box overlaps another by less than, greater than or equal to a given amount. For example, you could implement a tolerant collision check (one that only returns true if the boxes overlap by at least some small finite amount, and ignores boxes that just barely touch each other) as

boxesCollide box1 box2 =
    BoundingBox2d.overlappingBy GT 0.001 box1 box2

This can be read as "box1 and box2 are overlapping by greater than 0.001 units". (The Order type and its three values LT, GT and EQ are defined in Elm's Basics module so are available by default in any Elm program.)

Overlap is defined as the minimum distance one box would have to move so that it did not touch the other, and is always positive for any two overlapping boxes.

Boxes that just touch are considered to have an overlap of zero, which is distinct from 'no overlap'. Boxes that do not touch or overlap at all are considered to have an overlap which is less than zero but not comparable to any negative number.

Less than

Greater than

Equal to

Checking whether two boxes overlap by exactly a given amount is pretty weird and vulnerable to floating-point roundoff, but is defined as follows:

separatedBy : Basics.Order -> Basics.Float -> BoundingBox2d -> BoundingBox2d -> Basics.Bool

Check if one box is separated from another by less than, greater than or equal to a given amount. For example, to perform clash detection between some objects, you could use separatedBy on those objects' bounding boxes as a quick check to see if the objects had a gap of at least 1 cm between them:

safelySeparated box1 box2 =
    BoundingBox2d.separatedBy GT 0.01 box1 box2

This can be read as "box1 and box2 are separated by greater than 0.01 units". (The Order type and its three values LT, GT and EQ are defined in Elm's Basics module so are available by default in any Elm program.)

Separation is defined as the minimum distance one box would have to move so that it touched the other, and is always positive for any two boxes that do not touch.

Boxes that just touch are considered to have a separation of zero, which is distinct from 'no separation'. 'No separation' (overlap) is considered to be less than zero but not comparable to any negative number.

Less than

Greater than

Equal to

Checking whether two boxes are separated by exactly a given amount is pretty weird and vulnerable to floating-point roundoff, but is defined as follows:

Transformations

scaleAbout : Point2d -> Basics.Float -> BoundingBox2d -> BoundingBox2d

Scale a bounding box about a given point by a given scale.

point =
    Point2d.fromCoordinates ( 4, 4 )

BoundingBox2d.scaleAbout point 2 exampleBox
--> BoundingBox2d.fromExtrema
-->     { minX = 2
-->     , maxX = 12
-->     , minY = 0
-->     , maxY = 8
-->     }

translateBy : Vector2d -> BoundingBox2d -> BoundingBox2d

Translate a bounding box by a given displacement.

displacement =
    Vector2d.fromComponents ( 2, -3 )

BoundingBox2d.translateBy displacement exampleBox
--> BoundingBox2d.fromExtrema
-->     { minX = 5
-->     , maxX = 10
-->     , minY = -1
-->     , maxY = 3
-->     }

translateIn : Direction2d -> Basics.Float -> BoundingBox2d -> BoundingBox2d

Translate a bounding box in a given direction by a given distance;

BoundingBox2d.translateIn direction distance

is equivalent to

BoundingBox2d.translateBy
    (Vector2d.withLength distance direction)