Commit 14e4065b authored by reassembler's avatar reassembler
Browse files

Force Feedback. Tidy Up and Integration.

parent e5da8e5c
......@@ -103,7 +103,7 @@ set(src_sdl
set(src_directx
"${main_cpp_base}/directx/ffeedback.hpp"
"${main_cpp_base}/directx/ffeedback.cpp"
)
)
set(src_engine
"${main_cpp_base}/engine/oaddresses.hpp"
......
......@@ -129,8 +129,25 @@
Example: 0 means use the entire wheel
larger values mean to progressively use less of the wheel turning circle -->
<wheelzone>75</wheelzone>
<!-- Force Feedback / Haptic Support -->
<haptic enabled = "1">
<!-- Make both of the below values negative,
if force feedback is in the wrong direction -->
<!-- Maximum Force To Apply (0 to 10000) -->
<max_force>8500</max_force>
<!-- Minimum Force To Apply (0 to max_force) -->
<min_force>7000</min_force>
<!-- Length of each effect. (1/x seconds) -->
<force_duration>20</force_duration>
</haptic>
</analog>
<!-- Digital Controls: Steering Adjust Speed (1 to 9) -->
<steerspeed>3</steerspeed>
......
//#ifdef WIN32
/***************************************************************************
Microsoft DirectX 8 Force Feedback (aka Haptic) Support
- Currently, SDL does not support haptic devices. So this is Win32 only.
- DirectX 8 still works on Windows XP, so I'm not attempting to support
a higher version for now.
Ref: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417563%28v=vs.85%29.aspx
Copyright Chris White.
See license.txt for more details.
***************************************************************************/
#include "ffeedback.hpp"
//-----------------------------------------------------------------------------
// Dummy Functions For Non-Windows Builds
//-----------------------------------------------------------------------------
#ifndef WIN32
namespace forcefeedback
{
bool init(int a, int b, int c) { return false; } // Did not initialize
void close() {}
int set(int x, int f) { return 0; }
bool is_supported() { return false; } // Not supported
};
// DirectX 8 Needed.
// This version works on Windows XP, so best not to go for a higher version.
//-----------------------------------------------------------------------------
// DirectX 8 Code Below
//-----------------------------------------------------------------------------
#else
// DirectX 8 Needed (Windows XP and up)
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include <SDL_syswm.h> // Used to get window handle for DirectX
#include <iostream>
#include "ffeedback.hpp"
namespace forcefeedback
{
//-----------------------------------------------------------------------------
// Function prototypes
//-----------------------------------------------------------------------------
......@@ -28,26 +56,38 @@ HRESULT InitForceEffects();
LPDIRECTINPUT8 g_pDI = NULL;
LPDIRECTINPUTDEVICE8 g_pDevice = NULL;
LPDIRECTINPUTEFFECT g_pEffect = NULL; // Force Feedback Effect
LPDIRECTINPUTEFFECT g_pEffect = NULL; // Force Feedback Effect
DWORD g_dwNumForceFeedbackAxis = 0;
int diMaxForce = 10000;
int diMinForce = -1000;
bool g_supported = false; // Is Haptic Device Supported?
//-----------------------------------------------------------------------------
// User Configurable Values
//-----------------------------------------------------------------------------
// Lower = turn more slowly
int diForceFreq = 1;
int g_max_force; // Maximum Force To Apply (0 to DI_FFNOMINALMAX)
int g_min_force; // Minimum Force To Apply (0 to g_max_force)
int g_force_duration; // Length of each effect. (1/x seconds)
void init()
bool init(int max_force, int min_force, int force_duration)
{
// Platform Specific SDL code to get a window handle
SDL_SysWMinfo i;
SDL_VERSION(&i.version);
if (SDL_GetWMInfo(&i))
g_max_force = max_force;
g_min_force = min_force;
g_force_duration = force_duration;
if (!g_supported)
{
HWND hwnd = i.window;
InitDirectInput(hwnd);
// Platform Specific SDL code to get a window handle
SDL_SysWMinfo i;
SDL_VERSION(&i.version);
if (SDL_GetWMInfo(&i))
{
HWND hwnd = i.window;
InitDirectInput(hwnd);
}
}
return g_supported;
}
void close()
......@@ -61,6 +101,8 @@ void close()
SAFE_RELEASE( g_pEffect );
SAFE_RELEASE( g_pDevice );
SAFE_RELEASE( g_pDI );
g_supported = false;
}
HRESULT InitDirectInput( HWND hDlg )
......@@ -105,7 +147,7 @@ HRESULT InitDirectInput( HWND hDlg )
// Exclusive access is required in order to perform force feedback.
if( FAILED( hr = g_pDevice->SetCooperativeLevel( hDlg,
DISCL_EXCLUSIVE |
DISCL_FOREGROUND ) ) )
DISCL_BACKGROUND ) ) )
{
return hr;
}
......@@ -183,13 +225,8 @@ HRESULT InitForceEffects()
HRESULT hr;
// Cap Default Values
if (diForceFreq <= 0)
diForceFreq = 20;
if (diMaxForce > DI_FFNOMINALMAX || diMaxForce < -DI_FFNOMINALMAX)
diMaxForce = DI_FFNOMINALMAX;
if (diMinForce > DI_FFNOMINALMAX || diMinForce < -DI_FFNOMINALMAX)
diMinForce = 0;
if (g_force_duration <= 0)
g_force_duration = 20;
DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y };
LONG lDirection[2] = { 0, 0 };
......@@ -197,9 +234,8 @@ HRESULT InitForceEffects()
DIEFFECT eff;
ZeroMemory( &eff, sizeof(eff) );
eff.dwSize = sizeof(DIEFFECT) ;
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; // Using X/Y style coords
eff.dwDuration = DI_SECONDS/diForceFreq; // Duration: 20th of a second
// eff.dwDuration = INFINITE; // Duration: never-ending
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; // Using X/Y style coords
eff.dwDuration = DI_SECONDS/g_force_duration; // Duration: 20th of a second (use INFINITE for never ending)
eff.dwSamplePeriod = 0;
eff.dwGain = DI_FFNOMINALMAX;
eff.dwTriggerButton = DIEB_NOTRIGGER;
......@@ -218,7 +254,9 @@ HRESULT InitForceEffects()
if (g_pEffect == NULL)
return E_FAIL;
std::cout << "all ok!" << std::endl;
// Denote Force Feedback Device Found & Supported
g_supported = true;
return S_OK;
}
......@@ -229,8 +267,8 @@ HRESULT InitForceEffects()
// Higher force = Less aggressive steer
int set(int xdirection, int force)
{
if (g_pEffect == NULL) return -1;
if (force < 0) return -1;
if (!g_supported || g_pEffect == NULL || force < 0)
return -1;
LONG lDirection[2] = { 0, 0 }; // centred by default
......@@ -239,7 +277,14 @@ int set(int xdirection, int force)
else if (xdirection < 0x08) // push left
lDirection[0] = -1;
LONG magnitude = diMaxForce - ((diMaxForce-diMinForce) / 7*force);
// 7 possible force positions, so divide maximum amount to subtract by 7.
LONG magnitude = g_max_force - (((g_max_force-g_min_force) / 7) * force);
// Cap within range
if (magnitude > DI_FFNOMINALMAX)
magnitude = DI_FFNOMINALMAX;
else if (magnitude < -DI_FFNOMINALMAX)
magnitude = -DI_FFNOMINALMAX;
DICONSTANTFORCE cf; // Type-specific parameters
cf.lMagnitude = magnitude;
......@@ -256,6 +301,12 @@ int set(int xdirection, int force)
return g_pEffect->SetParameters(&eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START);
}
// Is Haptic Device Supported?
bool is_supported()
{
return g_supported;
}
};
//#endif
\ No newline at end of file
#endif
\ No newline at end of file
/***************************************************************************
Microsoft DirectX 8 Force Feedback (aka Haptic) Support
- Currently, SDL does not support haptic devices. So this is Win32 only.
- DirectX 8 still works on Windows XP, so I'm not attempting to support
a higher version for now.
Ref: http://msdn.microsoft.com/en-us/library/windows/desktop/ee417563%28v=vs.85%29.aspx
Copyright Chris White.
See license.txt for more details.
***************************************************************************/
#pragma once
//-----------------------------------------------------------------------------
......@@ -5,7 +19,8 @@
//-----------------------------------------------------------------------------
namespace forcefeedback
{
extern void init();
extern bool init(int max_force, int min_force, int force_duration);
extern void close();
extern int set(int xdirection, int force);
extern bool is_supported();
};
\ No newline at end of file
......@@ -2,48 +2,18 @@
Process Outputs.
- Only the Deluxe Moving Motor Code is ported for now.
- This is used by the force-feedback system.
- This is used by the force-feedback haptic system.
One thing to note is that this code was originally intended to drive
a moving hydraulic cabinet, not to be mapped to a haptic device.
Therefore, it's not perfect when used in this way, but the results
aren't bad :)
Copyright Chris White.
See license.txt for more details.
***************************************************************************/
// Port code at 0xE644
//REGISTERS:00140001 hw_motor_limit: ds.b 1 ; DATA XREF: sub_ED46:loc_ED56r
//REGISTERS:00140001 ; sub_ED46:loc_EDD4r ...
//REGISTERS:00140001 ; Bit 3 = Set to indicate left limit reached
//REGISTERS:00140001 ; Bit 4 = Set to indicate centre reached
//REGISTERS:00140001 ; Bit 5 = Set to indicate right limit reached
//REGISTERS:00140001 ;
//REGISTERS:00140002 ds.b 1
//REGISTERS:00140003 hw_motor_ctrl: ds.b 1 ; DATA XREF: DoMotors+9Ew
//REGISTERS:00140003 ; DoMotors+18Cw ...
//REGISTERS:00140003 ; Move motor
//REGISTERS:00140003 ;
//REGISTERS:00140003 ; 0 = Switch off?
//REGISTERS:00140003 ; 5 = Left
//REGISTERS:00140003 ; 8 = Centre
//REGISTERS:00140003 ; B = Right
//if (a==0x140001) {
// // Motor Limit Status
// // return 0x2d;
// if (OutAxis[0]<0x03) return 0x0d; // LEFT LIMIT REACHED
// if (OutAxis[0]>0xfc) return 0x20; // RIGHT LIMIT REACHED
// return 0x2d;
//}
//if (a==0x140031)
//{
// switch (AnalogSelect)
// {
// case 0x0: return OutAxis[0];
// case 0x4: return OutAxis[1];
// case 0x8: return OutAxis[2];
// case 0xc: return OutAxis[0]; // Motor Status x-pos
// }
#include "engine/outrun.hpp"
#include "engine/ocrash.hpp"
#include "engine/oferrari.hpp"
......@@ -51,6 +21,9 @@
#include "engine/ooutputs.hpp"
#include "directx/ffeedback.hpp"
// Disable areas of code that cause problems when mapping motor to haptic device
#define CHANGED_CODE 1
const static uint8_t MOTOR_VALUES[] =
{
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
......@@ -116,6 +89,14 @@ void OOutputs::init()
movement_adjust3 = 0;
}
void OOutputs::tick()
{
do_motors();
// Finally output the info to the force feedback handling class
motor_output(hw_motor_control);
}
// Process Motor Code.
// Note, that only the Deluxe Moving Motor Code is ported for now.
// Source: 0xE644
......@@ -125,7 +106,7 @@ void OOutputs::do_motors()
// Normally this would be done on H-Blank
// But instead we return the x-position of the wheel
input_motor = oinputs.input_steering;
motor_x_change = -(input_motor - input_motor_old);
motor_x_change = -(input_motor - MOTOR_PREV);
if (!motor_enabled)
{
......@@ -138,7 +119,7 @@ void OOutputs::do_motors()
{
if (ocrash.crash_counter)
{
if (oinitengine.car_increment <= 0x14)
if ((oinitengine.car_increment >> 16) <= 0x14)
car_stationary();
else
do_motor_crash();
......@@ -149,7 +130,7 @@ void OOutputs::do_motors()
}
else
{
if (oinitengine.car_increment <= 0x14)
if ((oinitengine.car_increment >> 16) <= 0x14)
{
if (!was_small_change)
done();
......@@ -186,15 +167,16 @@ void OOutputs::car_moving()
return;
}
if (oinitengine.car_increment <= 0x64) speed = 0;
else if (oinitengine.car_increment <= 0xA0) speed = 1 << 3;
else if (oinitengine.car_increment <= 0xDC) speed = 2 << 3;
else speed = 3 << 3;
const uint16_t car_inc = oinitengine.car_increment >> 16;
if (car_inc <= 0x64) speed = 0;
else if (car_inc <= 0xA0) speed = 1 << 3;
else if (car_inc <= 0xDC) speed = 2 << 3;
else speed = 3 << 3;
if (oinitengine.road_curve == 0) curve = 0;
else if (oinitengine.road_curve <= 0x3C) curve = 2; // sharp curve
else if (oinitengine.road_curve <= 0x5A) curve = 1; // gentle curve
else curve = 0;
if (oinitengine.road_curve == 0) curve = 0;
else if (oinitengine.road_curve <= 0x3C) curve = 2; // sharp curve
else if (oinitengine.road_curve <= 0x5A) curve = 1; // gentle curve
else curve = 0;
int16_t steering = oinputs.steering_adjust;
steering += (movement_adjust1 + movement_adjust2 + movement_adjust3);
......@@ -217,8 +199,9 @@ void OOutputs::car_moving()
steering--;
uint8_t motor_value = MOTOR_VALUES[speed + curve];
int16_t change = motor_x_change + (motor_value << 1);
#ifndef CHANGED_CODE
int16_t change = motor_x_change + (motor_value << 1);
// Latch left movement
if (change >= LEFT_LIMIT)
{
......@@ -228,6 +211,7 @@ void OOutputs::car_moving()
motor_change_latch = motor_x_change;
}
else
#endif
{
hw_motor_control = motor_value + 8;
}
......@@ -249,8 +233,9 @@ void OOutputs::car_moving()
steering--;
uint8_t motor_value = MOTOR_VALUES[speed + curve];
int16_t change = motor_x_change - (motor_value << 1);
#ifndef CHANGED_CODE
int16_t change = motor_x_change - (motor_value << 1);
// Latch right movement
if (change <= RIGHT_LIMIT)
{
......@@ -260,6 +245,7 @@ void OOutputs::car_moving()
motor_change_latch = motor_x_change;
}
else
#endif
{
hw_motor_control = -motor_value + 8;
}
......@@ -291,7 +277,7 @@ void OOutputs::car_stationary()
{
int8_t motor_value = MOTOR_VALUES_STATIONARY[change >> 3];
if (motor_x_change < 0)
if (motor_x_change >= 0)
motor_value = -motor_value;
hw_motor_control = motor_value + 8;
......@@ -331,23 +317,6 @@ void OOutputs::adjust_motor()
done();
}
// Source: 0xE94E
void OOutputs::done()
{
if (std::abs(motor_x_change) <= 8)
{
was_small_change = true;
motor_control = MOTOR_CENTRE;
}
else
{
was_small_change = false;
}
// Finally output the info to the force feedback handling class
motor_output(hw_motor_control);
}
// Adjust motor during crash/skid state
// Source: 0xE994
void OOutputs::do_motor_crash()
......@@ -366,11 +335,12 @@ void OOutputs::do_motor_offroad()
{
const uint8_t* table = (oferrari.wheel_state != OFerrari::WHEELS_OFF) ? MOTOR_VALUES_OFFROAD2 : MOTOR_VALUES_OFFROAD1;
const uint16_t car_inc = oinitengine.car_increment >> 16;
uint8_t index;
if (oinitengine.car_increment <= 0x32) index = 0;
else if (oinitengine.car_increment <= 0x50) index = 1;
else if (oinitengine.car_increment <= 0x6E) index = 2;
else index = 3;
if (car_inc <= 0x32) index = 0;
else if (car_inc <= 0x50) index = 1;
else if (car_inc <= 0x6E) index = 2;
else index = 3;
set_value(table, index);
}
......@@ -379,10 +349,23 @@ void OOutputs::set_value(const uint8_t* table, uint8_t index)
{
hw_motor_control = table[(index << 3) + (counter & 7)];
counter++;
done();
}
// Source: 0xE94E
void OOutputs::done()
{
if (std::abs(motor_x_change) <= 8)
{
was_small_change = true;
motor_control = MOTOR_CENTRE;
}
else
{
was_small_change = false;
}
}
// Send output commands to motor hardware
// This is the equivalent to writing to register 0x140003
void OOutputs::motor_output(uint8_t cmd)
......
/***************************************************************************
Process Outputs.
- Only the Deluxe Moving Motor Code is ported for now.
- This is used by the force-feedback haptic system.
One thing to note is that this code was originally intended to drive
a moving hydraulic cabinet, not to be mapped to a haptic device.
Therefore, it's not perfect when used in this way, but the results
aren't bad :)
Copyright Chris White.
See license.txt for more details.
***************************************************************************/
#pragma once
#include "stdint.hpp"
......@@ -17,7 +33,7 @@ public:
~OOutputs(void);
void init();
void do_motors();
void tick();
private:
const static uint8_t MOTOR_OFF = 0;
......@@ -25,8 +41,9 @@ private:
// These are calculated during startup in the original game.
// Here we just hardcode them, as the motor init code isn't ported.
const static int8_t LEFT_LIMIT = -14;
const static int8_t RIGHT_LIMIT = 14;
const static uint8_t MOTOR_PREV = 0x80;
const static uint8_t LEFT_LIMIT = 0xC1;
const static uint8_t RIGHT_LIMIT = 0x3C;
// Motor Value, representing the x-position of the cabinet.
// We fudge this and just use the x-position of the steering wheel
......@@ -62,7 +79,7 @@ private:
// 0x26: Adjusted movement value based on steering 3
int16_t movement_adjust3;
void motor_output(uint8_t cmd);
void do_motors();
void car_moving();
void car_stationary();
void adjust_motor();
......@@ -70,4 +87,5 @@ private:
void do_motor_offroad();
void set_value(const uint8_t*, uint8_t);
void done();
void motor_output(uint8_t cmd);
};
\ No newline at end of file
......@@ -49,6 +49,7 @@ Outrun outrun;
- Stage 3c: Clouds overlapping trees [unable to fix easily]
- Traffic spawns on horizon and transforms type as you approach.
This is mostly noticeable when using hi-res mode.
- Sometimes the Ferrari stalls on the start-line on game restart. Happens in Attract Mode too.
*/
......@@ -251,8 +252,8 @@ void Outrun::jump_table()
}
osprites.sprite_copy();
if (tick_frame)
outputs->do_motors();
if (tick_frame && config.controls.haptic && config.controls.analog)
outputs->tick();
}
// Source: 0xB15E
......
......@@ -121,6 +121,11 @@ void Config::load(const std::string &filename)
controls.axis[2] = pt_config.get("controls.analog.axis.brake", 3);
controls.analog_zone = pt_config.get("controls.analog.wheelzone", 75);
controls.haptic = pt_config.get("controls.analog.haptic.<xmlattr>.enabled", 0);
controls.max_force = pt_config.get("controls.analog.haptic.max_force", 9000);
controls.min_force = pt_config.get("controls.analog.haptic.min_force", 8500);
controls.force_duration= pt_config.get("controls.analog.haptic.force_duration", 20);
// ------------------------------------------------------------------------
// Engine Settings
// ------------------------------------------------------------------------
......
......@@ -69,6 +69,11 @@ struct controls_settings_t
int analog; // Use analog controls
int axis[3]; // Analog Axis
int analog_zone; // Percentage of wheel turning circle to use
int haptic; // Force Feedback Enabled
int max_force;
int min_force;
int force_duration;
};
struct engine_settings_t
......
......@@ -22,8 +22,6 @@
#include "frontend/ttrial.hpp"
#include "directx/ffeedback.hpp"
// Logo Y Position
const static int16_t LOGO_Y = -60;
......@@ -186,8 +184,6 @@ void Menu::populate()
void Menu::init()
{
forcefeedback::init();
// If we got a new high score on previous time trial, then save it!
if (outrun.ttrial.new_high_score)
{
......@@ -379,8 +375,6 @@ void Menu::tick_menu()
// Tick Controls
if (input.has_pressed(Input::DOWN))
{
forcefeedback::set(10, 1);
osoundint.queue_sound(sound::BEEP1);
if (++cursor >= (int16_t) menu_selected->size())
......@@ -388,8 +382,6 @@ void Menu::tick_menu()
}
else if (input.has_pressed(Input::UP))
{
forcefeedback::set(1, 1);