blob: 91a8196a1b509ff68e36ee410c9fdd70e5b49b2f [file] [log] [blame]
// Copyright (c) 2016 The Chromium 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 "chrome/browser/android/vr_shell/ui_scene.h"
#define _USE_MATH_DEFINES // For M_PI in MSVC.
#include <cmath>
#include <vector>
#include "base/values.h"
#include "chrome/browser/android/vr_shell/animation.h"
#include "chrome/browser/android/vr_shell/easing.h"
#include "chrome/browser/android/vr_shell/ui_elements.h"
#include "chrome/browser/android/vr_shell/vr_math.h"
#include "testing/gtest/include/gtest/gtest.h"
#define TOLERANCE 0.0001
#define EXPECT_VEC3F_NEAR(a, b) \
EXPECT_NEAR(a.x, b.x, TOLERANCE); \
EXPECT_NEAR(a.y, b.y, TOLERANCE); \
EXPECT_NEAR(a.z, b.z, TOLERANCE);
namespace vr_shell {
namespace {
void addElement(UiScene *scene, int id) {
std::unique_ptr<ContentRectangle> element(new ContentRectangle);
element->id = id;
scene->AddUiElement(element);
}
void addAnimation(UiScene *scene, int element_id, int animation_id,
Animation::Property property) {
std::unique_ptr<Animation> animation(new Animation(
animation_id, property,
std::unique_ptr<easing::Easing>(new easing::Linear()),
{}, {1, 1, 1, 1}, 0, 1));
scene->AddAnimation(element_id, animation);
}
} // namespace
TEST(UiScene, AddRemoveElements) {
UiScene scene;
EXPECT_EQ(scene.GetUiElements().size(), 0u);
addElement(&scene, 0);
EXPECT_EQ(scene.GetUiElements().size(), 1u);
addElement(&scene, 99);
EXPECT_EQ(scene.GetUiElements().size(), 2u);
EXPECT_NE(scene.GetUiElementById(0), nullptr);
EXPECT_NE(scene.GetUiElementById(99), nullptr);
EXPECT_EQ(scene.GetUiElementById(1), nullptr);
scene.RemoveUiElement(0);
EXPECT_EQ(scene.GetUiElements().size(), 1u);
EXPECT_EQ(scene.GetUiElementById(0), nullptr);
scene.RemoveUiElement(99);
EXPECT_EQ(scene.GetUiElements().size(), 0u);
EXPECT_EQ(scene.GetUiElementById(99), nullptr);
scene.RemoveUiElement(0);
scene.RemoveUiElement(99);
EXPECT_EQ(scene.GetUiElements().size(), 0u);
}
TEST(UiScene, AddRemoveContentQuad) {
UiScene scene;
EXPECT_EQ(scene.GetContentQuad(), nullptr);
base::DictionaryValue dict;
dict.SetInteger("id", 0);
dict.SetBoolean("contentQuad", true);
scene.AddUiElementFromDict(dict);
EXPECT_NE(scene.GetContentQuad(), nullptr);
dict.SetBoolean("contentQuad", false);
scene.UpdateUiElementFromDict(dict);
EXPECT_EQ(scene.GetContentQuad(), nullptr);
dict.SetBoolean("contentQuad", true);
scene.UpdateUiElementFromDict(dict);
EXPECT_NE(scene.GetContentQuad(), nullptr);
scene.RemoveUiElement(0);
EXPECT_EQ(scene.GetContentQuad(), nullptr);
}
TEST(UiScene, AddRemoveAnimations) {
UiScene scene;
addElement(&scene, 0);
auto *element = scene.GetUiElementById(0);
EXPECT_EQ(element->animations.size(), 0u);
addAnimation(&scene, 0, 0, Animation::Property::SIZE);
EXPECT_EQ(element->animations.size(), 1u);
EXPECT_EQ(element->animations[0]->property, Animation::Property::SIZE);
addAnimation(&scene, 0, 1, Animation::Property::SCALE);
EXPECT_EQ(element->animations.size(), 2u);
EXPECT_EQ(element->animations[1]->property, Animation::Property::SCALE);
scene.RemoveAnimation(0, 0);
EXPECT_EQ(element->animations.size(), 1u);
EXPECT_EQ(element->animations[0]->property, Animation::Property::SCALE);
scene.RemoveAnimation(0, 1);
EXPECT_EQ(element->animations.size(), 0u);
scene.RemoveAnimation(0, 0);
EXPECT_EQ(element->animations.size(), 0u);
}
// This test creates a parent and child UI element, each with their own
// transformations, and ensures that the child's computed total transform
// incorporates the parent's transform as well as its own.
TEST(UiScene, ParentTransformAppliesToChild) {
UiScene scene;
// Add a parent element, with distinct transformations.
// Size of the parent should be ignored by the child.
std::unique_ptr<ContentRectangle> element(new ContentRectangle);
element->id = 0;
element->size = {1000, 1000, 1};
element->scale = {3, 3, 1};
element->rotation = {0, 0, 1, M_PI / 2};
element->translation = {6, 1, 0};
scene.AddUiElement(element);
// Add a child to the parent, with different transformations.
element.reset(new ContentRectangle);
element->id = 1;
element->parent_id = 0;
element->size = {1, 1, 1};
element->scale = {2, 2, 1};
element->rotation = {0, 0, 1, M_PI / 2};
element->translation = {3, 0, 0};
scene.AddUiElement(element);
const ContentRectangle* child = scene.GetUiElementById(1);
const gvr::Vec3f origin({0,0,0});
const gvr::Vec3f point({1,0,0});
// Check resulting transform with no screen tilt.
scene.UpdateTransforms(0, 0);
auto new_origin = MatrixVectorMul(child->transform.to_world, origin);
auto new_point = MatrixVectorMul(child->transform.to_world, point);
EXPECT_VEC3F_NEAR(gvr::Vec3f({6, 10, 0}), new_origin);
EXPECT_VEC3F_NEAR(gvr::Vec3f({0, 10, 0}), new_point);
// Check with screen tilt (use 90 degrees for simplicity).
scene.UpdateTransforms(M_PI / 2, 0);
new_origin = MatrixVectorMul(child->transform.to_world, origin);
new_point = MatrixVectorMul(child->transform.to_world, point);
EXPECT_VEC3F_NEAR(gvr::Vec3f({6, 0, 10}), new_origin);
EXPECT_VEC3F_NEAR(gvr::Vec3f({0, 0, 10}), new_point);
}
typedef struct {
XAnchoring x_anchoring;
YAnchoring y_anchoring;
float expected_x;
float expected_y;
} AnchoringTestCase;
class AnchoringTest : public ::testing::TestWithParam<AnchoringTestCase> {};
TEST_P(AnchoringTest, VerifyCorrectPosition) {
UiScene scene;
// Create a parent element with non-unity size and scale.
std::unique_ptr<ContentRectangle> element(new ContentRectangle);
element->id = 0;
element->size = {2, 2, 1};
element->scale = {2, 2, 1};
scene.AddUiElement(element);
// Add a child to the parent, with anchoring.
element.reset(new ContentRectangle);
element->id = 1;
element->parent_id = 0;
element->x_anchoring = GetParam().x_anchoring;
element->y_anchoring = GetParam().y_anchoring;
scene.AddUiElement(element);
scene.UpdateTransforms(0, 0);
const ContentRectangle* child = scene.GetUiElementById(1);
EXPECT_NEAR(child->GetCenter().x, GetParam().expected_x, TOLERANCE);
EXPECT_NEAR(child->GetCenter().y, GetParam().expected_y, TOLERANCE);
scene.RemoveUiElement(1);
}
const std::vector<AnchoringTestCase> anchoring_test_cases = {
{ XAnchoring::XNONE, YAnchoring::YNONE, 0, 0},
{ XAnchoring::XLEFT, YAnchoring::YNONE, -2, 0},
{ XAnchoring::XRIGHT, YAnchoring::YNONE, 2, 0},
{ XAnchoring::XNONE, YAnchoring::YTOP, 0, 2},
{ XAnchoring::XNONE, YAnchoring::YBOTTOM, 0, -2},
{ XAnchoring::XLEFT, YAnchoring::YTOP, -2, 2},
};
INSTANTIATE_TEST_CASE_P(AnchoringTestCases, AnchoringTest,
::testing::ValuesIn(anchoring_test_cases));
TEST(UiScene, AddUiElementFromDictionary) {
UiScene scene;
addElement(&scene, 11);
base::DictionaryValue dict;
dict.SetInteger("id", 10);
dict.SetInteger("parentId", 11);
dict.SetBoolean("visible", false);
dict.SetBoolean("hitTestable", false);
dict.SetBoolean("lockToFov", true);
dict.SetBoolean("contentQuad", true);
std::unique_ptr<base::DictionaryValue> copy_rect(new base::DictionaryValue);
copy_rect->SetInteger("x", 100);
copy_rect->SetInteger("y", 101);
copy_rect->SetInteger("width", 102);
copy_rect->SetInteger("height", 103);
dict.Set("copyRect", std::move(copy_rect));
std::unique_ptr<base::DictionaryValue> size(new base::DictionaryValue);
size->SetDouble("x", 200);
size->SetDouble("y", 201);
dict.Set("size", std::move(size));
std::unique_ptr<base::DictionaryValue> scale(new base::DictionaryValue);
scale->SetDouble("x", 300);
scale->SetDouble("y", 301);
scale->SetDouble("z", 302);
dict.Set("scale", std::move(scale));
std::unique_ptr<base::DictionaryValue> rotation(new base::DictionaryValue);
rotation->SetDouble("x", 400);
rotation->SetDouble("y", 401);
rotation->SetDouble("z", 402);
rotation->SetDouble("a", 403);
dict.Set("rotation", std::move(rotation));
std::unique_ptr<base::DictionaryValue> translation(new base::DictionaryValue);
translation->SetDouble("x", 500);
translation->SetDouble("y", 501);
translation->SetDouble("z", 502);
dict.Set("translation", std::move(translation));
dict.SetInteger("xAnchoring", XAnchoring::XLEFT);
dict.SetInteger("yAnchoring", YAnchoring::YTOP);
scene.AddUiElementFromDict(dict);
const auto *element = scene.GetUiElementById(10);
EXPECT_NE(element, nullptr);
EXPECT_EQ(element->id, 10);
EXPECT_EQ(element->parent_id, 11);
EXPECT_EQ(element->visible, false);
EXPECT_EQ(element->hit_testable, false);
EXPECT_EQ(element->lock_to_fov, true);
EXPECT_EQ(element->content_quad, true);
EXPECT_EQ(element->copy_rect.x, 100);
EXPECT_EQ(element->copy_rect.y, 101);
EXPECT_EQ(element->copy_rect.width, 102);
EXPECT_EQ(element->copy_rect.height, 103);
EXPECT_FLOAT_EQ(element->size.x, 200);
EXPECT_FLOAT_EQ(element->size.y, 201);
EXPECT_FLOAT_EQ(element->size.z, 1);
EXPECT_FLOAT_EQ(element->scale.x, 300);
EXPECT_FLOAT_EQ(element->scale.y, 301);
EXPECT_FLOAT_EQ(element->scale.z, 302);
EXPECT_FLOAT_EQ(element->rotation.x, 400);
EXPECT_FLOAT_EQ(element->rotation.y, 401);
EXPECT_FLOAT_EQ(element->rotation.z, 402);
EXPECT_FLOAT_EQ(element->rotation.angle, 403);
EXPECT_FLOAT_EQ(element->translation.x, 500);
EXPECT_FLOAT_EQ(element->translation.y, 501);
EXPECT_FLOAT_EQ(element->translation.z, 502);
EXPECT_EQ(element->x_anchoring, XAnchoring::XLEFT);
EXPECT_EQ(element->y_anchoring, YAnchoring::YTOP);
}
TEST(UiScene, AddAnimationFromDictionary) {
UiScene scene;
addElement(&scene, 0);
base::DictionaryValue dict;
dict.SetInteger("id", 10);
dict.SetInteger("meshId", 0);
dict.SetDouble("startInMillis", 12345);
dict.SetDouble("durationMillis", 54321);
dict.SetInteger("property", Animation::Property::ROTATION);
std::unique_ptr<base::DictionaryValue> easing(new base::DictionaryValue);
easing->SetInteger("type", vr_shell::easing::EasingType::CUBICBEZIER);
easing->SetInteger("p1x", 101);
easing->SetInteger("p1y", 101);
easing->SetInteger("p2x", 101);
easing->SetInteger("p2y", 101);
dict.Set("easing", std::move(easing));
std::unique_ptr<base::DictionaryValue> to(new base::DictionaryValue);
to->SetInteger("x", 200);
to->SetInteger("y", 201);
to->SetInteger("z", 202);
to->SetInteger("a", 203);
dict.Set("to", std::move(to));
std::unique_ptr<base::DictionaryValue> from(new base::DictionaryValue);
from->SetInteger("x", 300);
from->SetInteger("y", 301);
from->SetInteger("z", 302);
from->SetInteger("a", 303);
dict.Set("from", std::move(from));
scene.AddAnimationFromDict(dict, 10000000);
const auto *element = scene.GetUiElementById(0);
const auto *animation = element->animations[0].get();
EXPECT_NE(animation, nullptr);
EXPECT_EQ(animation->id, 10);
EXPECT_FLOAT_EQ(animation->to[0], 200);
EXPECT_FLOAT_EQ(animation->to[1], 201);
EXPECT_FLOAT_EQ(animation->to[2], 202);
EXPECT_FLOAT_EQ(animation->to[3], 203);
EXPECT_FLOAT_EQ(animation->from[0], 300);
EXPECT_FLOAT_EQ(animation->from[1], 301);
EXPECT_FLOAT_EQ(animation->from[2], 302);
EXPECT_FLOAT_EQ(animation->from[3], 303);
EXPECT_EQ(animation->start, 22345000);
EXPECT_EQ(animation->duration, 54321000);
}
} // namespace vr_shell