#include "PointerStream.h" #include #include #include #include #include "../App.h" #define lerp(v0, v1, t) ((1 - t) * v0 + t * v1) #define nanos(millis) (millis * 1000000) void PointerStream::stats() { std::printf("PointerStream@%p rawInputs: %zu, filteredInputs: %zu\n", (void*)this, rawInputs.size(), filteredInputs.size()); } PointerEvent PointerStream::findNearestEventBeforeTimestamp(std::list events, unsigned long timestamp) { PointerEvent returnable = events.back(); returnable.timestampNanos = 0; for (PointerEvent event : events) { if (event.timestampNanos <= timestamp && event.timestampNanos > returnable.timestampNanos) { returnable = event; } } return returnable; } PointerEvent PointerStream::findNearestEventAfterTimestamp(std::list events, unsigned long timestamp) { PointerEvent returnable = events.front(); returnable.timestampNanos = UINT64_MAX; for (PointerEvent event : events) { if (event.timestampNanos > timestamp && event.timestampNanos < returnable.timestampNanos) { returnable = event; } } return returnable; } void PointerStream::resampleTimespans(uint64_t resolutionNanos) { if (rawInputs.size() < 2) { return; } std::list filteredData; Uint64 now = SDL_GetTicksNS(); Uint64 nextTimestamp = lastResampledNanos + resolutionNanos; while (nextTimestamp <= now) { PointerEvent before = findNearestEventBeforeTimestamp(rawInputs, nextTimestamp); PointerEvent after = findNearestEventAfterTimestamp(rawInputs, nextTimestamp); if (before.timestampNanos == 0 || after.timestampNanos == UINT64_MAX || before.timestampNanos >= after.timestampNanos) { break; } PointerEvent event; event.timestampNanos = nextTimestamp; double t = (double)(nextTimestamp - before.timestampNanos) / (double)(after.timestampNanos - before.timestampNanos); event.screenX = lerp(before.screenX, after.screenX, t); event.screenY = lerp(before.screenY, after.screenY, t); event.canvasX = lerp(before.canvasX, after.canvasX, t); event.canvasY = lerp(before.canvasY, after.canvasY, t); event.pressure = lerp(before.pressure, after.pressure, t); event.actDirection = before.actDirection; event.action = before.action; event.pointerId = before.pointerId; filteredData.push_back(event); lastResampledNanos = nextTimestamp; nextTimestamp += resolutionNanos; } filteredInputs.insert(filteredInputs.end(), filteredData.begin(), filteredData.end()); // keep before timestamp while (rawInputs.size() > 1 && std::next(rawInputs.begin())->timestampNanos <= lastResampledNanos) { rawInputs.pop_front(); } } void PointerStream::stabilize(double alpha) { // exponential moving average if (filteredInputs.size() < 2) { return; } int index = 0; PointerEvent previous; for (auto it = filteredInputs.begin(); it != filteredInputs.end(); previous = *it, ++it) { if (index > 0) { it->screenX = alpha * it->screenX + (1 - alpha) * previous.screenX; it->screenY = alpha * it->screenY + (1 - alpha) * previous.screenY; it->canvasX = alpha * it->canvasX + (1 - alpha) * previous.canvasX; it->canvasY = alpha * it->canvasY + (1 - alpha) * previous.canvasY; it->pressure = alpha * it->pressure + (1 - alpha) * previous.pressure; } index++; } } void PointerStream::screenToCanvas(float screenX, float screenY, float* canvasX, float* canvasY) { // orgin float dx = screenX - (App::Width / 2.0f); float dy = screenY - (App::Height / 2.0f); // pan dx -= panX; dy += panY; // rotate float cosR = cos(rotation); float sinR = sin(rotation); float rotatedX = dx * cosR - dy * sinR; float rotatedY = dx * sinR + dy * cosR; // zoom + canvas tl *canvasX = (rotatedX / zoom) + (canvasWidth / 2.0f); *canvasY = (rotatedY / zoom) + (canvasHeight / 2.0f); } bool PointerStream::GetComplete() { return isComplete; } PointerStream::PointerStream(PointerEvent input) { assert(input.actDirection == PointerDirection::Down); rawInputs.push_back(input); lastResampledNanos = input.timestampNanos; lastStabilizedNanos = input.timestampNanos; consumedFilteredNanos = input.timestampNanos; consumedRawNanos = input.timestampNanos; initialInput = input; } void PointerStream::AddInput(PointerEvent input) { assert(input.pointerId == rawInputs.back().pointerId); screenToCanvas(input.screenX, input.screenY, &input.canvasX, &input.canvasY); rawInputs.push_back(input); if (input.actDirection == PointerDirection::Up) { isComplete = true; } } void PointerStream::MakeCancelled() { cancelled = true; } void PointerStream::ApplyCanvasTranslation(double zoom, double panX, double panY, double rotation, double canvasWidth, double canvasHeight) { this->zoom = zoom; this->panX = panX; this->panY = panY; this->rotation = rotation; this->canvasWidth = canvasWidth; this->canvasHeight = canvasHeight; for (auto it = rawInputs.begin(); it != rawInputs.end(); ++it) { screenToCanvas(it->screenX, it->screenY, &it->canvasX, &it->canvasY); } } std::list PointerStream::PullRawInputs() { std::list returnable; returnable = rawInputs; auto final = rawInputs.back(); rawInputs.clear(); rawInputs.push_back(final); return returnable; } PointerEvent PointerStream::GetInitialInput() { return initialInput; } bool PointerStream::IsCancelled() { return cancelled; } void PointerStream::PullDeltaInput(float* deltaX, float* deltaY) { if (rawInputs.size() < 2) return; // Displacement is simply where we ended minus where we started *deltaX += (rawInputs.back().screenX - rawInputs.front().screenX); *deltaY -= (rawInputs.back().screenY - rawInputs.front().screenY); auto final = rawInputs.back(); rawInputs.clear(); // Keep the last position as the starting point for the next frame rawInputs.push_back(final); } std::list PointerStream::PullFilteredInputs(uint64_t resolutionNanos, double alpha) { // Emit initial down event if not yet emitted if (!emitedDown && !rawInputs.empty()) { std::list initial; initial.push_back(rawInputs.front()); emitedDown = true; return initial; } // insufficient data if (rawInputs.size() < 2) { return std::list(); } resampleTimespans(resolutionNanos); stabilize(alpha); std::list returnable; for (auto it = filteredInputs.begin(); it != filteredInputs.end(); ++it) { if (it->timestampNanos > consumedFilteredNanos) { PointerEvent event = *it; if (!emitedDown) { event.actDirection = PointerDirection::Down; emitedDown = true; } else if (isComplete && std::next(it) == filteredInputs.end()) { event.actDirection = PointerDirection::Up; } else { event.actDirection = PointerDirection::None; } returnable.push_back(event); } } if (isComplete) { emitedDown = false; firstPull = false; } // complete anyway and reset if (returnable.empty() && isComplete) { PointerEvent upEvent = rawInputs.back(); upEvent.actDirection = PointerDirection::Up; returnable.push_back(upEvent); consumedFilteredNanos = upEvent.timestampNanos; filteredInputs.clear(); return returnable; } filteredInputs.clear(); consumedFilteredNanos = lastResampledNanos; return returnable; } PointerStream PointerStream::CreatePreviewStream(double width, double height) { PointerEvent events[36] = {}; uint64_t baseTime = SDL_GetTicksNS(); // create events for (int i = 0; i < 36; i++) { double x = (i / 36.0) * width; PointerEvent event; event.screenX = (x + width / 9) / 9 * 7.5; event.screenY = SDL_sin(x / width * 2 * SDL_PI_D) * height / -4 + height / 2; event.canvasX = event.screenX; event.canvasY = event.screenY; event.actDirection = PointerDirection::None; event.action = (int)PointerAction::GenericPrimary; event.timestampNanos = baseTime + i * 100000; event.pointerId = 0; // pressure bell curve event.pressure = SDL_sin(x / width * SDL_PI_D);//* 0.5 + 0.5; events[i] = event; } events[0].actDirection = PointerDirection::Down; events[35].actDirection = PointerDirection::Up; PointerStream stream = PointerStream(events[0]); for (int i = 1; i < 36; i++) { stream.rawInputs.push_back(events[i]); // lol, NOTE: DO NOT USE ADDINPUT HERE, NEVER, THIS METHOD IS PART OF THIS CLASS AND KNOWS HOW TO HANDLE ITS OWN DATA; BY CALLING ADDINPUT YOU WILL USE SCREENTOCANVAS WHICH WILL RUIN POSITION DATA SINCE IT DOES NOT ACCOUNT FOR PREVIEW WIDTH AND HEIGHT. } return stream; }