diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ac56c5..aa64f5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ set(TARGET padlab) set(SOURCES maths.c maths.h draw.c draw.h + stick.c stick.h analogue.c) find_package(SDL2 REQUIRED) diff --git a/analogue.c b/analogue.c index d34a1d2..c06f5d7 100644 --- a/analogue.c +++ b/analogue.c @@ -1,5 +1,6 @@ #include "maths.h" #include "draw.h" +#include "stick.h" #include #include #include @@ -8,295 +9,22 @@ #define WINDOW_WIDTH 512 #define WINDOW_HEIGHT 288 -#define DISPLAY_SCALE 0.8889 - SDL_Window* window = NULL; SDL_JoystickID joyid = -1; SDL_GameController* pad = NULL; -bool UseGamepad(int a_joyid) +bool UseGamepad(int aJoyid) { - pad = SDL_GameControllerOpen(a_joyid); - joyid = SDL_JoystickGetDeviceInstanceID(a_joyid); + pad = SDL_GameControllerOpen(aJoyid); + joyid = SDL_JoystickGetDeviceInstanceID(aJoyid); if (pad != NULL) { - printf("using gamepad #%d, \"%s\"\n", a_joyid, SDL_GameControllerName(pad)); + printf("using gamepad #%d, \"%s\"\n", aJoyid, SDL_GameControllerName(pad)); return true; } return false; } -vector RadialDeadzone(vector v, double min, double max) -{ - double mag = sqrt(v.x * v.x + v.y * v.y); - if (mag == 0.0 || mag < min) - { - return (vector){0.0, 0.0}; - } - else if (mag > max) - { - return (vector){v.x / mag, v.y / mag}; - } - else - { - double rescale = (mag - min) / (max - min); - return (vector){v.x / mag * rescale, v.y / mag * rescale}; - } -} - -vector DigitalEight(vector v, double angle, double deadzone) -{ - vector res = {0, 0}; - - if (fabs(v.x) * angle > fabs(v.y)) - { - if (fabs(v.x) > deadzone) - res.x = copysign(1.0, v.x); - } - else if (fabs(v.y) * angle > fabs(v.x)) - { - if (fabs(v.y) > deadzone) - res.y = copysign(1.0, v.y); - } - else if (fabs(v.x) + fabs(v.y) > deadzone * (1.0 + angle)) - { - const double dscale = 1/sqrt(2); - res.x = copysign(dscale, v.x); - res.y = copysign(dscale, v.y); - } - - return res; -} - -vector ApplyAcceleration(vector v, double y) -{ - double mag = sqrt(v.x * v.x + v.y * v.y); - if (mag > 0.0) - { - double curve = AccelCurve(mag, y); - return (vector){v.x / mag * curve, v.y / mag * curve}; - } - else - { - return (vector){0.0, 0.0}; - } -} - -typedef struct -{ - // common - vector rawpos, compos; - bool recalc; - - // analogue - double preaccel, postacel; - double accelpow; - double deadzone; - - // digital - double digiangle; - double digideadzone; -} StickState; - -void InitDefaults(StickState* p) -{ - p->rawpos = (vector){0.0, 0.0}; - p->compos = (vector){0.0, 0.0}; - - p->recalc = true; - p->preaccel = 0.0; - p->postacel = 0.0; - p->accelpow = 1.25; - p->deadzone = 0.125; - - p->digiangle = sqrt(2.0) - 1.0; - p->digideadzone = 0.5; -} - -void DrawAnalogue(const SDL_Rect* win, StickState* p) -{ - if (p->recalc) - { - p->compos = RadialDeadzone(p->rawpos, p->deadzone, 0.99); - p->preaccel = sqrt(p->compos.x * p->compos.x + p->compos.y * p->compos.y); - p->compos = ApplyAcceleration(p->compos, p->accelpow); - p->postacel = sqrt(p->compos.x * p->compos.x + p->compos.y * p->compos.y); - - p->recalc = false; - } - - double size = (double)(win->w > win->h ? win->h : win->w) * DISPLAY_SCALE; - - // range rect - SetDrawColour(0x3F3F3FFF); - const int rectSz = (int)round(size); - DrawRect( - win->x + (win->w - rectSz) / 2, - win->y + (win->h - rectSz) / 2, - rectSz, rectSz); - - const int ox = win->x + win->w / 2; - const int oy = win->y + win->h / 2; - - // acceleration curve - SetDrawColour(0x4F4F4FFF); - const int accelsamp = (int)(sqrt(size) * 4.20); - const double step = 1.0 / (double)accelsamp; - double y1 = AccelCurve(0.0, p->accelpow); - for (int i = 1; i <= accelsamp; ++i) - { - double y2 = AccelCurve(step * i, p->accelpow); - DrawLine( - win->x + (int)(step * (i - 1) * size) + (win->w - (int)round(size)) / 2, - win->y + (int)((1.0 - y1) * size) + (win->h - (int)round(size)) / 2, - win->x + (int)(step * i * size) + (win->w - (int)round(size)) / 2, - win->y + (int)((1.0 - y2) * size) + (win->h - (int)round(size)) / 2); - y1 = y2; - } - const int tickerx = (int)((p->preaccel - 0.5) * size); - const int tickery = (int)((0.5 - p->postacel) * size); - SetDrawColour(0x2F3F1FFF); - DrawLine( - ox + tickerx, - win->y + (win->h - (int)round(size)) / 2, - ox + tickerx, - win->y + (win->h + (int)round(size)) / 2); - SetDrawColour(0x2F5F2FFF); - DrawLine( - win->x + (win->w - (int)round(size)) / 2, - oy + tickery, - win->x + (win->w + (int)round(size)) / 2, - oy + tickery); - - // guide circle - SetDrawColour(0x4F4F4FFF); - DrawCircle(ox, oy, (int)round(size) / 2); - - SetDrawColour(0x3F3F3FFF); - DrawCircle(ox, oy, (int)round(p->deadzone * size) / 2); - - // 0,0 line axis' - SetDrawColour(0x2F2F2FFF); - DrawLine( - win->x, oy, - win->x + win->w, oy); - DrawLine( - ox, win->y, - ox, win->y + win->h); - - // compensated position - SetDrawColour(0x1FFF1FFF); - DrawCircleSteps( - ox + (int)round(p->compos.x * size / 2.0), - oy + (int)round(p->compos.y * size / 2.0), - 8, 16); - DrawPoint( - ox + (int)round(p->compos.x * size / 2.0), - oy + (int)round(p->compos.y * size / 2.0)); - - // raw position - SetDrawColour(0xFFFFFFFF); - DrawLine( - ox + (int)round(p->rawpos.x * size / 2.0) - 4, - oy + (int)round(p->rawpos.y * size / 2.0), - ox + (int)round(p->rawpos.x * size / 2.0) + 4, - oy + (int)round(p->rawpos.y * size / 2.0)); - DrawLine( - ox + (int)round(p->rawpos.x * size / 2.0), - oy + (int)round(p->rawpos.y * size / 2.0) - 4, - ox + (int)round(p->rawpos.x * size / 2.0), - oy + (int)round(p->rawpos.y * size / 2.0) + 4); -} - -void DrawDigital(const SDL_Rect* win, StickState* p) -{ - if (p->recalc) - { - p->compos = DigitalEight(p->rawpos, p->digiangle, p->digideadzone); - p->recalc = false; - } - - const double size = (double)(win->w > win->h ? win->h : win->w) * DISPLAY_SCALE; - - // range rect - SetDrawColour(0x3F3F3FFF); - const int rectSz = (int)round(size); - DrawRect( - win->x + (win->w - rectSz) / 2, - win->y + (win->h - rectSz) / 2, - rectSz, rectSz); - - // window centre - const int ox = win->x + win->w / 2; - const int oy = win->y + win->h / 2; - - // guide circle - SetDrawColour(0x4F4F4FFF); - DrawCircle(ox, oy, (int)round(size) / 2); - - // 0,0 line axis' - SetDrawColour(0x2F2F2FFF); - DrawLine( - win->x, oy, - win->x + win->w, oy); - DrawLine( - ox, win->y, - ox, win->y + win->h); - - // calcuate points for the zone previews - const double outerinvmag = 1.0 / sqrt(1.0 + p->digiangle * p->digiangle); - const int outh = (int)round((size * outerinvmag) / 2.0); - const int outq = (int)round((size * outerinvmag) / 2.0 * p->digiangle); - const int innh = (int)round(p->digideadzone * size / 2.0); - const int innq = (int)round(p->digideadzone * size / 2.0 * p->digiangle); - - SetDrawColour(0x3F3F3FFF); - - // angles preview - DrawLine(ox - outq, oy - outh, ox - innq, oy - innh); - DrawLine(ox + outq, oy - outh, ox + innq, oy - innh); - DrawLine(ox + outh, oy - outq, ox + innh, oy - innq); - DrawLine(ox + outh, oy + outq, ox + innh, oy + innq); - DrawLine(ox + outq, oy + outh, ox + innq, oy + innh); - DrawLine(ox - outq, oy + outh, ox - innq, oy + innh); - DrawLine(ox - outh, oy + outq, ox - innh, oy + innq); - DrawLine(ox - outh, oy - outq, ox - innh, oy - innq); - - // deadzone octagon - DrawLine(ox - innq, oy - innh, ox + innq, oy - innh); - DrawLine(ox + innq, oy - innh, ox + innh, oy - innq); - DrawLine(ox + innh, oy - innq, ox + innh, oy - innq); - DrawLine(ox + innh, oy - innq, ox + innh, oy + innq); - DrawLine(ox + innh, oy + innq, ox + innq, oy + innh); - DrawLine(ox + innq, oy + innh, ox - innq, oy + innh); - DrawLine(ox - innq, oy + innh, ox - innh, oy + innq); - DrawLine(ox - innh, oy + innq, ox - innh, oy - innq); - DrawLine(ox - innh, oy - innq, ox - innq, oy - innh); - - // compensated position - SetDrawColour(0x1FFF1FFF); - DrawCircleSteps( - ox + (int)round(p->compos.x * size / 2.0), - oy + (int)round(p->compos.y * size / 2.0), - 8, 16); - DrawPoint( - ox + (int)round(p->compos.x * size / 2.0), - oy + (int)round(p->compos.y * size / 2.0)); - - // raw position - SetDrawColour(0xFFFFFFFF); - DrawLine( - ox + (int)round(p->rawpos.x * size / 2.0) - 4, - oy + (int)round(p->rawpos.y * size / 2.0), - ox + (int)round(p->rawpos.x * size / 2.0) + 4, - oy + (int)round(p->rawpos.y * size / 2.0)); - DrawLine( - ox + (int)round(p->rawpos.x * size / 2.0), - oy + (int)round(p->rawpos.y * size / 2.0) - 4, - ox + (int)round(p->rawpos.x * size / 2.0), - oy + (int)round(p->rawpos.y * size / 2.0) + 4); -} - int main(int argc, char** argv) { int res; @@ -504,8 +232,8 @@ int main(int argc, char** argv) DrawClear(); const int hrw = rw / 2; - DrawDigital(&(SDL_Rect){ 0, 0, hrw, rh }, &stickl); - DrawAnalogue(&(SDL_Rect){hrw, 0, hrw, rh}, &stickr); + DrawDigital(&(rect){0, 0, hrw, rh}, &stickl); + DrawAnalogue(&(rect){hrw, 0, hrw, rh}, &stickr); // test player thingo if (showavatar) diff --git a/draw.h b/draw.h index d81eca9..51fab08 100644 --- a/draw.h +++ b/draw.h @@ -1,6 +1,8 @@ #ifndef DRAW_H #define DRAW_H +#define DISPLAY_SCALE 0.8889 + #include typedef struct SDL_Renderer SDL_Renderer; diff --git a/maths.c b/maths.c index 2e02b77..60189ae 100644 --- a/maths.c +++ b/maths.c @@ -4,5 +4,3 @@ static inline vector VecAdd(vector l, vector r); static inline vector VecScale(vector v, vec_t x); static inline double pfmod(double x, double d); - -double AccelCurve(double x, double y); diff --git a/maths.h b/maths.h index 3773524..a1d7233 100644 --- a/maths.h +++ b/maths.h @@ -12,7 +12,9 @@ #define SATURATE(X) (CLAMP(X, 0, 1)) typedef double vec_t; -typedef struct {vec_t x, y;} vector; +typedef struct { vec_t x, y; } vector; + +typedef struct { int x, y, w, h; } rect; static inline vector VecAdd(vector l, vector r) { @@ -29,9 +31,4 @@ static inline double pfmod(double x, double d) return fmod(fmod(x, d) + d, (d)); } -static inline double AccelCurve(double x, double y) -{ - return (x * (x + y)) / (1.0 + y); -} - #endif//MATHS_H diff --git a/stick.c b/stick.c new file mode 100644 index 0000000..3263d40 --- /dev/null +++ b/stick.c @@ -0,0 +1,243 @@ +#include "stick.h" +#include "draw.h" + +extern inline void InitDefaults(StickState* p); + +vector RadialDeadzone(vector v, double min, double max) +{ + double mag = sqrt(v.x * v.x + v.y * v.y); + + if (mag <= min) + return (vector){0.0, 0.0}; + + if (mag >= max) + return (vector){v.x / mag, v.y / mag}; + + double rescale = (mag - min) / (max - min); + return (vector){v.x / mag * rescale, v.y / mag * rescale}; +} + +vector DigitalEight(vector v, double angle, double deadzone) +{ + vector res = {0, 0}; + + if (fabs(v.x) * angle > fabs(v.y)) + { + if (fabs(v.x) > deadzone) + res.x = copysign(1.0, v.x); + } + else if (fabs(v.y) * angle > fabs(v.x)) + { + if (fabs(v.y) > deadzone) + res.y = copysign(1.0, v.y); + } + else if (fabs(v.x) + fabs(v.y) > deadzone * (1.0 + angle)) + { + const double dscale = 1/sqrt(2); + res.x = copysign(dscale, v.x); + res.y = copysign(dscale, v.y); + } + + return res; +} + +static inline double AccelCurve(double x, double y) +{ + return (x * (x + y)) / (1.0 + y); +} + +vector ApplyAcceleration(vector v, double y) +{ + double mag = sqrt(v.x * v.x + v.y * v.y); + if (mag <= 0.0) + return (vector){0.0, 0.0}; + + double curve = AccelCurve(mag, y); + return (vector){v.x / mag * curve, v.y / mag * curve}; +} + +extern inline void InitDefaults(StickState* p); + +void DrawAnalogue(const rect* win, StickState* p) +{ + if (p->recalc) + { + p->compos = RadialDeadzone(p->rawpos, p->deadzone, 0.99); + p->preaccel = sqrt(p->compos.x * p->compos.x + p->compos.y * p->compos.y); + p->compos = ApplyAcceleration(p->compos, p->accelpow); + p->postacel = sqrt(p->compos.x * p->compos.x + p->compos.y * p->compos.y); + + p->recalc = false; + } + + double size = (double)(win->w > win->h ? win->h : win->w) * DISPLAY_SCALE; + + // range rect + SetDrawColour(0x3F3F3FFF); + const int rectSz = (int)round(size); + DrawRect( + win->x + (win->w - rectSz) / 2, + win->y + (win->h - rectSz) / 2, + rectSz, rectSz); + + const int ox = win->x + win->w / 2; + const int oy = win->y + win->h / 2; + + // acceleration curve + SetDrawColour(0x4F4F4FFF); + const int accelsamp = (int)(sqrt(size) * 4.20); + const double step = 1.0 / (double)accelsamp; + double y1 = AccelCurve(0.0, p->accelpow); + for (int i = 1; i <= accelsamp; ++i) + { + double y2 = AccelCurve(step * i, p->accelpow); + DrawLine( + win->x + (int)(step * (i - 1) * size) + (win->w - (int)round(size)) / 2, + win->y + (int)((1.0 - y1) * size) + (win->h - (int)round(size)) / 2, + win->x + (int)(step * i * size) + (win->w - (int)round(size)) / 2, + win->y + (int)((1.0 - y2) * size) + (win->h - (int)round(size)) / 2); + y1 = y2; + } + const int tickerx = (int)((p->preaccel - 0.5) * size); + const int tickery = (int)((0.5 - p->postacel) * size); + SetDrawColour(0x2F3F1FFF); + DrawLine( + ox + tickerx, + win->y + (win->h - (int)round(size)) / 2, + ox + tickerx, + win->y + (win->h + (int)round(size)) / 2); + SetDrawColour(0x2F5F2FFF); + DrawLine( + win->x + (win->w - (int)round(size)) / 2, + oy + tickery, + win->x + (win->w + (int)round(size)) / 2, + oy + tickery); + + // guide circle + SetDrawColour(0x4F4F4FFF); + DrawCircle(ox, oy, (int)round(size) / 2); + + SetDrawColour(0x3F3F3FFF); + DrawCircle(ox, oy, (int)round(p->deadzone * size) / 2); + + // 0,0 line axis' + SetDrawColour(0x2F2F2FFF); + DrawLine( + win->x, oy, + win->x + win->w, oy); + DrawLine( + ox, win->y, + ox, win->y + win->h); + + // compensated position + SetDrawColour(0x1FFF1FFF); + DrawCircleSteps( + ox + (int)round(p->compos.x * size / 2.0), + oy + (int)round(p->compos.y * size / 2.0), + 8, 16); + DrawPoint( + ox + (int)round(p->compos.x * size / 2.0), + oy + (int)round(p->compos.y * size / 2.0)); + + // raw position + SetDrawColour(0xFFFFFFFF); + DrawLine( + ox + (int)round(p->rawpos.x * size / 2.0) - 4, + oy + (int)round(p->rawpos.y * size / 2.0), + ox + (int)round(p->rawpos.x * size / 2.0) + 4, + oy + (int)round(p->rawpos.y * size / 2.0)); + DrawLine( + ox + (int)round(p->rawpos.x * size / 2.0), + oy + (int)round(p->rawpos.y * size / 2.0) - 4, + ox + (int)round(p->rawpos.x * size / 2.0), + oy + (int)round(p->rawpos.y * size / 2.0) + 4); +} + +void DrawDigital(const rect* win, StickState* p) +{ + if (p->recalc) + { + p->compos = DigitalEight(p->rawpos, p->digiangle, p->digideadzone); + p->recalc = false; + } + + const double size = (double)(win->w > win->h ? win->h : win->w) * DISPLAY_SCALE; + + // range rect + SetDrawColour(0x3F3F3FFF); + const int rectSz = (int)round(size); + DrawRect( + win->x + (win->w - rectSz) / 2, + win->y + (win->h - rectSz) / 2, + rectSz, rectSz); + + // window centre + const int ox = win->x + win->w / 2; + const int oy = win->y + win->h / 2; + + // guide circle + SetDrawColour(0x4F4F4FFF); + DrawCircle(ox, oy, (int)round(size) / 2); + + // 0,0 line axis' + SetDrawColour(0x2F2F2FFF); + DrawLine( + win->x, oy, + win->x + win->w, oy); + DrawLine( + ox, win->y, + ox, win->y + win->h); + + // calcuate points for the zone previews + const double outerinvmag = 1.0 / sqrt(1.0 + p->digiangle * p->digiangle); + const int outh = (int)round((size * outerinvmag) / 2.0); + const int outq = (int)round((size * outerinvmag) / 2.0 * p->digiangle); + const int innh = (int)round(p->digideadzone * size / 2.0); + const int innq = (int)round(p->digideadzone * size / 2.0 * p->digiangle); + + SetDrawColour(0x3F3F3FFF); + + // angles preview + DrawLine(ox - outq, oy - outh, ox - innq, oy - innh); + DrawLine(ox + outq, oy - outh, ox + innq, oy - innh); + DrawLine(ox + outh, oy - outq, ox + innh, oy - innq); + DrawLine(ox + outh, oy + outq, ox + innh, oy + innq); + DrawLine(ox + outq, oy + outh, ox + innq, oy + innh); + DrawLine(ox - outq, oy + outh, ox - innq, oy + innh); + DrawLine(ox - outh, oy + outq, ox - innh, oy + innq); + DrawLine(ox - outh, oy - outq, ox - innh, oy - innq); + + // deadzone octagon + DrawLine(ox - innq, oy - innh, ox + innq, oy - innh); + DrawLine(ox + innq, oy - innh, ox + innh, oy - innq); + DrawLine(ox + innh, oy - innq, ox + innh, oy - innq); + DrawLine(ox + innh, oy - innq, ox + innh, oy + innq); + DrawLine(ox + innh, oy + innq, ox + innq, oy + innh); + DrawLine(ox + innq, oy + innh, ox - innq, oy + innh); + DrawLine(ox - innq, oy + innh, ox - innh, oy + innq); + DrawLine(ox - innh, oy + innq, ox - innh, oy - innq); + DrawLine(ox - innh, oy - innq, ox - innq, oy - innh); + + // compensated position + SetDrawColour(0x1FFF1FFF); + DrawCircleSteps( + ox + (int)round(p->compos.x * size / 2.0), + oy + (int)round(p->compos.y * size / 2.0), + 8, 16); + DrawPoint( + ox + (int)round(p->compos.x * size / 2.0), + oy + (int)round(p->compos.y * size / 2.0)); + + // raw position + SetDrawColour(0xFFFFFFFF); + DrawLine( + ox + (int)round(p->rawpos.x * size / 2.0) - 4, + oy + (int)round(p->rawpos.y * size / 2.0), + ox + (int)round(p->rawpos.x * size / 2.0) + 4, + oy + (int)round(p->rawpos.y * size / 2.0)); + DrawLine( + ox + (int)round(p->rawpos.x * size / 2.0), + oy + (int)round(p->rawpos.y * size / 2.0) - 4, + ox + (int)round(p->rawpos.x * size / 2.0), + oy + (int)round(p->rawpos.y * size / 2.0) + 4); +} diff --git a/stick.h b/stick.h new file mode 100644 index 0000000..54a4871 --- /dev/null +++ b/stick.h @@ -0,0 +1,41 @@ +#ifndef STICK_H +#define STICK_H + +#include "maths.h" +#include + +typedef struct +{ + // common + vector rawpos, compos; + bool recalc; + + // analogue + double preaccel, postacel; + double accelpow; + double deadzone; + + // digital + double digiangle; + double digideadzone; +} StickState; + +inline void InitDefaults(StickState* p) +{ + p->rawpos = (vector){0.0, 0.0}; + p->compos = (vector){0.0, 0.0}; + + p->recalc = true; + p->preaccel = 0.0; + p->postacel = 0.0; + p->accelpow = 1.25; + p->deadzone = 0.125; + + p->digiangle = sqrt(2.0) - 1.0; + p->digideadzone = 0.5; +} + +void DrawAnalogue(const rect* win, StickState* p); +void DrawDigital(const rect* win, StickState* p); + +#endif//STICK_H