blob: a86e4c85bca4175b4dbf59e7a456bc628a5ae478 [file] [log] [blame]
// Copyright 2018 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/lib/ui/input/gesture.h"
namespace input {
Gesture::Delta& Gesture::Delta::operator+=(const Delta& other) {
translation += other.translation;
rotation += other.rotation;
scale *= other.scale;
return *this;
}
bool Gesture::Delta::operator==(const Delta& other) const {
return translation == other.translation && rotation == other.rotation && scale == other.scale;
}
void Gesture::AddPointer(PointerId pointer_id, const glm::vec2& position) {
// TODO(fxbug.dev/24596): This is sometimes violated.
// FX_DCHECK(pointers_.find(pointer_id) == pointers_.end());
pointers_[pointer_id] = {.absolute = position, .relative = {}, .distance = 0};
UpdateCentroid();
UpdateRelative();
}
Gesture::Delta Gesture::UpdatePointer(PointerId pointer_id, const glm::vec2& position) {
// TODO(fxbug.dev/24596): This is sometimes violated.
// FX_DCHECK(pointers_.find(pointer_id) != pointers_.end());
auto it = pointers_.find(pointer_id);
if (it == pointers_.end()) {
AddPointer(pointer_id, position);
return {.translation = {}, .rotation = 0, .scale = 1};
}
it->second.absolute = position;
Delta delta;
{
glm::vec2 old_centroid = centroid_;
UpdateCentroid();
delta.translation = centroid_ - old_centroid;
}
if (pointers_.size() > 1) {
float moment_sum = 0;
// Use an arithmetic mean as a decent approximation of a geometric mean.
float scale_sum = 0;
for (auto& entry : pointers_) {
PointerInfo& p = entry.second;
glm::vec2 old_relative = p.relative;
float old_distance = p.distance;
p.relative = p.absolute - centroid_;
p.distance = glm::length(p.relative);
// TODO(rosswang): The following has singular behavior when pointers have the same
// coordinates, which can in particular happen during testing. In such cases while it makes
// mathematical sense for rotation and scale to be NaN/infinite, perhaps we should default
// them to identity values.
// For small displacements, this approximates radians.
delta.rotation +=
(old_relative.x * p.relative.y - old_relative.y * p.relative.x) / old_distance;
moment_sum += old_distance;
scale_sum += p.distance / old_distance;
}
delta.rotation /= moment_sum;
delta.scale *= scale_sum / pointers_.size();
}
return delta;
}
void Gesture::RemovePointer(PointerId pointer_id) {
pointers_.erase(pointer_id);
if (!pointers_.empty()) {
UpdateCentroid();
UpdateRelative();
}
}
void Gesture::UpdateCentroid() {
// It would be more efficient to do this incrementally at the possible cost of precision. Gestures
// tend to be both short and with a small number of pointers, so neither the efficiency nor
// precision is particularly important. However, edge cases like new pointers are easier to deal
// with if we always recalculate, especially with fxbug.dev/24596.
centroid_ = {0, 0};
for (const auto& entry : pointers_) {
centroid_ += entry.second.absolute;
}
centroid_ /= pointers_.size();
}
void Gesture::UpdateRelative() {
for (auto& entry : pointers_) {
PointerInfo& p = entry.second;
p.relative = p.absolute - centroid_;
p.distance = glm::length(p.relative);
}
}
} // namespace input