github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/game/gamepad/xinput_windows.go (about)

     1  package gamepad
     2  
     3  import (
     4  	"math"
     5  	"syscall"
     6  	"unsafe"
     7  )
     8  
     9  type ID byte
    10  
    11  type All [ControllerCount]State
    12  
    13  func (all *All) Update() (firsterr error) {
    14  	for i := range all {
    15  		all[i].ID = ID(i)
    16  		err := all[i].Update()
    17  		if err != nil && firsterr == nil {
    18  			firsterr = err
    19  		}
    20  	}
    21  	return
    22  }
    23  
    24  type State struct {
    25  	ID        ID
    26  	Connected bool
    27  
    28  	Packet uint32
    29  	Raw    struct {
    30  		Buttons      Button
    31  		LeftTrigger  uint8
    32  		RightTrigger uint8
    33  		ThumbLX      int16
    34  		ThumbLY      int16
    35  		ThumbRX      int16
    36  		ThumbRY      int16
    37  	}
    38  }
    39  
    40  func (state *State) Pressed(button Button) bool { return state.Raw.Buttons&button != 0 }
    41  
    42  func (state *State) Update() error { return Get(state.ID, state) }
    43  
    44  type Thumb struct{ X, Y, Magnitude float32 }
    45  
    46  func (state *State) RectDPad() (thumb Thumb) {
    47  	if state.Pressed(DPadUp) {
    48  		thumb.Y += 1
    49  	}
    50  	if state.Pressed(DPadDown) {
    51  		thumb.Y -= 1
    52  	}
    53  	if state.Pressed(DPadLeft) {
    54  		thumb.X -= 1
    55  	}
    56  	if state.Pressed(DPadRight) {
    57  		thumb.X += 1
    58  	}
    59  	if thumb.X != 0 || thumb.Y != 0 {
    60  		thumb.Magnitude = 1
    61  	}
    62  	return
    63  }
    64  
    65  func (state *State) RoundDPad() (thumb Thumb) {
    66  	thumb = state.RectDPad()
    67  	if thumb.X != 0 && thumb.Y != 0 {
    68  		thumb.X *= isqrt2
    69  		thumb.Y *= isqrt2
    70  	}
    71  	return
    72  }
    73  
    74  func round16(rx, ry, deadzone int16) (thumb Thumb) {
    75  	//TODO: use sqrt32
    76  	fx, fy := float64(rx), float64(ry)
    77  	thumb.Magnitude = float32(math.Sqrt(fx*fx + fy*fy))
    78  
    79  	thumb.X = float32(rx) / thumb.Magnitude
    80  	thumb.Y = float32(ry) / thumb.Magnitude
    81  
    82  	if thumb.Magnitude > float32(deadzone) {
    83  		if thumb.Magnitude > 32767 {
    84  			thumb.Magnitude = 32767
    85  		}
    86  		thumb.Magnitude = (thumb.Magnitude - float32(deadzone)) / float32(32767-deadzone)
    87  	} else {
    88  		thumb.Magnitude = 0
    89  	}
    90  
    91  	thumb.X *= thumb.Magnitude
    92  	thumb.Y *= thumb.Magnitude
    93  
    94  	return
    95  }
    96  
    97  func (state *State) RoundLeft() Thumb {
    98  	return round16(state.Raw.ThumbLX, state.Raw.ThumbLY, LeftThumbDeadZone)
    99  }
   100  
   101  func (state *State) RoundRight() Thumb {
   102  	return round16(state.Raw.ThumbRX, state.Raw.ThumbRY, RightThumbDeadZone)
   103  }
   104  
   105  func linear16(v, deadzone int16) float32 {
   106  	if v < -deadzone {
   107  		return float32(v+deadzone) / float32(32767-deadzone)
   108  	}
   109  	if v > deadzone {
   110  		return float32(v-deadzone) / float32(32767-deadzone)
   111  	}
   112  	return 0
   113  }
   114  
   115  func rect16(rx, ry, deadzone int16) (thumb Thumb) {
   116  	thumb.X = linear16(rx, deadzone)
   117  	thumb.Y = linear16(ry, deadzone)
   118  	if thumb.X != 0 && thumb.Y != 0 {
   119  		thumb.Magnitude = 1
   120  	}
   121  	return
   122  }
   123  
   124  func (state *State) RectLeft() Thumb {
   125  	return rect16(state.Raw.ThumbLX, state.Raw.ThumbLY, LeftThumbDeadZone)
   126  }
   127  
   128  func (state *State) RectRight() Thumb {
   129  	return rect16(state.Raw.ThumbRX, state.Raw.ThumbRY, RightThumbDeadZone)
   130  }
   131  
   132  func (state *State) Vibrate(left, right uint16) {
   133  	if !state.Connected {
   134  		return
   135  	}
   136  	Vibrate(state.ID, &Vibration{left, right})
   137  }
   138  
   139  type Vibration struct {
   140  	LeftMotor  uint16
   141  	RightMotor uint16
   142  }
   143  
   144  const (
   145  	ControllerCount    = ID(4)
   146  	TriggerThreshold   = 30
   147  	LeftThumbDeadZone  = 7849
   148  	RightThumbDeadZone = 8689
   149  
   150  	sqrt2  = 1.4142135623730950488
   151  	isqrt2 = 1 / sqrt2
   152  )
   153  
   154  type Button uint16
   155  
   156  const (
   157  	DPadUp    Button = 0x0001
   158  	DPadDown         = 0x0002
   159  	DPadLeft         = 0x0004
   160  	DPadRight        = 0x0008
   161  
   162  	Start Button = 0x0010
   163  	Back         = 0x0020
   164  
   165  	LeftThumb  Button = 0x0040
   166  	RightThumb        = 0x0080
   167  
   168  	LeftShoulder  Button = 0x0100
   169  	RightShoulder        = 0x0200
   170  
   171  	ButtonA Button = 0x1000
   172  	ButtonB        = 0x2000
   173  	ButtonX        = 0x4000
   174  	ButtonY        = 0x8000
   175  )
   176  
   177  // Get retrieves the latest state of the controller.
   178  func Get(id ID, state *State) error {
   179  	r, _, _ := procGetState.Call(uintptr(id), uintptr(unsafe.Pointer(&state.Packet)))
   180  	state.ID = id
   181  	state.Connected = r == 0
   182  	if r == 0 {
   183  		return nil
   184  	}
   185  	return syscall.Errno(r)
   186  }
   187  
   188  func Vibrate(id ID, vibration *Vibration) error {
   189  	r, _, _ := procSetState.Call(uintptr(id), uintptr(unsafe.Pointer(vibration)))
   190  	if r == 0 {
   191  		return nil
   192  	}
   193  	return syscall.Errno(r)
   194  }
   195  
   196  var (
   197  	procGetState *syscall.Proc
   198  	procSetState *syscall.Proc
   199  )
   200  
   201  func init() {
   202  	dll, err := syscall.LoadDLL("xinput1_4.dll")
   203  	defer func() {
   204  		if err != nil {
   205  			panic(err)
   206  		}
   207  	}()
   208  
   209  	if err != nil {
   210  		dll, err = syscall.LoadDLL("xinput1_3.dll")
   211  		if err != nil {
   212  			dll, err = syscall.LoadDLL("xinput9_1_0.dll")
   213  			return
   214  		}
   215  	}
   216  
   217  	procGetState = dll.MustFindProc("XInputGetState")
   218  	procSetState = dll.MustFindProc("XInputSetState")
   219  }