|
| 1 | +// Copyright (c) 2022 Feng Yang |
| 2 | +// |
| 3 | +// I am making my contributions/submissions to this project solely in my |
| 4 | +// personal capacity and am not conveying any rights to any intellectual |
| 5 | +// property of any third parties. |
| 6 | + |
| 7 | +import Foundation |
| 8 | + |
| 9 | +/// Axis Aligned Bound Box (AABB). |
| 10 | +public struct BoundingBox { |
| 11 | + private var m_Center = Vector3() |
| 12 | + private var m_Extents = Vector3() |
| 13 | + |
| 14 | + /// The center of the bounding box. |
| 15 | + public var center: Vector3 { |
| 16 | + get { |
| 17 | + m_Center |
| 18 | + } |
| 19 | + set { |
| 20 | + m_Center = newValue |
| 21 | + } |
| 22 | + } |
| 23 | + |
| 24 | + /// The total size of the box. This is always twice as large as the extents. |
| 25 | + public var size: Vector3 { |
| 26 | + get { |
| 27 | + m_Extents * 2 |
| 28 | + } |
| 29 | + set { |
| 30 | + m_Extents = newValue * 0.5 |
| 31 | + } |
| 32 | + } |
| 33 | + |
| 34 | + /// The extents of the Bounding Box. This is always half of the size of the Bounds. |
| 35 | + public var extents: Vector3 { |
| 36 | + get { |
| 37 | + m_Extents |
| 38 | + } |
| 39 | + set { |
| 40 | + m_Extents = newValue |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + /// The minimal point of the box. This is always equal to center-extents. |
| 45 | + public var min: Vector3 { |
| 46 | + get { |
| 47 | + center - extents |
| 48 | + } |
| 49 | + set { |
| 50 | + setMinMax(newValue, max) |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + /// The maximal point of the box. This is always equal to center+extents. |
| 55 | + public var max: Vector3 { |
| 56 | + get { |
| 57 | + center + extents |
| 58 | + } |
| 59 | + set { |
| 60 | + setMinMax(min, newValue) |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + /// Constructor of BoundingBox. |
| 65 | + /// - Parameters: |
| 66 | + /// - min: The minimum point of the box |
| 67 | + /// - max: The maximum point of the box |
| 68 | + public init(_ min: Vector3? = nil, _ max: Vector3? = nil) { |
| 69 | + if let min { |
| 70 | + self.min = min |
| 71 | + } |
| 72 | + if let max { |
| 73 | + self.max = max |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + /// Sets the bounds to the min and max value of the box. |
| 78 | + public mutating func setMinMax(_ min: Vector3, _ max: Vector3) { |
| 79 | + extents = (max - min) * 0.5 |
| 80 | + center = min + extents; |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +//MARK: - Static Methods |
| 85 | + |
| 86 | +extension BoundingBox { |
| 87 | + /// Calculate a bounding box from the center point and the extent of the bounding box. |
| 88 | + /// - Parameters: |
| 89 | + /// - center: The center point |
| 90 | + /// - extent: The extent of the bounding box |
| 91 | + /// - Returns: The calculated bounding box |
| 92 | + public static func fromCenterAndExtent(center: Vector3, extent: Vector3) -> BoundingBox { |
| 93 | + BoundingBox(center - extent, center + extent) |
| 94 | + } |
| 95 | + |
| 96 | + /// Calculate a bounding box that fully contains the given points. |
| 97 | + /// - Parameters: |
| 98 | + /// - points: The given points |
| 99 | + /// - Returns: The calculated bounding box |
| 100 | + public static func fromPoints(points: [Vector3]) -> BoundingBox { |
| 101 | + if (points.count == 0) { |
| 102 | + fatalError("points must be array and length must > 0") |
| 103 | + } |
| 104 | + |
| 105 | + var out = BoundingBox(Vector3(Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude), |
| 106 | + Vector3(-Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude)) |
| 107 | + for point in points { |
| 108 | + out.setMinMax(Vector3.min(left: out.min, right: point), |
| 109 | + Vector3.max(left: out.max, right: point)) |
| 110 | + } |
| 111 | + return out |
| 112 | + } |
| 113 | + |
| 114 | + /// Calculate a bounding box from a given sphere. |
| 115 | + /// - Parameters: |
| 116 | + /// - sphere: The given sphere |
| 117 | + /// - Returns: The calculated bounding box |
| 118 | + public static func fromSphere(sphere: BoundingSphere) -> BoundingBox { |
| 119 | + let center = sphere.center |
| 120 | + let radius = sphere.radius |
| 121 | + return BoundingBox(Vector3(center.x - radius, center.y - radius, center.z - radius), |
| 122 | + Vector3(center.x + radius, center.y + radius, center.z + radius)) |
| 123 | + } |
| 124 | + |
| 125 | + /// Transform a bounding box. |
| 126 | + /// - Parameters: |
| 127 | + /// - source: The original bounding box |
| 128 | + /// - matrix: The transform to apply to the bounding box |
| 129 | + /// - Returns: The transformed bounding box |
| 130 | + public static func transform(source: BoundingBox, matrix: Matrix) -> BoundingBox { |
| 131 | + // https://zeux.io/2010/10/17/aabb-from-obb-with-component-wise-abs/ |
| 132 | + var center = source.getCenter() |
| 133 | + var extent = source.getExtent() |
| 134 | + center = Vector3.transformCoordinate(v: center, m: matrix) |
| 135 | + |
| 136 | + let x = extent.x |
| 137 | + let y = extent.y |
| 138 | + let z = extent.z |
| 139 | + |
| 140 | + extent = Vector3(abs(x * matrix.elements.columns.0[0]) + abs(y * matrix.elements.columns.1[0]) + abs(z * matrix.elements.columns.2[0]), |
| 141 | + abs(x * matrix.elements.columns.0[1]) + abs(y * matrix.elements.columns.1[1]) + abs(z * matrix.elements.columns.2[1]), |
| 142 | + abs(x * matrix.elements.columns.0[2]) + abs(y * matrix.elements.columns.1[2]) + abs(z * matrix.elements.columns.2[2])) |
| 143 | + |
| 144 | + // set min、max |
| 145 | + return BoundingBox(center - extent, center + extent) |
| 146 | + } |
| 147 | + |
| 148 | + /// Calculate a bounding box that is as large as the total combined area of the two specified boxes. |
| 149 | + /// - Parameters: |
| 150 | + /// - box1: The first box to merge |
| 151 | + /// - box2: The second box to merge |
| 152 | + /// - Returns: The merged bounding box |
| 153 | + public static func merge(box1: BoundingBox, box2: BoundingBox) -> BoundingBox { |
| 154 | + BoundingBox(Vector3.min(left: box1.min, right: box2.min), Vector3.max(left: box1.max, right: box2.max)) |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +extension BoundingBox { |
| 159 | + /// Get the center point of this bounding box. |
| 160 | + /// - Returns: The center point of this bounding box |
| 161 | + public func getCenter() -> Vector3 { |
| 162 | + (min + max) * 0.5 |
| 163 | + } |
| 164 | + |
| 165 | + /// Get the extent of this bounding box. |
| 166 | + /// - Returns: The extent of this bounding box |
| 167 | + public func getExtent() -> Vector3 { |
| 168 | + (max - min) * 0.5 |
| 169 | + } |
| 170 | + |
| 171 | + /// Get the eight corners of this bounding box. |
| 172 | + /// - Returns: An array of points representing the eight corners of this bounding box |
| 173 | + public func getCorners() -> [Vector3] { |
| 174 | + let minX = min.x |
| 175 | + let minY = min.y |
| 176 | + let minZ = min.z |
| 177 | + let maxX = max.x |
| 178 | + let maxY = max.y |
| 179 | + let maxZ = max.z |
| 180 | + |
| 181 | + // The array length is less than 8 to make up |
| 182 | + var out = Array<Vector3>(repeating: Vector3(), count: 8) |
| 183 | + _ = out[0].set(x: minX, y: maxY, z: maxZ) |
| 184 | + _ = out[1].set(x: maxX, y: maxY, z: maxZ) |
| 185 | + _ = out[2].set(x: maxX, y: minY, z: maxZ) |
| 186 | + _ = out[3].set(x: minX, y: minY, z: maxZ) |
| 187 | + _ = out[4].set(x: minX, y: maxY, z: minZ) |
| 188 | + _ = out[5].set(x: maxX, y: maxY, z: minZ) |
| 189 | + _ = out[6].set(x: maxX, y: minY, z: minZ) |
| 190 | + _ = out[7].set(x: minX, y: minY, z: minZ) |
| 191 | + |
| 192 | + return out |
| 193 | + } |
| 194 | + |
| 195 | + /// Transform a bounding box. |
| 196 | + /// - Parameter matrix: The transform to apply to the bounding box |
| 197 | + /// - Returns: The transformed bounding box |
| 198 | + public mutating func transform(matrix: Matrix) -> BoundingBox { |
| 199 | + self = BoundingBox.transform(source: self, matrix: matrix) |
| 200 | + return self |
| 201 | + } |
| 202 | +} |
| 203 | + |
| 204 | +public typealias Bounds = BoundingBox |
| 205 | + |
| 206 | +extension BoundingBox: Codable { |
| 207 | +} |
0 commit comments