// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "OrientedBoundingBox.h" #include "BoundingSphere.h" #include "BoundingBox.h" #include "Ray.h" #include "../Types/String.h" OrientedBoundingBox::OrientedBoundingBox(const BoundingBox& bb) { const Vector3 center = bb.Minimum + (bb.Maximum - bb.Minimum) / 2.0f; Extents = bb.Maximum - center; Matrix::Translation(center, Transformation); } OrientedBoundingBox::OrientedBoundingBox(Vector3 points[], int32 pointCount) { ASSERT(points && pointCount > 0); Vector3 minimum = points[0]; Vector3 maximum = points[0]; for (int32 i = 1; i < pointCount; i++) { Vector3::Min(minimum, points[i], minimum); Vector3::Max(maximum, points[i], maximum); } const Vector3 center = minimum + (maximum - minimum) / 2.0f; Extents = maximum - center; Matrix::Translation(center, Transformation); } String OrientedBoundingBox::ToString() const { return String::Format(TEXT("{}"), *this); } void OrientedBoundingBox::GetCorners(Vector3 corners[8]) const { Vector3 xv = Vector3(Extents.X, 0, 0); Vector3 yv = Vector3(0, Extents.Y, 0); Vector3 zv = Vector3(0, 0, Extents.Z); Vector3::TransformNormal(xv, Transformation, xv); Vector3::TransformNormal(yv, Transformation, yv); Vector3::TransformNormal(zv, Transformation, zv); const Vector3 center = Transformation.GetTranslation(); 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; } Vector3 OrientedBoundingBox::GetSize() const { Vector3 xv = Vector3(Extents.X * 2, 0, 0); Vector3 yv = Vector3(0, Extents.Y * 2, 0); Vector3 zv = Vector3(0, 0, Extents.Z * 2); Vector3::TransformNormal(xv, Transformation, xv); Vector3::TransformNormal(yv, Transformation, yv); Vector3::TransformNormal(zv, Transformation, zv); return Vector3(xv.Length(), yv.Length(), zv.Length()); } Vector3 OrientedBoundingBox::GetSizeSquared() const { Vector3 xv = Vector3(Extents.X * 2, 0, 0); Vector3 yv = Vector3(0, Extents.Y * 2, 0); Vector3 zv = Vector3(0, 0, Extents.Z * 2); Vector3::TransformNormal(xv, Transformation, xv); Vector3::TransformNormal(yv, Transformation, yv); Vector3::TransformNormal(zv, Transformation, zv); return Vector3(xv.LengthSquared(), yv.LengthSquared(), zv.LengthSquared()); } BoundingBox OrientedBoundingBox::GetBoundingBox() const { BoundingBox result; Vector3 corners[8]; GetCorners(corners); BoundingBox::FromPoints(corners, 8, result); return result; } void OrientedBoundingBox::GetBoundingBox(BoundingBox& result) const { Vector3 corners[8]; GetCorners(corners); BoundingBox::FromPoints(corners, 8, result); } ContainmentType OrientedBoundingBox::Contains(const Vector3& point, float* distance) const { // Transform the point into the obb coordinates Matrix invTrans; Matrix::Invert(Transformation, invTrans); Vector3 locPoint; Vector3::TransformCoordinate(point, invTrans, locPoint); locPoint.X = Math::Abs(locPoint.X); locPoint.Y = Math::Abs(locPoint.Y); locPoint.Z = Math::Abs(locPoint.Z); if (distance) { // Get minimum distance to edge in local space Vector3 tmp; Vector3::Subtract(Extents, locPoint, tmp); tmp.Absolute(); const float minDstToEdgeLocal = tmp.MinValue(); // Transform distance to world space Vector3 dstVec = Vector3::UnitX * minDstToEdgeLocal; Vector3::TransformNormal(dstVec, Transformation, dstVec); *distance = dstVec.Length(); } // Simple axes-aligned BB check if (locPoint.X < Extents.X && locPoint.Y < Extents.Y && locPoint.Z < Extents.Z) return ContainmentType::Contains; if (Vector3::NearEqual(locPoint, Extents)) return ContainmentType::Intersects; return ContainmentType::Disjoint; } ContainmentType OrientedBoundingBox::Contains(int32 pointsCnt, Vector3 points[]) const { ASSERT(pointsCnt > 0); Matrix invTrans; Matrix::Invert(Transformation, invTrans); bool containsAll = true; bool containsAny = false; for (int32 i = 0; i < pointsCnt; i++) { Vector3 locPoint; Vector3::TransformCoordinate(points[i], invTrans, 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 (Vector3::NearEqual(locPoint, Extents)) containsAny = true; if (locPoint.X < Extents.X && locPoint.Y < Extents.Y && locPoint.Z < Extents.Z) containsAny = true; else containsAll = false; } if (containsAll) return ContainmentType::Contains; if (containsAny) return ContainmentType::Intersects; return ContainmentType::Disjoint; } ContainmentType OrientedBoundingBox::Contains(const BoundingSphere& sphere, bool ignoreScale) const { Matrix invTrans; Matrix::Invert(Transformation, invTrans); // Transform sphere center into the obb coordinates Vector3 locCenter; Vector3::TransformCoordinate(sphere.Center, invTrans, locCenter); float locRadius; if (ignoreScale) { locRadius = sphere.Radius; } else { // Transform sphere radius into the obb coordinates Vector3 vRadius = Vector3::UnitX * sphere.Radius; Vector3::TransformNormal(vRadius, invTrans, vRadius); locRadius = vRadius.Length(); } // Perform regular BoundingBox to BoundingSphere containment check const Vector3 minusExtents = -Extents; Vector3 vector; Vector3::Clamp(locCenter, minusExtents, Extents, vector); const float distance = Vector3::DistanceSquared(locCenter, vector); if (distance > locRadius * locRadius) return ContainmentType::Disjoint; if (minusExtents.X + locRadius <= locCenter.X && locCenter.X <= Extents.X - locRadius && (Extents.X - minusExtents.X > locRadius && minusExtents.Y + locRadius <= locCenter.Y) && (locCenter.Y <= Extents.Y - locRadius && Extents.Y - minusExtents.Y > locRadius && (minusExtents.Z + locRadius <= locCenter.Z && locCenter.Z <= Extents.Z - locRadius && Extents.Z - minusExtents.Z > locRadius))) { return ContainmentType::Contains; } return ContainmentType::Intersects; } /*ContainmentType OrientedBoundingBox::Contains(const OrientedBoundingBox& obb) { Vector3 obbCorners[8]; obb.GetCorners(obbCorners); auto cornersCheck = Contains(8, obbCorners); if (cornersCheck != ContainmentType::Disjoint) return cornersCheck; // http://www.3dkingdoms.com/weekly/bbox.cpp Vector3 SizeA = Extents; Vector3 SizeB = obb.Extents; Vector3 RotA[3]; Vector3 RotB[3]; GetRows(Transformation, RotA); GetRows(obb.Transformation, RotB); Matrix R = Matrix::Identity; // Rotation from B to A Matrix AR = Matrix::Identity; // absolute values of R matrix, to use with box extents float ExtentA, ExtentB, Separation; int i, k; // Calculate B to A rotation matrix for (i = 0; i < 3; i++) { for (k = 0; k < 3; k++) { R[i, k] = Vector3::Dot(RotA[i], RotB[k]); AR[i, k] = Math::Abs(R[i, k]); } } // Vector separating the centers of Box B and of Box A Vector3 vSepWS = obb.GetCenter() - GetCenter(); // Rotated into Box A's coordinates Vector3 vSepA = Vector3(Vector3::Dot(vSepWS, RotA[0]), Vector3::Dot(vSepWS, RotA[1]), Vector3::Dot(vSepWS, RotA[2])); // Test if any of A's basis vectors separate the box for (i = 0; i < 3; i++) { ExtentA = SizeA[i]; ExtentB = Vector3::Dot(SizeB, Vector3(AR[i, 0], AR[i, 1], AR[i, 2])); Separation = Math::Abs(vSepA[i]); if (Separation > ExtentA + ExtentB) return ContainmentType::Disjoint; } // Test if any of B's basis vectors separate the box for (k = 0; k < 3; k++) { ExtentA = Vector3::Dot(SizeA, Vector3(AR[0, k], AR[1, k], AR[2, k])); ExtentB = SizeB[k]; Separation = Math::Abs(Vector3::Dot(vSepA, Vector3(R[0, k], R[1, k], R[2, k]))); if (Separation > ExtentA + ExtentB) return ContainmentType::Disjoint; } // Now test Cross Products of each basis vector combination ( A[i], B[k] ) for (i = 0; i < 3; i++) { for (k = 0; k < 3; k++) { int i1 = (i + 1) % 3, i2 = (i + 2) % 3; int k1 = (k + 1) % 3, k2 = (k + 2) % 3; ExtentA = SizeA[i1] * AR[i2, k] + SizeA[i2] * AR[i1, k]; ExtentB = SizeB[k1] * AR[i, k2] + SizeB[k2] * AR[i, k1]; Separation = Math::Abs(vSepA[i2] * R[i1, k] - vSepA[i1] * R[i2, k]); if (Separation > ExtentA + ExtentB) return ContainmentType::Disjoint; } } // No separating axis found, the boxes overlap return ContainmentType::Intersects; }*/ bool OrientedBoundingBox::Intersects(const Ray& ray, Vector3& point) const { // Put ray in box space Matrix invTrans; Matrix::Invert(Transformation, invTrans); Ray bRay; Vector3::TransformNormal(ray.Direction, invTrans, bRay.Direction); Vector3::TransformCoordinate(ray.Position, invTrans, bRay.Position); // Perform a regular ray to BoundingBox check const BoundingBox bb = BoundingBox(-Extents, Extents); const bool intersects = CollisionsHelper::RayIntersectsBox(bRay, bb, point); // Put the result intersection back to world if (intersects) Vector3::TransformCoordinate(point, Transformation, point); return intersects; } bool OrientedBoundingBox::Intersects(const Ray& ray, float& distance) const { Vector3 point; const bool result = Intersects(ray, point); distance = Vector3::Distance(ray.Position, point); return result; } bool OrientedBoundingBox::Intersects(const Ray& ray, float& distance, Vector3& normal) const { // Put ray in box space Matrix invTrans; Matrix::Invert(Transformation, invTrans); Ray bRay; Vector3::TransformNormal(ray.Direction, invTrans, bRay.Direction); Vector3::TransformCoordinate(ray.Position, invTrans, bRay.Position); // Perform a regular ray to BoundingBox check const BoundingBox bb(-Extents, Extents); if (CollisionsHelper::RayIntersectsBox(bRay, bb, distance, normal)) { // Put the result intersection back to world Vector3::TransformNormal(normal, Transformation, normal); normal.Normalize(); return true; } return false; }