| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/lib/escher/geometry/bounding_box.h" |
| |
| #include <array> |
| |
| #include "src/lib/fxl/logging.h" |
| #include "src/ui/lib/escher/geometry/intersection.h" |
| #include "src/ui/lib/escher/geometry/plane_ops.h" |
| |
| namespace escher { |
| |
| namespace { |
| |
| static const size_t kNumPlanes = 6; |
| |
| bool PlanesAreValid(const std::array<vec4, kNumPlanes>& planes) { |
| for (size_t i = 0; i < kNumPlanes; ++i) { |
| if (std::abs(glm::length(vec3(planes[i])) - 1.f) > kEpsilon) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| BoundingBox::BoundingBox(vec3 min, vec3 max) : min_(min), max_(max) { |
| #ifndef NDEBUG |
| FXL_DCHECK(min.x <= max.x) << min << " " << max; |
| FXL_DCHECK(min.y <= max.y) << min << " " << max; |
| FXL_DCHECK(min.z <= max.z) << min << " " << max; |
| int dimensions = (min.x != max.x ? 1 : 0) + (min.y != max.y ? 1 : 0) + (min.z != max.z ? 1 : 0); |
| // Should use empty bounding-box if box is 1D or 0D. |
| FXL_DCHECK(dimensions >= 2); |
| #endif |
| } |
| |
| BoundingBox BoundingBox::NewChecked(vec3 min, vec3 max, uint32_t max_degenerate_dimensions) { |
| vec3 diff = max - min; |
| if (diff.x < 0.f || diff.y < 0.f || diff.z < 0.f) |
| return BoundingBox(); |
| |
| uint32_t degenerate_dimensions = |
| (diff.x == 0.f ? 1 : 0) + (diff.y == 0.f ? 1 : 0) + (diff.z == 0.f ? 1 : 0); |
| return degenerate_dimensions > max_degenerate_dimensions ? BoundingBox() : BoundingBox(min, max); |
| } |
| |
| BoundingBox& BoundingBox::Join(const BoundingBox& box) { |
| if (is_empty()) { |
| min_ = box.min_; |
| max_ = box.max_; |
| } else if (!box.is_empty()) { |
| min_ = glm::min(min_, box.min_); |
| max_ = glm::max(max_, box.max_); |
| } |
| return *this; |
| } |
| |
| BoundingBox& BoundingBox::Intersect(const BoundingBox& box) { |
| if (is_empty()) { |
| return *this; |
| } else if (box.is_empty()) { |
| *this = BoundingBox(); |
| } else { |
| min_ = glm::max(min_, box.min_); |
| max_ = glm::min(max_, box.max_); |
| if (min_.x > max_.x || min_.y > max_.y || min_.z > max_.z) { |
| *this = BoundingBox(); |
| } else { |
| int dimensions = |
| (min_.x != max_.x ? 1 : 0) + (min_.y != max_.y ? 1 : 0) + (min_.z != max_.z ? 1 : 0); |
| if (dimensions < 2) { |
| // We consider the intersection between boxes that touch at only one |
| // point or an edge to be empty. |
| *this = BoundingBox(); |
| } |
| } |
| } |
| return *this; |
| } |
| |
| BoundingBox operator*(const mat4& matrix, const BoundingBox& box) { |
| FXL_DCHECK(matrix[3][3] == 1.f); // No perspective allowed. |
| |
| if (box.is_empty()) { |
| return box; |
| } |
| |
| // Fancy trick to transform an AABB. |
| // See http://dev.theomader.com/transform-bounding-boxes/ |
| vec4 xa = box.min().x * matrix[0]; |
| vec4 xb = box.max().x * matrix[0]; |
| vec4 ya = box.min().y * matrix[1]; |
| vec4 yb = box.max().y * matrix[1]; |
| vec4 za = box.min().z * matrix[2]; |
| vec4 zb = box.max().z * matrix[2]; |
| vec3 min = glm::min(vec3(xa), vec3(xb)) + glm::min(vec3(ya), vec3(yb)) + |
| glm::min(vec3(za), vec3(zb)) + vec3(matrix[3]); |
| vec3 max = glm::max(vec3(xa), vec3(xb)) + glm::max(vec3(ya), vec3(yb)) + |
| glm::max(vec3(za), vec3(zb)) + vec3(matrix[3]); |
| |
| return BoundingBox(min, max); |
| } |
| |
| std::vector<plane3> BoundingBox::CreatePlanes() const { |
| std::array<glm::vec4, kNumPlanes> planes; |
| std::vector<escher::plane3> result; |
| result.resize(kNumPlanes); |
| |
| planes[0] = glm::vec4(1, 0, 0, min_.x); |
| planes[1] = glm::vec4(0, 1, 0, min_.y); |
| planes[2] = glm::vec4(0, 0, 1, min_.z); |
| planes[3] = glm::vec4(-1, 0, 0, -max_.x); |
| planes[4] = glm::vec4(0, -1, 0, -max_.y); |
| planes[5] = glm::vec4(0, 0, -1, -max_.z); |
| |
| FXL_DCHECK(PlanesAreValid(planes)); |
| |
| for (size_t i = 0; i < kNumPlanes; i++) { |
| glm::vec4 plane = planes[i]; |
| result[i] = plane3(glm::vec3(plane), plane.w); |
| } |
| return result; |
| } |
| |
| mat4 BoundingBox::CreateTransform() const { |
| mat4 transform = glm::translate(mat4(), min_); |
| transform = glm::scale(transform, vec3(width(), height(), depth())); |
| return transform; |
| } |
| |
| uint32_t BoundingBox::NumClippedCorners(const plane2& plane, const float_t& epsilon) const { |
| uint32_t count = 0; |
| float_t adjusted_epsilon = std::max(epsilon, 0.f); |
| count += PlaneClipsPoint(plane, vec2(min_.x, min_.y), adjusted_epsilon) ? 2 : 0; |
| count += PlaneClipsPoint(plane, vec2(min_.x, max_.y), adjusted_epsilon) ? 2 : 0; |
| count += PlaneClipsPoint(plane, vec2(max_.x, max_.y), adjusted_epsilon) ? 2 : 0; |
| count += PlaneClipsPoint(plane, vec2(max_.x, min_.y), adjusted_epsilon) ? 2 : 0; |
| return count; |
| } |
| |
| uint32_t BoundingBox::NumClippedCorners(const plane3& plane, const float_t& epsilon) const { |
| uint32_t count = 0; |
| float_t adjusted_epsilon = std::max(epsilon, 0.f); |
| count += PlaneClipsPoint(plane, vec3(min_.x, min_.y, min_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(min_.x, min_.y, max_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(min_.x, max_.y, min_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(min_.x, max_.y, max_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(max_.x, min_.y, min_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(max_.x, min_.y, max_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(max_.x, max_.y, min_.z), adjusted_epsilon) ? 1 : 0; |
| count += PlaneClipsPoint(plane, vec3(max_.x, max_.y, max_.z), adjusted_epsilon) ? 1 : 0; |
| |
| return count; |
| } |
| |
| } // namespace escher |