ianmackenzie / elm-geometry / BoundingBox3d

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:


type alias BoundingBox3d units coordinates =
Geometry.Types.BoundingBox3d units coordinates

Constructors

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.

Booleans

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)

Hull

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.

Aggregation

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.

Properties

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.

Queries

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.

Interpolation

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.

Transformations

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).

Unit conversions

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.

Random point generation

randomPoint : BoundingBox3d units coordinates -> Random.Generator (Point3d units coordinates)

Create a random generator for points within a given bounding box.