blob: b37ff2668d33a83c2e0246320f35e3e35c459eb7 [file] [log] [blame]
// Copyright 2013 The Flutter 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 "gtest/gtest.h"
#include "flutter/impeller/geometry/rect.h"
#include "flutter/impeller/geometry/geometry_asserts.h"
namespace impeller {
namespace testing {
TEST(RectTest, RectEmptyDeclaration) {
Rect rect;
EXPECT_EQ(rect.GetLeft(), 0.0f);
EXPECT_EQ(rect.GetTop(), 0.0f);
EXPECT_EQ(rect.GetRight(), 0.0f);
EXPECT_EQ(rect.GetBottom(), 0.0f);
EXPECT_EQ(rect.GetX(), 0.0f);
EXPECT_EQ(rect.GetY(), 0.0f);
EXPECT_EQ(rect.GetWidth(), 0.0f);
EXPECT_EQ(rect.GetHeight(), 0.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
TEST(RectTest, IRectEmptyDeclaration) {
IRect rect;
EXPECT_EQ(rect.GetLeft(), 0);
EXPECT_EQ(rect.GetTop(), 0);
EXPECT_EQ(rect.GetRight(), 0);
EXPECT_EQ(rect.GetBottom(), 0);
EXPECT_EQ(rect.GetX(), 0);
EXPECT_EQ(rect.GetY(), 0);
EXPECT_EQ(rect.GetWidth(), 0);
EXPECT_EQ(rect.GetHeight(), 0);
EXPECT_TRUE(rect.IsEmpty());
// EXPECT_TRUE(rect.IsFinite()); // should fail to compile
}
TEST(RectTest, RectDefaultConstructor) {
Rect rect = Rect();
EXPECT_EQ(rect.GetLeft(), 0.0f);
EXPECT_EQ(rect.GetTop(), 0.0f);
EXPECT_EQ(rect.GetRight(), 0.0f);
EXPECT_EQ(rect.GetBottom(), 0.0f);
EXPECT_EQ(rect.GetX(), 0.0f);
EXPECT_EQ(rect.GetY(), 0.0f);
EXPECT_EQ(rect.GetWidth(), 0.0f);
EXPECT_EQ(rect.GetHeight(), 0.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
TEST(RectTest, IRectDefaultConstructor) {
IRect rect = IRect();
EXPECT_EQ(rect.GetLeft(), 0);
EXPECT_EQ(rect.GetTop(), 0);
EXPECT_EQ(rect.GetRight(), 0);
EXPECT_EQ(rect.GetBottom(), 0);
EXPECT_EQ(rect.GetX(), 0);
EXPECT_EQ(rect.GetY(), 0);
EXPECT_EQ(rect.GetWidth(), 0);
EXPECT_EQ(rect.GetHeight(), 0);
EXPECT_TRUE(rect.IsEmpty());
}
TEST(RectTest, RectSimpleLTRB) {
// Using fractional-power-of-2 friendly values for equality tests
Rect rect = Rect::MakeLTRB(5.125f, 10.25f, 20.625f, 25.375f);
EXPECT_EQ(rect.GetLeft(), 5.125f);
EXPECT_EQ(rect.GetTop(), 10.25f);
EXPECT_EQ(rect.GetRight(), 20.625f);
EXPECT_EQ(rect.GetBottom(), 25.375f);
EXPECT_EQ(rect.GetX(), 5.125f);
EXPECT_EQ(rect.GetY(), 10.25f);
EXPECT_EQ(rect.GetWidth(), 15.5f);
EXPECT_EQ(rect.GetHeight(), 15.125f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
TEST(RectTest, IRectSimpleLTRB) {
IRect rect = IRect::MakeLTRB(5, 10, 20, 25);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), 20);
EXPECT_EQ(rect.GetBottom(), 25);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), 15);
EXPECT_EQ(rect.GetHeight(), 15);
EXPECT_FALSE(rect.IsEmpty());
}
TEST(RectTest, RectSimpleXYWH) {
// Using fractional-power-of-2 friendly values for equality tests
Rect rect = Rect::MakeXYWH(5.125f, 10.25f, 15.5f, 15.125f);
EXPECT_EQ(rect.GetLeft(), 5.125f);
EXPECT_EQ(rect.GetTop(), 10.25f);
EXPECT_EQ(rect.GetRight(), 20.625f);
EXPECT_EQ(rect.GetBottom(), 25.375f);
EXPECT_EQ(rect.GetX(), 5.125f);
EXPECT_EQ(rect.GetY(), 10.25f);
EXPECT_EQ(rect.GetWidth(), 15.5f);
EXPECT_EQ(rect.GetHeight(), 15.125f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
TEST(RectTest, IRectSimpleXYWH) {
IRect rect = IRect::MakeXYWH(5, 10, 15, 16);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), 20);
EXPECT_EQ(rect.GetBottom(), 26);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), 15);
EXPECT_EQ(rect.GetHeight(), 16);
EXPECT_FALSE(rect.IsEmpty());
}
TEST(RectTest, RectOverflowXYWH) {
auto min = std::numeric_limits<Scalar>::lowest();
auto max = std::numeric_limits<Scalar>::max();
auto inf = std::numeric_limits<Scalar>::infinity();
// 8 cases:
// finite X, max W
// max X, max W
// finite Y, max H
// max Y, max H
// finite X, min W
// min X, min W
// finite Y, min H
// min Y, min H
// a small finite value added to a max value will remain max
// a very large finite value (like max) added to max will go to infinity
{
Rect rect = Rect::MakeXYWH(5.0, 10.0f, max, 15.0f);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), max);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), max);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(max, 10.0f, max, 15.0f);
EXPECT_EQ(rect.GetLeft(), max);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), inf);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), max);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), inf);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_FALSE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(5.0f, 10.0f, 20.0f, max);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), 25.0f);
EXPECT_EQ(rect.GetBottom(), max);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), 20.0f);
EXPECT_EQ(rect.GetHeight(), max);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(5.0f, max, 20.0f, max);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), max);
EXPECT_EQ(rect.GetRight(), 25.0f);
EXPECT_EQ(rect.GetBottom(), inf);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), max);
EXPECT_EQ(rect.GetWidth(), 20.0f);
EXPECT_EQ(rect.GetHeight(), inf);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_FALSE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(5.0, 10.0f, min, 15.0f);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), min);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), min);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(min, 10.0f, min, 15.0f);
EXPECT_EQ(rect.GetLeft(), min);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), -inf);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), min);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), -inf);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_FALSE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(5.0f, 10.0f, 20.0f, min);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), 25.0f);
EXPECT_EQ(rect.GetBottom(), min);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), 20.0f);
EXPECT_EQ(rect.GetHeight(), min);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeXYWH(5.0f, min, 20.0f, min);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), min);
EXPECT_EQ(rect.GetRight(), 25.0f);
EXPECT_EQ(rect.GetBottom(), -inf);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), min);
EXPECT_EQ(rect.GetWidth(), 20.0f);
EXPECT_EQ(rect.GetHeight(), -inf);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_FALSE(rect.IsFinite());
}
}
TEST(RectTest, IRectOverflowXYWH) {
auto min = std::numeric_limits<int64_t>::min();
auto max = std::numeric_limits<int64_t>::max();
// 4 cases
// x near max, positive w takes it past max
// x near min, negative w takes it below min
// y near max, positive h takes it past max
// y near min, negative h takes it below min
{
IRect rect = IRect::MakeXYWH(max - 5, 10, 10, 16);
EXPECT_EQ(rect.GetLeft(), max - 5);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), max);
EXPECT_EQ(rect.GetBottom(), 26);
EXPECT_EQ(rect.GetX(), max - 5);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), 5);
EXPECT_EQ(rect.GetHeight(), 16);
EXPECT_FALSE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeXYWH(min + 5, 10, -10, 16);
EXPECT_EQ(rect.GetLeft(), min + 5);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), min);
EXPECT_EQ(rect.GetBottom(), 26);
EXPECT_EQ(rect.GetX(), min + 5);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), -5);
EXPECT_EQ(rect.GetHeight(), 16);
EXPECT_TRUE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeXYWH(5, max - 10, 10, 16);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), max - 10);
EXPECT_EQ(rect.GetRight(), 15);
EXPECT_EQ(rect.GetBottom(), max);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), max - 10);
EXPECT_EQ(rect.GetWidth(), 10);
EXPECT_EQ(rect.GetHeight(), 10);
EXPECT_FALSE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeXYWH(5, min + 10, 10, -16);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), min + 10);
EXPECT_EQ(rect.GetRight(), 15);
EXPECT_EQ(rect.GetBottom(), min);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), min + 10);
EXPECT_EQ(rect.GetWidth(), 10);
EXPECT_EQ(rect.GetHeight(), -10);
EXPECT_TRUE(rect.IsEmpty());
}
}
TEST(RectTest, RectOverflowLTRB) {
auto min = std::numeric_limits<Scalar>::lowest();
auto max = std::numeric_limits<Scalar>::max();
auto inf = std::numeric_limits<Scalar>::infinity();
// 8 cases:
// finite negative X, max W
// ~min X, ~max W
// finite negative Y, max H
// ~min Y, ~max H
// finite positive X, min W
// ~min X, ~min W
// finite positive Y, min H
// ~min Y, ~min H
// a small finite value subtracted from a max value will remain max
// a very large finite value (like min) subtracted from max will go to inf
{
Rect rect = Rect::MakeLTRB(-5.0f, 10.0f, max, 25.0f);
EXPECT_EQ(rect.GetLeft(), -5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), max);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), -5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), max);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(min + 5.0f, 10.0f, max - 5.0f, 25.0f);
EXPECT_EQ(rect.GetLeft(), min + 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), max - 5.0f);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), min + 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), inf);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(5.0f, -10.0f, 20.0f, max);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), -10.0f);
EXPECT_EQ(rect.GetRight(), 20.0f);
EXPECT_EQ(rect.GetBottom(), max);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), -10.0f);
EXPECT_EQ(rect.GetWidth(), 15.0f);
EXPECT_EQ(rect.GetHeight(), max);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(5.0f, min + 10.0f, 20.0f, max - 15.0f);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), min + 10.0f);
EXPECT_EQ(rect.GetRight(), 20.0f);
EXPECT_EQ(rect.GetBottom(), max - 15.0f);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), min + 10.0f);
EXPECT_EQ(rect.GetWidth(), 15.0f);
EXPECT_EQ(rect.GetHeight(), inf);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(5.0f, 10.0f, min, 25.0f);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), min);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), min);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(max - 5.0f, 10.0f, min + 10.0f, 25.0f);
EXPECT_EQ(rect.GetLeft(), max - 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), min + 10.0f);
EXPECT_EQ(rect.GetBottom(), 25.0f);
EXPECT_EQ(rect.GetX(), max - 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), -inf);
EXPECT_EQ(rect.GetHeight(), 15.0f);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(5.0f, 10.0f, 20.0f, min);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), 10.0f);
EXPECT_EQ(rect.GetRight(), 20.0f);
EXPECT_EQ(rect.GetBottom(), min);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), 10.0f);
EXPECT_EQ(rect.GetWidth(), 15.0f);
EXPECT_EQ(rect.GetHeight(), min);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
{
Rect rect = Rect::MakeLTRB(5.0f, max - 5.0f, 20.0f, min + 10.0f);
EXPECT_EQ(rect.GetLeft(), 5.0f);
EXPECT_EQ(rect.GetTop(), max - 5.0f);
EXPECT_EQ(rect.GetRight(), 20.0f);
EXPECT_EQ(rect.GetBottom(), min + 10.0f);
EXPECT_EQ(rect.GetX(), 5.0f);
EXPECT_EQ(rect.GetY(), max - 5.0f);
EXPECT_EQ(rect.GetWidth(), 15.0f);
EXPECT_EQ(rect.GetHeight(), -inf);
EXPECT_TRUE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
}
TEST(RectTest, IRectOverflowLTRB) {
auto min = std::numeric_limits<int64_t>::min();
auto max = std::numeric_limits<int64_t>::max();
// 4 cases
// negative l, r near max takes width past max
// positive l, r near min takes width below min
// negative t, b near max takes width past max
// positive t, b near min takes width below min
{
IRect rect = IRect::MakeLTRB(-10, 10, max - 5, 26);
EXPECT_EQ(rect.GetLeft(), -10);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), max - 5);
EXPECT_EQ(rect.GetBottom(), 26);
EXPECT_EQ(rect.GetX(), -10);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), max);
EXPECT_EQ(rect.GetHeight(), 16);
EXPECT_FALSE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeLTRB(10, 10, min + 5, 26);
EXPECT_EQ(rect.GetLeft(), 10);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), min + 5);
EXPECT_EQ(rect.GetBottom(), 26);
EXPECT_EQ(rect.GetX(), 10);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), min);
EXPECT_EQ(rect.GetHeight(), 16);
EXPECT_TRUE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeLTRB(5, -10, 15, max - 5);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), -10);
EXPECT_EQ(rect.GetRight(), 15);
EXPECT_EQ(rect.GetBottom(), max - 5);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), -10);
EXPECT_EQ(rect.GetWidth(), 10);
EXPECT_EQ(rect.GetHeight(), max);
EXPECT_FALSE(rect.IsEmpty());
}
{
IRect rect = IRect::MakeLTRB(5, 10, 15, min + 5);
EXPECT_EQ(rect.GetLeft(), 5);
EXPECT_EQ(rect.GetTop(), 10);
EXPECT_EQ(rect.GetRight(), 15);
EXPECT_EQ(rect.GetBottom(), min + 5);
EXPECT_EQ(rect.GetX(), 5);
EXPECT_EQ(rect.GetY(), 10);
EXPECT_EQ(rect.GetWidth(), 10);
EXPECT_EQ(rect.GetHeight(), min);
EXPECT_TRUE(rect.IsEmpty());
}
}
TEST(RectTest, RectMakeSize) {
{
Size s(100, 200);
Rect r = Rect::MakeSize(s);
Rect expected = Rect::MakeLTRB(0, 0, 100, 200);
EXPECT_RECT_NEAR(r, expected);
}
{
ISize s(100, 200);
Rect r = Rect::MakeSize(s);
Rect expected = Rect::MakeLTRB(0, 0, 100, 200);
EXPECT_RECT_NEAR(r, expected);
}
{
Size s(100, 200);
IRect r = IRect::MakeSize(s);
IRect expected = IRect::MakeLTRB(0, 0, 100, 200);
EXPECT_EQ(r, expected);
}
{
ISize s(100, 200);
IRect r = IRect::MakeSize(s);
IRect expected = IRect::MakeLTRB(0, 0, 100, 200);
EXPECT_EQ(r, expected);
}
}
TEST(RectTest, RectMakeMaximum) {
Rect rect = Rect::MakeMaximum();
auto inf = std::numeric_limits<Scalar>::infinity();
auto min = std::numeric_limits<Scalar>::lowest();
auto max = std::numeric_limits<Scalar>::max();
EXPECT_EQ(rect.GetLeft(), min);
EXPECT_EQ(rect.GetTop(), min);
EXPECT_EQ(rect.GetRight(), max);
EXPECT_EQ(rect.GetBottom(), max);
EXPECT_EQ(rect.GetX(), min);
EXPECT_EQ(rect.GetY(), min);
EXPECT_EQ(rect.GetWidth(), inf);
EXPECT_EQ(rect.GetHeight(), inf);
EXPECT_FALSE(rect.IsEmpty());
EXPECT_TRUE(rect.IsFinite());
}
TEST(RectTest, IRectMakeMaximum) {
IRect rect = IRect::MakeMaximum();
auto min = std::numeric_limits<int64_t>::min();
auto max = std::numeric_limits<int64_t>::max();
EXPECT_EQ(rect.GetLeft(), min);
EXPECT_EQ(rect.GetTop(), min);
EXPECT_EQ(rect.GetRight(), max);
EXPECT_EQ(rect.GetBottom(), max);
EXPECT_EQ(rect.GetX(), min);
EXPECT_EQ(rect.GetY(), min);
EXPECT_EQ(rect.GetWidth(), max);
EXPECT_EQ(rect.GetHeight(), max);
EXPECT_FALSE(rect.IsEmpty());
}
TEST(RectTest, RectFromRect) {
EXPECT_EQ(Rect(Rect::MakeXYWH(2, 3, 7, 15)),
Rect::MakeXYWH(2.0, 3.0, 7.0, 15.0));
EXPECT_EQ(Rect(Rect::MakeLTRB(2, 3, 7, 15)),
Rect::MakeLTRB(2.0, 3.0, 7.0, 15.0));
}
TEST(RectTest, IRectFromIRect) {
EXPECT_EQ(IRect(IRect::MakeXYWH(2, 3, 7, 15)), //
IRect::MakeXYWH(2, 3, 7, 15));
EXPECT_EQ(IRect(IRect::MakeLTRB(2, 3, 7, 15)), //
IRect::MakeLTRB(2, 3, 7, 15));
}
TEST(RectTest, RectCopy) {
// Using fractional-power-of-2 friendly values for equality tests
Rect rect = Rect::MakeLTRB(5.125f, 10.25f, 20.625f, 25.375f);
Rect copy = rect;
EXPECT_EQ(rect, copy);
EXPECT_EQ(copy.GetLeft(), 5.125f);
EXPECT_EQ(copy.GetTop(), 10.25f);
EXPECT_EQ(copy.GetRight(), 20.625f);
EXPECT_EQ(copy.GetBottom(), 25.375f);
EXPECT_EQ(copy.GetX(), 5.125f);
EXPECT_EQ(copy.GetY(), 10.25f);
EXPECT_EQ(copy.GetWidth(), 15.5f);
EXPECT_EQ(copy.GetHeight(), 15.125f);
EXPECT_FALSE(copy.IsEmpty());
EXPECT_TRUE(copy.IsFinite());
}
TEST(RectTest, IRectCopy) {
IRect rect = IRect::MakeLTRB(5, 10, 20, 25);
IRect copy = rect;
EXPECT_EQ(rect, copy);
EXPECT_EQ(copy.GetLeft(), 5);
EXPECT_EQ(copy.GetTop(), 10);
EXPECT_EQ(copy.GetRight(), 20);
EXPECT_EQ(copy.GetBottom(), 25);
EXPECT_EQ(copy.GetX(), 5);
EXPECT_EQ(copy.GetY(), 10);
EXPECT_EQ(copy.GetWidth(), 15);
EXPECT_EQ(copy.GetHeight(), 15);
EXPECT_FALSE(copy.IsEmpty());
}
TEST(RectTest, RectOriginSizeXYWHGetters) {
{
Rect r = Rect::MakeOriginSize({10, 20}, {50, 40});
EXPECT_EQ(r.GetOrigin(), Point(10, 20));
EXPECT_EQ(r.GetSize(), Size(50, 40));
EXPECT_EQ(r.GetX(), 10);
EXPECT_EQ(r.GetY(), 20);
EXPECT_EQ(r.GetWidth(), 50);
EXPECT_EQ(r.GetHeight(), 40);
auto expected_array = std::array<Scalar, 4>{10, 20, 50, 40};
EXPECT_EQ(r.GetXYWH(), expected_array);
}
{
Rect r = Rect::MakeLTRB(10, 20, 50, 40);
EXPECT_EQ(r.GetOrigin(), Point(10, 20));
EXPECT_EQ(r.GetSize(), Size(40, 20));
EXPECT_EQ(r.GetX(), 10);
EXPECT_EQ(r.GetY(), 20);
EXPECT_EQ(r.GetWidth(), 40);
EXPECT_EQ(r.GetHeight(), 20);
auto expected_array = std::array<Scalar, 4>{10, 20, 40, 20};
EXPECT_EQ(r.GetXYWH(), expected_array);
}
}
TEST(RectTest, IRectOriginSizeXYWHGetters) {
{
IRect r = IRect::MakeOriginSize({10, 20}, {50, 40});
EXPECT_EQ(r.GetOrigin(), IPoint(10, 20));
EXPECT_EQ(r.GetSize(), ISize(50, 40));
EXPECT_EQ(r.GetX(), 10);
EXPECT_EQ(r.GetY(), 20);
EXPECT_EQ(r.GetWidth(), 50);
EXPECT_EQ(r.GetHeight(), 40);
auto expected_array = std::array<int64_t, 4>{10, 20, 50, 40};
EXPECT_EQ(r.GetXYWH(), expected_array);
}
{
IRect r = IRect::MakeLTRB(10, 20, 50, 40);
EXPECT_EQ(r.GetOrigin(), IPoint(10, 20));
EXPECT_EQ(r.GetSize(), ISize(40, 20));
EXPECT_EQ(r.GetX(), 10);
EXPECT_EQ(r.GetY(), 20);
EXPECT_EQ(r.GetWidth(), 40);
EXPECT_EQ(r.GetHeight(), 20);
auto expected_array = std::array<int64_t, 4>{10, 20, 40, 20};
EXPECT_EQ(r.GetXYWH(), expected_array);
}
}
TEST(RectTest, RectRoundOutEmpty) {
Rect rect;
EXPECT_EQ(Rect::RoundOut(rect), Rect());
EXPECT_EQ(IRect::RoundOut(rect), IRect());
}
TEST(RectTest, RectRoundOutSimple) {
Rect rect = Rect::MakeLTRB(5.125f, 10.75f, 20.625f, 25.375f);
EXPECT_EQ(Rect::RoundOut(rect), Rect::MakeLTRB(5.0f, 10.0f, 21.0f, 26.0f));
EXPECT_EQ(IRect::RoundOut(rect), IRect::MakeLTRB(5, 10, 21, 26));
}
TEST(RectTest, RectRoundOutToIRectHuge) {
auto test = [](int corners) {
EXPECT_TRUE(corners >= 0 && corners <= 0xf);
Scalar l, t, r, b;
int64_t il, it, ir, ib;
l = il = 50;
t = it = 50;
r = ir = 80;
b = ib = 80;
if ((corners & (1 << 0)) != 0) {
l = -1E20;
il = std::numeric_limits<int64_t>::min();
}
if ((corners & (1 << 1)) != 0) {
t = -1E20;
it = std::numeric_limits<int64_t>::min();
}
if ((corners & (1 << 2)) != 0) {
r = +1E20;
ir = std::numeric_limits<int64_t>::max();
}
if ((corners & (1 << 3)) != 0) {
b = +1E20;
ib = std::numeric_limits<int64_t>::max();
}
Rect rect = Rect::MakeLTRB(l, t, r, b);
IRect irect = IRect::RoundOut(rect);
EXPECT_EQ(irect.GetLeft(), il) << corners;
EXPECT_EQ(irect.GetTop(), it) << corners;
EXPECT_EQ(irect.GetRight(), ir) << corners;
EXPECT_EQ(irect.GetBottom(), ib) << corners;
};
for (int corners = 0; corners <= 15; corners++) {
test(corners);
}
}
TEST(RectTest, RectDoesNotIntersectEmpty) {
Rect rect = Rect::MakeLTRB(50, 50, 100, 100);
auto test = [&rect](Scalar l, Scalar t, Scalar r, Scalar b,
const std::string& label) {
EXPECT_FALSE(rect.IntersectsWithRect(Rect::MakeLTRB(l, b, r, t)))
<< label << " with Top/Bottom swapped";
EXPECT_FALSE(rect.IntersectsWithRect(Rect::MakeLTRB(r, b, l, t)))
<< label << " with Left/Right swapped";
EXPECT_FALSE(rect.IntersectsWithRect(Rect::MakeLTRB(r, t, l, b)))
<< label << " with all sides swapped";
};
test(20, 20, 30, 30, "Above and Left");
test(70, 20, 80, 30, "Above");
test(120, 20, 130, 30, "Above and Right");
test(120, 70, 130, 80, "Right");
test(120, 120, 130, 130, "Below and Right");
test(70, 120, 80, 130, "Below");
test(20, 120, 30, 130, "Below and Left");
test(20, 70, 30, 80, "Left");
test(70, 70, 80, 80, "Inside");
test(40, 70, 60, 80, "Straddling Left");
test(70, 40, 80, 60, "Straddling Top");
test(90, 70, 110, 80, "Straddling Right");
test(70, 90, 80, 110, "Straddling Bottom");
}
TEST(RectTest, IRectDoesNotIntersectEmpty) {
IRect rect = IRect::MakeLTRB(50, 50, 100, 100);
auto test = [&rect](int64_t l, int64_t t, int64_t r, int64_t b,
const std::string& label) {
EXPECT_FALSE(rect.IntersectsWithRect(IRect::MakeLTRB(l, b, r, t)))
<< label << " with Top/Bottom swapped";
EXPECT_FALSE(rect.IntersectsWithRect(IRect::MakeLTRB(r, b, l, t)))
<< label << " with Left/Right swapped";
EXPECT_FALSE(rect.IntersectsWithRect(IRect::MakeLTRB(r, t, l, b)))
<< label << " with all sides swapped";
};
test(20, 20, 30, 30, "Above and Left");
test(70, 20, 80, 30, "Above");
test(120, 20, 130, 30, "Above and Right");
test(120, 70, 130, 80, "Right");
test(120, 120, 130, 130, "Below and Right");
test(70, 120, 80, 130, "Below");
test(20, 120, 30, 130, "Below and Left");
test(20, 70, 30, 80, "Left");
test(70, 70, 80, 80, "Inside");
test(40, 70, 60, 80, "Straddling Left");
test(70, 40, 80, 60, "Straddling Top");
test(90, 70, 110, 80, "Straddling Right");
test(70, 90, 80, 110, "Straddling Bottom");
}
TEST(RectTest, EmptyRectDoesNotIntersect) {
Rect rect = Rect::MakeLTRB(50, 50, 100, 100);
auto test = [&rect](Scalar l, Scalar t, Scalar r, Scalar b,
const std::string& label) {
EXPECT_FALSE(Rect::MakeLTRB(l, b, r, t).IntersectsWithRect(rect))
<< label << " with Top/Bottom swapped";
EXPECT_FALSE(Rect::MakeLTRB(r, b, l, t).IntersectsWithRect(rect))
<< label << " with Left/Right swapped";
EXPECT_FALSE(Rect::MakeLTRB(r, t, l, b).IntersectsWithRect(rect))
<< label << " with all sides swapped";
};
test(20, 20, 30, 30, "Above and Left");
test(70, 20, 80, 30, "Above");
test(120, 20, 130, 30, "Above and Right");
test(120, 70, 130, 80, "Right");
test(120, 120, 130, 130, "Below and Right");
test(70, 120, 80, 130, "Below");
test(20, 120, 30, 130, "Below and Left");
test(20, 70, 30, 80, "Left");
test(70, 70, 80, 80, "Inside");
test(40, 70, 60, 80, "Straddling Left");
test(70, 40, 80, 60, "Straddling Top");
test(90, 70, 110, 80, "Straddling Right");
test(70, 90, 80, 110, "Straddling Bottom");
}
TEST(RectTest, EmptyIRectDoesNotIntersect) {
IRect rect = IRect::MakeLTRB(50, 50, 100, 100);
auto test = [&rect](int64_t l, int64_t t, int64_t r, int64_t b,
const std::string& label) {
EXPECT_FALSE(IRect::MakeLTRB(l, b, r, t).IntersectsWithRect(rect))
<< label << " with Top/Bottom swapped";
EXPECT_FALSE(IRect::MakeLTRB(r, b, l, t).IntersectsWithRect(rect))
<< label << " with Left/Right swapped";
EXPECT_FALSE(IRect::MakeLTRB(r, t, l, b).IntersectsWithRect(rect))
<< label << " with all sides swapped";
};
test(20, 20, 30, 30, "Above and Left");
test(70, 20, 80, 30, "Above");
test(120, 20, 130, 30, "Above and Right");
test(120, 70, 130, 80, "Right");
test(120, 120, 130, 130, "Below and Right");
test(70, 120, 80, 130, "Below");
test(20, 120, 30, 130, "Below and Left");
test(20, 70, 30, 80, "Left");
test(70, 70, 80, 80, "Inside");
test(40, 70, 60, 80, "Straddling Left");
test(70, 40, 80, 60, "Straddling Top");
test(90, 70, 110, 80, "Straddling Right");
test(70, 90, 80, 110, "Straddling Bottom");
}
TEST(RectTest, RectScale) {
auto test1 = [](Rect rect, Scalar scale) {
Rect expected = Rect::MakeXYWH(rect.GetX() * scale, //
rect.GetY() * scale, //
rect.GetWidth() * scale, //
rect.GetHeight() * scale);
EXPECT_RECT_NEAR(rect.Scale(scale), expected) //
<< rect << " * " << scale;
EXPECT_RECT_NEAR(rect.Scale(scale, scale), expected) //
<< rect << " * " << scale;
EXPECT_RECT_NEAR(rect.Scale(Point(scale, scale)), expected) //
<< rect << " * " << scale;
EXPECT_RECT_NEAR(rect.Scale(Size(scale, scale)), expected) //
<< rect << " * " << scale;
};
auto test2 = [&test1](Rect rect, Scalar scale_x, Scalar scale_y) {
Rect expected = Rect::MakeXYWH(rect.GetX() * scale_x, //
rect.GetY() * scale_y, //
rect.GetWidth() * scale_x, //
rect.GetHeight() * scale_y);
EXPECT_RECT_NEAR(rect.Scale(scale_x, scale_y), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
EXPECT_RECT_NEAR(rect.Scale(Point(scale_x, scale_y)), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
EXPECT_RECT_NEAR(rect.Scale(Size(scale_x, scale_y)), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
test1(rect, scale_x);
test1(rect, scale_y);
};
test2(Rect::MakeLTRB(10, 15, 100, 150), 1.0, 0.0);
test2(Rect::MakeLTRB(10, 15, 100, 150), 0.0, 1.0);
test2(Rect::MakeLTRB(10, 15, 100, 150), 0.0, 0.0);
test2(Rect::MakeLTRB(10, 15, 100, 150), 2.5, 3.5);
test2(Rect::MakeLTRB(10, 15, 100, 150), 3.5, 2.5);
test2(Rect::MakeLTRB(10, 15, -100, 150), 2.5, 3.5);
test2(Rect::MakeLTRB(10, 15, 100, -150), 2.5, 3.5);
test2(Rect::MakeLTRB(10, 15, 100, 150), -2.5, 3.5);
test2(Rect::MakeLTRB(10, 15, 100, 150), 2.5, -3.5);
}
TEST(RectTest, IRectScale) {
auto test1 = [](IRect rect, int64_t scale) {
IRect expected = IRect::MakeXYWH(rect.GetX() * scale, //
rect.GetY() * scale, //
rect.GetWidth() * scale, //
rect.GetHeight() * scale);
EXPECT_EQ(rect.Scale(scale), expected) //
<< rect << " * " << scale;
EXPECT_EQ(rect.Scale(scale, scale), expected) //
<< rect << " * " << scale;
EXPECT_EQ(rect.Scale(IPoint(scale, scale)), expected) //
<< rect << " * " << scale;
EXPECT_EQ(rect.Scale(ISize(scale, scale)), expected) //
<< rect << " * " << scale;
};
auto test2 = [&test1](IRect rect, int64_t scale_x, int64_t scale_y) {
IRect expected = IRect::MakeXYWH(rect.GetX() * scale_x, //
rect.GetY() * scale_y, //
rect.GetWidth() * scale_x, //
rect.GetHeight() * scale_y);
EXPECT_EQ(rect.Scale(scale_x, scale_y), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
EXPECT_EQ(rect.Scale(IPoint(scale_x, scale_y)), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
EXPECT_EQ(rect.Scale(ISize(scale_x, scale_y)), expected) //
<< rect << " * " << scale_x << ", " << scale_y;
test1(rect, scale_x);
test1(rect, scale_y);
};
test2(IRect::MakeLTRB(10, 15, 100, 150), 2, 3);
test2(IRect::MakeLTRB(10, 15, 100, 150), 3, 2);
test2(IRect::MakeLTRB(10, 15, -100, 150), 2, 3);
test2(IRect::MakeLTRB(10, 15, 100, -150), 2, 3);
test2(IRect::MakeLTRB(10, 15, 100, 150), -2, 3);
test2(IRect::MakeLTRB(10, 15, 100, 150), 2, -3);
}
TEST(RectTest, RectArea) {
EXPECT_EQ(Rect::MakeXYWH(0, 0, 100, 200).Area(), 20000);
EXPECT_EQ(Rect::MakeXYWH(10, 20, 100, 200).Area(), 20000);
EXPECT_EQ(Rect::MakeXYWH(0, 0, 200, 100).Area(), 20000);
EXPECT_EQ(Rect::MakeXYWH(10, 20, 200, 100).Area(), 20000);
EXPECT_EQ(Rect::MakeXYWH(0, 0, 100, 100).Area(), 10000);
EXPECT_EQ(Rect::MakeXYWH(10, 20, 100, 100).Area(), 10000);
}
TEST(RectTest, IRectArea) {
EXPECT_EQ(IRect::MakeXYWH(0, 0, 100, 200).Area(), 20000);
EXPECT_EQ(IRect::MakeXYWH(10, 20, 100, 200).Area(), 20000);
EXPECT_EQ(IRect::MakeXYWH(0, 0, 200, 100).Area(), 20000);
EXPECT_EQ(IRect::MakeXYWH(10, 20, 200, 100).Area(), 20000);
EXPECT_EQ(IRect::MakeXYWH(0, 0, 100, 100).Area(), 10000);
EXPECT_EQ(IRect::MakeXYWH(10, 20, 100, 100).Area(), 10000);
}
TEST(RectTest, RectGetNormalizingTransform) {
{
// Checks for expected matrix values
auto r = Rect::MakeXYWH(100, 200, 200, 400);
EXPECT_EQ(r.GetNormalizingTransform(),
Matrix::MakeScale({0.005, 0.0025, 1.0}) *
Matrix::MakeTranslation({-100, -200}));
}
{
// Checks for expected transform of points relative to the rect
auto r = Rect::MakeLTRB(300, 500, 400, 700);
auto m = r.GetNormalizingTransform();
// The 4 corners of the rect => (0, 0) to (1, 1)
EXPECT_EQ(m * Point(300, 500), Point(0, 0));
EXPECT_EQ(m * Point(400, 500), Point(1, 0));
EXPECT_EQ(m * Point(400, 700), Point(1, 1));
EXPECT_EQ(m * Point(300, 700), Point(0, 1));
// The center => (0.5, 0.5)
EXPECT_EQ(m * Point(350, 600), Point(0.5, 0.5));
// Outside the 4 corners => (-1, -1) to (2, 2)
EXPECT_EQ(m * Point(200, 300), Point(-1, -1));
EXPECT_EQ(m * Point(500, 300), Point(2, -1));
EXPECT_EQ(m * Point(500, 900), Point(2, 2));
EXPECT_EQ(m * Point(200, 900), Point(-1, 2));
}
{
// Checks for behavior with empty rects
auto zero = Matrix::MakeScale({0.0, 0.0, 1.0});
// Empty for width and/or height == 0
EXPECT_EQ(Rect::MakeXYWH(10, 10, 0, 10).GetNormalizingTransform(), zero);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, 0).GetNormalizingTransform(), zero);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 0, 0).GetNormalizingTransform(), zero);
// Empty for width and/or height < 0
EXPECT_EQ(Rect::MakeXYWH(10, 10, -1, 10).GetNormalizingTransform(), zero);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, -1).GetNormalizingTransform(), zero);
EXPECT_EQ(Rect::MakeXYWH(10, 10, -1, -1).GetNormalizingTransform(), zero);
}
{
// Checks for behavior with non-finite rects
auto z = Matrix::MakeScale({0.0, 0.0, 1.0});
auto nan = std::numeric_limits<Scalar>::quiet_NaN();
auto inf = std::numeric_limits<Scalar>::infinity();
// Non-finite for width and/or height == nan
EXPECT_EQ(Rect::MakeXYWH(10, 10, nan, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, nan).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, nan, nan).GetNormalizingTransform(), z);
// Non-finite for width and/or height == inf
EXPECT_EQ(Rect::MakeXYWH(10, 10, inf, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, inf).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, inf, inf).GetNormalizingTransform(), z);
// Non-finite for width and/or height == -inf
EXPECT_EQ(Rect::MakeXYWH(10, 10, -inf, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, -inf).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, 10, -inf, -inf).GetNormalizingTransform(), z);
// Non-finite for origin X and/or Y == nan
EXPECT_EQ(Rect::MakeXYWH(nan, 10, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, nan, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(nan, nan, 10, 10).GetNormalizingTransform(), z);
// Non-finite for origin X and/or Y == inf
EXPECT_EQ(Rect::MakeXYWH(inf, 10, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, inf, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(inf, inf, 10, 10).GetNormalizingTransform(), z);
// Non-finite for origin X and/or Y == -inf
EXPECT_EQ(Rect::MakeXYWH(-inf, 10, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(10, -inf, 10, 10).GetNormalizingTransform(), z);
EXPECT_EQ(Rect::MakeXYWH(-inf, -inf, 10, 10).GetNormalizingTransform(), z);
}
}
TEST(RectTest, IRectGetNormalizingTransform) {
{
// Checks for expected matrix values
auto r = IRect::MakeXYWH(100, 200, 200, 400);
EXPECT_EQ(r.GetNormalizingTransform(),
Matrix::MakeScale({0.005, 0.0025, 1.0}) *
Matrix::MakeTranslation({-100, -200}));
}
{
// Checks for expected transform of points relative to the rect
auto r = IRect::MakeLTRB(300, 500, 400, 700);
auto m = r.GetNormalizingTransform();
// The 4 corners of the rect => (0, 0) to (1, 1)
EXPECT_EQ(m * Point(300, 500), Point(0, 0));
EXPECT_EQ(m * Point(400, 500), Point(1, 0));
EXPECT_EQ(m * Point(400, 700), Point(1, 1));
EXPECT_EQ(m * Point(300, 700), Point(0, 1));
// The center => (0.5, 0.5)
EXPECT_EQ(m * Point(350, 600), Point(0.5, 0.5));
// Outside the 4 corners => (-1, -1) to (2, 2)
EXPECT_EQ(m * Point(200, 300), Point(-1, -1));
EXPECT_EQ(m * Point(500, 300), Point(2, -1));
EXPECT_EQ(m * Point(500, 900), Point(2, 2));
EXPECT_EQ(m * Point(200, 900), Point(-1, 2));
}
{
// Checks for behavior with empty rects
auto zero = Matrix::MakeScale({0.0, 0.0, 1.0});
// Empty for width and/or height == 0
EXPECT_EQ(IRect::MakeXYWH(10, 10, 0, 10).GetNormalizingTransform(), zero);
EXPECT_EQ(IRect::MakeXYWH(10, 10, 10, 0).GetNormalizingTransform(), zero);
EXPECT_EQ(IRect::MakeXYWH(10, 10, 0, 0).GetNormalizingTransform(), zero);
// Empty for width and/or height < 0
EXPECT_EQ(IRect::MakeXYWH(10, 10, -1, 10).GetNormalizingTransform(), zero);
EXPECT_EQ(IRect::MakeXYWH(10, 10, 10, -1).GetNormalizingTransform(), zero);
EXPECT_EQ(IRect::MakeXYWH(10, 10, -1, -1).GetNormalizingTransform(), zero);
}
}
TEST(RectTest, RectXYWHIsEmpty) {
auto nan = std::numeric_limits<Scalar>::quiet_NaN();
// Non-empty
EXPECT_FALSE(Rect::MakeXYWH(1.5, 2.3, 10.5, 7.2).IsEmpty());
// Empty both width and height both 0 or negative, in all combinations
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 0.0, 0.0).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, -1.0, -1.0).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 0.0, -1.0).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, -1.0, 0.0).IsEmpty());
// Empty for 0 or negative width or height (but not both at the same time)
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 10.5, 0.0).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 10.5, -1.0).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 0.0, 7.2).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, -1.0, 7.2).IsEmpty());
// Empty for NaN in width or height or both
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, 10.5, nan).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, nan, 7.2).IsEmpty());
EXPECT_TRUE(Rect::MakeXYWH(1.5, 2.3, nan, nan).IsEmpty());
}
TEST(RectTest, IRectXYWHIsEmpty) {
// Non-empty
EXPECT_FALSE(IRect::MakeXYWH(1, 2, 10, 7).IsEmpty());
// Empty both width and height both 0 or negative, in all combinations
EXPECT_TRUE(IRect::MakeXYWH(1, 2, 0, 0).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, -1, -1).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, -1, 0).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, 0, -1).IsEmpty());
// Empty for 0 or negative width or height (but not both at the same time)
EXPECT_TRUE(IRect::MakeXYWH(1, 2, 10, 0).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, 10, -1).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, 0, 7).IsEmpty());
EXPECT_TRUE(IRect::MakeXYWH(1, 2, -1, 7).IsEmpty());
}
TEST(RectTest, MakePointBoundsQuad) {
Quad quad = {
Point(10, 10),
Point(20, 10),
Point(10, 20),
Point(20, 20),
};
std::optional<Rect> bounds = Rect::MakePointBounds(quad);
EXPECT_TRUE(bounds.has_value());
if (bounds.has_value()) {
EXPECT_TRUE(RectNear(bounds.value(), Rect::MakeLTRB(10, 10, 20, 20)));
}
}
TEST(RectTest, IsSquare) {
EXPECT_TRUE(Rect::MakeXYWH(10, 30, 20, 20).IsSquare());
EXPECT_FALSE(Rect::MakeXYWH(10, 30, 20, 19).IsSquare());
EXPECT_FALSE(Rect::MakeXYWH(10, 30, 19, 20).IsSquare());
EXPECT_TRUE(Rect::MakeMaximum().IsSquare());
EXPECT_TRUE(IRect::MakeXYWH(10, 30, 20, 20).IsSquare());
EXPECT_FALSE(IRect::MakeXYWH(10, 30, 20, 19).IsSquare());
EXPECT_FALSE(IRect::MakeXYWH(10, 30, 19, 20).IsSquare());
EXPECT_TRUE(IRect::MakeMaximum().IsSquare());
}
TEST(RectTest, GetCenter) {
EXPECT_EQ(Rect::MakeXYWH(10, 30, 20, 20).GetCenter(), Point(20, 40));
EXPECT_EQ(Rect::MakeXYWH(10, 30, 20, 19).GetCenter(), Point(20, 39.5));
EXPECT_EQ(Rect::MakeMaximum().GetCenter(), Point(0, 0));
// Note that we expect a Point as the answer from an IRect
EXPECT_EQ(IRect::MakeXYWH(10, 30, 20, 20).GetCenter(), Point(20, 40));
EXPECT_EQ(IRect::MakeXYWH(10, 30, 20, 19).GetCenter(), Point(20, 39.5));
EXPECT_EQ(IRect::MakeMaximum().GetCenter(), Point(0, 0));
}
TEST(RectTest, RectExpand) {
auto rect = Rect::MakeLTRB(100, 100, 200, 200);
// Expand(T amount)
EXPECT_EQ(rect.Expand(10), Rect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(-10), Rect::MakeLTRB(110, 110, 190, 190));
// Expand(amount, amount)
EXPECT_EQ(rect.Expand(10, 10), Rect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(10, -10), Rect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(-10, 10), Rect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(-10, -10), Rect::MakeLTRB(110, 110, 190, 190));
// Expand(amount, amount, amount, amount)
EXPECT_EQ(rect.Expand(10, 20, 30, 40), Rect::MakeLTRB(90, 80, 230, 240));
EXPECT_EQ(rect.Expand(-10, 20, 30, 40), Rect::MakeLTRB(110, 80, 230, 240));
EXPECT_EQ(rect.Expand(10, -20, 30, 40), Rect::MakeLTRB(90, 120, 230, 240));
EXPECT_EQ(rect.Expand(10, 20, -30, 40), Rect::MakeLTRB(90, 80, 170, 240));
EXPECT_EQ(rect.Expand(10, 20, 30, -40), Rect::MakeLTRB(90, 80, 230, 160));
// Expand(Point amount)
EXPECT_EQ(rect.Expand(Point{10, 10}), Rect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(Point{10, -10}), Rect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(Point{-10, 10}), Rect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(Point{-10, -10}), Rect::MakeLTRB(110, 110, 190, 190));
// Expand(Size amount)
EXPECT_EQ(rect.Expand(Size{10, 10}), Rect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(Size{10, -10}), Rect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(Size{-10, 10}), Rect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(Size{-10, -10}), Rect::MakeLTRB(110, 110, 190, 190));
}
TEST(RectTest, IRectExpand) {
auto rect = IRect::MakeLTRB(100, 100, 200, 200);
// Expand(T amount)
EXPECT_EQ(rect.Expand(10), IRect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(-10), IRect::MakeLTRB(110, 110, 190, 190));
// Expand(amount, amount)
EXPECT_EQ(rect.Expand(10, 10), IRect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(10, -10), IRect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(-10, 10), IRect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(-10, -10), IRect::MakeLTRB(110, 110, 190, 190));
// Expand(amount, amount, amount, amount)
EXPECT_EQ(rect.Expand(10, 20, 30, 40), IRect::MakeLTRB(90, 80, 230, 240));
EXPECT_EQ(rect.Expand(-10, 20, 30, 40), IRect::MakeLTRB(110, 80, 230, 240));
EXPECT_EQ(rect.Expand(10, -20, 30, 40), IRect::MakeLTRB(90, 120, 230, 240));
EXPECT_EQ(rect.Expand(10, 20, -30, 40), IRect::MakeLTRB(90, 80, 170, 240));
EXPECT_EQ(rect.Expand(10, 20, 30, -40), IRect::MakeLTRB(90, 80, 230, 160));
// Expand(IPoint amount)
EXPECT_EQ(rect.Expand(IPoint{10, 10}), IRect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(IPoint{10, -10}), IRect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(IPoint{-10, 10}), IRect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(IPoint{-10, -10}), IRect::MakeLTRB(110, 110, 190, 190));
// Expand(ISize amount)
EXPECT_EQ(rect.Expand(ISize{10, 10}), IRect::MakeLTRB(90, 90, 210, 210));
EXPECT_EQ(rect.Expand(ISize{10, -10}), IRect::MakeLTRB(90, 110, 210, 190));
EXPECT_EQ(rect.Expand(ISize{-10, 10}), IRect::MakeLTRB(110, 90, 190, 210));
EXPECT_EQ(rect.Expand(ISize{-10, -10}), IRect::MakeLTRB(110, 110, 190, 190));
}
TEST(RectTest, ContainsFloatingPoint) {
auto rect1 =
Rect::MakeXYWH(472.599945f, 440.999969f, 1102.80005f, 654.000061f);
auto rect2 = Rect::MakeXYWH(724.f, 618.f, 600.f, 300.f);
EXPECT_TRUE(rect1.Contains(rect2));
}
template <typename R>
static constexpr inline R flip_lr(R rect) {
return R::MakeLTRB(rect.GetRight(), rect.GetTop(), //
rect.GetLeft(), rect.GetBottom());
}
template <typename R>
static constexpr inline R flip_tb(R rect) {
return R::MakeLTRB(rect.GetLeft(), rect.GetBottom(), //
rect.GetRight(), rect.GetTop());
}
template <typename R>
static constexpr inline R flip_lrtb(R rect) {
return flip_lr(flip_tb(rect));
}
static constexpr inline Rect swap_nan(const Rect& rect, int index) {
Scalar nan = std::numeric_limits<Scalar>::quiet_NaN();
FML_DCHECK(index >= 0 && index <= 15);
Scalar l = ((index & (1 << 0)) != 0) ? nan : rect.GetLeft();
Scalar t = ((index & (1 << 1)) != 0) ? nan : rect.GetTop();
Scalar r = ((index & (1 << 2)) != 0) ? nan : rect.GetRight();
Scalar b = ((index & (1 << 3)) != 0) ? nan : rect.GetBottom();
return Rect::MakeLTRB(l, t, r, b);
}
static constexpr inline Point swap_nan(const Point& point, int index) {
Scalar nan = std::numeric_limits<Scalar>::quiet_NaN();
FML_DCHECK(index >= 0 && index <= 3);
Scalar x = ((index & (1 << 0)) != 0) ? nan : point.x;
Scalar y = ((index & (1 << 1)) != 0) ? nan : point.y;
return Point(x, y);
}
TEST(RectTest, RectUnion) {
auto check_nans = [](const Rect& a, const Rect& b, const std::string& label) {
ASSERT_TRUE(a.IsFinite()) << label;
ASSERT_TRUE(b.IsFinite()) << label;
ASSERT_FALSE(a.Union(b).IsEmpty());
for (int i = 1; i < 16; i++) {
// NaN in a produces b
EXPECT_EQ(swap_nan(a, i).Union(b), b) << label << ", index = " << i;
// NaN in b produces a
EXPECT_EQ(a.Union(swap_nan(b, i)), a) << label << ", index = " << i;
// NaN in both is empty
for (int j = 1; j < 16; j++) {
EXPECT_TRUE(swap_nan(a, i).Union(swap_nan(b, j)).IsEmpty())
<< label << ", indices = " << i << ", " << j;
}
}
};
auto check_empty_flips = [](const Rect& a, const Rect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_EQ(a.Union(flip_lr(b)), a) << label;
EXPECT_EQ(a.Union(flip_tb(b)), a) << label;
EXPECT_EQ(a.Union(flip_lrtb(b)), a) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_EQ(flip_lr(a).Union(b), b) << label;
EXPECT_EQ(flip_tb(a).Union(b), b) << label;
EXPECT_EQ(flip_lrtb(a).Union(b), b) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_TRUE(flip_lr(a).Union(flip_lr(b)).IsEmpty()) << label;
EXPECT_TRUE(flip_tb(a).Union(flip_tb(b)).IsEmpty()) << label;
EXPECT_TRUE(flip_lrtb(a).Union(flip_lrtb(b)).IsEmpty()) << label;
};
auto test = [&check_nans, &check_empty_flips](const Rect& a, const Rect& b,
const Rect& result) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_EQ(a.Union(b), result) << label;
EXPECT_EQ(b.Union(a), result) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 0, 0);
auto expected = Rect::MakeXYWH(100, 100, 100, 100);
test(a, b, expected);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 1, 1);
auto expected = Rect::MakeXYWH(0, 0, 200, 200);
test(a, b, expected);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 1, 1);
auto expected = Rect::MakeXYWH(10, 10, 190, 190);
test(a, b, expected);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 100, 100);
auto expected = Rect::MakeXYWH(0, 0, 110, 110);
test(a, b, expected);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(100, 100, 100, 100);
auto expected = Rect::MakeXYWH(0, 0, 200, 200);
test(a, b, expected);
}
}
TEST(RectTest, OptRectUnion) {
auto a = Rect::MakeLTRB(0, 0, 100, 100);
auto b = Rect::MakeLTRB(100, 100, 200, 200);
auto c = Rect::MakeLTRB(100, 0, 200, 100);
// NullOpt, NullOpt
EXPECT_FALSE(Rect::Union(std::nullopt, std::nullopt).has_value());
EXPECT_EQ(Rect::Union(std::nullopt, std::nullopt), std::nullopt);
auto test1 = [](const Rect& r) {
// Rect, NullOpt
EXPECT_TRUE(Rect::Union(r, std::nullopt).has_value());
EXPECT_EQ(Rect::Union(r, std::nullopt).value(), r);
// OptRect, NullOpt
EXPECT_TRUE(Rect::Union(std::optional(r), std::nullopt).has_value());
EXPECT_EQ(Rect::Union(std::optional(r), std::nullopt).value(), r);
// NullOpt, Rect
EXPECT_TRUE(Rect::Union(std::nullopt, r).has_value());
EXPECT_EQ(Rect::Union(std::nullopt, r).value(), r);
// NullOpt, OptRect
EXPECT_TRUE(Rect::Union(std::nullopt, std::optional(r)).has_value());
EXPECT_EQ(Rect::Union(std::nullopt, std::optional(r)).value(), r);
};
test1(a);
test1(b);
test1(c);
auto test2 = [](const Rect& a, const Rect& b, const Rect& u) {
ASSERT_EQ(a.Union(b), u);
// Rect, OptRect
EXPECT_TRUE(Rect::Union(a, std::optional(b)).has_value());
EXPECT_EQ(Rect::Union(a, std::optional(b)).value(), u);
// OptRect, Rect
EXPECT_TRUE(Rect::Union(std::optional(a), b).has_value());
EXPECT_EQ(Rect::Union(std::optional(a), b).value(), u);
// OptRect, OptRect
EXPECT_TRUE(Rect::Union(std::optional(a), std::optional(b)).has_value());
EXPECT_EQ(Rect::Union(std::optional(a), std::optional(b)).value(), u);
};
test2(a, b, Rect::MakeLTRB(0, 0, 200, 200));
test2(a, c, Rect::MakeLTRB(0, 0, 200, 100));
test2(b, c, Rect::MakeLTRB(100, 0, 200, 200));
}
TEST(RectTest, IRectUnion) {
auto check_empty_flips = [](const IRect& a, const IRect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_EQ(a.Union(flip_lr(b)), a) << label;
EXPECT_EQ(a.Union(flip_tb(b)), a) << label;
EXPECT_EQ(a.Union(flip_lrtb(b)), a) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_EQ(flip_lr(a).Union(b), b) << label;
EXPECT_EQ(flip_tb(a).Union(b), b) << label;
EXPECT_EQ(flip_lrtb(a).Union(b), b) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_TRUE(flip_lr(a).Union(flip_lr(b)).IsEmpty()) << label;
EXPECT_TRUE(flip_tb(a).Union(flip_tb(b)).IsEmpty()) << label;
EXPECT_TRUE(flip_lrtb(a).Union(flip_lrtb(b)).IsEmpty()) << label;
};
auto test = [&check_empty_flips](const IRect& a, const IRect& b,
const IRect& result) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_EQ(a.Union(b), result) << label;
EXPECT_EQ(b.Union(a), result) << label;
check_empty_flips(a, b, label);
};
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 0, 0);
auto expected = IRect::MakeXYWH(100, 100, 100, 100);
test(a, b, expected);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 1, 1);
auto expected = IRect::MakeXYWH(0, 0, 200, 200);
test(a, b, expected);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 1, 1);
auto expected = IRect::MakeXYWH(10, 10, 190, 190);
test(a, b, expected);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 100, 100);
auto expected = IRect::MakeXYWH(0, 0, 110, 110);
test(a, b, expected);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(100, 100, 100, 100);
auto expected = IRect::MakeXYWH(0, 0, 200, 200);
test(a, b, expected);
}
}
TEST(RectTest, OptIRectUnion) {
auto a = IRect::MakeLTRB(0, 0, 100, 100);
auto b = IRect::MakeLTRB(100, 100, 200, 200);
auto c = IRect::MakeLTRB(100, 0, 200, 100);
// NullOpt, NullOpt
EXPECT_FALSE(IRect::Union(std::nullopt, std::nullopt).has_value());
EXPECT_EQ(IRect::Union(std::nullopt, std::nullopt), std::nullopt);
auto test1 = [](const IRect& r) {
// Rect, NullOpt
EXPECT_TRUE(IRect::Union(r, std::nullopt).has_value());
EXPECT_EQ(IRect::Union(r, std::nullopt).value(), r);
// OptRect, NullOpt
EXPECT_TRUE(IRect::Union(std::optional(r), std::nullopt).has_value());
EXPECT_EQ(IRect::Union(std::optional(r), std::nullopt).value(), r);
// NullOpt, Rect
EXPECT_TRUE(IRect::Union(std::nullopt, r).has_value());
EXPECT_EQ(IRect::Union(std::nullopt, r).value(), r);
// NullOpt, OptRect
EXPECT_TRUE(IRect::Union(std::nullopt, std::optional(r)).has_value());
EXPECT_EQ(IRect::Union(std::nullopt, std::optional(r)).value(), r);
};
test1(a);
test1(b);
test1(c);
auto test2 = [](const IRect& a, const IRect& b, const IRect& u) {
ASSERT_EQ(a.Union(b), u);
// Rect, OptRect
EXPECT_TRUE(IRect::Union(a, std::optional(b)).has_value());
EXPECT_EQ(IRect::Union(a, std::optional(b)).value(), u);
// OptRect, Rect
EXPECT_TRUE(IRect::Union(std::optional(a), b).has_value());
EXPECT_EQ(IRect::Union(std::optional(a), b).value(), u);
// OptRect, OptRect
EXPECT_TRUE(IRect::Union(std::optional(a), std::optional(b)).has_value());
EXPECT_EQ(IRect::Union(std::optional(a), std::optional(b)).value(), u);
};
test2(a, b, IRect::MakeLTRB(0, 0, 200, 200));
test2(a, c, IRect::MakeLTRB(0, 0, 200, 100));
test2(b, c, IRect::MakeLTRB(100, 0, 200, 200));
}
TEST(RectTest, RectIntersection) {
auto check_nans = [](const Rect& a, const Rect& b, const std::string& label) {
ASSERT_TRUE(a.IsFinite()) << label;
ASSERT_TRUE(b.IsFinite()) << label;
for (int i = 1; i < 16; i++) {
// NaN in a produces empty
EXPECT_FALSE(swap_nan(a, i).Intersection(b).has_value())
<< label << ", index = " << i;
// NaN in b produces empty
EXPECT_FALSE(a.Intersection(swap_nan(b, i)).has_value())
<< label << ", index = " << i;
// NaN in both is empty
for (int j = 1; j < 16; j++) {
EXPECT_FALSE(swap_nan(a, i).Intersection(swap_nan(b, j)).has_value())
<< label << ", indices = " << i << ", " << j;
}
}
};
auto check_empty_flips = [](const Rect& a, const Rect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_FALSE(a.Intersection(flip_lr(b)).has_value()) << label;
EXPECT_FALSE(a.Intersection(flip_tb(b)).has_value()) << label;
EXPECT_FALSE(a.Intersection(flip_lrtb(b)).has_value()) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_FALSE(flip_lr(a).Intersection(b).has_value()) << label;
EXPECT_FALSE(flip_tb(a).Intersection(b).has_value()) << label;
EXPECT_FALSE(flip_lrtb(a).Intersection(b).has_value()) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).Intersection(flip_lr(b)).has_value()) << label;
EXPECT_FALSE(flip_tb(a).Intersection(flip_tb(b)).has_value()) << label;
EXPECT_FALSE(flip_lrtb(a).Intersection(flip_lrtb(b)).has_value()) << label;
};
auto test_non_empty = [&check_nans, &check_empty_flips](
const Rect& a, const Rect& b, const Rect& result) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_TRUE(a.Intersection(b).has_value()) << label;
EXPECT_TRUE(b.Intersection(a).has_value()) << label;
EXPECT_EQ(a.Intersection(b), result) << label;
EXPECT_EQ(b.Intersection(a), result) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
auto test_empty = [&check_nans, &check_empty_flips](const Rect& a,
const Rect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_FALSE(a.Intersection(b).has_value()) << label;
EXPECT_FALSE(b.Intersection(a).has_value()) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 0, 0);
test_empty(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 0, 0);
test_empty(a, b);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 100, 100);
auto expected = Rect::MakeXYWH(10, 10, 90, 90);
test_non_empty(a, b, expected);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(100, 100, 100, 100);
test_empty(a, b);
}
{
auto a = Rect::MakeMaximum();
auto b = Rect::MakeXYWH(10, 10, 300, 300);
test_non_empty(a, b, b);
}
{
auto a = Rect::MakeMaximum();
auto b = Rect::MakeMaximum();
test_non_empty(a, b, Rect::MakeMaximum());
}
}
TEST(RectTest, OptRectIntersection) {
auto a = Rect::MakeLTRB(0, 0, 110, 110);
auto b = Rect::MakeLTRB(100, 100, 200, 200);
auto c = Rect::MakeLTRB(100, 0, 200, 110);
// NullOpt, NullOpt
EXPECT_FALSE(Rect::Intersection(std::nullopt, std::nullopt).has_value());
EXPECT_EQ(Rect::Intersection(std::nullopt, std::nullopt), std::nullopt);
auto test1 = [](const Rect& r) {
// Rect, NullOpt
EXPECT_TRUE(Rect::Intersection(r, std::nullopt).has_value());
EXPECT_EQ(Rect::Intersection(r, std::nullopt).value(), r);
// OptRect, NullOpt
EXPECT_TRUE(Rect::Intersection(std::optional(r), std::nullopt).has_value());
EXPECT_EQ(Rect::Intersection(std::optional(r), std::nullopt).value(), r);
// NullOpt, Rect
EXPECT_TRUE(Rect::Intersection(std::nullopt, r).has_value());
EXPECT_EQ(Rect::Intersection(std::nullopt, r).value(), r);
// NullOpt, OptRect
EXPECT_TRUE(Rect::Intersection(std::nullopt, std::optional(r)).has_value());
EXPECT_EQ(Rect::Intersection(std::nullopt, std::optional(r)).value(), r);
};
test1(a);
test1(b);
test1(c);
auto test2 = [](const Rect& a, const Rect& b, const Rect& i) {
ASSERT_EQ(a.Intersection(b), i);
// Rect, OptRect
EXPECT_TRUE(Rect::Intersection(a, std::optional(b)).has_value());
EXPECT_EQ(Rect::Intersection(a, std::optional(b)).value(), i);
// OptRect, Rect
EXPECT_TRUE(Rect::Intersection(std::optional(a), b).has_value());
EXPECT_EQ(Rect::Intersection(std::optional(a), b).value(), i);
// OptRect, OptRect
EXPECT_TRUE(
Rect::Intersection(std::optional(a), std::optional(b)).has_value());
EXPECT_EQ(Rect::Intersection(std::optional(a), std::optional(b)).value(),
i);
};
test2(a, b, Rect::MakeLTRB(100, 100, 110, 110));
test2(a, c, Rect::MakeLTRB(100, 0, 110, 110));
test2(b, c, Rect::MakeLTRB(100, 100, 200, 110));
}
TEST(RectTest, IRectIntersection) {
auto check_empty_flips = [](const IRect& a, const IRect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_FALSE(a.Intersection(flip_lr(b)).has_value()) << label;
EXPECT_FALSE(a.Intersection(flip_tb(b)).has_value()) << label;
EXPECT_FALSE(a.Intersection(flip_lrtb(b)).has_value()) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_FALSE(flip_lr(a).Intersection(b).has_value()) << label;
EXPECT_FALSE(flip_tb(a).Intersection(b).has_value()) << label;
EXPECT_FALSE(flip_lrtb(a).Intersection(b).has_value()) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).Intersection(flip_lr(b)).has_value()) << label;
EXPECT_FALSE(flip_tb(a).Intersection(flip_tb(b)).has_value()) << label;
EXPECT_FALSE(flip_lrtb(a).Intersection(flip_lrtb(b)).has_value()) << label;
};
auto test_non_empty = [&check_empty_flips](const IRect& a, const IRect& b,
const IRect& result) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_TRUE(a.Intersection(b).has_value()) << label;
EXPECT_TRUE(b.Intersection(a).has_value()) << label;
EXPECT_EQ(a.Intersection(b), result) << label;
EXPECT_EQ(b.Intersection(a), result) << label;
check_empty_flips(a, b, label);
};
auto test_empty = [&check_empty_flips](const IRect& a, const IRect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_FALSE(a.Intersection(b).has_value()) << label;
EXPECT_FALSE(b.Intersection(a).has_value()) << label;
check_empty_flips(a, b, label);
};
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 0, 0);
test_empty(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 0, 0);
test_empty(a, b);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 100, 100);
auto expected = IRect::MakeXYWH(10, 10, 90, 90);
test_non_empty(a, b, expected);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(100, 100, 100, 100);
test_empty(a, b);
}
{
auto a = IRect::MakeMaximum();
auto b = IRect::MakeXYWH(10, 10, 300, 300);
test_non_empty(a, b, b);
}
{
auto a = IRect::MakeMaximum();
auto b = IRect::MakeMaximum();
test_non_empty(a, b, IRect::MakeMaximum());
}
}
TEST(RectTest, OptIRectIntersection) {
auto a = IRect::MakeLTRB(0, 0, 110, 110);
auto b = IRect::MakeLTRB(100, 100, 200, 200);
auto c = IRect::MakeLTRB(100, 0, 200, 110);
// NullOpt, NullOpt
EXPECT_FALSE(IRect::Intersection(std::nullopt, std::nullopt).has_value());
EXPECT_EQ(IRect::Intersection(std::nullopt, std::nullopt), std::nullopt);
auto test1 = [](const IRect& r) {
// Rect, NullOpt
EXPECT_TRUE(IRect::Intersection(r, std::nullopt).has_value());
EXPECT_EQ(IRect::Intersection(r, std::nullopt).value(), r);
// OptRect, NullOpt
EXPECT_TRUE(
IRect::Intersection(std::optional(r), std::nullopt).has_value());
EXPECT_EQ(IRect::Intersection(std::optional(r), std::nullopt).value(), r);
// NullOpt, Rect
EXPECT_TRUE(IRect::Intersection(std::nullopt, r).has_value());
EXPECT_EQ(IRect::Intersection(std::nullopt, r).value(), r);
// NullOpt, OptRect
EXPECT_TRUE(
IRect::Intersection(std::nullopt, std::optional(r)).has_value());
EXPECT_EQ(IRect::Intersection(std::nullopt, std::optional(r)).value(), r);
};
test1(a);
test1(b);
test1(c);
auto test2 = [](const IRect& a, const IRect& b, const IRect& i) {
ASSERT_EQ(a.Intersection(b), i);
// Rect, OptRect
EXPECT_TRUE(IRect::Intersection(a, std::optional(b)).has_value());
EXPECT_EQ(IRect::Intersection(a, std::optional(b)).value(), i);
// OptRect, Rect
EXPECT_TRUE(IRect::Intersection(std::optional(a), b).has_value());
EXPECT_EQ(IRect::Intersection(std::optional(a), b).value(), i);
// OptRect, OptRect
EXPECT_TRUE(
IRect::Intersection(std::optional(a), std::optional(b)).has_value());
EXPECT_EQ(IRect::Intersection(std::optional(a), std::optional(b)).value(),
i);
};
test2(a, b, IRect::MakeLTRB(100, 100, 110, 110));
test2(a, c, IRect::MakeLTRB(100, 0, 110, 110));
test2(b, c, IRect::MakeLTRB(100, 100, 200, 110));
}
TEST(RectTest, RectIntersectsWithRect) {
auto check_nans = [](const Rect& a, const Rect& b, const std::string& label) {
ASSERT_TRUE(a.IsFinite()) << label;
ASSERT_TRUE(b.IsFinite()) << label;
for (int i = 1; i < 16; i++) {
// NaN in a produces b
EXPECT_FALSE(swap_nan(a, i).IntersectsWithRect(b))
<< label << ", index = " << i;
// NaN in b produces a
EXPECT_FALSE(a.IntersectsWithRect(swap_nan(b, i)))
<< label << ", index = " << i;
// NaN in both is empty
for (int j = 1; j < 16; j++) {
EXPECT_FALSE(swap_nan(a, i).IntersectsWithRect(swap_nan(b, j)))
<< label << ", indices = " << i << ", " << j;
}
}
};
auto check_empty_flips = [](const Rect& a, const Rect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_FALSE(a.IntersectsWithRect(flip_lr(b))) << label;
EXPECT_FALSE(a.IntersectsWithRect(flip_tb(b))) << label;
EXPECT_FALSE(a.IntersectsWithRect(flip_lrtb(b))) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_FALSE(flip_lr(a).IntersectsWithRect(b)) << label;
EXPECT_FALSE(flip_tb(a).IntersectsWithRect(b)) << label;
EXPECT_FALSE(flip_lrtb(a).IntersectsWithRect(b)) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).IntersectsWithRect(flip_lr(b))) << label;
EXPECT_FALSE(flip_tb(a).IntersectsWithRect(flip_tb(b))) << label;
EXPECT_FALSE(flip_lrtb(a).IntersectsWithRect(flip_lrtb(b))) << label;
};
auto test_non_empty = [&check_nans, &check_empty_flips](const Rect& a,
const Rect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_TRUE(a.IntersectsWithRect(b)) << label;
EXPECT_TRUE(b.IntersectsWithRect(a)) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
auto test_empty = [&check_nans, &check_empty_flips](const Rect& a,
const Rect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_FALSE(a.IntersectsWithRect(b)) << label;
EXPECT_FALSE(b.IntersectsWithRect(a)) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 0, 0);
test_empty(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 0, 0);
test_empty(a, b);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(10, 10, 100, 100);
test_non_empty(a, b);
}
{
auto a = Rect::MakeXYWH(0, 0, 100, 100);
auto b = Rect::MakeXYWH(100, 100, 100, 100);
test_empty(a, b);
}
{
auto a = Rect::MakeMaximum();
auto b = Rect::MakeXYWH(10, 10, 100, 100);
test_non_empty(a, b);
}
{
auto a = Rect::MakeMaximum();
auto b = Rect::MakeMaximum();
test_non_empty(a, b);
}
}
TEST(RectTest, IRectIntersectsWithRect) {
auto check_empty_flips = [](const IRect& a, const IRect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// b is allowed to be empty
// unflipped a vs flipped (empty) b yields a
EXPECT_FALSE(a.IntersectsWithRect(flip_lr(b))) << label;
EXPECT_FALSE(a.IntersectsWithRect(flip_tb(b))) << label;
EXPECT_FALSE(a.IntersectsWithRect(flip_lrtb(b))) << label;
// flipped (empty) a vs unflipped b yields b
EXPECT_FALSE(flip_lr(a).IntersectsWithRect(b)) << label;
EXPECT_FALSE(flip_tb(a).IntersectsWithRect(b)) << label;
EXPECT_FALSE(flip_lrtb(a).IntersectsWithRect(b)) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).IntersectsWithRect(flip_lr(b))) << label;
EXPECT_FALSE(flip_tb(a).IntersectsWithRect(flip_tb(b))) << label;
EXPECT_FALSE(flip_lrtb(a).IntersectsWithRect(flip_lrtb(b))) << label;
};
auto test_non_empty = [&check_empty_flips](const IRect& a, const IRect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_TRUE(a.IntersectsWithRect(b)) << label;
EXPECT_TRUE(b.IntersectsWithRect(a)) << label;
check_empty_flips(a, b, label);
};
auto test_empty = [&check_empty_flips](const IRect& a, const IRect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// b is allowed to be empty
std::stringstream stream;
stream << a << " union " << b;
auto label = stream.str();
EXPECT_FALSE(a.IntersectsWithRect(b)) << label;
EXPECT_FALSE(b.IntersectsWithRect(a)) << label;
check_empty_flips(a, b, label);
};
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 0, 0);
test_empty(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 0, 0);
test_empty(a, b);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(10, 10, 100, 100);
test_non_empty(a, b);
}
{
auto a = IRect::MakeXYWH(0, 0, 100, 100);
auto b = IRect::MakeXYWH(100, 100, 100, 100);
test_empty(a, b);
}
{
auto a = IRect::MakeMaximum();
auto b = IRect::MakeXYWH(10, 10, 100, 100);
test_non_empty(a, b);
}
{
auto a = IRect::MakeMaximum();
auto b = IRect::MakeMaximum();
test_non_empty(a, b);
}
}
TEST(RectTest, RectContainsPoint) {
auto check_nans = [](const Rect& rect, const Point& point,
const std::string& label) {
ASSERT_TRUE(rect.IsFinite()) << label;
ASSERT_TRUE(point.IsFinite()) << label;
for (int i = 1; i < 16; i++) {
EXPECT_FALSE(swap_nan(rect, i).Contains(point))
<< label << ", index = " << i;
for (int j = 1; j < 4; j++) {
EXPECT_FALSE(swap_nan(rect, i).Contains(swap_nan(point, j)))
<< label << ", indices = " << i << ", " << j;
}
}
};
auto check_empty_flips = [](const Rect& rect, const Point& point,
const std::string& label) {
ASSERT_FALSE(rect.IsEmpty());
EXPECT_FALSE(flip_lr(rect).Contains(point)) << label;
EXPECT_FALSE(flip_tb(rect).Contains(point)) << label;
EXPECT_FALSE(flip_lrtb(rect).Contains(point)) << label;
};
auto test_inside = [&check_nans, &check_empty_flips](const Rect& rect,
const Point& point) {
ASSERT_FALSE(rect.IsEmpty()) << rect;
std::stringstream stream;
stream << rect << " contains " << point;
auto label = stream.str();
EXPECT_TRUE(rect.Contains(point)) << label;
check_empty_flips(rect, point, label);
check_nans(rect, point, label);
};
auto test_outside = [&check_nans, &check_empty_flips](const Rect& rect,
const Point& point) {
ASSERT_FALSE(rect.IsEmpty()) << rect;
std::stringstream stream;
stream << rect << " contains " << point;
auto label = stream.str();
EXPECT_FALSE(rect.Contains(point)) << label;
check_empty_flips(rect, point, label);
check_nans(rect, point, label);
};
{
// Origin is inclusive
auto r = Rect::MakeXYWH(100, 100, 100, 100);
auto p = Point(100, 100);
test_inside(r, p);
}
{
// Size is exclusive
auto r = Rect::MakeXYWH(100, 100, 100, 100);
auto p = Point(200, 200);
test_outside(r, p);
}
{
auto r = Rect::MakeXYWH(100, 100, 100, 100);
auto p = Point(99, 99);
test_outside(r, p);
}
{
auto r = Rect::MakeXYWH(100, 100, 100, 100);
auto p = Point(199, 199);
test_inside(r, p);
}
{
auto r = Rect::MakeMaximum();
auto p = Point(199, 199);
test_inside(r, p);
}
}
TEST(RectTest, IRectContainsIPoint) {
auto check_empty_flips = [](const IRect& rect, const IPoint& point,
const std::string& label) {
ASSERT_FALSE(rect.IsEmpty());
EXPECT_FALSE(flip_lr(rect).Contains(point)) << label;
EXPECT_FALSE(flip_tb(rect).Contains(point)) << label;
EXPECT_FALSE(flip_lrtb(rect).Contains(point)) << label;
};
auto test_inside = [&check_empty_flips](const IRect& rect,
const IPoint& point) {
ASSERT_FALSE(rect.IsEmpty()) << rect;
std::stringstream stream;
stream << rect << " contains " << point;
auto label = stream.str();
EXPECT_TRUE(rect.Contains(point)) << label;
check_empty_flips(rect, point, label);
};
auto test_outside = [&check_empty_flips](const IRect& rect,
const IPoint& point) {
ASSERT_FALSE(rect.IsEmpty()) << rect;
std::stringstream stream;
stream << rect << " contains " << point;
auto label = stream.str();
EXPECT_FALSE(rect.Contains(point)) << label;
check_empty_flips(rect, point, label);
};
{
// Origin is inclusive
auto r = IRect::MakeXYWH(100, 100, 100, 100);
auto p = IPoint(100, 100);
test_inside(r, p);
}
{
// Size is exclusive
auto r = IRect::MakeXYWH(100, 100, 100, 100);
auto p = IPoint(200, 200);
test_outside(r, p);
}
{
auto r = IRect::MakeXYWH(100, 100, 100, 100);
auto p = IPoint(99, 99);
test_outside(r, p);
}
{
auto r = IRect::MakeXYWH(100, 100, 100, 100);
auto p = IPoint(199, 199);
test_inside(r, p);
}
{
auto r = IRect::MakeMaximum();
auto p = IPoint(199, 199);
test_inside(r, p);
}
}
TEST(RectTest, RectContainsRect) {
auto check_nans = [](const Rect& a, const Rect& b, const std::string& label) {
ASSERT_TRUE(a.IsFinite()) << label;
ASSERT_TRUE(b.IsFinite()) << label;
ASSERT_FALSE(a.IsEmpty());
for (int i = 1; i < 16; i++) {
// NaN in a produces false
EXPECT_FALSE(swap_nan(a, i).Contains(b)) << label << ", index = " << i;
// NaN in b produces false
EXPECT_TRUE(a.Contains(swap_nan(b, i))) << label << ", index = " << i;
// NaN in both is false
for (int j = 1; j < 16; j++) {
EXPECT_FALSE(swap_nan(a, i).Contains(swap_nan(b, j)))
<< label << ", indices = " << i << ", " << j;
}
}
};
auto check_empty_flips = [](const Rect& a, const Rect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// test b rects are allowed to have 0 w/h, but not be backwards
ASSERT_FALSE(b.GetLeft() > b.GetRight() || b.GetTop() > b.GetBottom());
// unflipped a vs flipped (empty) b yields false
EXPECT_TRUE(a.Contains(flip_lr(b))) << label;
EXPECT_TRUE(a.Contains(flip_tb(b))) << label;
EXPECT_TRUE(a.Contains(flip_lrtb(b))) << label;
// flipped (empty) a vs unflipped b yields false
EXPECT_FALSE(flip_lr(a).Contains(b)) << label;
EXPECT_FALSE(flip_tb(a).Contains(b)) << label;
EXPECT_FALSE(flip_lrtb(a).Contains(b)) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).Contains(flip_lr(b))) << label;
EXPECT_FALSE(flip_tb(a).Contains(flip_tb(b))) << label;
EXPECT_FALSE(flip_lrtb(a).Contains(flip_lrtb(b))) << label;
};
auto test_inside = [&check_nans, &check_empty_flips](const Rect& a,
const Rect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// test b rects are allowed to have 0 w/h, but not be backwards
ASSERT_FALSE(b.GetLeft() > b.GetRight() || b.GetTop() > b.GetBottom());
std::stringstream stream;
stream << a << " contains " << b;
auto label = stream.str();
EXPECT_TRUE(a.Contains(b)) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
auto test_not_inside = [&check_nans, &check_empty_flips](const Rect& a,
const Rect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// If b was empty, it would be contained and should not be tested with
// this function - use |test_inside| instead.
ASSERT_FALSE(b.IsEmpty()) << b;
std::stringstream stream;
stream << a << " contains " << b;
auto label = stream.str();
EXPECT_FALSE(a.Contains(b)) << label;
check_empty_flips(a, b, label);
check_nans(a, b, label);
};
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
test_inside(a, a);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 0, 0);
test_inside(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(150, 150, 20, 20);
test_inside(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(150, 150, 100, 100);
test_not_inside(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(50, 50, 100, 100);
test_not_inside(a, b);
}
{
auto a = Rect::MakeXYWH(100, 100, 100, 100);
auto b = Rect::MakeXYWH(0, 0, 300, 300);
test_not_inside(a, b);
}
{
auto a = Rect::MakeMaximum();
auto b = Rect::MakeXYWH(0, 0, 300, 300);
test_inside(a, b);
}
}
TEST(RectTest, IRectContainsIRect) {
auto check_empty_flips = [](const IRect& a, const IRect& b,
const std::string& label) {
ASSERT_FALSE(a.IsEmpty());
// test b rects are allowed to have 0 w/h, but not be backwards
ASSERT_FALSE(b.GetLeft() > b.GetRight() || b.GetTop() > b.GetBottom());
// unflipped a vs flipped (empty) b yields true
EXPECT_TRUE(a.Contains(flip_lr(b))) << label;
EXPECT_TRUE(a.Contains(flip_tb(b))) << label;
EXPECT_TRUE(a.Contains(flip_lrtb(b))) << label;
// flipped (empty) a vs unflipped b yields false
EXPECT_FALSE(flip_lr(a).Contains(b)) << label;
EXPECT_FALSE(flip_tb(a).Contains(b)) << label;
EXPECT_FALSE(flip_lrtb(a).Contains(b)) << label;
// flipped (empty) a vs flipped (empty) b yields empty
EXPECT_FALSE(flip_lr(a).Contains(flip_lr(b))) << label;
EXPECT_FALSE(flip_tb(a).Contains(flip_tb(b))) << label;
EXPECT_FALSE(flip_lrtb(a).Contains(flip_lrtb(b))) << label;
};
auto test_inside = [&check_empty_flips](const IRect& a, const IRect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// test b rects are allowed to have 0 w/h, but not be backwards
ASSERT_FALSE(b.GetLeft() > b.GetRight() || b.GetTop() > b.GetBottom());
std::stringstream stream;
stream << a << " contains " << b;
auto label = stream.str();
EXPECT_TRUE(a.Contains(b)) << label;
check_empty_flips(a, b, label);
};
auto test_not_inside = [&check_empty_flips](const IRect& a, const IRect& b) {
ASSERT_FALSE(a.IsEmpty()) << a;
// If b was empty, it would be contained and should not be tested with
// this function - use |test_inside| instead.
ASSERT_FALSE(b.IsEmpty()) << b;
std::stringstream stream;
stream << a << " contains " << b;
auto label = stream.str();
EXPECT_FALSE(a.Contains(b)) << label;
check_empty_flips(a, b, label);
};
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
test_inside(a, a);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 0, 0);
test_inside(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(150, 150, 20, 20);
test_inside(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(150, 150, 100, 100);
test_not_inside(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(50, 50, 100, 100);
test_not_inside(a, b);
}
{
auto a = IRect::MakeXYWH(100, 100, 100, 100);
auto b = IRect::MakeXYWH(0, 0, 300, 300);
test_not_inside(a, b);
}
{
auto a = IRect::MakeMaximum();
auto b = IRect::MakeXYWH(0, 0, 300, 300);
test_inside(a, b);
}
}
TEST(RectTest, RectCutOut) {
Rect cull_rect = Rect::MakeLTRB(20, 20, 40, 40);
auto check_nans = [&cull_rect](const Rect& diff_rect,
const std::string& label) {
EXPECT_TRUE(cull_rect.IsFinite()) << label;
EXPECT_TRUE(diff_rect.IsFinite()) << label;
for (int i = 1; i < 16; i++) {
// NaN in cull_rect produces empty
EXPECT_FALSE(swap_nan(cull_rect, i).Cutout(diff_rect).has_value())
<< label << ", index " << i;
EXPECT_EQ(swap_nan(cull_rect, i).CutoutOrEmpty(diff_rect), Rect())
<< label << ", index " << i;
// NaN in diff_rect is nop
EXPECT_TRUE(cull_rect.Cutout(swap_nan(diff_rect, i)).has_value())
<< label << ", index " << i;
EXPECT_EQ(cull_rect.CutoutOrEmpty(swap_nan(diff_rect, i)), cull_rect)
<< label << ", index " << i;
for (int j = 1; j < 16; j++) {
// NaN in both is also empty
EXPECT_FALSE(
swap_nan(cull_rect, i).Cutout(swap_nan(diff_rect, j)).has_value())
<< label << ", indices " << i << ", " << j;
EXPECT_EQ(swap_nan(cull_rect, i).CutoutOrEmpty(swap_nan(diff_rect, j)),
Rect())
<< label << ", indices " << i << ", " << j;
}
}
};
auto check_empty_flips = [&cull_rect](const Rect& diff_rect,
const std::string& label) {
EXPECT_FALSE(cull_rect.IsEmpty()) << label;
EXPECT_FALSE(diff_rect.IsEmpty()) << label;
// unflipped cull_rect vs flipped(empty) diff_rect
// == cull_rect
EXPECT_TRUE(cull_rect.Cutout(flip_lr(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_lr(diff_rect)), cull_rect) << label;
EXPECT_TRUE(cull_rect.Cutout(flip_tb(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_tb(diff_rect)), cull_rect) << label;
EXPECT_TRUE(cull_rect.Cutout(flip_lrtb(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_lrtb(diff_rect)), cull_rect) << label;
// flipped(empty) cull_rect vs unflipped diff_rect
// == empty
EXPECT_FALSE(flip_lr(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_lr(cull_rect).CutoutOrEmpty(diff_rect), Rect()) << label;
EXPECT_FALSE(flip_tb(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_tb(cull_rect).CutoutOrEmpty(diff_rect), Rect()) << label;
EXPECT_FALSE(flip_lrtb(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_lrtb(cull_rect).CutoutOrEmpty(diff_rect), Rect()) << label;
// flipped(empty) cull_rect vs flipped(empty) diff_rect
// == empty
EXPECT_FALSE(flip_lr(cull_rect).Cutout(flip_lr(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_lr(cull_rect).CutoutOrEmpty(flip_lr(diff_rect)), Rect())
<< label;
EXPECT_FALSE(flip_tb(cull_rect).Cutout(flip_tb(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_tb(cull_rect).CutoutOrEmpty(flip_tb(diff_rect)), Rect())
<< label;
EXPECT_FALSE(flip_lrtb(cull_rect).Cutout(flip_lrtb(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_lrtb(cull_rect).CutoutOrEmpty(flip_lrtb(diff_rect)), Rect())
<< label;
};
auto non_reducing = [&cull_rect, &check_empty_flips, &check_nans](
const Rect& diff_rect, const std::string& label) {
EXPECT_EQ(cull_rect.Cutout(diff_rect), cull_rect) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), cull_rect) << label;
check_empty_flips(diff_rect, label);
check_nans(diff_rect, label);
};
auto reducing = [&cull_rect, &check_empty_flips, &check_nans](
const Rect& diff_rect, const Rect& result_rect,
const std::string& label) {
EXPECT_TRUE(!result_rect.IsEmpty());
EXPECT_EQ(cull_rect.Cutout(diff_rect), result_rect) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), result_rect) << label;
check_empty_flips(diff_rect, label);
check_nans(diff_rect, label);
};
auto emptying = [&cull_rect, &check_empty_flips, &check_nans](
const Rect& diff_rect, const std::string& label) {
EXPECT_FALSE(cull_rect.Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), Rect()) << label;
check_empty_flips(diff_rect, label);
check_nans(diff_rect, label);
};
// Skim the corners and edge
non_reducing(Rect::MakeLTRB(10, 10, 20, 20), "outside UL corner");
non_reducing(Rect::MakeLTRB(20, 10, 40, 20), "Above");
non_reducing(Rect::MakeLTRB(40, 10, 50, 20), "outside UR corner");
non_reducing(Rect::MakeLTRB(40, 20, 50, 40), "Right");
non_reducing(Rect::MakeLTRB(40, 40, 50, 50), "outside LR corner");
non_reducing(Rect::MakeLTRB(20, 40, 40, 50), "Below");
non_reducing(Rect::MakeLTRB(10, 40, 20, 50), "outside LR corner");
non_reducing(Rect::MakeLTRB(10, 20, 20, 40), "Left");
// Overlap corners
non_reducing(Rect::MakeLTRB(15, 15, 25, 25), "covering UL corner");
non_reducing(Rect::MakeLTRB(35, 15, 45, 25), "covering UR corner");
non_reducing(Rect::MakeLTRB(35, 35, 45, 45), "covering LR corner");
non_reducing(Rect::MakeLTRB(15, 35, 25, 45), "covering LL corner");
// Overlap edges, but not across an entire side
non_reducing(Rect::MakeLTRB(20, 15, 39, 25), "Top edge left-biased");
non_reducing(Rect::MakeLTRB(21, 15, 40, 25), "Top edge, right biased");
non_reducing(Rect::MakeLTRB(35, 20, 45, 39), "Right edge, top-biased");
non_reducing(Rect::MakeLTRB(35, 21, 45, 40), "Right edge, bottom-biased");
non_reducing(Rect::MakeLTRB(20, 35, 39, 45), "Bottom edge, left-biased");
non_reducing(Rect::MakeLTRB(21, 35, 40, 45), "Bottom edge, right-biased");
non_reducing(Rect::MakeLTRB(15, 20, 25, 39), "Left edge, top-biased");
non_reducing(Rect::MakeLTRB(15, 21, 25, 40), "Left edge, bottom-biased");
// Slice all the way through the middle
non_reducing(Rect::MakeLTRB(25, 15, 35, 45), "Vertical interior slice");
non_reducing(Rect::MakeLTRB(15, 25, 45, 35), "Horizontal interior slice");
// Slice off each edge
reducing(Rect::MakeLTRB(20, 15, 40, 25), //
Rect::MakeLTRB(20, 25, 40, 40), //
"Slice off top");
reducing(Rect::MakeLTRB(35, 20, 45, 40), //
Rect::MakeLTRB(20, 20, 35, 40), //
"Slice off right");
reducing(Rect::MakeLTRB(20, 35, 40, 45), //
Rect::MakeLTRB(20, 20, 40, 35), //
"Slice off bottom");
reducing(Rect::MakeLTRB(15, 20, 25, 40), //
Rect::MakeLTRB(25, 20, 40, 40), //
"Slice off left");
// cull rect contains diff rect
non_reducing(Rect::MakeLTRB(21, 21, 39, 39), "Contained, non-covering");
// cull rect equals diff rect
emptying(cull_rect, "Perfectly covering");
// diff rect contains cull rect
emptying(Rect::MakeLTRB(15, 15, 45, 45), "Smothering");
}
TEST(RectTest, IRectCutOut) {
IRect cull_rect = IRect::MakeLTRB(20, 20, 40, 40);
auto check_empty_flips = [&cull_rect](const IRect& diff_rect,
const std::string& label) {
EXPECT_FALSE(diff_rect.IsEmpty());
EXPECT_FALSE(cull_rect.IsEmpty());
// unflipped cull_rect vs flipped(empty) diff_rect
// == cull_rect
EXPECT_TRUE(cull_rect.Cutout(flip_lr(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_lr(diff_rect)), cull_rect) << label;
EXPECT_TRUE(cull_rect.Cutout(flip_tb(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_tb(diff_rect)), cull_rect) << label;
EXPECT_TRUE(cull_rect.Cutout(flip_lrtb(diff_rect)).has_value()) << label;
EXPECT_EQ(cull_rect.Cutout(flip_lrtb(diff_rect)), cull_rect) << label;
// flipped(empty) cull_rect vs flipped(empty) diff_rect
// == empty
EXPECT_FALSE(flip_lr(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_lr(cull_rect).CutoutOrEmpty(diff_rect), IRect()) << label;
EXPECT_FALSE(flip_tb(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_tb(cull_rect).CutoutOrEmpty(diff_rect), IRect()) << label;
EXPECT_FALSE(flip_lrtb(cull_rect).Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(flip_lrtb(cull_rect).CutoutOrEmpty(diff_rect), IRect()) << label;
// flipped(empty) cull_rect vs unflipped diff_rect
// == empty
EXPECT_FALSE(flip_lr(cull_rect).Cutout(flip_lr(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_lr(cull_rect).CutoutOrEmpty(flip_lr(diff_rect)), IRect())
<< label;
EXPECT_FALSE(flip_tb(cull_rect).Cutout(flip_tb(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_tb(cull_rect).CutoutOrEmpty(flip_tb(diff_rect)), IRect())
<< label;
EXPECT_FALSE(flip_lrtb(cull_rect).Cutout(flip_lrtb(diff_rect)).has_value())
<< label;
EXPECT_EQ(flip_lrtb(cull_rect).CutoutOrEmpty(flip_lrtb(diff_rect)), IRect())
<< label;
};
auto non_reducing = [&cull_rect, &check_empty_flips](
const IRect& diff_rect, const std::string& label) {
EXPECT_EQ(cull_rect.Cutout(diff_rect), cull_rect) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), cull_rect) << label;
check_empty_flips(diff_rect, label);
};
auto reducing = [&cull_rect, &check_empty_flips](const IRect& diff_rect,
const IRect& result_rect,
const std::string& label) {
EXPECT_TRUE(!result_rect.IsEmpty());
EXPECT_EQ(cull_rect.Cutout(diff_rect), result_rect) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), result_rect) << label;
check_empty_flips(diff_rect, label);
};
auto emptying = [&cull_rect, &check_empty_flips](const IRect& diff_rect,
const std::string& label) {
EXPECT_FALSE(cull_rect.Cutout(diff_rect).has_value()) << label;
EXPECT_EQ(cull_rect.CutoutOrEmpty(diff_rect), IRect()) << label;
check_empty_flips(diff_rect, label);
};
// Skim the corners and edge
non_reducing(IRect::MakeLTRB(10, 10, 20, 20), "outside UL corner");
non_reducing(IRect::MakeLTRB(20, 10, 40, 20), "Above");
non_reducing(IRect::MakeLTRB(40, 10, 50, 20), "outside UR corner");
non_reducing(IRect::MakeLTRB(40, 20, 50, 40), "Right");
non_reducing(IRect::MakeLTRB(40, 40, 50, 50), "outside LR corner");
non_reducing(IRect::MakeLTRB(20, 40, 40, 50), "Below");
non_reducing(IRect::MakeLTRB(10, 40, 20, 50), "outside LR corner");
non_reducing(IRect::MakeLTRB(10, 20, 20, 40), "Left");
// Overlap corners
non_reducing(IRect::MakeLTRB(15, 15, 25, 25), "covering UL corner");
non_reducing(IRect::MakeLTRB(35, 15, 45, 25), "covering UR corner");
non_reducing(IRect::MakeLTRB(35, 35, 45, 45), "covering LR corner");
non_reducing(IRect::MakeLTRB(15, 35, 25, 45), "covering LL corner");
// Overlap edges, but not across an entire side
non_reducing(IRect::MakeLTRB(20, 15, 39, 25), "Top edge left-biased");
non_reducing(IRect::MakeLTRB(21, 15, 40, 25), "Top edge, right biased");
non_reducing(IRect::MakeLTRB(35, 20, 45, 39), "Right edge, top-biased");
non_reducing(IRect::MakeLTRB(35, 21, 45, 40), "Right edge, bottom-biased");
non_reducing(IRect::MakeLTRB(20, 35, 39, 45), "Bottom edge, left-biased");
non_reducing(IRect::MakeLTRB(21, 35, 40, 45), "Bottom edge, right-biased");
non_reducing(IRect::MakeLTRB(15, 20, 25, 39), "Left edge, top-biased");
non_reducing(IRect::MakeLTRB(15, 21, 25, 40), "Left edge, bottom-biased");
// Slice all the way through the middle
non_reducing(IRect::MakeLTRB(25, 15, 35, 45), "Vertical interior slice");
non_reducing(IRect::MakeLTRB(15, 25, 45, 35), "Horizontal interior slice");
// Slice off each edge
reducing(IRect::MakeLTRB(20, 15, 40, 25), //
IRect::MakeLTRB(20, 25, 40, 40), //
"Slice off top");
reducing(IRect::MakeLTRB(35, 20, 45, 40), //
IRect::MakeLTRB(20, 20, 35, 40), //
"Slice off right");
reducing(IRect::MakeLTRB(20, 35, 40, 45), //
IRect::MakeLTRB(20, 20, 40, 35), //
"Slice off bottom");
reducing(IRect::MakeLTRB(15, 20, 25, 40), //
IRect::MakeLTRB(25, 20, 40, 40), //
"Slice off left");
// cull rect contains diff rect
non_reducing(IRect::MakeLTRB(21, 21, 39, 39), "Contained, non-covering");
// cull rect equals diff rect
emptying(cull_rect, "Perfectly covering");
// diff rect contains cull rect
emptying(IRect::MakeLTRB(15, 15, 45, 45), "Smothering");
}
TEST(RectTest, RectGetPoints) {
{
Rect r = Rect::MakeXYWH(100, 200, 300, 400);
auto points = r.GetPoints();
EXPECT_POINT_NEAR(points[0], Point(100, 200));
EXPECT_POINT_NEAR(points[1], Point(400, 200));
EXPECT_POINT_NEAR(points[2], Point(100, 600));
EXPECT_POINT_NEAR(points[3], Point(400, 600));
}
{
Rect r = Rect::MakeMaximum();
auto points = r.GetPoints();
EXPECT_EQ(points[0], Point(std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::lowest()));
EXPECT_EQ(points[1], Point(std::numeric_limits<float>::max(),
std::numeric_limits<float>::lowest()));
EXPECT_EQ(points[2], Point(std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::max()));
EXPECT_EQ(points[3], Point(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max()));
}
}
TEST(RectTest, RectShift) {
auto r = Rect::MakeLTRB(0, 0, 100, 100);
EXPECT_EQ(r.Shift(Point(10, 5)), Rect::MakeLTRB(10, 5, 110, 105));
EXPECT_EQ(r.Shift(Point(-10, -5)), Rect::MakeLTRB(-10, -5, 90, 95));
}
TEST(RectTest, RectGetTransformedPoints) {
Rect r = Rect::MakeXYWH(100, 200, 300, 400);
auto points = r.GetTransformedPoints(Matrix::MakeTranslation({10, 20}));
EXPECT_POINT_NEAR(points[0], Point(110, 220));
EXPECT_POINT_NEAR(points[1], Point(410, 220));
EXPECT_POINT_NEAR(points[2], Point(110, 620));
EXPECT_POINT_NEAR(points[3], Point(410, 620));
}
TEST(RectTest, RectMakePointBounds) {
{
std::vector<Point> points{{1, 5}, {4, -1}, {0, 6}};
auto r = Rect::MakePointBounds(points.begin(), points.end());
auto expected = Rect::MakeXYWH(0, -1, 4, 7);
EXPECT_TRUE(r.has_value());
if (r.has_value()) {
EXPECT_RECT_NEAR(r.value(), expected);
}
}
{
std::vector<Point> points;
std::optional<Rect> r = Rect::MakePointBounds(points.begin(), points.end());
EXPECT_FALSE(r.has_value());
}
}
TEST(RectTest, RectGetPositive) {
{
Rect r = Rect::MakeXYWH(100, 200, 300, 400);
auto actual = r.GetPositive();
EXPECT_RECT_NEAR(r, actual);
}
{
Rect r = Rect::MakeXYWH(100, 200, -100, -100);
auto actual = r.GetPositive();
Rect expected = Rect::MakeXYWH(0, 100, 100, 100);
EXPECT_RECT_NEAR(expected, actual);
}
}
TEST(RectTest, RectDirections) {
auto r = Rect::MakeLTRB(1, 2, 3, 4);
EXPECT_EQ(r.GetLeft(), 1);
EXPECT_EQ(r.GetTop(), 2);
EXPECT_EQ(r.GetRight(), 3);
EXPECT_EQ(r.GetBottom(), 4);
EXPECT_POINT_NEAR(r.GetLeftTop(), Point(1, 2));
EXPECT_POINT_NEAR(r.GetRightTop(), Point(3, 2));
EXPECT_POINT_NEAR(r.GetLeftBottom(), Point(1, 4));
EXPECT_POINT_NEAR(r.GetRightBottom(), Point(3, 4));
}
TEST(RectTest, RectProject) {
{
auto r = Rect::MakeLTRB(-100, -100, 100, 100);
auto actual = r.Project(r);
auto expected = Rect::MakeLTRB(0, 0, 1, 1);
EXPECT_RECT_NEAR(expected, actual);
}
{
auto r = Rect::MakeLTRB(-100, -100, 100, 100);
auto actual = r.Project(Rect::MakeLTRB(0, 0, 100, 100));
auto expected = Rect::MakeLTRB(0.5, 0.5, 1, 1);
EXPECT_RECT_NEAR(expected, actual);
}
}
TEST(RectTest, RectRoundOut) {
{
auto r = Rect::MakeLTRB(-100, -200, 300, 400);
EXPECT_EQ(Rect::RoundOut(r), r);
}
{
auto r = Rect::MakeLTRB(-100.1, -200.1, 300.1, 400.1);
EXPECT_EQ(Rect::RoundOut(r), Rect::MakeLTRB(-101, -201, 301, 401));
}
}
TEST(RectTest, IRectRoundOut) {
{
auto r = Rect::MakeLTRB(-100, -200, 300, 400);
auto ir = IRect::MakeLTRB(-100, -200, 300, 400);
EXPECT_EQ(IRect::RoundOut(r), ir);
}
{
auto r = Rect::MakeLTRB(-100.1, -200.1, 300.1, 400.1);
auto ir = IRect::MakeLTRB(-101, -201, 301, 401);
EXPECT_EQ(IRect::RoundOut(r), ir);
}
}
TEST(RectTest, RectRound) {
{
auto r = Rect::MakeLTRB(-100, -200, 300, 400);
EXPECT_EQ(Rect::Round(r), r);
}
{
auto r = Rect::MakeLTRB(-100.4, -200.4, 300.4, 400.4);
EXPECT_EQ(Rect::Round(r), Rect::MakeLTRB(-100, -200, 300, 400));
}
{
auto r = Rect::MakeLTRB(-100.5, -200.5, 300.5, 400.5);
EXPECT_EQ(Rect::Round(r), Rect::MakeLTRB(-101, -201, 301, 401));
}
}
TEST(RectTest, IRectRound) {
{
auto r = Rect::MakeLTRB(-100, -200, 300, 400);
auto ir = IRect::MakeLTRB(-100, -200, 300, 400);
EXPECT_EQ(IRect::Round(r), ir);
}
{
auto r = Rect::MakeLTRB(-100.4, -200.4, 300.4, 400.4);
auto ir = IRect::MakeLTRB(-100, -200, 300, 400);
EXPECT_EQ(IRect::Round(r), ir);
}
{
auto r = Rect::MakeLTRB(-100.5, -200.5, 300.5, 400.5);
auto ir = IRect::MakeLTRB(-101, -201, 301, 401);
EXPECT_EQ(IRect::Round(r), ir);
}
}
TEST(RectTest, TransformAndClipBounds) {
{
// This matrix should clip no corners.
auto matrix = impeller::Matrix::MakeColumn(
// clang-format off
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 4.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 8.0f
// clang-format on
);
Rect src = Rect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f);
// None of these should have a W<0
EXPECT_EQ(matrix.TransformHomogenous(src.GetLeftTop()),
Vector3(200.0f, 400.0f, 8.0f));
EXPECT_EQ(matrix.TransformHomogenous(src.GetRightTop()),
Vector3(400.0f, 400.0f, 8.0f));
EXPECT_EQ(matrix.TransformHomogenous(src.GetLeftBottom()),
Vector3(200.0f, 800.0f, 8.0f));
EXPECT_EQ(matrix.TransformHomogenous(src.GetRightBottom()),
Vector3(400.0f, 800.0f, 8.0f));
Rect expect = Rect::MakeLTRB(25.0f, 50.0f, 50.0f, 100.0f);
EXPECT_FALSE(src.TransformAndClipBounds(matrix).IsEmpty());
EXPECT_EQ(src.TransformAndClipBounds(matrix), expect);
}
{
// This matrix should clip one corner.
auto matrix = impeller::Matrix::MakeColumn(
// clang-format off
2.0f, 0.0f, 0.0f, -0.01f,
0.0f, 2.0f, 0.0f, -0.006f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 3.0f
// clang-format on
);
Rect src = Rect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f);
// Exactly one of these should have a W<0
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftTop()),
Vector3(200.0f, 200.0f, 1.4f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightTop()),
Vector3(400.0f, 200.0f, 0.4f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftBottom()),
Vector3(200.0f, 400.0f, 0.8f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightBottom()),
Vector3(400.0f, 400.0f, -0.2f));
Rect expect = Rect::MakeLTRB(142.85715f, 142.85715f, 6553600.f, 6553600.f);
EXPECT_FALSE(src.TransformAndClipBounds(matrix).IsEmpty());
EXPECT_RECT_NEAR(src.TransformAndClipBounds(matrix), expect);
}
{
// This matrix should clip two corners.
auto matrix = impeller::Matrix::MakeColumn(
// clang-format off
2.0f, 0.0f, 0.0f, -.015f,
0.0f, 2.0f, 0.0f, -.006f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 3.0f
// clang-format on
);
Rect src = Rect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f);
// Exactly two of these should have a W<0
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftTop()),
Vector3(200.0f, 200.0f, 0.9f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightTop()),
Vector3(400.0f, 200.0f, -0.6f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftBottom()),
Vector3(200.0f, 400.0f, 0.3f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightBottom()),
Vector3(400.0f, 400.0f, -1.2f));
Rect expect = Rect::MakeLTRB(222.2222f, 222.2222f, 5898373.f, 6553600.f);
EXPECT_FALSE(src.TransformAndClipBounds(matrix).IsEmpty());
EXPECT_RECT_NEAR(src.TransformAndClipBounds(matrix), expect);
}
{
// This matrix should clip three corners.
auto matrix = impeller::Matrix::MakeColumn(
// clang-format off
2.0f, 0.0f, 0.0f, -.02f,
0.0f, 2.0f, 0.0f, -.006f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 3.0f
// clang-format on
);
Rect src = Rect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f);
// Exactly three of these should have a W<0
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftTop()),
Vector3(200.0f, 200.0f, 0.4f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightTop()),
Vector3(400.0f, 200.0f, -1.6f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftBottom()),
Vector3(200.0f, 400.0f, -0.2f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightBottom()),
Vector3(400.0f, 400.0f, -2.2f));
Rect expect = Rect::MakeLTRB(499.99988f, 499.99988f, 5898340.f, 4369400.f);
EXPECT_FALSE(src.TransformAndClipBounds(matrix).IsEmpty());
EXPECT_RECT_NEAR(src.TransformAndClipBounds(matrix), expect);
}
{
// This matrix should clip all four corners.
auto matrix = impeller::Matrix::MakeColumn(
// clang-format off
2.0f, 0.0f, 0.0f, -.025f,
0.0f, 2.0f, 0.0f, -.006f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 3.0f
// clang-format on
);
Rect src = Rect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f);
// All of these should have a W<0
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftTop()),
Vector3(200.0f, 200.0f, -0.1f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightTop()),
Vector3(400.0f, 200.0f, -2.6f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetLeftBottom()),
Vector3(200.0f, 400.0f, -0.7f));
EXPECT_VECTOR3_NEAR(matrix.TransformHomogenous(src.GetRightBottom()),
Vector3(400.0f, 400.0f, -3.2f));
EXPECT_TRUE(src.TransformAndClipBounds(matrix).IsEmpty());
}
}
} // namespace testing
} // namespace impeller