A BoundingBox3d
is a rectangular box in 3D defined by its minimum and
maximum X, Y and Z values. It is possible to generate bounding boxes for most
geometric objects; for example, Triangle3d.boundingBox
takes a Triangle3d
and returns a BoundingBox3d
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:
Geometry.Types.BoundingBox3d units coordinates
from : Point3d units coordinates -> Point3d units coordinates -> BoundingBox3d units coordinates
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.
BoundingBox3d.from
(Point3d.meters 2 1 3)
(Point3d.meters -1 5 -2)
--> BoundingBox3d.fromExtrema
--> { minX = Length.meters -1
--> , maxX = Length.meters 2
--> , minY = Length.meters 1
--> , maxY = Length.meters 5
--> , minZ = Length.meters -2
--> , maxZ = Length.meters 3
--> }
fromExtrema : { minX : Quantity Basics.Float units, maxX : Quantity Basics.Float units, minY : Quantity Basics.Float units, maxY : Quantity Basics.Float units, minZ : Quantity Basics.Float units, maxZ : Quantity Basics.Float units } -> BoundingBox3d units coordinates
Construct a bounding box from its minimum and maximum X, Y and Z values:
exampleBox =
BoundingBox3d.fromExtrema
{ minX = Length.meters -2
, maxX = Length.meters 2
, minY = Length.meters 2
, maxY = Length.meters 5
, minZ = Length.meters 3
, maxZ = Length.meters 4
}
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.
withDimensions : ( Quantity Basics.Float units, Quantity Basics.Float units, Quantity Basics.Float units ) -> Point3d units coordinates -> BoundingBox3d units coordinates
Construct a bounding box given its overall dimensions (length, width, height) and center point.
singleton : Point3d units coordinates -> BoundingBox3d units coordinates
Construct a zero-width bounding box containing a single point.
xyz : Quantity.Interval.Interval Basics.Float units -> Quantity.Interval.Interval Basics.Float units -> Quantity.Interval.Interval Basics.Float units -> BoundingBox3d units coordinates
Construct a bounding box from separate X, Y and Z intervals.
fromIntervals : ( Quantity.Interval.Interval Basics.Float units, Quantity.Interval.Interval Basics.Float units, Quantity.Interval.Interval Basics.Float units ) -> BoundingBox3d units coordinates
Construct a bounding box from a tuple of X, Y and Z intervals.
union : BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Build a bounding box that contains both given bounding boxes.
firstBox =
BoundingBox3d.from
(Point3d.meters 1 2 0)
(Point3d.meters 4 3 5)
secondBox =
BoundingBox3d.from
(Point3d.meters -2 4 -1)
(Point3d.meters 2 5 0)
BoundingBox3d.union firstBox secondBox
--> BoundingBox3d.from
--> (Point3d.meters -2 2 -1)
--> (Point3d.meters 4 5 5)
(Note that this is not strictly speaking a 'union' in the precise mathematical sense.)
intersection : BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> Maybe (BoundingBox3d units coordinates)
Attempt to build a bounding box that contains all points common to both
given bounding boxes. If the given boxes do not overlap, returns Nothing
.
firstBox =
BoundingBox3d.from
(Point3d.meters 1 2 5)
(Point3d.meters 4 3 8)
secondBox =
BoundingBox3d.from
(Point3d.meters 2 1 6)
(Point3d.meters 5 4 7)
BoundingBox3d.intersection firstBox secondBox
--> Just <|
--> BoundingBox3d.from
--> (Point3d.meters 2 2 6)
--> (Point3d.meters 4 3 7)
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 volume (at least one of its dimensions will be zero):
firstBox =
BoundingBox3d.from
(Point3d.meters 0 0 0)
(Point3d.meters 1 2 3)
secondBox =
BoundingBox3d.from
(Point3d.meters 1 1 1)
(Point3d.meters 2 3 4)
BoundingBox3d.intersection firstBox secondBox
--> Just <|
--> BoundingBox3d.from
--> (Point3d.meters 1 1 1)
--> (Point3d.meters 1 2 3)
Functions for building bounding boxes containing several points.
hull : Point3d units coordinates -> List (Point3d units coordinates) -> BoundingBox3d units coordinates
Find the bounding box containing one or more input points. You would
generally use this within a case
expression:
case points of
[] ->
-- some default behavior
first :: rest ->
let
boundingBox =
BoundingBox3d.hull first rest
in
...
If you need to handle the case of zero input points, see hullN
.
hull3 : Point3d units coordinates -> Point3d units coordinates -> Point3d units coordinates -> BoundingBox3d units coordinates
Build a bounding box that contains all three of the given points;
BoundingBox3d.hull3 p1 p2 p3
is equivalent to
BoundingBox3d.hull p1 [ p2, p3 ]
but is more efficient.
hullN : List (Point3d units coordinates) -> Maybe (BoundingBox3d units coordinates)
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.
hullOf : (a -> Point3d units coordinates) -> a -> List a -> BoundingBox3d units coordinates
Like hull
, but lets you work on any kind of item as long as a
point can be extracted from it. For example, to get the bounding box around the
centroids of four triangles:
BoundingBox3d.hullOf Triangle3d.centroid
firstTriangle
[ secondTriangle
, thirdTriangle
, fourthTriangle
]
hullOfN : (a -> Point3d units coordinates) -> List a -> Maybe (BoundingBox3d units coordinates)
Combination of hullOf
and hullN
.
Functions for combining several bounding boxes into one bounding box that contains all of the input boxes.
aggregate : BoundingBox3d units coordinates -> List (BoundingBox3d units coordinates) -> BoundingBox3d units coordinates
Find the bounding box containing one or more input boxes; works much
like hull
. If you need to handle the case of zero input boxes, see
aggregateN
.
aggregate3 : BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Build a bounding box that contains all three of the given bounding boxes;
BoundingBox3d.aggregate3 b1 b2 b3
is equivalent to
BoundingBox3d.aggregate b1 [ b2, b3 ]
but is more efficient.
aggregateN : List (BoundingBox3d units coordinates) -> Maybe (BoundingBox3d units coordinates)
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.
aggregateOf : (a -> BoundingBox3d units coordinates) -> a -> List a -> BoundingBox3d units coordinates
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:
BoundingBox3d.aggregateOf Triangle3d.boundingBox
firstTriangle
[ secondTriangle
, thirdTriangle
, fourthTriangle
]
aggregateOfN : (a -> BoundingBox3d units coordinates) -> List a -> Maybe (BoundingBox3d units coordinates)
Combination of aggregateOf
and
aggregateN
.
extrema : BoundingBox3d units coordinates -> { minX : Quantity Basics.Float units, maxX : Quantity Basics.Float units, minY : Quantity Basics.Float units, maxY : Quantity Basics.Float units, minZ : Quantity Basics.Float units, maxZ : Quantity Basics.Float units }
Get the minimum and maximum X, Y and Z values of a bounding box in a single record.
BoundingBox3d.extrema exampleBox
--> { minX = Length.meters -2
--> , maxX = Length.meters 2
--> , minY = Length.meters 2
--> , maxY = Length.meters 5
--> , minZ = Length.meters 3
--> , maxZ = Length.meters 4
--> }
Can be useful when combined with record destructuring, e.g.
{ minX, maxX, minY, maxY, minZ, maxZ } =
BoundingBox3d.extrema exampleBox
minX : BoundingBox3d units coordinates -> Quantity Basics.Float units
maxX : BoundingBox3d units coordinates -> Quantity Basics.Float units
minY : BoundingBox3d units coordinates -> Quantity Basics.Float units
maxY : BoundingBox3d units coordinates -> Quantity Basics.Float units
minZ : BoundingBox3d units coordinates -> Quantity Basics.Float units
maxZ : BoundingBox3d units coordinates -> Quantity Basics.Float units
dimensions : BoundingBox3d units coordinates -> ( Quantity Basics.Float units, Quantity Basics.Float units, Quantity Basics.Float units )
Get the X, Y and Z dimensions (widths) of a bounding box.
BoundingBox3d.dimensions exampleBox
--> ( Length.meters 4
--> , Length.meters 3
--> , Length.meters 1
--> )
midX : BoundingBox3d units coordinates -> Quantity Basics.Float units
Get the median (central) X value of a bounding box.
midY : BoundingBox3d units coordinates -> Quantity Basics.Float units
Get the median (central) Y value of a bounding box.
midZ : BoundingBox3d units coordinates -> Quantity Basics.Float units
Get the median (central) Z value of a bounding box.
xInterval : BoundingBox3d units coordinates -> Quantity.Interval.Interval Basics.Float units
Get the range of X values contained by a bounding box.
yInterval : BoundingBox3d units coordinates -> Quantity.Interval.Interval Basics.Float units
Get the range of Y values contained by a bounding box.
zInterval : BoundingBox3d units coordinates -> Quantity.Interval.Interval Basics.Float units
Get the range of Y values contained by a bounding box.
intervals : BoundingBox3d units coordinates -> ( Quantity.Interval.Interval Basics.Float units, Quantity.Interval.Interval Basics.Float units, Quantity.Interval.Interval Basics.Float units )
Convert a bounding box to a pair of X and Y intervals.
contains : Point3d units coordinates -> BoundingBox3d units coordinates -> Basics.Bool
Check if a bounding box contains a particular point.
isContainedIn : BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> Basics.Bool
Test if the second given bounding box is fully contained within the first (is a subset of it).
outerBox =
BoundingBox3d.from
(Point3d.meters 0 0 0)
(Point3d.meters 10 10 10)
innerBox =
BoundingBox3d.from
(Point3d.meters 1 3 7)
(Point3d.meters 5 9 8)
overlappingBox =
BoundingBox3d.from
(Point3d.meters 1 3 7)
(Point3d.meters 5 12 8)
innerBox |> BoundingBox3d.isContainedIn outerBox
--> True
overlappingBox |> BoundingBox3d.isContainedIn outerBox
--> False
intersects : BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> Basics.Bool
Test if two boxes touch or overlap at all (have any points in common);
BoundingBox3d.intersects firstBox secondBox
is equivalent to
BoundingBox3d.intersection firstBox secondBox
/= Nothing
but is more efficient.
overlappingByAtLeast : Quantity Basics.Float units -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> Basics.Bool
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 =
BoundingBox3d.overlappingByAtLeast
(Length.millimeters 1)
firstBox
secondBox
Overlap 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
BoundingBox3d.overlappingByAtLeast Quantity.zero
firstBox
secondBox
will return true even if the two boxes just touch each other.
separatedByAtLeast : Quantity Basics.Float units -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates -> Basics.Bool
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 =
BoundingBox3d.separatedByAtLeast
(Length.centimeters 1)
firstBox
secondBox
Separation 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
BoundingBox3d.separatedByAtLeast Quantity.zero
firstBox
secondBox
will return true even if the two boxes just touch each other.
interpolate : BoundingBox3d units coordinates -> Basics.Float -> Basics.Float -> Basics.Float -> Point3d units coordinates
Interpolate within a bounding box based on parameter values which range from 0 to 1.
scaleAbout : Point3d units coordinates -> Basics.Float -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Scale a bounding box about a given point by a given scale.
translateBy : Vector3d units coordinates -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Translate a bounding box by a given displacement.
translateIn : Direction3d coordinates -> Quantity Basics.Float units -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Translate a bounding box in a given direction by a given distance.
expandBy : Quantity Basics.Float units -> BoundingBox3d units coordinates -> BoundingBox3d units coordinates
Expand the given bounding box in all directions by the given offset:
BoundingBox3d.expandBy (Length.meters 3) exampleBox
--> BoundingBox3d.fromExtrema
--> { minX = Length.meters -5
--> , maxX = Length.meters 5
--> , minY = Length.meters -1
--> , maxY = Length.meters 8
--> , minZ = Length.meters 0
--> , maxZ = Length.meters 7
--> }
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.
offsetBy : Quantity Basics.Float units -> BoundingBox3d units coordinates -> Maybe (BoundingBox3d units coordinates)
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.
BoundingBox3d.offsetBy (Length.meters -0.5) exampleBox
--> Just <|
--> BoundingBox3d.fromExtrema
--> { minX = Length.meters -1.5
--> , maxX = Length.meters 1.5
--> , minY = Length.meters 2.5
--> , maxY = Length.meters 4.5
--> , minZ = Length.meters 3.5
--> , maxZ = Length.meters 3.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):
BoundingBox3d.offsetBy (Length.meters -1) exampleBox
--> Nothing
If you only want to expand a bounding box, you can use
expandBy
instead (which does not return a Maybe
).
at : Quantity Basics.Float (Quantity.Rate units2 units1) -> BoundingBox3d units1 coordinates -> BoundingBox3d units2 coordinates
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.
at_ : Quantity Basics.Float (Quantity.Rate units1 units2) -> BoundingBox3d units1 coordinates -> BoundingBox3d units2 coordinates
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.
randomPoint : BoundingBox3d units coordinates -> Random.Generator (Point3d units coordinates)
Create a random generator for points within a given bounding box.