From 837fb145e1aa898c4bdc2931abf65306e34d8a7f Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sun, 23 Feb 2020 12:01:51 +1100 Subject: [PATCH] Initial workspace 6/1/2019 - 13/10/2019 --- CMakeLists.txt | 13 ++ analogue.c | 388 +++++++++++++++++++++++++++++++++++++++++++ gamecontrollerdb.txt | 4 + maths.c | 5 + maths.h | 17 ++ 5 files changed, 427 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 analogue.c create mode 100644 gamecontrollerdb.txt create mode 100644 maths.c create mode 100644 maths.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8ff27bd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.1) +set(PROJECT analogue) +set(SOURCES analogue.c) + +project(${PROJECT} C) +set(CMAKE_C_STANDARD 11) +find_package(SDL2 REQUIRED) +add_definitions(${SDL2_CFLAGS}) +include_directories(${SDL2_INCLUDE_DIRS}) +set(LIBRARIES ${SDL2_LIBRARIES} m) + +add_executable(${PROJECT} ${SOURCES}) +target_link_libraries(${PROJECT} ${LIBRARIES}) diff --git a/analogue.c b/analogue.c new file mode 100644 index 0000000..13d2e5b --- /dev/null +++ b/analogue.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include + +#define CAPTIION "Analogue" +#define WINDOW_WIDTH 512 +#define WINDOW_HEIGHT 288 + +#define DISPLAY_SCALE 0.8889 + +SDL_Window* window = NULL; +SDL_Renderer* rend = NULL; +SDL_JoystickID joyid = -1; +SDL_GameController* pad = NULL; + +typedef double vec_t; +typedef struct {vec_t x, y;} vector; + +static inline vector VecAdd(vector l, vector r) +{ + return (vector){l.x + r.x, l.y + r.y}; +} + +static inline vector VecScale(vector v, vec_t x) +{ + return (vector){v.x * x, v.y * x}; +} + +static inline double pfmod(double x, double d) +{ + return fmod(fmod(x, d) + d, (d)); +} + +bool UseGamepad(int a_joyid) +{ + pad = SDL_GameControllerOpen(a_joyid); + joyid = SDL_JoystickGetDeviceInstanceID(a_joyid); + if (pad != NULL) + { + printf("using gamepad #%d, \"%s\"\n", a_joyid, SDL_GameControllerName(pad)); + return true; + } + return false; +} + +void DrawCircle(int x, int y, int r, int steps) +{ + double stepsz = (M_PI * 2.0) / steps; + int lastx = r; + int lasty = 0; + for (int i = 0; i <= steps; ++i) + { + const double mag = (double)r; + int ofsx = (int)round(cos(stepsz * i) * mag); + int ofsy = (int)round(sin(stepsz * i) * mag); + + SDL_RenderDrawLine(rend, + x + lastx, y + lasty, + x + ofsx, y + ofsy); + + lastx = ofsx; + lasty = ofsy; + } +} + +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 deadzone) +{ + vector res = {0, 0}; + + if (fabs(v.x) > fabs(v.y * 2.0)) + { + if (fabs(v.x) > deadzone) + res.x = copysign(1.0, v.x); + } + else if (fabs(v.y) > fabs(v.x * 2.0)) + { + if (fabs(v.y) > deadzone) + res.y = copysign(1.0, v.y); + } + else if (fabs(v.x) + fabs(v.y) > deadzone * 1.5) + { + 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) + { + double curve = AccelCurve(mag, y); + return (vector){v.x / mag * curve, v.y / mag * curve}; + } + else + { + return (vector){0.0, 0.0}; + } +} + +int main(int argc, char** argv) +{ + int res; + + res = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + if (res < 0) + goto error; + + const int winpos = SDL_WINDOWPOS_CENTERED; + int winw = WINDOW_WIDTH; + int winh = WINDOW_HEIGHT; + window = SDL_CreateWindow(CAPTIION, winpos, winpos, winw, winh, SDL_WINDOW_RESIZABLE); + if (window == NULL) + { + res = -1; + goto error; + } + + rend = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); + if (rend == NULL) + { + res = -1; + goto error; + } + + if ((res = SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt")) != -1) + printf("read %d mappings from gamecontrollerdb.txt\n", res); + for (int i = 0; i < SDL_NumJoysticks(); ++i) + { + if (SDL_IsGameController(i)) + { + if (UseGamepad(i)) + break; + } + } + + vector plrpos = {10.0, 10.0}; + vector rawpos = {0.0, 0.0}; + vector compos = {0.0, 0.0}; + + double preaccel = 0.0, postacel = 0.0; + double accelpow = 1.25; + double deadzone = 0.125; + + bool running = true; + bool recalcs = true; + bool repaint = true; + bool avatare = false; + uint32_t tickslast = SDL_GetTicks(); + + while (running) + { + const uint32_t ticks = SDL_GetTicks(); + double framedelta = (double)(ticks - tickslast); + tickslast = ticks; + + SDL_Event event; + bool onevent = false; + if (!avatare || (compos.x == 0.0 && compos.y == 0.0)) + { + onevent = SDL_WaitEvent(&event) != 0; + } + else + { + onevent = SDL_PollEvent(&event) > 0; + repaint = true; + } + if (onevent) + { + do + { + switch (event.type) + { + case (SDL_KEYDOWN): + if (event.key.keysym.sym == SDLK_ESCAPE) + { + running = false; + } + else if (event.key.keysym.sym == SDLK_e) + { + avatare = !avatare; + repaint = true; + } + break; + + case (SDL_QUIT): + running = false; + break; + + case (SDL_WINDOWEVENT): + if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + winw = event.window.data1; + winh = event.window.data2; + repaint = true; + } + else if (event.window.event == SDL_WINDOWEVENT_EXPOSED) + { + repaint = true; + } + break; + + case (SDL_CONTROLLERAXISMOTION): + if (event.caxis.which == joyid) + { + if (event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX) + { + rawpos.x = (vec_t)event.caxis.value / (vec_t)0x7FFF; + repaint = recalcs = true; + } + else if (event.caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) + { + rawpos.y = (vec_t)event.caxis.value / (vec_t)0x7FFF; + repaint = recalcs = true; + } + } + break; + case (SDL_MOUSEMOTION): + if (event.motion.state & SDL_BUTTON_LMASK) + { + rawpos.x = ((double)event.motion.x - winw / 2.0) / (winh * DISPLAY_SCALE / 2.0); + rawpos.y = ((double)event.motion.y - winh / 2.0) / (winh * DISPLAY_SCALE / 2.0); + repaint = recalcs = true; + } + else if (event.motion.state & SDL_BUTTON_RMASK) + { + double scale = 1.0 - ((double)event.motion.y / (double)winh); + //accelpow = scale * scale * scale * 32; + accelpow = pow(scale * 3, 1.0 + scale * 3); + repaint = recalcs = true; + } + + case (SDL_CONTROLLERDEVICEADDED): + if (pad == NULL) + UseGamepad(event.cdevice.which); + break; + case (SDL_CONTROLLERDEVICEREMOVED): + if (pad != NULL && event.cdevice.which == joyid) + { + SDL_GameControllerClose(pad); + pad = NULL; + printf("active gamepad was removed\n"); + } + break; + + default: + break; + } + } + while (SDL_PollEvent(&event) > 0); + } + + if (recalcs) + { + compos = RadialDeadzone(rawpos, deadzone, 0.99); + //compos = DigitalEight(rawpos, 0.5); + preaccel = sqrt(compos.x * compos.x + compos.y * compos.y); + compos = ApplyAcceleration(compos, accelpow); + postacel = sqrt(compos.x * compos.x + compos.y * compos.y); + recalcs = false; + } + + if (repaint) + { + SDL_Rect rect; + double size = (double)(winw > winh ? winh : winw) * DISPLAY_SCALE; + + // background + SDL_SetRenderDrawColor(rend, 0x1F, 0x1F, 0x1F, 0xFF); + SDL_RenderClear(rend); + + // test player thingo + if (avatare) + { + plrpos = VecAdd(plrpos, VecScale(compos, framedelta * 0.5)); + plrpos.x = pfmod(plrpos.x, winw); + plrpos.y = pfmod(plrpos.y, winh); + + SDL_SetRenderDrawColor(rend, 0xFF, 0x00, 0x00, 0xFF); + rect.w = rect.h = 32; + rect.x = (int)plrpos.x - rect.w / 2; + rect.y = (int)plrpos.y - rect.h / 2; + SDL_RenderDrawRect(rend, &rect); + } + + // range rect + SDL_SetRenderDrawColor(rend, 0x3F, 0x3F, 0x3F, 0xFF); + rect.w = rect.h = (int)round(size); + rect.x = (winw - rect.w) / 2; + rect.y = (winh - rect.h) / 2; + SDL_RenderDrawRect(rend, &rect); + + // acceleration curve + SDL_SetRenderDrawColor(rend, 0x4F, 0x4F, 0x4F, 0xFF); + const int accelsamp = (int)(sqrt(size) * 4.20); + double step = 1.0 / (double)accelsamp; + double y1 = AccelCurve(0.0, accelpow); + for (int i = 1; i <= accelsamp; ++i) + { + double y2 = AccelCurve(step * i, accelpow); + SDL_RenderDrawLine(rend, + (int)(step * (i - 1) * size) + (winw - (int)round(size)) / 2, + (int)((1.0 - y1) * size) + (winh - (int)round(size)) / 2, + (int)(step * i * size) + (winw - (int)round(size)) / 2, + (int)((1.0 - y2) * size) + (winh - (int)round(size)) / 2); + y1 = y2; + } + int tickerx = (int)((preaccel - 0.5) * size); + int tickery = (int)((0.5 - postacel) * size); + SDL_SetRenderDrawColor(rend, 0x2F, 0x3F, 0x1F, 0xFF); + SDL_RenderDrawLine(rend, + winw / 2 + tickerx, + (winh - (int)round(size)) / 2, + winw / 2 + tickerx, + (winh + (int)round(size)) / 2); + SDL_SetRenderDrawColor(rend, 0x2F, 0x5F, 0x2F, 0xFF); + SDL_RenderDrawLine(rend, + (winw - (int)round(size)) / 2, + winh / 2 + tickery, + (winw + (int)round(size)) / 2, + winh / 2 + tickery); + + // guide circle + SDL_SetRenderDrawColor(rend, 0x4F, 0x4F, 0x4F, 0xFF); + DrawCircle(winw / 2, winh / 2, (int)round(size) / 2, (int)(sqrt(size) * 8.0)); + + SDL_SetRenderDrawColor(rend, 0x3F, 0x3F, 0x3F, 0xFF); + DrawCircle(winw / 2, winh / 2, (int)round(deadzone * size) / 2, (int)(sqrt(size) * 2.0)); + + // 0,0 line axis' + SDL_SetRenderDrawColor(rend, 0x2F, 0x2F, 0x2F, 0xFF); + SDL_RenderDrawLine(rend, 0, winh / 2, winw, winh / 2); + SDL_RenderDrawLine(rend, winw / 2, 0, winw / 2, winh); + + // compensated position + SDL_SetRenderDrawColor(rend, 0x1F, 0xFF, 0x1F, 0xFF); + DrawCircle( + winw / 2 + (int)round(compos.x * size / 2.0), + winh / 2 + (int)round(compos.y * size / 2.0), + 4, 8); + + // raw position + SDL_SetRenderDrawColor(rend, 0xFF, 0xFF, 0xFF, 0xFF); + DrawCircle( + winw / 2 + (int)round(rawpos.x * size / 2.0), + winh / 2 + (int)round(rawpos.y * size / 2.0), + 8, 16); + + SDL_RenderPresent(rend); + repaint = false; + } + } + + res = 0; +error: + SDL_GameControllerClose(pad); + SDL_DestroyRenderer(rend); + SDL_DestroyWindow(window); + SDL_Quit(); + return res; +} diff --git a/gamecontrollerdb.txt b/gamecontrollerdb.txt new file mode 100644 index 0000000..cf51e11 --- /dev/null +++ b/gamecontrollerdb.txt @@ -0,0 +1,4 @@ +030000004c050000c405000011810000,PS4 Controller,platform:Linux,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2, +03000000830500006020000010010000,iBuffalo Classic USB Gamepad (SNES),platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1, +050000004c050000c405000001800000,Wireless Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,guide:b10,start:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5, +050000004c0500006802000000800000,Sony PLAYSTATION(R)3 Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,guide:b10,start:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpdown:b14,dpleft:b15,dpright:b16,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5, diff --git a/maths.c b/maths.c new file mode 100644 index 0000000..bc9bb1f --- /dev/null +++ b/maths.c @@ -0,0 +1,5 @@ +#include "vector.h" + +static inline vector VecAdd(vector l, vector r); + +static inline vector VecScale(vector v, vec_t x); diff --git a/maths.h b/maths.h new file mode 100644 index 0000000..01602d5 --- /dev/null +++ b/maths.h @@ -0,0 +1,17 @@ +#ifndef VECTOR_H +#define VECTOR_H + +typedef double vec_t; +typedef struct {vec_t x, y;} vector; + +static inline vector VecAdd(vector l, vector r) +{ + return (vector){l.x + r.x, l.y + r.y}; +} + +static inline vector VecScale(vector v, vec_t x) +{ + return (vector){v.x * x, v.y * x}; +} + +#endif//VECTOR_H