// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #if USE_LARGE_WORLDS using Real = System.Double; #else using Real = System.Single; #endif // ----------------------------------------------------------------------------- // Original code from SharpDX project. https://github.com/sharpdx/SharpDX/ // Greetings to Alexandre Mutel. Original code published with the following license: // ----------------------------------------------------------------------------- // Copyright (c) 2010-2014 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Runtime.CompilerServices; namespace FlaxEngine { [Serializable] partial struct OrientedBoundingBox : IEquatable, IFormattable { /// /// Creates an from a BoundingBox. /// /// The BoundingBox to create from. /// Initially, the OBB is axis-aligned box, but it can be rotated and transformed later. public OrientedBoundingBox(BoundingBox bb) { Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) * 0.5f; Extents = bb.Maximum - center; Transformation = new Transform(center); } /// /// Initializes a new instance of the struct. /// /// The half lengths of the box along each axis. /// The matrix which aligns and scales the box, and its translation vector represents the center of the box. public OrientedBoundingBox(Vector3 extents, Matrix transformation) { Extents = extents; transformation.Decompose(out Transformation); } /// /// Creates an which contained between two minimum and maximum points. /// /// The minimum vertex of the bounding box. /// The maximum vertex of the bounding box. /// Initially, the OrientedBoundingBox is axis-aligned box, but it can be rotated and transformed later. public OrientedBoundingBox(Vector3 minimum, Vector3 maximum) { Vector3 center = minimum + (maximum - minimum) * 0.5f; Extents = maximum - center; Transformation = new Transform(center); } /// /// Creates an that fully contains the given points. /// /// The points that will be contained by the box. /// This method is not for computing the best tight-fitting OrientedBoundingBox. And initially, the OrientedBoundingBox is axis-aligned box, but it can be rotated and transformed later. public OrientedBoundingBox(Vector3[] points) { if ((points == null) || (points.Length == 0)) throw new ArgumentNullException(nameof(points)); var minimum = points[0]; var maximum = points[0]; for (var i = 1; i < points.Length; ++i) { Vector3.Min(ref minimum, ref points[i], out minimum); Vector3.Max(ref maximum, ref points[i], out maximum); } Vector3 center = minimum + (maximum - minimum) * 0.5f; Extents = maximum - center; Transformation = new Transform(center); } /// /// Retrieves the eight corners of the bounding box. /// /// An array of points representing the eight corners of the bounding box. public Vector3[] GetCorners() { var corners = new Vector3[8]; GetCorners(corners); return corners; } /// /// Retrieves the eight corners of the bounding box. /// /// An array of points representing the eight corners of the bounding box. public unsafe void GetCorners(Vector3[] corners) { if (corners == null || corners.Length != 8) throw new ArgumentException(); fixed (Vector3* ptr = corners) GetCorners(ptr); } /// /// Retrieves the eight corners of the bounding box. /// /// An array of points representing the eight corners of the bounding box. public unsafe void GetCorners(Vector3* corners) { Vector3 xv = Transformation.LocalToWorldVector(new Vector3(Extents.X, 0, 0)); Vector3 yv = Transformation.LocalToWorldVector(new Vector3(0, Extents.Y, 0)); Vector3 zv = Transformation.LocalToWorldVector(new Vector3(0, 0, Extents.Z)); Vector3 center = Transformation.Translation; corners[0] = center + xv + yv + zv; corners[1] = center + xv + yv - zv; corners[2] = center - xv + yv - zv; corners[3] = center - xv + yv + zv; corners[4] = center + xv - yv + zv; corners[5] = center + xv - yv - zv; corners[6] = center - xv - yv - zv; corners[7] = center - xv - yv + zv; } /// /// Retrieves the eight corners of the bounding box. /// /// An collection to add the corners of the bounding box. public void GetCorners(List corners) { if (corners == null) throw new ArgumentNullException(); corners.AddRange(GetCorners()); } /// /// Transforms this box using a transformation. /// /// The transformation. /// While any kind of transformation can be applied, it is recommended to apply scaling using scale method instead, which scales the Extents and keeps the Transformation for rotation only, and that preserves collision detection accuracy. public void Transform(ref Transform transform) { Transformation = transform.LocalToWorld(Transformation); } /// /// Transforms this box using a transformation matrix. /// /// The transformation matrix. /// While any kind of transformation can be applied, it is recommended to apply scaling using scale method instead, which scales the Extents and keeps the Transformation matrix for rotation only, and that preserves collision detection accuracy. public void Transform(ref Matrix mat) { mat.Decompose(out var transform); Transformation = transform.LocalToWorld(Transformation); } /// /// Transforms this box using a transformation matrix. /// /// The transformation matrix. /// While any kind of transformation can be applied, it is recommended to apply scaling using scale method instead, which scales the Extents and keeps the Transformation matrix for rotation only, and that preserves collision detection accuracy. public void Transform(Matrix mat) { Transform(ref mat); } /// /// Scales the by scaling its Extents without affecting the Transformation matrix, By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate. /// /// public void Scale(ref Vector3 scaling) { Extents *= scaling; } /// /// Scales the by scaling its Extents without affecting the Transformation matrix, By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate. /// /// public void Scale(Vector3 scaling) { Extents *= scaling; } /// /// Scales the by scaling its Extents without affecting the Transformation matrix, By keeping Transformation matrix scaling-free, the collision detection methods will be more accurate. /// /// public void Scale(Real scaling) { Extents *= scaling; } /// /// Translates the to a new position using a translation vector; /// /// the translation vector. public void Translate(ref Vector3 translation) { Transformation.Translation += translation; } /// /// Translates the to a new position using a translation vector; /// /// the translation vector. public void Translate(Vector3 translation) { Transformation.Translation += translation; } /// /// The size of the if no scaling is applied to the transformation matrix. /// /// The property will return the actual size even if the scaling is applied using Scale method, but if the scaling is applied to transformation matrix, use GetSize Function instead. public Vector3 Size => Extents * 2; /// /// Returns the size of the taking into consideration the scaling applied to the transformation matrix. /// /// The size of the consideration /// This method is computationally expensive, so if no scale is applied to the transformation matrix use property instead. public Vector3 GetSize() { Vector3 xv = Transformation.LocalToWorldVector(new Vector3(Extents.X * 2, 0, 0)); Vector3 yv = Transformation.LocalToWorldVector(new Vector3(0, Extents.Y * 2, 0)); Vector3 zv = Transformation.LocalToWorldVector(new Vector3(0, 0, Extents.Z * 2)); return new Vector3(xv.Length, yv.Length, zv.Length); } /// /// Returns the square size of the taking into consideration the scaling applied to the transformation matrix. /// /// The size of the consideration public Vector3 GetSizeSquared() { Vector3 xv = Transformation.LocalToWorldVector(new Vector3(Extents.X * 2, 0, 0)); Vector3 yv = Transformation.LocalToWorldVector(new Vector3(0, Extents.Y * 2, 0)); Vector3 zv = Transformation.LocalToWorldVector(new Vector3(0, 0, Extents.Z * 2)); return new Vector3(xv.LengthSquared, yv.LengthSquared, zv.LengthSquared); } /// /// Returns the center of the . /// public Vector3 Center => Transformation.Translation; /// /// Determines whether a contains a point. /// /// The point to test. /// The type of containment the two objects have. public ContainmentType Contains(ref Vector3 point) { // Transform the point into the obb coordinates Transformation.WorldToLocal(ref point, out Vector3 locPoint); locPoint.X = Math.Abs(locPoint.X); locPoint.Y = Math.Abs(locPoint.Y); locPoint.Z = Math.Abs(locPoint.Z); // Simple axes-aligned BB check if (Mathf.NearEqual(locPoint.X, Extents.X) && Mathf.NearEqual(locPoint.Y, Extents.Y) && Mathf.NearEqual(locPoint.Z, Extents.Z)) return ContainmentType.Intersects; if ((locPoint.X < Extents.X) && (locPoint.Y < Extents.Y) && (locPoint.Z < Extents.Z)) return ContainmentType.Contains; return ContainmentType.Disjoint; } /// /// Determines whether a contains a point. /// /// The point to test. /// The type of containment the two objects have. public ContainmentType Contains(Vector3 point) { return Contains(ref point); } /// /// Determines whether a contains a . /// /// The sphere to test. /// Optimize the check operation by assuming that has no scaling applied. /// The type of containment the two objects have. /// This method is not designed for which has a non-uniform scaling applied to its transformation matrix. But any type of scaling applied using Scale method will keep this method accurate. public ContainmentType Contains(BoundingSphere sphere, bool ignoreScale = false) { // Transform sphere center into the obb coordinates Transformation.WorldToLocal(ref sphere.Center, out Vector3 locCenter); Real locRadius; if (ignoreScale) locRadius = sphere.Radius; else { // Transform sphere radius into the obb coordinates Vector3 vRadius = Vector3.UnitX * sphere.Radius; Transformation.LocalToWorldVector(ref vRadius, out vRadius); locRadius = vRadius.Length; } // Perform regular BoundingBox to BoundingSphere containment check Vector3 minusExtens = -Extents; Vector3.Clamp(ref locCenter, ref minusExtens, ref Extents, out Vector3 vector); Real distance = Vector3.DistanceSquared(ref locCenter, ref vector); if (distance > locRadius * locRadius) return ContainmentType.Disjoint; if ((minusExtens.X + locRadius <= locCenter.X) && (locCenter.X <= Extents.X - locRadius) && (Extents.X - minusExtens.X > locRadius) && (minusExtens.Y + locRadius <= locCenter.Y) && (locCenter.Y <= Extents.Y - locRadius) && (Extents.Y - minusExtens.Y > locRadius) && (minusExtens.Z + locRadius <= locCenter.Z) && (locCenter.Z <= Extents.Z - locRadius) && (Extents.Z - minusExtens.Z > locRadius)) return ContainmentType.Contains; return ContainmentType.Intersects; } /// /// Determines whether there is an intersection between a and a . /// /// The ray to test. /// When the method completes, contains the point of intersection, or if there was no intersection. /// Whether the two objects intersected. public bool Intersects(ref Ray ray, out Vector3 point) { // Put ray in box space Ray bRay; Transformation.WorldToLocalVector(ref ray.Direction, out bRay.Direction); Transformation.WorldToLocal(ref ray.Position, out bRay.Position); // Perform a regular ray to BoundingBox check var bb = new BoundingBox(-Extents, Extents); bool intersects = CollisionsHelper.RayIntersectsBox(ref bRay, ref bb, out point); // Put the result intersection back to world if (intersects) Transformation.LocalToWorld(ref point, out point); return intersects; } /// /// Determines whether there is an intersection between a and a . /// /// The ray to test. /// When the method completes, contains the distance of intersection from the ray start, or 0 if there was no intersection. /// Whether the two objects intersected. public bool Intersects(ref Ray ray, out Real distance) { if (Intersects(ref ray, out Vector3 point)) { Vector3.Distance(ref ray.Position, ref point, out distance); return true; } distance = 0; return false; } /// /// Determines whether there is an intersection between a and a . /// /// The ray to test. /// Whether the two objects intersected. public bool Intersects(ref Ray ray) { return Intersects(ref ray, out Vector3 _); } /// /// Get the axis-aligned which contains all corners. /// /// The axis-aligned BoundingBox of this OrientedBoundingBox. public BoundingBox GetBoundingBox() { return BoundingBox.FromPoints(GetCorners()); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ref OrientedBoundingBox value) { return (Extents == value.Extents) && (Transformation == value.Transformation); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(OrientedBoundingBox value) { return Equals(ref value); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { return value is OrientedBoundingBox other && Equals(ref other); } /// /// Transforms bounding box using the given transformation matrix. /// /// The bounding box to transform. /// The transformation matrix. /// The result of the transformation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OrientedBoundingBox operator *(OrientedBoundingBox box, Matrix transform) { OrientedBoundingBox result = box; result.Transform(ref transform); return result; } /// /// Transforms bounding box using the given transformation. /// /// The bounding box to transform. /// The transformation. /// The result of the transformation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OrientedBoundingBox operator *(OrientedBoundingBox box, Transform transform) { OrientedBoundingBox result = box; result.Transformation = transform.LocalToWorld(result.Transformation); return result; } /// /// Tests for equality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has the same value as ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(OrientedBoundingBox left, OrientedBoundingBox right) { return left.Equals(ref right); } /// /// Tests for inequality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has a different value than ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(OrientedBoundingBox left, OrientedBoundingBox right) { return !left.Equals(ref right); } /// /// Returns a hash code for this instance. /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() { return Extents.GetHashCode() + Transformation.GetHashCode(); } /// /// Returns a that represents this instance. /// /// A that represents this instance. public override string ToString() { return string.Format(CultureInfo.CurrentCulture, "Center: {0}, Extents: {1}", Center, Extents); } /// /// Returns a that represents this instance. /// /// The format. /// A that represents this instance. public string ToString(string format) { if (format == null) return ToString(); return string.Format(CultureInfo.CurrentCulture, "Center: {0}, Extents: {1}", Center.ToString(format, CultureInfo.CurrentCulture), Extents.ToString(format, CultureInfo.CurrentCulture)); } /// /// Returns a that represents this instance. /// /// The format provider. /// A that represents this instance. public string ToString(IFormatProvider formatProvider) { return string.Format(formatProvider, "Center: {0}, Extents: {1}", Center.ToString(), Extents.ToString()); } /// /// Returns a that represents this instance. /// /// The format. /// The format provider. /// A that represents this instance. public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return ToString(formatProvider); return string.Format(formatProvider, "Center: {0}, Extents: {1}", Center.ToString(format, formatProvider), Extents.ToString(format, formatProvider)); } } }