Files
2026-06-14 19:09:18 +01:00

331 lines
13 KiB
C++

/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */
/*
* Copyright 2017-2020 Mark Callow.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @internal
* @class LoadTestSample
* @~English
*
* @brief Definition of a base class for texture loading test samples.
*
* @author Mark Callow, github.com/MarkCallow.
*/
#if defined(_WIN32)
#define _USE_MATH_DEFINES
#endif
#include "LoadTestSample.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/vector_angle.hpp>
#include <SDL3/SDL_log.h>
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_DETECTION)
// Log detected and completed gestures.
#define LOADTESTSAMPLE_LOG_GESTURE_DETECTION 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_GESTURE_EVENTS)
// Log events contributing to gesture detection and gestures.
#define LOADTESTSAMPLE_LOG_GESTURE_EVENTS 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS)
#define LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS 0
#endif
#if !defined(LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS)
#define LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS 0
#endif
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
#include <sstream>
const std::string printFingerIds(SDL_Finger* fingers[], uint32_t numFingers) {
std::stringstream msg;
assert(numFingers > 0);
msg << std::hex << std::showbase;
msg << "finger id" << (numFingers > 1 ? "s" : "") << ": ";
for (uint32_t f = 0; f < numFingers; f++) {
if (f > 0) {
if (f == numFingers - 1)
msg << " & ";
else
msg << ", ";
}
msg << fingers[f]->id;
}
return msg.str();
}
const std::string printVector(const std::string& name, glm::vec2 v) {
std::stringstream msg;
msg << name << " (" << v.x << ", " << v.y << ")";
return msg.str();
}
#endif
[[maybe_unused]] static const char*
buttonName(Uint8 button) {
switch(button) {
case SDL_BUTTON_LEFT: return "left";
case SDL_BUTTON_MIDDLE: return "middle";
case SDL_BUTTON_RIGHT: return "right";
default: return "other";
}
}
int
LoadTestSample::doEvent(SDL_Event* event)
{
switch (event->type) {
case SDL_EVENT_MOUSE_MOTION:
{
SDL_MouseMotionEvent& motion = event->motion;
#if LOADTESTSAMPLE_LOG_MOUSE_MOTION_EVENTS
SDL_Log("LTS: MOUSE_MOTION - x: %f, y: %f", motion.x, motion.y);
#endif
// On macOS with trackpad, SDL_TOUCH_MOUSEID is never set.
// Prefer mouse events on macOS because press is required. When
// finger motion events are used the object starts to rotate when
// you drag the cursor over the window. Not nice.
if (mouseButtons.left)
{
rotation.x -= yflip * (mousePos.y - (float)motion.y) * 1.25f;
rotation.y -= (mousePos.x - (float)motion.x) * 1.25f;
viewChanged();
}
if (mouseButtons.right)
{
zoom += (mousePos.y - (float)motion.y) * .005f;
viewChanged();
}
if (mouseButtons.middle)
{
cameraPos.x -= (mousePos.x - (float)motion.x) * 0.01f;
cameraPos.y += yflip * (mousePos.y - (float)motion.y) * 0.01f;
viewChanged();
}
mousePos = glm::vec2((float)motion.x, (float)motion.y);
return 0;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
mousePos = glm::vec2((float)event->button.x, (float)event->button.y);
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
SDL_Log("LTS: MOUSE_DOWN - button: %s, x: %f, y: %f", buttonName(event->button.button),
event->button.x, event->button.y);
}
switch (event->button.button) {
case SDL_BUTTON_LEFT:
mouseButtons.left = true;
break;
case SDL_BUTTON_MIDDLE:
mouseButtons.middle = true;
break;
case SDL_BUTTON_RIGHT:
mouseButtons.right = true;
break;
default:
return 1;
}
return 0;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (LOADTESTSAMPLE_LOG_MOUSE_UP_DOWN_EVENTS) {
SDL_Log("LTS: MOUSE_UP - button: %s, x: %f, y: %f", buttonName(event->button.button),
event->button.x, event->button.y);
}
switch (event->button.button) {
case SDL_BUTTON_LEFT:
mouseButtons.left = false;
break;
case SDL_BUTTON_MIDDLE:
mouseButtons.middle = false;
break;
case SDL_BUTTON_RIGHT:
mouseButtons.right = false;
break;
default:
return 1;
}
return 0;
case SDL_EVENT_FINGER_DOWN: {
// Prevent multifingers from triggering the left button action and
// interfering with multigestures.
//
// On iOS you get a left button down event no matter how many fingers
// you touch to the screen. We want 1 finger mouse to work so
// behaviour is same as pressing the trackpad on macOS, etc. As iOS
// button_down events come before finger_down we can clear the left
// button down state, if we have multiple fingers. Hope this ordering
// is the same on other touch screen platforms that send a left-button
// event regardless of the number of fingers.
//
// On macOS button_down events come after finger_down so this code has
// no effect.
//
// Another way to handle this is to identify the platform and work
// differently for each platform.
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
int retVal = 0;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " down - fingers: %i, %s, x: %f, y: %f",
event->tfinger.fingerID, numFingers,
printFingerIds(fingers, numFingers).c_str(),
event->tfinger.x, event->tfinger.y);
#endif
if (numFingers > 1) {
mouseButtons.left = false;
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS: FINGER_DOWN with multiple fingers received."
" Resetting mouseButtons.left.");
}
if (numFingers == 2) {
firstFingerId = fingers[0]->id;
// Calc. difference vector between fingers.
glm::vec2 vDifference;
vDifference.x = fingers[1]->x - fingers[0]->x;
vDifference.y = fingers[1]->y - fingers[0]->y;
distanceStart = glm::length(vDifference);
distanceLast = distanceStart;
// Need normalized vectors for glm::orientedAngle
nvDifferenceStart = glm::normalize(vDifference);
nvDifferenceLast = nvDifferenceStart;
processingGesture = true;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
// Angle of vector to X axis.
xAngleStart = atan2f(vDifference.y, vDifference.x);
SDL_Log("LTS: FINGER_DOWN, start values: %s, Distance = %f, XAngle = %f°",
printVector("Difference", vDifference).c_str(),
distanceStart, xAngleStart * 180.0 / M_PI
);
#endif
retVal = 1;
}
}
// It is possible to somehow get out of the window without seeing
// FINGER_UP so as a safeguard stop any previous gesture.
zooming = rotating = false;
SDL_free(fingers);
return retVal;
}
case SDL_EVENT_FINGER_UP: {
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
SDL_Log("LTS: Finger: %#" SDL_PRIx64 " up - fingers: %i, %s, x: %f, y: %f",
event->tfinger.fingerID, numFingers,
printFingerIds(fingers, numFingers).c_str(),
event->tfinger.x, event->tfinger.y);
#endif
if (processingGesture && numFingers == 2) {
// There may still be one finger down. Even so the action is completed.
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("-------------- LTS: %s complete. -----------------",
zooming ? "zooming" : rotating ? "rotating" : "gesture");
}
zooming = rotating = processingGesture = false;
}
SDL_free(fingers);
break;
}
case SDL_EVENT_FINGER_MOTION: {
int numFingers;
SDL_Finger** fingers = SDL_GetTouchFingers(event->tfinger.touchID, &numFingers);
if (numFingers != 2)
return 1;
if (!processingGesture) {
// Protect against FINGER_MOTION without FINGER_DOWN. This can
// happen when the sample is switched by a swipe and the new sample
// receives the tail end of the swipe motion.
return 1;
}
// With two fingers down, events come in pairs. No point in processing
// both.
if (event->tfinger.fingerID == firstFingerId) {
return 0;
}
glm::vec2 vDifference; // Difference vector between the fingers.
vDifference.x = fingers[1]->x - fingers[0]->x;
vDifference.y = fingers[1]->y - fingers[0]->y;
float distance = glm::length(vDifference);
// Normalized vectors required by glm::orientedAngle
glm::vec2 nvDifference = glm::normalize(vDifference);
// Angle between start and current difference vectors
float sAngle = glm::orientedAngle(nvDifferenceStart, nvDifference);
// Angle between current and previous difference vectors
float dAngle = glm::orientedAngle(nvDifferenceLast, nvDifference);
// Difference in distance since last motion event.
float dDist = distance - distanceLast;
// Difference in distance since start.
float dDistStart = distance - distanceStart;
#if LOADTESTSAMPLE_LOG_GESTURE_EVENTS
if (!(rotating || zooming)) {
// Angle from X axis to vDifference vector
float xAngle = atan2f(vDifference.y, vDifference.x);
SDL_Log("LTS FINGER_MOTION: Not zooming or rotating. "
" timestamp = %" SDL_PRIu64 ", %s, %s",
event->tfinger.timestamp,
printFingerIds(fingers, numFingers).c_str(),
printVector("Difference", vDifference).c_str());
SDL_Log("... distanceLast = %f, distance = %f, dDist = %f, dDistStart = %f, xAngle = %f°, sAngle = %f°, dAngle = %f°",
distanceLast, distance, dDist, dDistStart,
xAngle * 180.0 / M_PI, sAngle * 180.0 / M_PI, dAngle * 180.0 / M_PI);
}
#endif
nvDifferenceLast = nvDifference;
distanceLast = distance;
// This is all heuristics derived from use.
if (zooming) {
zoom += dDist * 10.0f;
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS MG: Zooming. zoom = %f", zoom);
}
} else if (!rotating) {
if (fabs(dDistStart) >= 0.1 && fabs(dAngle) < 0.5 * M_PI / 180.0) {
zooming = true;
zoom += dDist * 10.0f;
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("---------------- LTS MG: pinch/zoom detected ---------------\n"
" dAngle = %f°, dDistStart = %f, dDist = %f, zoom = %f",
dAngle * 180.0 / M_PI, dDistStart, dDist, zoom);
}
}
}
if (rotating) {
rotation.z +=
static_cast<float>(dAngle * 180.0 / M_PI);
if (LOADTESTSAMPLE_LOG_GESTURE_EVENTS) {
SDL_Log("LTS MG: Rotating around Z. rotation.z = %f°", rotation.z);
}
} else if (!zooming) {
if (fabs(sAngle) > 15 * M_PI / 180.0 && fabs(dDistStart) < 0.1) {
rotating = true;
rotation.z += static_cast<float>(dAngle * 180.0 / M_PI);
if (LOADTESTSAMPLE_LOG_GESTURE_DETECTION) {
SDL_Log("---------------- LTS MG: rotation detected ---------------\n"
" sAngle = %f°, dAngle = %f°, dDistStart = %f, rotation.z = %f°",
sAngle * 180 / M_PI, dAngle * 180.0 / M_PI, dDistStart, rotation.z);
}
}
}
viewChanged();
SDL_free(fingers);
return 0;
}
case SDL_EVENT_KEY_UP:
if (event->key.key == 'q')
quit = true;
keyPressed(event->key.key);
return 0;
default:
break;
}
return 1;
}