| // 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 |