Skip to content

Commit

Permalink
N64 joystick emulation (MiSTer-devel#828)
Browse files Browse the repository at this point in the history
* input.cpp: pass dev nr to joy_analog

This allows for per-controller behavior

* N64: add emulation for the analog joystick

This applies the reduced-range octagonal gate of the N64 analog stick to
regular analog sticks.

The code was mostly written by Patrick Pollock with small optimizations
by Martin Donlon and myself.
  • Loading branch information
markun authored Sep 24, 2023
1 parent 257e81e commit d53a998
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 17 deletions.
49 changes: 32 additions & 17 deletions input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,7 @@ typedef struct
char id[80];
char name[128];
char sysfs[512];
int max_range;
} devInput;

static devInput input[NUMDEV] = {};
Expand Down Expand Up @@ -2074,16 +2075,30 @@ static void joy_digital(int jnum, uint32_t mask, uint32_t code, char press, int
}
}

static void joy_analog(int num, int axis, int offset, int stick = 0)
static void joy_analog(int dev, int axis, int offset, int stick = 0)
{
int num = input[dev].num;
static int pos[2][NUMPLAYERS][2] = {};

if (grabbed && num > 0 && num < NUMPLAYERS+1)
{
num--;
pos[stick][num][axis] = offset;
if(stick) user_io_r_analog_joystick(num, (char)(pos[1][num][0]), (char)(pos[1][num][1]));
else user_io_l_analog_joystick(num, (char)(pos[0][num][0]), (char)(pos[0][num][1]));
int x = pos[stick][num][0];
int y = pos[stick][num][1];
if (is_n64() && stick == 0)
{
const int abs_x = abs(x);
const int abs_y = abs(y);

if (abs_x > input[dev].max_range) input[dev].max_range = abs_x;
if (abs_y > input[dev].max_range) input[dev].max_range = abs_y;

// emulate n64 joystick range and shape for regular -127-+127 controllers
n64_joy_emu(x, y, &x, &y, input[dev].max_range);
}
if(stick) user_io_r_analog_joystick(num, (char)x, (char)y);
else user_io_l_analog_joystick(num, (char)x, (char)y);
}
}

Expand Down Expand Up @@ -3323,58 +3338,58 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
// steering wheel passes full range, pedals are standardised in +127 to 0 to -127 range
if (ev->code == input[dev].wh_steer)
{
joy_analog(input[dev].num, 0, value, 0);
joy_analog(dev, 0, value, 0);
}
else if (ev->code == input[dev].wh_accel)
{
joy_analog(input[dev].num, 1, wh_value, 0);
joy_analog(dev, 1, wh_value, 0);
}
else if (ev->code == input[dev].wh_brake)
{
joy_analog(input[dev].num, 1, wh_value, 1);
joy_analog(dev, 1, wh_value, 1);
}
else if (ev->code == input[dev].wh_clutch)
{
joy_analog(input[dev].num, 0, wh_value, 1);
joy_analog(dev, 0, wh_value, 1);
}
else if (ev->code == input[dev].wh_combo)
{
// if accel and brake pedal use a shared axis then map negative to accel and positive to brake
if (value < -1) joy_analog(input[dev].num, 1, value, 0);
else if (value > 1) joy_analog(input[dev].num, 1, -value, 1);
if (value < -1) joy_analog(dev, 1, value, 0);
else if (value > 1) joy_analog(dev, 1, -value, 1);
else
{
joy_analog(input[dev].num, 1, 0, 0);
joy_analog(input[dev].num, 1, 0, 0);
joy_analog(dev, 1, 0, 0);
joy_analog(dev, 1, 0, 0);
}
}
}
else if (ev->code == 0 && input[dev].lightgun)
{
joy_analog(input[dev].num, 0, value);
joy_analog(dev, 0, value);
}
else if (ev->code == 1 && input[dev].lightgun)
{
joy_analog(input[dev].num, 1, value);
joy_analog(dev, 1, value);
}
else
{
int offset = (value < -1 || value>1) ? value : 0;
if (input[dev].stick_l[0] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_l[0]])
{
joy_analog(input[dev].num, 0, offset, 0);
joy_analog(dev, 0, offset, 0);
}
else if (input[dev].stick_l[1] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_l[1]])
{
joy_analog(input[dev].num, 1, offset, 0);
joy_analog(dev, 1, offset, 0);
}
else if (input[dev].stick_r[0] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_r[0]])
{
joy_analog(input[dev].num, 0, offset, 1);
joy_analog(dev, 0, offset, 1);
}
else if (input[dev].stick_r[1] && ev->code == (uint16_t)input[dev].mmap[input[dev].stick_r[1]])
{
joy_analog(input[dev].num, 1, offset, 1);
joy_analog(dev, 1, offset, 1);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions support.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@

// N64 support
#include "support/n64/n64.h"
#include "support/n64/n64_joy_emu.h"
52 changes: 52 additions & 0 deletions support/n64/n64_joy_emu.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdint.h>
#include <math.h>

void n64_joy_emu(int x, int y, int* x2, int* y2, int max_range)
{
// Move to top right quadrant to standardize solutions
const int x_flip = x < 0 ? -1 : 1;
const int y_flip = y < 0 ? -1 : 1;
const int abs_x = x * x_flip;
const int abs_y = y * y_flip;

// Reduce range to radius 97.5807358037f ((69,69) diagonal of original controller)
// assumes the max range is at least 85 (max cardinal of original controller)
if (max_range < 85) max_range = 85;
float scale = 97.5807358037f / max_range;
float scaled_x = abs_x * scale;
float scaled_y = abs_y * scale;

// Move to octagon's lower wedge in top right quadrant to further standardize solution
float scaled_max;
float scaled_min;
if (abs_x > abs_y) {
scaled_max = scaled_x;
scaled_min = scaled_y;
} else {
scaled_max = scaled_y;
scaled_min = scaled_x;
}

// Clamp scaled_min and scaled_max
// Note: wedge boundary is given by x = 85 - y * ((85 - 69) / 69)
// If x + y * (16 / 69) > 85, coordinates exceed boundary and need clamped
float boundary = scaled_max + scaled_min * 0.231884057971f;
if (boundary > 85) {
// We know target value is on:
// 1) Boundary line: x = 85 - y * (16 / 69)
// 2) Observed slope line: y = (scaled_max / scaled_min) * x
// Solving system of equations yields:
scaled_min = 85 * scaled_min / boundary;
scaled_max = 85 - scaled_min * 0.231884057971f; // Boundary line
}

// Move back from wedge to actual coordinates
if (abs_x > abs_y) {
*x2 = x_flip * scaled_max;
*y2 = y_flip * scaled_min;
} else {
*x2 = x_flip * scaled_min;
*y2 = y_flip * scaled_max;
}
}
1 change: 1 addition & 0 deletions support/n64/n64_joy_emu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
void n64_joy_emu(int x, int y, int* x2, int* y2, int max_range);

0 comments on commit d53a998

Please sign in to comment.