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:
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.
BoundingBox2d.from
(Point2d.meters 2 3)
(Point2d.meters -1 5)
--> BoundingBox2d.fromExtrema
--> { minX = Length.meters -1
--> , maxX = Length.meters 2
--> , minY = Length.meters 3
--> , maxY = Length.meters 5
--> }Construct a bounding box from its minimum and maximum X and Y values:
exampleBox =
BoundingBox2d.fromExtrema
{ minX = Length.meters 3
, maxX = Length.meters 8
, minY = Length.meters 2
, maxY = Length.meters 6
}If the minimum and maximum values are provided in the wrong order (for example
if minX is greater than maxX), then they will be swapped so that the
resulting bounding box is valid.
Construct a bounding box given its overall dimensions (width and height) and center point.
Construct a zero-width bounding box containing a single point.
Construct a bounding box from separate X and Y intervals.
Construct a bounding box from a pair of X and Y intervals.
Build a bounding box that contains both given bounding boxes.
firstBox =
BoundingBox2d.from
(Point2d.meters 1 2)
(Point2d.meters 4 3)
secondBox =
BoundingBox2d.from
(Point2d.meters -2 4)
(Point2d.meters 2 5)
BoundingBox2d.union firstBox secondBox
--> BoundingBox2d.from
--> (Point2d.meters -2 2)
--> (Point2d.meters 4 5)(Note that this is not strictly speaking a 'union' in the precise mathematical sense.)
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.from
(Point2d.meters 1 2)
(Point2d.meters 4 3)
secondBox =
BoundingBox2d.from
(Point2d.meters 2 1)
(Point2d.meters 5 4)
BoundingBox2d.intersection firstBox secondBox
--> Just <|
--> BoundingBox2d.from
--> (Point2d.meters 2 2)
--> (Point2d.meters 4 3)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.from
(Point2d.meters 0 0)
(Point2d.meters 1 2)
secondBox =
BoundingBox2d.from
(Point2d.meters 1 1)
(Point2d.meters 2 3)
BoundingBox2d.intersection firstBox secondBox
--> Just <|
--> BoundingBox2d.from
--> (Point2d.meters 1 1)
--> (Point2d.meters 1 2)Functions for building bounding boxes containing several points.
Find the bounding box containing one or more input points:
BoundingBox2d.hull p1 [ p2, p3, p4 ]Often ends up being used within a case expression:
case points of
[] ->
-- some default behavior
first :: rest ->
let
boundingBox =
BoundingBox2d.hull first rest
in
-- normal behavior using 'boundingBox'See also hullN.
Build a bounding box that contains all three of the given points;
BoundingBox2d.hull3 p1 p2 p3is equivalent to
BoundingBox2d.hull p1 [ p2, p3 ]but is more efficient.
Construct a bounding box containing all N points in the given list. If the
list is empty, returns Nothing. If you know you have at least one point, you
can use hull instead.
Like hull, but lets you work on any kind of item as long as a
point can be extracted from it. For example, if you had
type alias Vertex =
{ id : Int
, position : Point2d Meters WorldCoordinates
, color : Color
}then you could get the bounding box around several vertices using
BoundingBox2d.hullOf .position
firstVertex
[ secondVertex
, thirdVertex
, fourthVertex
]Functions for combining several bounding boxes into one bounding box that contains all of the input boxes.
Find the bounding box containing one or more input boxes; works much like
hull. See also aggregateN.
Build a bounding box that contains all three of the given bounding boxes;
BoundingBox2d.aggregate3 b1 b2 b3is equivalent to
BoundingBox2d.aggregate b1 [ b2, b3 ]but is more efficient.
Construct a bounding box containing all bounding boxes in the given list. If
the list is empty, returns Nothing. If you know you have at least one bounding
box, you can use aggregate instead.
Like aggregate, but lets you work on any kind of item as
long as a bounding box can be extracted from it. For example, to get the
bounding box around four triangles:
BoundingBox2d.aggregateOf Triangle2d.boundingBox
firstTriangle
[ secondTriangle
, thirdTriangle
, fourthTriangle
]Combination of aggregateOf and aggregateN.
Get the minimum and maximum X and Y values of a bounding box in a single record.
BoundingBox2d.extrema exampleBox
--> { minX = Length.meters 3
--> , maxX = Length.meters 8
--> , minY = Length.meters 2
--> , maxY = Length.meters 6
--> }Can be useful when combined with record destructuring, e.g.
{ minX, maxX, minY, maxY } =
BoundingBox2d.extrema exampleBoxYou can also get these values separately:
Get the X and Y dimensions (width and height) of a bounding box.
( width, height ) =
BoundingBox2d.dimensions exampleBoxGet the median (central) X value of a bounding box.
Get the median (central) Y value of a bounding box.
Get the point at the center of a bounding box.
You can also get the X and Y ranges of a bounding box as intervals:
Get the range of X values contained by a bounding box.
Get the range of Y values contained by a bounding box.
Convert a bounding box to a pair of X and Y intervals.
Check if a bounding box contains a particular point.
Test if the second given bounding box is fully contained within the first (is a subset of it).
outerBox =
BoundingBox2d.from
(Point2d.meters 0 0)
(Point2d.meters 10 10)
innerBox =
BoundingBox2d.from
(Point2d.meters 1 3)
(Point2d.meters 5 9)
overlappingBox =
BoundingBox2d.from
(Point2d.meters 1 3)
(Point2d.meters 5 12)
innerBox |> BoundingBox2d.isContainedIn outerBox
--> True
overlappingBox |> BoundingBox2d.isContainedIn outerBox
--> FalseTest if two boxes touch or overlap at all (have any points in common);
BoundingBox2d.intersects firstBox secondBoxis equivalent to
BoundingBox2d.intersection firstBox secondBox
/= Nothingbut is more efficient.
Check two boxes overlap by at least the given amount. For example, you could implement a tolerant collision check (one that only returns true if the boxes overlap by at least a millimeter, and ignores boxes that just barely touch each other) as
boxesCollide firstBox secondBox =
BoundingBox2d.overlappingByAtLeast
(Length.millimeters 1)
firstBox
secondBoxOverlap is defined as the minimum distance one box would have to move so that it did not touch the other. Boxes that just touch are considered to have an overlap of zero, so
BoundingBox2d.overlappingByAtLeast Quantity.zero
firstBox
secondBoxwill return true even if the two boxes just touch each other.
Check if two boxes are separated by at least the 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 firstBox secondBox =
BoundingBox2d.separatedByAtLeast
(Length.centimeters 1)
firstBox
secondBoxSeparation is defined as the minimum distance one box would have to move so that it touched the other. (Note that this may be a diagonal distance between corners.) Boxes that just touch are considered to have a separation of zero, so
BoundingBox2d.separatedByAtLeast Quantity.zero
firstBox
secondBoxwill return true even if the two boxes just touch each other.
Scale a bounding box about a given point by a given scale.
Translate a bounding box by a given displacement.
Translate a bounding box in a given direction by a given distance.
Expand the given bounding box in all directions by the given offset:
BoundingBox2d.expandBy (Length.meters 3) exampleBox
--> BoundingBox2d.fromExtrema
--> { minX = Length.meters 0
--> , maxX = Length.meters 11
--> , minY = Length.meters -1
--> , maxY = Length.meters 9
--> }Negative offsets will be treated as positive (the absolute value will be used),
so the resulting box will always be at least as large as the original. If you
need to be able to contract a bounding box, use
offsetBy instead.
Expand or shrink the given bounding box in all the directions by the given distance. A positive offset will cause the bounding box to expand and a negative value will cause it to shrink.
BoundingBox2d.offsetBy (Length.meters -1) exampleBox
--> Just <|
--> BoundingBox2d.fromExtrema
--> { minX = Length.meters 4
--> , maxX = Length.meters 7
--> , minY = Length.meters 3
--> , maxY = Length.meters 5
--> }Returns Nothing if the offset is negative and large enough to cause the
bounding box to vanish (that is, if the offset is larger than half the height or
half the width of the bounding box, whichever is less):
BoundingBox2d.offsetBy (Length.meters -3) exampleBox
--> NothingIf you only want to expand a bounding box, you can use
expandBy instead (which does not return a Maybe).
Convert a bounding box from one units type to another, by providing a conversion factor given as a rate of change of destination units with respect to source units.
Convert a bounding box from one units type to another, by providing an 'inverse' conversion factor given as a rate of change of source units with respect to destination units.
Create a random generator for points within a given bounding box.