blob: be4d83b15f0660cc95ea659bc5a7ab779beae070 [file] [log] [blame]
// Copyright 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"
#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"
namespace vr_shell {
namespace {
void ParseRecti(const base::DictionaryValue& dict, const std::string& key,
Recti* output) {
const base::DictionaryValue* item_dict;
if (dict.GetDictionary(key, &item_dict)) {
CHECK(item_dict->GetInteger("x", &output->x));
CHECK(item_dict->GetInteger("y", &output->y));
CHECK(item_dict->GetInteger("width", &output->width));
CHECK(item_dict->GetInteger("height", &output->height));
}
}
void Parse2DVec3f(const base::DictionaryValue& dict, const std::string& key,
gvr::Vec3f* output) {
const base::DictionaryValue* item_dict;
if (dict.GetDictionary(key, &item_dict)) {
double value;
CHECK(item_dict->GetDouble("x", &value));
output->x = value;
CHECK(item_dict->GetDouble("y", &value));
output->y = value;
output->z = 1.0f;
}
}
void ParseVec3f(const base::DictionaryValue& dict, const std::string& key,
gvr::Vec3f* output) {
const base::DictionaryValue* item_dict;
if (dict.GetDictionary(key, &item_dict)) {
double value;
CHECK(item_dict->GetDouble("x", &value));
output->x = value;
CHECK(item_dict->GetDouble("y", &value));
output->y = value;
CHECK(item_dict->GetDouble("z", &value));
output->z = value;
}
}
void ParseRotationAxisAngle(const base::DictionaryValue& dict,
const std::string& key, RotationAxisAngle* output) {
const base::DictionaryValue* item_dict;
if (dict.GetDictionary(key, &item_dict)) {
double value;
CHECK(item_dict->GetDouble("x", &value));
output->x = value;
CHECK(item_dict->GetDouble("y", &value));
output->y = value;
CHECK(item_dict->GetDouble("z", &value));
output->z = value;
CHECK(item_dict->GetDouble("a", &value));
output->angle = value;
}
}
void ParseFloats(const base::DictionaryValue& dict,
const std::vector<std::string>& keys,
std::vector<float>* vec) {
for (const auto& key : keys) {
double value;
CHECK(dict.GetDouble(key, &value)) << "parsing tag " << key;
vec->push_back(value);
}
}
void ParseEndpointToFloats(Animation::Property property,
const base::DictionaryValue& dict,
std::vector<float>* vec) {
switch (property) {
case Animation::Property::COPYRECT:
ParseFloats(dict, {"x", "y", "width", "height"}, vec);
break;
case Animation::Property::SIZE:
ParseFloats(dict, {"x", "y"}, vec);
break;
case Animation::Property::SCALE:
ParseFloats(dict, {"x", "y", "z"}, vec);
break;
case Animation::Property::ROTATION:
ParseFloats(dict, {"x", "y", "z", "a"}, vec);
break;
case Animation::Property::TRANSLATION:
ParseFloats(dict, {"x", "y", "z"}, vec);
break;
}
}
std::unique_ptr<easing::Easing> ParseEasing(
const base::DictionaryValue& dict) {
easing::EasingType easingType;
CHECK(dict.GetInteger("type", reinterpret_cast<int*>(&easingType)));
std::unique_ptr<easing::Easing> result;
switch (easingType) {
case easing::EasingType::LINEAR: {
result.reset(new easing::Linear());
break;
}
case easing::EasingType::CUBICBEZIER: {
double p1x, p1y, p2x, p2y;
CHECK(dict.GetDouble("p1x", &p1x));
CHECK(dict.GetDouble("p1y", &p1y));
CHECK(dict.GetDouble("p2x", &p2x));
CHECK(dict.GetDouble("p2y", &p2y));
result.reset(new easing::CubicBezier(p1x, p1y, p2x, p2y));
break;
}
case easing::EasingType::EASEIN: {
double pow;
CHECK(dict.GetDouble("pow", &pow));
result.reset(new easing::EaseIn(pow));
break;
}
case easing::EasingType::EASEOUT: {
double pow;
CHECK(dict.GetDouble("pow", &pow));
result.reset(new easing::EaseOut(pow));
break;
}
}
return result;
}
void ApplyAnchoring(const ContentRectangle& parent, XAnchoring x_anchoring,
YAnchoring y_anchoring, ReversibleTransform* transform) {
// To anchor a child, use the parent's size to find its edge.
float x_offset;
switch (x_anchoring) {
case XLEFT:
x_offset = -0.5f * parent.size.x;
break;
case XRIGHT:
x_offset = 0.5f * parent.size.x;
break;
case XNONE:
x_offset = 0.0f;
break;
}
float y_offset;
switch (y_anchoring) {
case YTOP:
y_offset = 0.5f * parent.size.y;
break;
case YBOTTOM:
y_offset = -0.5f * parent.size.y;
break;
case YNONE:
y_offset = 0.0f;
break;
}
transform->Translate(x_offset, y_offset, 0);
}
} // namespace
void UiScene::AddUiElement(std::unique_ptr<ContentRectangle>& element) {
CHECK_GE(element->id, 0);
CHECK_EQ(GetUiElementById(element->id), nullptr);
if (element->parent_id >= 0) {
CHECK_NE(GetUiElementById(element->parent_id), nullptr);
} else {
CHECK_EQ(element->x_anchoring, XAnchoring::XNONE);
CHECK_EQ(element->y_anchoring, YAnchoring::YNONE);
}
ui_elements_.push_back(std::move(element));
}
void UiScene::AddUiElementFromDict(const base::DictionaryValue& dict) {
int id;
CHECK(dict.GetInteger("id", &id));
CHECK_EQ(GetUiElementById(id), nullptr);
std::unique_ptr<ContentRectangle> element(new ContentRectangle);
element->id = id;
ApplyDictToElement(dict, element.get());
ui_elements_.push_back(std::move(element));
}
void UiScene::UpdateUiElementFromDict(const base::DictionaryValue& dict) {
int id;
CHECK(dict.GetInteger("id", &id));
ContentRectangle* element = GetUiElementById(id);
CHECK_NE(element, nullptr);
ApplyDictToElement(dict, element);
}
void UiScene::RemoveUiElement(int element_id) {
for (auto it = ui_elements_.begin(); it != ui_elements_.end(); ++it) {
if ((*it)->id == element_id) {
if ((*it)->content_quad) {
content_element_ = nullptr;
}
ui_elements_.erase(it);
return;
}
}
}
void UiScene::AddAnimation(int element_id,
std::unique_ptr<Animation>& animation) {
ContentRectangle* element = GetUiElementById(element_id);
CHECK_NE(element, nullptr);
for (auto& existing_animation : element->animations) {
CHECK_NE(existing_animation->id, animation->id);
}
element->animations.emplace_back(std::move(animation));
}
void UiScene::AddAnimationFromDict(const base::DictionaryValue& dict,
int64_t time_in_micro) {
int animation_id;
int element_id;
Animation::Property property;
double start_time_ms;
double duration_ms;
const base::DictionaryValue* easing_dict = nullptr;
const base::DictionaryValue* from_dict = nullptr;
const base::DictionaryValue* to_dict = nullptr;
std::vector<float> from;
std::vector<float> to;
CHECK(dict.GetInteger("id", &animation_id));
CHECK(dict.GetInteger("meshId", &element_id));
CHECK(dict.GetInteger("property", reinterpret_cast<int*>(&property)));
CHECK(dict.GetDouble("startInMillis", &start_time_ms));
CHECK(dict.GetDouble("durationMillis", &duration_ms));
CHECK(dict.GetDictionary("easing", &easing_dict));
auto easing = ParseEasing(*easing_dict);
CHECK(dict.GetDictionary("to", &to_dict));
ParseEndpointToFloats(property, *to_dict, &to);
// The start location is optional. If not specified, the animation will
// start from the element's current location.
dict.GetDictionary("from", &from_dict);
if (from_dict != nullptr) {
ParseEndpointToFloats(property, *from_dict, &from);
}
int64_t start = time_in_micro + (long)(start_time_ms * 1000.0);
int64_t duration = duration_ms * 1000.0;
ContentRectangle* element = GetUiElementById(element_id);
CHECK_NE(element, nullptr);
element->animations.emplace_back(std::unique_ptr<Animation>(
new Animation(
animation_id, static_cast<Animation::Property>(property),
std::move(easing), from, to, start, duration)));
}
void UiScene::RemoveAnimation(int element_id, int animation_id) {
ContentRectangle* element = GetUiElementById(element_id);
CHECK_NE(element, nullptr);
auto& animations = element->animations;
for (auto it = animations.begin(); it != animations.end(); ++it) {
const Animation& existing_animation = **it;
if (existing_animation.id == animation_id) {
animations.erase(it);
return;
}
}
}
void UiScene::HandleCommands(const base::ListValue* commands,
int64_t time_in_micro) {
for (auto& item : *commands) {
base::DictionaryValue* dict;
CHECK(item->GetAsDictionary(&dict));
Command type;
base::DictionaryValue* data;
CHECK(dict->GetInteger("type", reinterpret_cast<int*>(&type)));
CHECK(dict->GetDictionary("data", &data));
switch (type) {
case Command::ADD_ELEMENT:
AddUiElementFromDict(*data);
break;
case Command::UPDATE_ELEMENT:
UpdateUiElementFromDict(*data);
break;
case Command::REMOVE_ELEMENT: {
int element_id;
CHECK(data->GetInteger("id", &element_id));
RemoveUiElement(element_id);
break;
}
case Command::ADD_ANIMATION:
AddAnimationFromDict(*data, time_in_micro);
break;
case Command::REMOVE_ANIMATION: {
int element_id, animation_id;
CHECK(data->GetInteger("id", &animation_id));
CHECK(data->GetInteger("meshId", &element_id));
RemoveAnimation(element_id, animation_id);
break;
}
}
}
}
void UiScene::UpdateTransforms(float screen_tilt, int64_t time_in_micro) {
// Process all animations before calculating object transforms.
for (auto& element : ui_elements_) {
element->Animate(time_in_micro);
}
for (auto& element : ui_elements_) {
element->transform.MakeIdentity();
element->transform.Scale(element->size.x, element->size.y, element->size.z);
ApplyRecursiveTransforms(*element.get(), &element->transform);
element->transform.Rotate(1.0f, 0.0f, 0.0f, screen_tilt);
}
}
ContentRectangle* UiScene::GetUiElementById(int element_id) {
for (auto& element : ui_elements_) {
if (element->id == element_id) {
return element.get();
}
}
return nullptr;
}
ContentRectangle* UiScene::GetContentQuad() {
return content_element_;
}
const std::vector<std::unique_ptr<ContentRectangle>>&
UiScene::GetUiElements() const {
return ui_elements_;
}
UiScene::UiScene() = default;
UiScene::~UiScene() = default;
int64_t UiScene::TimeInMicroseconds() {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count();
}
void UiScene::ApplyRecursiveTransforms(const ContentRectangle& element,
ReversibleTransform* transform) {
transform->Scale(element.scale.x, element.scale.y, element.scale.z);
transform->Rotate(element.rotation.x, element.rotation.y,
element.rotation.z, element.rotation.angle);
transform->Translate(element.translation.x, element.translation.y,
element.translation.z);
if (element.parent_id >= 0) {
const ContentRectangle* parent = GetUiElementById(element.parent_id);
CHECK(parent != nullptr);
ApplyAnchoring(*parent, element.x_anchoring, element.y_anchoring,
transform);
ApplyRecursiveTransforms(*parent, transform);
}
}
void UiScene::ApplyDictToElement(const base::DictionaryValue& dict,
ContentRectangle *element) {
int parent_id;
if (dict.GetInteger("parentId", &parent_id)) {
CHECK_GE(parent_id, 0);
CHECK_NE(GetUiElementById(parent_id), nullptr);
element->parent_id = parent_id;
}
dict.GetBoolean("visible", &element->visible);
dict.GetBoolean("hitTestable", &element->hit_testable);
dict.GetBoolean("lockToFov", &element->lock_to_fov);
ParseRecti(dict, "copyRect", &element->copy_rect);
Parse2DVec3f(dict, "size", &element->size);
ParseVec3f(dict, "scale", &element->scale);
ParseRotationAxisAngle(dict, "rotation", &element->rotation);
ParseVec3f(dict, "translation", &element->translation);
if (dict.GetBoolean("contentQuad", &element->content_quad)) {
if (element->content_quad) {
CHECK_EQ(content_element_, nullptr);
content_element_ = element;
} else {
if (content_element_ == element) {
content_element_ = nullptr;
}
}
}
if (dict.GetInteger("xAnchoring",
reinterpret_cast<int*>(&element->x_anchoring))) {
CHECK_GE(element->parent_id, 0);
}
if (dict.GetInteger("yAnchoring",
reinterpret_cast<int*>(&element->y_anchoring))) {
CHECK_GE(element->parent_id, 0);
}
}
} // namespace vr_shell