Something went wrong. Try again.
art software for the rest of us
drawing paint image sdl art
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295#include "PointerStream.h"#include <cassert>#include <cmath>#include <cstdio>#include <SDL3/SDL_timer.h>#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<PointerEvent> 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<PointerEvent> 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<PointerEvent> 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<PointerEvent> PointerStream::PullRawInputs(){ std::list<PointerEvent> 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<PointerEvent> PointerStream::PullFilteredInputs(uint64_t resolutionNanos, double alpha){ // Emit initial down event if not yet emitted if (!emitedDown && !rawInputs.empty()) { std::list<PointerEvent> initial; initial.push_back(rawInputs.front()); emitedDown = true; return initial; } // insufficient data if (rawInputs.size() < 2) { return std::list<PointerEvent>(); } resampleTimespans(resolutionNanos); stabilize(alpha);
std::list<PointerEvent> 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;}