Files
OpenVulkano/openVulkanoCpp/Input/Touch/GesturePinch.cpp
2023-10-15 13:39:22 +02:00

244 lines
5.1 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "GesturePinch.hpp"
namespace OpenVulkano::Input
{
namespace
{
constexpr float MIN_DISTANCE = 2;
constexpr float MIN_DISTANCE2 = MIN_DISTANCE * MIN_DISTANCE;
constexpr uint32_t REQUIRED_TOUCHES = 2;
}
void GesturePinch::TryStart()
{
uint32_t dragCount = 0, existingActive = 0;
for (const auto& t : m_pendingTouches)
{
if (t.isDrag) dragCount++;
if (t.active) existingActive++;
}
if (dragCount >= REQUIRED_TOUCHES)
{
if (!IsActive() && !ResolveConflicts()) return;
uint32_t activeCount = existingActive;
for (auto& t : m_pendingTouches)
{
if (t.isDrag && !t.active && activeCount < REQUIRED_TOUCHES)
{
t.active = true;
++activeCount;
}
}
m_paused = false;
if (!IsActive())
{
SetActive(true);
m_initialDistance = CalculateDistance();
float newDistance = m_initialDistance;
if (m_initialDistance > 0)
{
m_pinchInfo.scale = newDistance > 0 ? m_initialDistance / newDistance : m_lastDistance;
m_pinchInfo.position = CalculateCenter();
}
else
{
m_pinchInfo.scale = 1.0f;
m_pinchInfo.position = m_pendingTouches.front().currentPosition;
}
if (m_lastDistance == 0)
{
m_lastDistance = newDistance;
}
// fire event
OnPinchStarted.NotifyAll(this, m_pinchInfo);
m_lastDistance = newDistance;
}
else
{
OnPinchMoved.NotifyAll(this, m_pinchInfo);
}
}
}
float GesturePinch::LinearLength(const std::vector<Math::Vector2f>& points) const
{
auto start = points.begin();
if (start == points.end()) return 0;
auto finish = start + 1;
float sum = 0;
while (finish != points.end())
{
Math::Vector2f displacement = *start - *finish;
sum += Math::Utils::length(displacement);
start = finish++;
}
return sum;
}
float GesturePinch::CalculateDistance() const
{
if (m_pendingTouches.size() >= REQUIRED_TOUCHES && IsActive() && !m_paused)
{
std::vector<Math::Vector2f> positions;
for (const auto& t : m_pendingTouches)
{
if (t.active) positions.push_back(t.currentPosition);
}
return LinearLength(positions);
}
return 0;
}
Math::Vector2f GesturePinch::CalculateCenter() const
{
auto center = Math::Vector2f();
if (m_pendingTouches.size() >= REQUIRED_TOUCHES && IsActive() && !m_paused)
{
std::vector<Math::Vector2f> positions;
for (const auto& t : m_pendingTouches)
{
if (t.active) positions.push_back(t.currentPosition);
}
for (const auto& position : positions)
{
center += position / (float)positions.size();
}
}
return center;
}
void GesturePinch::Cancel()
{
for (auto& t : m_pendingTouches)
{
t.active = false;
}
SetActive(false);
m_paused = false;
OnPinchEnded.NotifyAll(this, m_pinchInfo);
m_pinchInfo.position = {};
}
void GesturePinch::TouchDown(const Touch& touch)
{
TouchInfo ti;
ti.touchId = touch.GetId();
ti.currentPosition = touch.GetPosition();
ti.initialPosition = touch.GetPosition();
ti.delta = touch.GetPosition() - touch.GetLastPosition();
m_pendingTouches.push_back(ti);
}
void GesturePinch::TouchMoved(const Touch& touch)
{
if (!m_pendingTouches.empty())
{
bool isPending = false;
bool isActive = false;
bool oneIsDrag = false;
for (auto& t : m_pendingTouches)
{
if (touch.GetId() == t.touchId)
{
t.currentPosition = touch.GetPosition();
t.delta = touch.GetPosition() - t.initialPosition;
Math::Vector2f displacement = t.currentPosition - t.initialPosition;
if (Math::Utils::length2(displacement) > MIN_DISTANCE2)
{
t.isDrag = true;
oneIsDrag = true;
}
if (t.active) isActive = true;
isPending = true;
}
}
if (oneIsDrag)
{
// Just to activate both cursors
for (auto& t : m_pendingTouches)
{
t.isDrag = true;
}
}
if (isPending)
{
if (!IsActive() || m_paused)
{
TryStart();
}
else if (isActive)
{
float newDistance = CalculateDistance();
if (m_lastDistance == 0)
{
m_lastDistance = newDistance;
}
if (m_initialDistance > 0)
{
m_pinchInfo.scale = newDistance > 0 ? m_initialDistance / newDistance : m_lastDistance;
m_pinchInfo.position = CalculateCenter();
}
else
{
m_pinchInfo.scale = 1.0f;
m_pinchInfo.position = m_pendingTouches.front().currentPosition;
}
OnPinchMoved.NotifyAll(this, m_pinchInfo);
m_lastDistance = newDistance;
}
}
}
}
void GesturePinch::TouchUp(const Touch& touch)
{
if (!m_pendingTouches.empty())
{
for (auto touchIter = m_pendingTouches.begin(); touchIter != m_pendingTouches.end(); touchIter++)
{
if (touch.GetId() == touchIter->touchId)
{
if (touchIter->active) m_paused = true;
m_pendingTouches.erase(touchIter);
break;
}
}
if (m_pendingTouches.empty() && IsActive()) Cancel();
}
}
}