From 4255841a6b9a1f46472693416fc9ba59fc3edfa1 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Fri, 18 Nov 2022 05:44:30 +1100 Subject: [PATCH] Improve drawing with digital zone highligts, futher colour tweaks, & thicker lines in GL. It's also now no longer possible to trigger NW & SE angles when digital is set to 4 direction. --- CMakeLists.txt | 12 ++++-- analogue.c | 4 ++ draw.c | 29 +++++++++++++ draw.h | 6 +++ draw_opengl.c | 31 ++++++++++++++ maths.h | 3 ++ stick.c | 108 ++++++++++++++++++++++++++++++++++++------------- stick.h | 2 + util.h | 24 ++++++----- 9 files changed, 177 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec00483..09ad7d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,19 +2,25 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) project(padlab C) set(TARGET padlab) +option(USE_OPENGL "Use legacy OpenGL for drawing" ON) + set(CMAKE_C_STANDARD 99) find_package(SDL2 REQUIRED) -find_package(OpenGL) +if (USE_OPENGL) + find_package(OpenGL REQUIRED) +endif() set(SOURCES maths.h - draw.h $,draw_opengl.c,draw.c> + draw.h $,draw_opengl.c,draw.c> stick.c stick.h analogue.c) add_executable(${TARGET} ${SOURCES}) target_link_libraries(${TARGET} - SDL2::SDL2 $<$:OpenGL::GL> m) + SDL2::SDL2 $<$:OpenGL::GL> m) target_compile_options(${TARGET} PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter) +target_compile_definitions(${TARGET} PRIVATE + $<$:USE_OPENGL>) diff --git a/analogue.c b/analogue.c index a498920..33e588c 100644 --- a/analogue.c +++ b/analogue.c @@ -35,7 +35,11 @@ int main(int argc, char** argv) goto error; const int winpos = SDL_WINDOWPOS_CENTERED; +#ifdef USE_OPENGL + const int winflg = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI; +#else const int winflg = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; +#endif int winw = WINDOW_WIDTH; int winh = WINDOW_HEIGHT; DrawWindowHints(); diff --git a/draw.c b/draw.c index c579ba1..4a82b25 100644 --- a/draw.c +++ b/draw.c @@ -87,6 +87,35 @@ void DrawCircleSteps(int x, int y, int r, int steps) } } +void DrawArc(int x, int y, int r, int startAng, int endAng) +{ + const int steps = (int)(sqrt((double)r) * (double)abs(endAng - startAng) / 360.0 * 8.0); + DrawArcSteps(x, y, r, startAng, endAng, steps); +} + +void DrawArcSteps(int x, int y, int r, int startAng, int endAng, int steps) +{ + const double fstart = (double)startAng * DEG2RAD; + const double fstepSz = (double)(endAng - startAng) / abs(steps) * DEG2RAD; + const double mag = (double)r; + + int lastx = (int)round(cos(fstart) * mag); + int lasty = (int)round(-sin(fstart) * mag); + for (int i = 1; i <= steps; ++i) + { + const double theta = fstart + fstepSz * (double)i; + int ofsx = (int)round(cos(theta) * mag); + int ofsy = (int)round(-sin(theta) * mag); + + SDL_RenderDrawLine(rend, + x + lastx, y + lasty, + x + ofsx, y + ofsy); + + lastx = ofsx; + lasty = ofsy; + } +} + void DrawPresent(void) { SDL_RenderPresent(rend); diff --git a/draw.h b/draw.h index c53b136..a578f7f 100644 --- a/draw.h +++ b/draw.h @@ -67,6 +67,12 @@ void DrawCircle(int x, int y, int r); // Can be used to draw regular convex polygons such as an octagon. void DrawCircleSteps(int x, int y, int r, int steps); +// Draw an arc. +void DrawArc(int x, int y, int r, int startAng, int endAng); + +// Draw an arc with a discrete number of steps. +void DrawArcSteps(int x, int y, int r, int startAng, int endAng, int steps); + // Present the current buffer to the screen. void DrawPresent(void); diff --git a/draw_opengl.c b/draw_opengl.c index b70e22b..5da0eff 100644 --- a/draw_opengl.c +++ b/draw_opengl.c @@ -45,6 +45,8 @@ int InitDraw(SDL_Window* w) SetDrawViewport(GetDrawSizeInPixels()); // Fills scaleWidth & scaleHeight + glLineWidth(2.0f); + // Setup orthographic viewport glMatrixMode(GL_PROJECTION); glOrtho(0.0, 1.0f, 1.0f, 0.0, 1.0, -1.0); @@ -176,6 +178,35 @@ void DrawCircleSteps(int x, int y, int r, int steps) glEnd(); } +void DrawArc(int x, int y, int r, int startAng, int endAng) +{ + const int steps = (int)(sqrt((double)r) * (double)abs(endAng - startAng) / 360.0 * 8.0); + DrawArcSteps(x, y, r, startAng, endAng, steps); +} + +void DrawArcSteps(int x, int y, int r, int startAng, int endAng, int steps) +{ + // Arcs look better when offset negatively by half a pixel w/o MSAA + const double fx = (antialias ? (double)x : (double)x - 0.5f) * scaleWidth; + const double fy = (antialias ? (double)y : (double)y - 0.5f) * scaleHeight; + + const double magw = (double)r * scaleWidth; + const double magh = (double)r * scaleHeight; + + glBegin(GL_LINE_STRIP); + GlColour(); + const double fstart = (double)startAng * DEG2RAD; + const double fstepSz = (double)(endAng - startAng) / abs(steps) * DEG2RAD; + for (int i = 0; i <= steps; ++i) + { + const double theta = fstart + fstepSz * (double)i; + double ofsx = cos(theta) * magw; + double ofsy = sin(theta) * magh; + glVertex2d(fx + ofsx, fy - ofsy); + } + glEnd(); +} + void DrawPresent(void) { SDL_GL_SwapWindow(window); diff --git a/maths.h b/maths.h index 589a5ff..0cc76b7 100644 --- a/maths.h +++ b/maths.h @@ -6,6 +6,9 @@ #define PI 3.141592653589793238462643383279502884L #define TAU 6.283185307179586476925286766559005768L +#define RAD2DEG 57.2957795130823208768 +#define DEG2RAD 0.01745329251994329577 + typedef double vec_t; typedef struct { vec_t x, y; } vector; diff --git a/stick.c b/stick.c index b43e2c4..b9c0b4b 100644 --- a/stick.c +++ b/stick.c @@ -17,28 +17,37 @@ vector RadialDeadzone(vector v, double min, double max) return (vector){v.x / mag * rescale, v.y / mag * rescale}; } -vector DigitalEight(vector v, double angle, double deadzone) +point DigitalEight(vector v, double angle, double deadzone) { - vector res = {0, 0}; + const double absx = fabs(v.x); + const double absy = fabs(v.y); + point p = {0, 0}; - if (fabs(v.x) * angle > fabs(v.y)) + if (absx * angle >= absy) { - if (fabs(v.x) > deadzone) - res.x = copysign(1.0, v.x); + if (absx > deadzone) + p.x = signbit(v.x) ? -1 : 1; } - else if (fabs(v.y) * angle > fabs(v.x)) + else if (absy * angle > absx) { - if (fabs(v.y) > deadzone) - res.y = copysign(1.0, v.y); + if (absy > deadzone) + p.y = signbit(v.y) ? -1 : 1; } - else if (fabs(v.x) + fabs(v.y) > deadzone * (1.0 + angle)) + else if (absx + absy >= deadzone * (1.0 + angle)) { - const double dscale = 1/sqrt(2); - res.x = copysign(dscale, v.x); - res.y = copysign(dscale, v.y); + p.x = signbit(v.x) ? -1 : 1; + p.y = signbit(v.y) ? -1 : 1; } - return res; + return p; +} + +vector DigitalToVector(point p) +{ + const double dscale = (p.x && p.y) ? 1.0 / sqrt(2.0) : 1.0; + return (vector){ + p.x ? copysign(dscale, (double)p.x) : 0.0, + p.y ? copysign(dscale, (double)p.y) : 0.0}; } static inline double AccelCurve(double x, double y) @@ -84,7 +93,7 @@ void DrawAnalogue(const rect* win, StickState* p) const int oy = win->y + win->h / 2; // acceleration curve - SetDrawColour(GREY4); + SetDrawColour(GREY5); const int accelsamp = (int)(sqrt(size) * 4.20); const double step = 1.0 / (double)accelsamp; double y1 = AccelCurve(0.0, p->accelpow); @@ -92,10 +101,10 @@ void DrawAnalogue(const rect* win, StickState* p) { 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); + win->x + (int)(step * (i - 1) * size) + (win->w - rectSz) / 2, + win->y + (int)((1.0 - y1) * size) + (win->h - rectSz) / 2, + win->x + (int)(step * i * size) + (win->w - rectSz) / 2, + win->y + (int)((1.0 - y2) * size) + (win->h - rectSz) / 2); y1 = y2; } const int tickerx = (int)((p->preaccel - 0.5) * size); @@ -103,21 +112,21 @@ void DrawAnalogue(const rect* win, StickState* p) SetDrawColour(HILIGHT_PU1); DrawLine( ox + tickerx, - win->y + (win->h - (int)round(size)) / 2, + win->y + (win->h - rectSz) / 2, ox + tickerx, - win->y + (win->h + (int)round(size)) / 2); + win->y + (win->h + rectSz) / 2); SetDrawColour(HILIGHT_PU2); DrawLine( - win->x + (win->w - (int)round(size)) / 2, + win->x + (win->w - rectSz) / 2, oy + tickery, - win->x + (win->w + (int)round(size)) / 2, + win->x + (win->w + rectSz) / 2, oy + tickery); // guide circle - SetDrawColour(GREY4); - DrawCircle(ox, oy, (int)round(size) / 2); + SetDrawColour(GREY5); + DrawCircle(ox, oy, rectSz / 2); - SetDrawColour(GREY3); + SetDrawColour(GREY4); DrawCircle(ox, oy, (int)round(p->deadzone * size) / 2); // 0,0 line axis' @@ -157,7 +166,8 @@ void DrawDigital(const rect* win, StickState* p) { if (p->recalc) { - p->compos = DigitalEight(p->rawpos, p->digiangle, p->digideadzone); + p->digixy = DigitalEight(p->rawpos, p->digiangle, p->digideadzone); + p->compos = DigitalToVector(p->digixy); p->recalc = false; } @@ -176,8 +186,9 @@ void DrawDigital(const rect* win, StickState* p) const int oy = win->y + win->h / 2; // guide circle - SetDrawColour(GREY4); - DrawCircle(ox, oy, (int)round(size) / 2); + SetDrawColour(GREY5); + int radius = rectSz / 2; + DrawCircle(ox, oy, radius); // 0,0 line axis' SetDrawColour(GREY2); @@ -195,7 +206,7 @@ void DrawDigital(const rect* win, StickState* p) const int innh = (int)round(p->digideadzone * size / 2.0); const int innq = (int)round(p->digideadzone * size / 2.0 * p->digiangle); - SetDrawColour(GREY3); + SetDrawColour(GREY4); // angles preview DrawLine(ox - outq, oy - outh, ox - innq, oy - innh); @@ -218,6 +229,45 @@ void DrawDigital(const rect* win, StickState* p) DrawLine(ox - innh, oy + innq, ox - innh, oy - innq); DrawLine(ox - innh, oy - innq, ox - innq, oy - innh); + // highlight active zone + if (p->digixy.x || p->digixy.y) + { + const int x = p->digixy.x; + const int y = p->digixy.y; + + SetDrawColour(HILIGHT_GR2); + + if (x) + { + if (y <= 0) DrawLine(ox + outh * x, oy - outq, ox + innh * x, oy - innq); + if (!y) DrawLine(ox + innh * x, oy + innq, ox + innh * x, oy - innq); + if (y >= 0) DrawLine(ox + outh * x, oy + outq, ox + innh * x, oy + innq); + } + + if (y) + { + if (x <= 0) DrawLine(ox - outq, oy + outh * y, ox - innq, oy + innh * y); + if (!x) DrawLine(ox + innq, oy + innh * y, ox - innq, oy + innh * y); + if (x >= 0) DrawLine(ox + outq, oy + outh * y, ox + innq, oy + innh * y); + } + + if (x && y) + { + DrawLine(ox + innh * x, oy + innq * y, ox + innq * x, oy + innh * y); + DrawArc(ox, oy, radius, + -(int)round(atan2(outerinvmag * p->digiangle * y, outerinvmag * x) * RAD2DEG), + -(int)round(atan2(outerinvmag * y, outerinvmag * p->digiangle * x) * RAD2DEG)); + } + else + { + const int hemi = (int)round(atan2(outerinvmag * p->digiangle, outerinvmag) * RAD2DEG); + if (x > 0) DrawArc(ox, oy, radius, -hemi, hemi); + else if (y < 0) DrawArc(ox, oy, radius, -hemi + 90, hemi + 90); + else if (x < 0) DrawArc(ox, oy, radius, -hemi + 180, hemi + 180); + else if (y > 0) DrawArc(ox, oy, radius, -hemi + 270, hemi + 270); + } + } + // compensated position SetDrawColour(HILIGHT_GR3); DrawCircleSteps( diff --git a/stick.h b/stick.h index 6d52d8b..460eab4 100644 --- a/stick.h +++ b/stick.h @@ -17,6 +17,7 @@ typedef struct double deadzone; // digital + point digixy; double digiangle; double digideadzone; } StickState; @@ -32,6 +33,7 @@ inline void InitDefaults(StickState* p) p->accelpow = 1.25; p->deadzone = 0.125; + p->digixy = (point){0, 0}; p->digiangle = sqrt(2.0) - 1.0; p->digideadzone = 0.5; } diff --git a/util.h b/util.h index b3e2d88..6adc3c1 100644 --- a/util.h +++ b/util.h @@ -8,6 +8,7 @@ #define CLAMP(X, A, B) (MIN((B), MAX((A), (X)))) #define SATURATE(X) (CLAMP((X), 0, 1)) +typedef struct { int x, y; } point; typedef struct { int w, h; } size; typedef struct { int x, y, w, h; } rect; @@ -17,17 +18,20 @@ typedef struct { int x, y, w, h; } rect; (((L) << 8) & 0x0000FF00) | \ ((A) & 0x000000FF)) +#define MKRGB(C) (uint32_t)(((C) << 8) | 0x000000FF) + #define WHITE 0xFFFFFFFF #define GREY1 MKGREY(0x1F, 0xFF) -#define GREY2 MKGREY(0x47, 0xFF) -#define GREY3 MKGREY(0x67, 0xFF) -#define GREY4 MKGREY(0x83, 0xFF) -#define HILIGHT_GR1 0x2F3F1FFF -#define HILIGHT_GR2 0x2F5F2FFF -#define HILIGHT_GR3 0x1FFF1FFF -#define HILIGHT_PU1 0x632E63FF -#define HILIGHT_PU2 0x8A418AFF -#define HILIGHT_PU3 0xFF68FFFF -#define AVATAR 0xFF3333FF +#define GREY2 MKGREY(0x37, 0xFF) +#define GREY3 MKGREY(0x4F, 0xFF) +#define GREY4 MKGREY(0x5F, 0xFF) +#define GREY5 MKGREY(0x83, 0xFF) +#define HILIGHT_GR1 MKRGB(0x2F3F1F) +#define HILIGHT_GR2 MKRGB(0x387138) +#define HILIGHT_GR3 MKRGB(0x1FFF1F) +#define HILIGHT_PU1 MKRGB(0x632E63) +#define HILIGHT_PU2 MKRGB(0x8A418A) +#define HILIGHT_PU3 MKRGB(0xFF68FF) +#define AVATAR MKRGB(0xFF3333) #endif//UTIL_H