blob: 9335befccb3229716dfb8186c0073ca6cbcfd403 [file] [log] [blame]
// Copyright 2020 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/scenic/lib/flatland/global_matrix_data.h"
#include <lib/syslog/cpp/macros.h>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/ui/scenic/lib/flatland/global_topology_data.h"
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/matrix_transform_2d.hpp>
namespace flatland {
namespace test {
namespace {
// Helper function to generate an escher::Rectangle2D from a glm::mat3 for tests that are strictly
// testing the conversion math.
escher::Rectangle2D GetRectangleForMatrix(const glm::mat3& matrix) {
// Compute the global rectangle vector and return the first entry.
const auto rectangles = ComputeGlobalRectangles({matrix});
EXPECT_EQ(rectangles.size(), 1ul);
return rectangles[0];
}
} // namespace
// The following tests ensure the transform hierarchy is properly reflected in the list of global
// rectangles.
TEST(GlobalMatrixDataTest, EmptyTopologyReturnsEmptyMatrices) {
UberStruct::InstanceMap uber_structs;
GlobalTopologyData::TopologyVector topology_vector;
GlobalTopologyData::ParentIndexVector parent_indices;
auto global_matrices = ComputeGlobalMatrices(topology_vector, parent_indices, uber_structs);
EXPECT_TRUE(global_matrices.empty());
}
TEST(GlobalMatrixDataTest, EmptyLocalMatricesAreIdentity) {
UberStruct::InstanceMap uber_structs;
// Make a global topology representing the following graph:
//
// 1:0 - 1:1
GlobalTopologyData::TopologyVector topology_vector = {{1, 0}, {1, 1}};
GlobalTopologyData::ParentIndexVector parent_indices = {0, 0};
// The UberStruct for instance ID 1 must exist, but it contains no local matrices.
auto uber_struct = std::make_unique<UberStruct>();
uber_structs[1] = std::move(uber_struct);
// The root matrix is set to the identity matrix, and the second inherits that.
std::vector<glm::mat3> expected_matrices = {
glm::mat3(),
glm::mat3(),
};
auto global_matrices = ComputeGlobalMatrices(topology_vector, parent_indices, uber_structs);
EXPECT_THAT(global_matrices, ::testing::ElementsAreArray(expected_matrices));
}
TEST(GlobalMatrixDataTest, GlobalMatricesIncludeParentMatrix) {
UberStruct::InstanceMap uber_structs;
// Make a global topology representing the following graph:
//
// 1:0 - 1:1 - 1:2
// \
// 1:3 - 1:4
GlobalTopologyData::TopologyVector topology_vector = {{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}};
GlobalTopologyData::ParentIndexVector parent_indices = {0, 0, 1, 0, 3};
auto uber_struct = std::make_unique<UberStruct>();
static const glm::vec2 kTranslation = {1.f, 2.f};
static const float kRotation = glm::half_pi<float>();
static const glm::vec2 kScale = {3.f, 5.f};
// All transforms will get the translation from 1:0
uber_struct->local_matrices[{1, 0}] = glm::translate(glm::mat3(), kTranslation);
// The 1:1 - 1:2 branch rotates, then scales.
uber_struct->local_matrices[{1, 1}] = glm::rotate(glm::mat3(), kRotation);
uber_struct->local_matrices[{1, 2}] = glm::scale(glm::mat3(), kScale);
// The 1:3 - 1:4 branch scales, then rotates.
uber_struct->local_matrices[{1, 3}] = glm::scale(glm::mat3(), kScale);
uber_struct->local_matrices[{1, 4}] = glm::rotate(glm::mat3(), kRotation);
uber_structs[1] = std::move(uber_struct);
// The expected matrices apply the operations in the correct order. The translation always comes
// first, followed by the operations of the children.
std::vector<glm::mat3> expected_matrices = {
glm::translate(glm::mat3(), kTranslation),
glm::rotate(glm::translate(glm::mat3(), kTranslation), kRotation),
glm::scale(glm::rotate(glm::translate(glm::mat3(), kTranslation), kRotation), kScale),
glm::scale(glm::translate(glm::mat3(), kTranslation), kScale),
glm::rotate(glm::scale(glm::translate(glm::mat3(), kTranslation), kScale), kRotation),
};
auto global_matrices = ComputeGlobalMatrices(topology_vector, parent_indices, uber_structs);
EXPECT_THAT(global_matrices, ::testing::ElementsAreArray(expected_matrices));
}
TEST(GlobalMatrixDataTest, GlobalMatricesMultipleUberStructs) {
UberStruct::InstanceMap uber_structs;
// Make a global topology representing the following graph:
//
// 1:0 - 2:0
// \
// 1:1
GlobalTopologyData::TopologyVector topology_vector = {{1, 0}, {2, 0}, {1, 1}};
GlobalTopologyData::ParentIndexVector parent_indices = {0, 0, 0};
auto uber_struct1 = std::make_unique<UberStruct>();
auto uber_struct2 = std::make_unique<UberStruct>();
// Each matrix scales by a different prime number to distinguish the branches.
uber_struct1->local_matrices[{1, 0}] = glm::scale(glm::mat3(), {2.f, 2.f});
uber_struct1->local_matrices[{1, 1}] = glm::scale(glm::mat3(), {3.f, 3.f});
uber_struct2->local_matrices[{2, 0}] = glm::scale(glm::mat3(), {5.f, 5.f});
uber_structs[1] = std::move(uber_struct1);
uber_structs[2] = std::move(uber_struct2);
std::vector<glm::mat3> expected_matrices = {
glm::scale(glm::mat3(), glm::vec2(2.f)), // 1:0 = 2
glm::scale(glm::mat3(), glm::vec2(10.f)), // 1:0 * 2:0 = 2 * 5 = 10
glm::scale(glm::mat3(), glm::vec2(6.f)), // 1:0 * 1:1 = 2 * 3 = 6
};
auto global_matrices = ComputeGlobalMatrices(topology_vector, parent_indices, uber_structs);
EXPECT_THAT(global_matrices, ::testing::ElementsAreArray(expected_matrices));
}
// The following tests ensure that different geometric attributes (translation, rotation, scale)
// modify the final rectangle as expected.
TEST(Rectangle2DTest, ScaleAndRotate90DegreesTest) {
const glm::vec2 extent(100.f, 50.f);
glm::mat3 matrix = glm::rotate(glm::mat3(), glm::half_pi<float>());
matrix = glm::scale(matrix, extent);
const escher::Rectangle2D expected_rectangle(
glm::vec2(0.f, 100.f), glm::vec2(50.f, 100.f),
{glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
TEST(Rectangle2DTest, ScaleAndRotate180DegreesTest) {
const glm::vec2 extent(100.f, 50.f);
glm::mat3 matrix = glm::rotate(glm::mat3(), glm::pi<float>());
matrix = glm::scale(matrix, extent);
const escher::Rectangle2D expected_rectangle(
glm::vec2(-100.f, 50.f), glm::vec2(100.f, 50.f),
{glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
TEST(Rectangle2DTest, ScaleAndRotate270DegreesTest) {
const glm::vec2 extent(100.f, 50.f);
glm::mat3 matrix = glm::rotate(glm::mat3(), glm::three_over_two_pi<float>());
matrix = glm::scale(matrix, extent);
const escher::Rectangle2D expected_rectangle(
glm::vec2(-50.f, 0.f), glm::vec2(50.f, 100.f),
{glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 1)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
// Make sure that floating point transform values that aren't exactly
// integers are also respected.
TEST(Rectangle2DTest, FloatingPointTranslateAndScaleTest) {
const glm::vec2 offset(10.9f, 20.5f);
const glm::vec2 extent(100.3f, 200.7f);
glm::mat3 matrix = glm::translate(glm::mat3(), offset);
matrix = glm::scale(matrix, extent);
const escher::Rectangle2D expected_rectangle(
offset, extent, {glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
TEST(Rectangle2DTest, NegativeScaleTest) {
// If both the x and y scale components are negative, this is equivalent
// to a positive scale rotated by 180 degrees (PI radians).
{
const glm::vec2 extent(-10.f, -5.f);
glm::mat3 matrix = glm::scale(glm::mat3(), extent);
// These are the expected UVs for a 180 degree rotation.
const escher::Rectangle2D expected_rectangle(
glm::vec2(-10.f, 5.f), glm::vec2(10.f, 5.f),
{glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
// If just the x scale component is negative and the y component is positive,
// this is equivalent to a flip about the y axis (horiziontal).
{
const glm::vec2 extent(-10.f, 5.f);
glm::mat3 matrix = glm::scale(glm::mat3(), extent);
// These are the expected UVs for a horizontal flip.
const escher::Rectangle2D expected_rectangle(
glm::vec2(-10.f, 0.f), glm::vec2(10.f, 5.f),
{glm::vec2(1, 0), glm::vec2(0, 0), glm::vec2(0, 1), glm::vec2(1, 1)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
// If just the y scale component is negative and the x component is positive,
// this is equivalent to a vertical flip about the x axis.
{
const glm::vec2 extent(10.f, -5.f);
glm::mat3 matrix = glm::scale(glm::mat3(), extent);
// These are the expected UVs for a vertical flip.
const escher::Rectangle2D expected_rectangle(
glm::vec2(0.f, 5.f), glm::vec2(10.f, 5.f),
{glm::vec2(0, 1), glm::vec2(1, 1), glm::vec2(1, 0), glm::vec2(0, 0)});
const auto rectangle = GetRectangleForMatrix(matrix);
EXPECT_EQ(rectangle, expected_rectangle);
}
}
// The same operations of translate/rotate/scale on a single matrix.
TEST(Rectangle2DTest, OrderOfOperationsTest) {
// First subtest tests swapping scaling and translation.
{
// Here we scale and then translate. The origin should be at (10,5) and the extent should also
// still be (2,2) since the scale is being applied on the untranslated coordinates.
const glm::mat3 test_1 =
glm::scale(glm::translate(glm::mat3(), glm::vec2(10.f, 5.f)), glm::vec2(2.f, 2.f));
const escher::Rectangle2D expected_rectangle_1(glm::vec2(10.f, 5.f), glm::vec2(2.f, 2.f));
const auto rectangle_1 = GetRectangleForMatrix(test_1);
EXPECT_EQ(rectangle_1, expected_rectangle_1);
// Here we translate first, and then scale the translation, resulting in the origin point
// doubling from (10, 5) to (20, 10).
const glm::mat3 test_2 =
glm::translate(glm::scale(glm::mat3(), glm::vec2(2.f, 2.f)), glm::vec2(10.f, 5.f));
const escher::Rectangle2D expected_rectangle_2(glm::vec2(20.f, 10.f), glm::vec2(2.f, 2.f));
const auto rectangle_2 = GetRectangleForMatrix(test_2);
EXPECT_EQ(rectangle_2, expected_rectangle_2);
}
// Second subtest tests swapping translation and rotation.
{
// Since the rotation is applied first, the origin point rotates around (0,0) and then we
// translate and wind up at (10, 5).
const glm::mat3 test_1 =
glm::rotate(glm::translate(glm::mat3(), glm::vec2(10.f, 5.f)), glm::half_pi<float>());
const escher::Rectangle2D expected_rectangle_1(
glm::vec2(10.f, 6.f), glm::vec2(1.f, 1.f),
{glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0)});
const auto rectangle_1 = GetRectangleForMatrix(test_1);
EXPECT_EQ(rectangle_1, expected_rectangle_1);
// Since we translated first here, the point goes from (0,0) to (10,5) and then rotates
// 90 degrees counterclockwise and winds up at (-5, 10).
const glm::mat3 test_2 =
glm::translate(glm::rotate(glm::mat3(), glm::half_pi<float>()), glm::vec2(10.f, 5.f));
const escher::Rectangle2D expected_rectangle_2(
glm::vec2(-5.f, 11.f), glm::vec2(1.f, 1.f),
{glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0)});
const auto rectangle_2 = GetRectangleForMatrix(test_2);
EXPECT_EQ(rectangle_2, expected_rectangle_2);
}
// Third subtest tests swapping non-uniform scaling and rotation.
{
// We rotate first and then scale, so the scaling isn't affected by the rotation.
const glm::mat3 test_1 =
glm::rotate(glm::scale(glm::mat3(), glm::vec2(9.f, 7.f)), glm::half_pi<float>());
const escher::Rectangle2D expected_rectangle_1(
glm::vec2(0.f, 7.f), glm::vec2(9.f, 7.f),
{glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0)});
const auto rectangle_1 = GetRectangleForMatrix(test_1);
EXPECT_EQ(rectangle_1, expected_rectangle_1);
// Here we scale and then rotate so the scale winds up rotated.
const glm::mat3 test_2 =
glm::scale(glm::rotate(glm::mat3(), glm::half_pi<float>()), glm::vec2(9.f, 7.f));
const escher::Rectangle2D expected_rectangle_2(
glm::vec2(0.f, 9.f), glm::vec2(7.f, 9.f),
{glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0)});
const auto rectangle_2 = GetRectangleForMatrix(test_2);
EXPECT_EQ(rectangle_2, expected_rectangle_2);
}
}
} // namespace test
} // namespace flatland
#undef EXPECT_VEC2