unsigned int SdlJoystick::getCapabilities() { #if SDL_VERSION_ATLEAST(1,3,0) if (mHaptic) return SDL_HapticQuery(mHaptic); #endif return 0; }
/** * @brief Initializes force feedback for the loaded device. */ static void joystick_initHaptic (void) { #if SDL_VERSION_ATLEAST(1,3,0) if (has_haptic && SDL_JoystickIsHaptic(joystick)) { /* Close haptic if already open. */ if (haptic != NULL) { SDL_HapticClose(haptic); haptic = NULL; } /* Try to create haptic device. */ haptic = SDL_HapticOpenFromJoystick(joystick); if (haptic == NULL) { WARN("Unable to initialize force feedback: %s", SDL_GetError()); return; } /* Check to see what it supports. */ haptic_query = SDL_HapticQuery(haptic); if (!(haptic_query & SDL_HAPTIC_SINE)) { SDL_HapticClose(haptic); haptic = NULL; return; } DEBUG(" force feedback enabled"); } #endif /* SDL_VERSION_ATLEAST(1,3,0) */ }
bool CSDLPad::OpenDevice() { m_pSDLDevice = SDL_JoystickOpen(m_deviceNo); if (m_pSDLDevice) { // SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK) should skip the accelerometer when // enumerating joysticks, but it doesn't seem to work. Manually check for it here // and close it. // FIXME: Need a more robust way of detecting this (or get SetHint() to work). if (strcmp(SDL_JoystickName(m_pSDLDevice), "Android Accelerometer") == 0) { SDL_JoystickClose(m_pSDLDevice); return false; } m_connected = true; #if defined(SDL_USE_HAPTIC_FEEDBACK) m_pHapticDevice = SDL_HapticOpenFromJoystick(m_pSDLDevice); if (m_pHapticDevice && (SDL_HapticQuery(m_pHapticDevice) & SDL_HAPTIC_LEFTRIGHT)) { m_supportsFeedback = true; } else { if (m_pHapticDevice) SDL_HapticClose(m_pHapticDevice); m_supportsFeedback = false; } #else m_supportsFeedback = false; #endif m_curHapticEffect = -1; gEnv->pLog->Log("CSDLPad - Gamepad [%d] supports feedback (Y/N): %s", m_deviceNo, m_supportsFeedback ? "Y" : "N"); } return m_pSDLDevice != NULL; }
/* * Displays information about the haptic device. */ static void HapticPrintSupported(SDL_Haptic * haptic) { unsigned int supported; supported = SDL_HapticQuery(haptic); SDL_Log(" Supported effects [%d effects, %d playing]:\n", SDL_HapticNumEffects(haptic), SDL_HapticNumEffectsPlaying(haptic)); if (supported & SDL_HAPTIC_CONSTANT) SDL_Log(" constant\n"); if (supported & SDL_HAPTIC_SINE) SDL_Log(" sine\n"); /* !!! FIXME: put this back when we have more bits in 2.1 */ /* if (supported & SDL_HAPTIC_SQUARE) SDL_Log(" square\n"); */ if (supported & SDL_HAPTIC_TRIANGLE) SDL_Log(" triangle\n"); if (supported & SDL_HAPTIC_SAWTOOTHUP) SDL_Log(" sawtoothup\n"); if (supported & SDL_HAPTIC_SAWTOOTHDOWN) SDL_Log(" sawtoothdown\n"); if (supported & SDL_HAPTIC_RAMP) SDL_Log(" ramp\n"); if (supported & SDL_HAPTIC_FRICTION) SDL_Log(" friction\n"); if (supported & SDL_HAPTIC_SPRING) SDL_Log(" spring\n"); if (supported & SDL_HAPTIC_DAMPER) SDL_Log(" damper\n"); if (supported & SDL_HAPTIC_INERTIA) SDL_Log(" inertia\n"); if (supported & SDL_HAPTIC_CUSTOM) SDL_Log(" custom\n"); if (supported & SDL_HAPTIC_LEFTRIGHT) SDL_Log(" left/right\n"); SDL_Log(" Supported capabilities:\n"); if (supported & SDL_HAPTIC_GAIN) SDL_Log(" gain\n"); if (supported & SDL_HAPTIC_AUTOCENTER) SDL_Log(" autocenter\n"); if (supported & SDL_HAPTIC_STATUS) SDL_Log(" status\n"); }
bool Joystick::isVibrationSupported() { if (!checkCreateHaptic()) return false; unsigned int features = SDL_HapticQuery(haptic); if ((features & SDL_HAPTIC_LEFTRIGHT) != 0) return true; // Some gamepad drivers only support left/right motors via a custom effect. if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0) return true; // Test for simple sine wave support as a last resort. if ((features & SDL_HAPTIC_SINE) != 0) return true; return false; }
/* * Displays information about the haptic device. */ static void HapticPrintSupported(SDL_Haptic * haptic) { unsigned int supported; supported = SDL_HapticQuery(haptic); printf(" Supported effects [%d effects, %d playing]:\n", SDL_HapticNumEffects(haptic), SDL_HapticNumEffectsPlaying(haptic)); if (supported & SDL_HAPTIC_CONSTANT) printf(" constant\n"); if (supported & SDL_HAPTIC_SINE) printf(" sine\n"); if (supported & SDL_HAPTIC_SQUARE) printf(" square\n"); if (supported & SDL_HAPTIC_TRIANGLE) printf(" triangle\n"); if (supported & SDL_HAPTIC_SAWTOOTHUP) printf(" sawtoothup\n"); if (supported & SDL_HAPTIC_SAWTOOTHDOWN) printf(" sawtoothdown\n"); if (supported & SDL_HAPTIC_RAMP) printf(" ramp\n"); if (supported & SDL_HAPTIC_FRICTION) printf(" friction\n"); if (supported & SDL_HAPTIC_SPRING) printf(" spring\n"); if (supported & SDL_HAPTIC_DAMPER) printf(" damper\n"); if (supported & SDL_HAPTIC_INERTIA) printf(" intertia\n"); if (supported & SDL_HAPTIC_CUSTOM) printf(" custom\n"); printf(" Supported capabilities:\n"); if (supported & SDL_HAPTIC_GAIN) printf(" gain\n"); if (supported & SDL_HAPTIC_AUTOCENTER) printf(" autocenter\n"); if (supported & SDL_HAPTIC_STATUS) printf(" status\n"); }
Joystick::Joystick(SDL_Joystick* const joystick, const int sdl_index, const unsigned int index) : m_joystick(joystick) , m_sdl_index(sdl_index) , m_index(index) { // really bad HACKS: // to not use SDL for an XInput device // too many people on the forums pick the SDL device and ask: // "why don't my 360 gamepad triggers/rumble work correctly" #ifdef _WIN32 // checking the name is probably good (and hacky) enough // but i'll double check with the num of buttons/axes std::string lcasename = GetName(); std::transform(lcasename.begin(), lcasename.end(), lcasename.begin(), tolower); if ((std::string::npos != lcasename.find("xbox 360")) && (10 == SDL_JoystickNumButtons(joystick)) && (5 == SDL_JoystickNumAxes(joystick)) && (1 == SDL_JoystickNumHats(joystick)) && (0 == SDL_JoystickNumBalls(joystick)) ) { // this device won't be used return; } #endif // get buttons for (u8 i = 0; i != SDL_JoystickNumButtons(m_joystick); ++i) AddInput(new Button(i, m_joystick)); // get hats for (u8 i = 0; i != SDL_JoystickNumHats(m_joystick); ++i) { // each hat gets 4 input instances associated with it, (up down left right) for (u8 d = 0; d != 4; ++d) AddInput(new Hat(i, m_joystick, d)); } // get axes for (u8 i = 0; i != SDL_JoystickNumAxes(m_joystick); ++i) { // each axis gets a negative and a positive input instance associated with it AddAnalogInputs(new Axis(i, m_joystick, -32768), new Axis(i, m_joystick, 32767)); } #ifdef USE_SDL_HAPTIC // try to get supported ff effects m_haptic = SDL_HapticOpenFromJoystick( m_joystick ); if (m_haptic) { //SDL_HapticSetGain( m_haptic, 1000 ); //SDL_HapticSetAutocenter( m_haptic, 0 ); const unsigned int supported_effects = SDL_HapticQuery( m_haptic ); // constant effect if (supported_effects & SDL_HAPTIC_CONSTANT) { m_state_out.push_back(EffectIDState()); AddOutput(new ConstantEffect(m_state_out.back())); } // ramp effect if (supported_effects & SDL_HAPTIC_RAMP) { m_state_out.push_back(EffectIDState()); AddOutput(new RampEffect(m_state_out.back())); } // sine effect if (supported_effects & SDL_HAPTIC_SINE) { m_state_out.push_back(EffectIDState()); AddOutput(new SineEffect(m_state_out.back())); } // square effect if (supported_effects & SDL_HAPTIC_SQUARE) { m_state_out.push_back(EffectIDState()); AddOutput(new SquareEffect(m_state_out.back())); } // triangle effect if (supported_effects & SDL_HAPTIC_TRIANGLE) { m_state_out.push_back(EffectIDState()); AddOutput(new TriangleEffect(m_state_out.back())); } } #endif }
/** * @brief The entry point of this force feedback demo. * @param[in] argc Number of arguments. * @param[in] argv Array of argc arguments. */ int main(int argc, char **argv) { int i; char *name; int index; SDL_HapticEffect efx[5]; int id[5]; int nefx; unsigned int supported; name = NULL; index = -1; if (argc > 1) { name = argv[1]; if ((strcmp(name, "--help") == 0) || (strcmp(name, "-h") == 0)) { printf("USAGE: %s [device]\n" "If device is a two-digit number it'll use it as an index, otherwise\n" "it'll use it as if it were part of the device's name.\n", argv[0]); return 0; } i = strlen(name); if ((i < 3) && isdigit(name[0]) && ((i == 1) || isdigit(name[1]))) { index = atoi(name); name = NULL; } } /* Initialize the force feedbackness */ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC); printf("%d Haptic devices detected.\n", SDL_NumHaptics()); if (SDL_NumHaptics() > 0) { /* We'll just use index or the first force feedback device found */ if (name == NULL) { i = (index != -1) ? index : 0; } /* Try to find matching device */ else { for (i = 0; i < SDL_NumHaptics(); i++) { if (strstr(SDL_HapticName(i), name) != NULL) break; } if (i >= SDL_NumHaptics()) { printf("Unable to find device matching '%s', aborting.\n", name); return 1; } } haptic = SDL_HapticOpen(i); if (haptic == NULL) { printf("Unable to create the haptic device: %s\n", SDL_GetError()); return 1; } printf("Device: %s\n", SDL_HapticName(i)); HapticPrintSupported(haptic); } else { printf("No Haptic devices found!\n"); return 1; } /* We only want force feedback errors. */ SDL_ClearError(); /* Create effects. */ memset(&efx, 0, sizeof(efx)); nefx = 0; supported = SDL_HapticQuery(haptic); printf("\nUploading effects\n"); /* First we'll try a SINE effect. */ if (supported & SDL_HAPTIC_SINE) { printf(" effect %d: Sine Wave\n", nefx); efx[nefx].type = SDL_HAPTIC_SINE; efx[nefx].periodic.period = 1000; efx[nefx].periodic.magnitude = 0x4000; efx[nefx].periodic.length = 5000; efx[nefx].periodic.attack_length = 1000; efx[nefx].periodic.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { printf("UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Now we'll try a SAWTOOTHUP */ if (supported & SDL_HAPTIC_SAWTOOTHUP) { printf(" effect %d: Sawtooth Up\n", nefx); efx[nefx].type = SDL_HAPTIC_SQUARE; efx[nefx].periodic.period = 500; efx[nefx].periodic.magnitude = 0x5000; efx[nefx].periodic.length = 5000; efx[nefx].periodic.attack_length = 1000; efx[nefx].periodic.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { printf("UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Now the classical constant effect. */ if (supported & SDL_HAPTIC_CONSTANT) { printf(" effect %d: Constant Force\n", nefx); efx[nefx].type = SDL_HAPTIC_CONSTANT; efx[nefx].constant.direction.type = SDL_HAPTIC_POLAR; efx[nefx].constant.direction.dir[0] = 20000; /* Force comes from the south-west. */ efx[nefx].constant.length = 5000; efx[nefx].constant.level = 0x6000; efx[nefx].constant.attack_length = 1000; efx[nefx].constant.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { printf("UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The cute spring effect. */ if (supported & SDL_HAPTIC_SPRING) { printf(" effect %d: Condition Spring\n", nefx); efx[nefx].type = SDL_HAPTIC_SPRING; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0x7FFF; efx[nefx].condition.left_sat[i] = 0x7FFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; efx[nefx].condition.center[i] = 0x1000; /* Displace the center for it to move. */ } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { printf("UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The pretty awesome inertia effect. */ if (supported & SDL_HAPTIC_INERTIA) { printf(" effect %d: Condition Inertia\n", nefx); efx[nefx].type = SDL_HAPTIC_SPRING; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0x7FFF; efx[nefx].condition.left_sat[i] = 0x7FFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { printf("UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } printf ("\nNow playing effects for 5 seconds each with 1 second delay between\n"); for (i = 0; i < nefx; i++) { printf(" Playing effect %d\n", i); SDL_HapticRunEffect(haptic, id[i], 1); SDL_Delay(6000); /* Effects only have length 5000 */ } /* Quit */ if (haptic != NULL) SDL_HapticClose(haptic); SDL_Quit(); return 0; }
static int IN_Haptic_Effects_To_Id(int haptic_effect, int effect_volume, int effect_x, int effect_y, int effect_z) { if ((SDL_HapticQuery(joystick_haptic) & SDL_HAPTIC_SINE)==0) return -1; int hapric_volume = joy_haptic_magnitude->value * effect_volume * 16; // * 128 = 32767 max strength; if (hapric_volume > 255) hapric_volume = 255; else if (hapric_volume < 0) hapric_volume = 0; switch(haptic_effect) { case HAPTIC_EFFECT_MENY: case HAPTIC_EFFECT_TRAPCOCK: case HAPTIC_EFFECT_STEP: /* North */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 48, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_PAIN: return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 196, 300/* 0.3 seconds long */, 200/* Takes 0.2 second to get max strength */, 200/* Takes 0.2 second to fade away */); case HAPTIC_EFFECT_BLASTER: /* 30 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_HYPER_BLASTER: return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_ETFRIFLE: /* 60 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_TRACKER: return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_MACHINEGUN: /* 90 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 800/* 800 ms*/, hapric_volume * 88, 600/* 0.6 seconds long */, 200/* Takes 0.2 second to get max strength */, 400/* Takes 0.4 second to fade away */); case HAPTIC_EFFECT_SHOTGUN: /* 120 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 100, 500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */, 200/* Takes 0.2 second to fade away */); case HAPTIC_EFFECT_SHOTGUN2: /* 150 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 96, 500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_SSHOTGUN: return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 96, 500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_RAILGUN: /* 180 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 64, 400/* 0.4 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_ROCKETGUN: /* 210 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 700/* 700 ms*/, hapric_volume * 128, 400/* 0.4 seconds long */, 300/* Takes 0.3 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_GRENADE: /* 240 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_BFG: /* 270 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 800/* 800 ms*/, hapric_volume * 100, 600/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_PALANX: /* 300 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); case HAPTIC_EFFECT_IONRIPPER: /* 330 degrees */ return IN_Haptic_Effect_Init( effect_x, effect_y, effect_z, 500/* 500 ms*/, hapric_volume * 64, 200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */, 100/* Takes 0.1 second to fade away */); default: return -1; } }
bool Joystick::setVibration(float left, float right, float duration) { left = std::min(std::max(left, 0.0f), 1.0f); right = std::min(std::max(right, 0.0f), 1.0f); if (left == 0.0f && right == 0.0f) return setVibration(); if (!checkCreateHaptic()) return false; Uint32 length = SDL_HAPTIC_INFINITY; if (duration >= 0.0f) { float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f; length = Uint32(std::min(duration, maxduration) * 1000); } bool success = false; unsigned int features = SDL_HapticQuery(haptic); int axes = SDL_HapticNumAxes(haptic); if ((features & SDL_HAPTIC_LEFTRIGHT) != 0) { memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); vibration.effect.type = SDL_HAPTIC_LEFTRIGHT; vibration.effect.leftright.length = length; vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX); vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX); success = runVibrationEffect(); } // Some gamepad drivers only give support for controlling individual motors // through a custom FF effect. if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2) { // NOTE: this may cause issues with drivers which support custom effects // but aren't similar to https://github.com/d235j/360Controller . // Custom effect data is clamped to 0x7FFF in SDL. vibration.data[0] = vibration.data[2] = Uint16(left * 0x7FFF); vibration.data[1] = vibration.data[3] = Uint16(right * 0x7FFF); memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); vibration.effect.type = SDL_HAPTIC_CUSTOM; vibration.effect.custom.length = length; vibration.effect.custom.channels = 2; vibration.effect.custom.period = 10; vibration.effect.custom.samples = 2; vibration.effect.custom.data = vibration.data; success = runVibrationEffect(); } // Fall back to a simple sine wave if all else fails. This only supports a // single strength value. if (!success && (features & SDL_HAPTIC_SINE) != 0) { memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); vibration.effect.type = SDL_HAPTIC_SINE; vibration.effect.periodic.length = length; vibration.effect.periodic.period = 10; float strength = std::max(left, right); vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF); success = runVibrationEffect(); } if (success) { vibration.left = left; vibration.right = right; if (length == SDL_HAPTIC_INFINITY) vibration.endtime = SDL_HAPTIC_INFINITY; else vibration.endtime = SDL_GetTicks() + length; } else { vibration.left = vibration.right = 0.0f; vibration.endtime = SDL_HAPTIC_INFINITY; } return success; }
ForceFeedback::ForceFeedback( const std::string & device, std::ostream & error_output, std::ostream & info_output) : device_name(device), enabled(true), lastforce(0), haptic(0), effect_id(-1) { // Close haptic if already open. if (haptic) { SDL_HapticClose(haptic); haptic = NULL; } // Do we have force feedback devices? int haptic_num = SDL_NumHaptics(); if (haptic_num == 0) { info_output << "No force feedback devices found." << std::endl; return; } info_output << "Number of force feedback devices: " << haptic_num << std::endl; // Try to create haptic device. int haptic_id = 0; haptic = SDL_HapticOpen(haptic_id); if (!haptic) { error_output << "Failed to initialize force feedback device: " << SDL_GetError(); return; } // Check for constant force support. unsigned int haptic_query = SDL_HapticQuery(haptic); if (!(haptic_query & SDL_HAPTIC_CONSTANT)) { error_output << "Constant force feedback not supported: " << SDL_GetError(); return; } // Create the effect. memset(&effect, 0, sizeof(SDL_HapticEffect) ); // 0 is safe default effect.type = SDL_HAPTIC_CONSTANT; effect.constant.direction.type = SDL_HAPTIC_CARTESIAN; // Using cartesian direction encoding. effect.constant.direction.dir[0] = 1; // X position effect.constant.direction.dir[1] = 0; // Y position effect.constant.length = 0xffff; effect.constant.delay = 0; effect.constant.button = 0; effect.constant.interval = 0; effect.constant.level = 0; effect.constant.attack_length = 0; effect.constant.attack_level = 0; effect.constant.fade_length = 0; effect.constant.fade_level = 0; // Upload the effect. effect_id = SDL_HapticNewEffect(haptic, &effect); if (effect_id == -1) { error_output << "Failed to initialize force feedback effect: " << SDL_GetError(); return; } info_output << "Force feedback enabled." << std::endl; }
bool SDL2FFBDevice::queryDeviceCapabilities() { unsigned int caps; bool hasPeriodic = false; bool hasCondition = false; caps = SDL_HapticQuery(c_haptic); if (caps == 0) return false; if (caps & SDL_HAPTIC_CONSTANT) m_availableEffects.push_back(FFBEffectTypes::CONSTANT); if (caps & SDL_HAPTIC_SINE) { hasPeriodic = true; m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SINE); } if (caps & SDL_HAPTIC_TRIANGLE) { hasPeriodic = true; m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::TRIANGLE); } if (caps & SDL_HAPTIC_SAWTOOTHUP) { hasPeriodic = true; m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_UP); } if (caps & SDL_HAPTIC_SAWTOOTHDOWN) { hasPeriodic = true; m_availablePeriodicWaveforms.push_back(PeriodicWaveforms::SAW_DOWN); } if (hasPeriodic) m_availableEffects.push_back(FFBEffectTypes::PERIODIC); if (caps & SDL_HAPTIC_RAMP) m_availableEffects.push_back(FFBEffectTypes::RAMP); if (caps & SDL_HAPTIC_SPRING) { hasCondition = true; m_availableConditionSubtypes.push_back(ConditionSubtypes::SPRING); } if (caps & SDL_HAPTIC_DAMPER) { hasCondition = true; m_availableConditionSubtypes.push_back(ConditionSubtypes::DAMPER); } if (caps & SDL_HAPTIC_INERTIA) { hasCondition = true; m_availableConditionSubtypes.push_back(ConditionSubtypes::INERTIA); } if (caps & SDL_HAPTIC_FRICTION) { hasCondition = true; m_availableConditionSubtypes.push_back(ConditionSubtypes::FRICTION); } if (hasCondition) m_availableEffects.push_back(FFBEffectTypes::CONDITION); if (caps & SDL_HAPTIC_GAIN) m_adjustableGain = true; if (caps & SDL_HAPTIC_AUTOCENTER) m_adjustableAC = true; return true; }
// First time (lazy) initialization. void gfctrlJoyInit(void) { #ifndef SDL_JOYSTICK gfctrlJoyPresent = GFCTRL_JOY_NONE; for (int index = 0; index < GFCTRL_JOY_NUMBER; index++) { if (!Joysticks[index]) { Joysticks[index] = new jsJoystick(index); } // Don't configure the joystick if it doesn't work if (Joysticks[index]->notWorking()) { delete Joysticks[index]; Joysticks[index] = 0; } else { gfctrlJoyPresent = GFCTRL_JOY_PRESENT; } } #else #if SDL_MAJOR_VERSION >= 2 memset(&cfx, 0, sizeof(cfx)); if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) < 0) { #else if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { #endif GfLogError("Couldn't initialize SDL: %s\n", SDL_GetError()); gfctrlJoyPresent = GFCTRL_JOY_UNTESTED; return; } #if SDL_MAJOR_VERSION >= 2 // Ignore the joystick events, we will poll directly as it is faster SDL_JoystickEventState(SDL_IGNORE); #endif gfctrlJoyPresent = SDL_NumJoysticks(); if (gfctrlJoyPresent > GFCTRL_JOY_NUMBER) gfctrlJoyPresent = GFCTRL_JOY_NUMBER; for (int index = 0; index < gfctrlJoyPresent; index++) { if (!Joysticks[index]) { Joysticks[index] = SDL_JoystickOpen(index); } // Don't configure the joystick if it doesn't work if (Joysticks[index] == NULL) { GfLogError("Couldn't open joystick %d: %s\n", index, SDL_GetError()); #if SDL_MAJOR_VERSION >= 2 } else { cfx_timeout[index] = 0; rfx_timeout[index] = 0; // Find which Haptic device relates to this joystick Haptics[index] = SDL_HapticOpenFromJoystick(Joysticks[index]); if (!Haptics[index]) { GfLogInfo("Joystick %d does not support haptic\n", index); break; #if 0 } else { // add an CF effect on startup gfctrlJoyConstantForce(index, 50000, 9000); #endif } // Check for Rumble capability if (SDL_HapticRumbleSupported(Haptics[index]) == SDL_TRUE) { if (SDL_HapticRumbleInit(Haptics[index]) != 0) GfLogError("Couldn't init rumble on joystick %d: %s\n", index, SDL_GetError()); #if 0 else gfctrlJoyRumble(index, 0.5); #endif } #endif } } #endif } #if SDL_JOYSTICK void gfctrlJoyConstantForce(int index, unsigned int level, int dir) { #if SDL_MAJOR_VERSION >= 2 if (!Haptics[index]) return; if ((SDL_HapticQuery(Haptics[index]) & SDL_HAPTIC_CONSTANT) == 0) return; cfx[index].type = SDL_HAPTIC_CONSTANT; cfx[index].constant.direction.type = SDL_HAPTIC_POLAR; cfx[index].constant.direction.dir[0] = dir; cfx[index].constant.length = 1000; cfx[index].constant.level = level; cfx[index].constant.attack_length = 0; cfx[index].constant.fade_length = 1000; #if __WIN32__ if (SDL_HapticGetEffectStatus(Haptics[index], id[index]) == SDL_TRUE) #else // Linux SDL doesn't support checking status at the moment :-( if (cfx_timeout[index] > SDL_GetTicks()) #endif SDL_HapticUpdateEffect(Haptics[index], id[index], &cfx[index]); else { SDL_HapticDestroyEffect(Haptics[index], id[index]); id[index] = SDL_HapticNewEffect(Haptics[index], &cfx[index]); SDL_HapticRunEffect(Haptics[index], id[index], 1); } cfx_timeout[index] = SDL_GetTicks() + cfx[index].constant.length; #endif } void gfctrlJoyRumble(int index, float level) { #if SDL_MAJOR_VERSION >= 2 if (!Haptics[index]) return; if (SDL_HapticRumbleSupported(Haptics[index]) != SDL_TRUE) return; // we have to stop the rumble before updating if (rfx_timeout[index] > SDL_GetTicks()) { if (SDL_HapticRumbleStop(Haptics[index]) != 0) GfLogError("Failed to stop rumble: %s\n", SDL_GetError() ); } if (SDL_HapticRumblePlay(Haptics[index], level, 100) != 0) GfLogError("Failed to play rumble: %s\n", SDL_GetError() ); rfx_timeout[index] = SDL_GetTicks() + 100; #endif } #endif // Shutdown time. void gfctrlJoyShutdown(void) { if (gfctrlJoyPresent != GFCTRL_JOY_UNTESTED) #ifndef SDL_JOYSTICK for (int index = 0; index < GFCTRL_JOY_NUMBER; index++) delete Joysticks[index]; #else for (int index = 0; index < gfctrlJoyPresent; index++) { SDL_JoystickClose(Joysticks[index]); Joysticks[index] = NULL; #if SDL_MAJOR_VERSION >= 2 if (Haptics[index]) { SDL_HapticClose(Haptics[index]); Haptics[index] = NULL; } #endif } #endif gfctrlJoyPresent = GFCTRL_JOY_UNTESTED; } /** Create the joystick control @ingroup ctrl @return pointer on a tCtrlJoyInfo structure <br>0 .. if no joystick present @note call GfctrlJoyRelease to free the tCtrlJoyInfo structure @see GfctrlJoyRelease @see tCtrlJoyInfo */ tCtrlJoyInfo * GfctrlJoyCreate(void) { if (gfctrlJoyPresent == GFCTRL_JOY_UNTESTED) gfctrlJoyInit(); tCtrlJoyInfo* joyInfo = (tCtrlJoyInfo *)calloc(1, sizeof(tCtrlJoyInfo)); #if SDL_JOYSTICK joyInfoCopy = joyInfo; #endif return joyInfo; } /** Release the tCtrlJoyInfo structure @ingroup ctrl @param joyInfo joystick structure @return none */ void GfctrlJoyRelease(tCtrlJoyInfo *joyInfo) { freez(joyInfo); }
/** * @brief The entry point of this force feedback demo. * @param[in] argc Number of arguments. * @param[in] argv Array of argc arguments. */ int main(int argc, char **argv) { int i; char *name; int index; SDL_HapticEffect efx[9]; int id[9]; int nefx; unsigned int supported; /* Enable standard application logging */ SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); name = NULL; index = -1; if (argc > 1) { name = argv[1]; if ((strcmp(name, "--help") == 0) || (strcmp(name, "-h") == 0)) { SDL_Log("USAGE: %s [device]\n" "If device is a two-digit number it'll use it as an index, otherwise\n" "it'll use it as if it were part of the device's name.\n", argv[0]); return 0; } i = strlen(name); if ((i < 3) && isdigit(name[0]) && ((i == 1) || isdigit(name[1]))) { index = atoi(name); name = NULL; } } /* Initialize the force feedbackness */ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC); SDL_Log("%d Haptic devices detected.\n", SDL_NumHaptics()); if (SDL_NumHaptics() > 0) { /* We'll just use index or the first force feedback device found */ if (name == NULL) { i = (index != -1) ? index : 0; } /* Try to find matching device */ else { for (i = 0; i < SDL_NumHaptics(); i++) { if (strstr(SDL_HapticName(i), name) != NULL) break; } if (i >= SDL_NumHaptics()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to find device matching '%s', aborting.\n", name); return 1; } } haptic = SDL_HapticOpen(i); if (haptic == NULL) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create the haptic device: %s\n", SDL_GetError()); return 1; } SDL_Log("Device: %s\n", SDL_HapticName(i)); HapticPrintSupported(haptic); } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No Haptic devices found!\n"); return 1; } /* We only want force feedback errors. */ SDL_ClearError(); /* Create effects. */ memset(&efx, 0, sizeof(efx)); nefx = 0; supported = SDL_HapticQuery(haptic); SDL_Log("\nUploading effects\n"); /* First we'll try a SINE effect. */ if (supported & SDL_HAPTIC_SINE) { SDL_Log(" effect %d: Sine Wave\n", nefx); efx[nefx].type = SDL_HAPTIC_SINE; efx[nefx].periodic.period = 1000; efx[nefx].periodic.magnitude = -0x2000; /* Negative magnitude and ... */ efx[nefx].periodic.phase = 18000; /* ... 180 degrees phase shift => cancel eachother */ efx[nefx].periodic.length = 5000; efx[nefx].periodic.attack_length = 1000; efx[nefx].periodic.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Now we'll try a SAWTOOTHUP */ if (supported & SDL_HAPTIC_SAWTOOTHUP) { SDL_Log(" effect %d: Sawtooth Up\n", nefx); efx[nefx].type = SDL_HAPTIC_SAWTOOTHUP; efx[nefx].periodic.period = 500; efx[nefx].periodic.magnitude = 0x5000; efx[nefx].periodic.length = 5000; efx[nefx].periodic.attack_length = 1000; efx[nefx].periodic.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Now the classical constant effect. */ if (supported & SDL_HAPTIC_CONSTANT) { SDL_Log(" effect %d: Constant Force\n", nefx); efx[nefx].type = SDL_HAPTIC_CONSTANT; efx[nefx].constant.direction.type = SDL_HAPTIC_POLAR; efx[nefx].constant.direction.dir[0] = 20000; /* Force comes from the south-west. */ efx[nefx].constant.length = 5000; efx[nefx].constant.level = 0x6000; efx[nefx].constant.attack_length = 1000; efx[nefx].constant.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The cute spring effect. */ if (supported & SDL_HAPTIC_SPRING) { SDL_Log(" effect %d: Condition Spring\n", nefx); efx[nefx].type = SDL_HAPTIC_SPRING; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0xFFFF; efx[nefx].condition.left_sat[i] = 0xFFFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; efx[nefx].condition.center[i] = 0x1000; /* Displace the center for it to move. */ } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The interesting damper effect. */ if (supported & SDL_HAPTIC_DAMPER) { SDL_Log(" effect %d: Condition Damper\n", nefx); efx[nefx].type = SDL_HAPTIC_DAMPER; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0xFFFF; efx[nefx].condition.left_sat[i] = 0xFFFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The pretty awesome inertia effect. */ if (supported & SDL_HAPTIC_INERTIA) { SDL_Log(" effect %d: Condition Inertia\n", nefx); efx[nefx].type = SDL_HAPTIC_INERTIA; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0xFFFF; efx[nefx].condition.left_sat[i] = 0xFFFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; efx[nefx].condition.deadband[i] = 0x1000; /* 1/16th of axis-range around the center is 'dead'. */ } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* The hot friction effect. */ if (supported & SDL_HAPTIC_FRICTION) { SDL_Log(" effect %d: Condition Friction\n", nefx); efx[nefx].type = SDL_HAPTIC_FRICTION; efx[nefx].condition.length = 5000; for (i = 0; i < SDL_HapticNumAxes(haptic); i++) { efx[nefx].condition.right_sat[i] = 0xFFFF; efx[nefx].condition.left_sat[i] = 0xFFFF; efx[nefx].condition.right_coeff[i] = 0x2000; efx[nefx].condition.left_coeff[i] = 0x2000; } id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Now we'll try a ramp effect */ if (supported & SDL_HAPTIC_RAMP) { SDL_Log(" effect %d: Ramp\n", nefx); efx[nefx].type = SDL_HAPTIC_RAMP; efx[nefx].ramp.direction.type = SDL_HAPTIC_CARTESIAN; efx[nefx].ramp.direction.dir[0] = 1; /* Force comes from */ efx[nefx].ramp.direction.dir[1] = -1; /* the north-east. */ efx[nefx].ramp.length = 5000; efx[nefx].ramp.start = 0x4000; efx[nefx].ramp.end = -0x4000; efx[nefx].ramp.attack_length = 1000; efx[nefx].ramp.fade_length = 1000; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } /* Finally we'll try a left/right effect. */ if (supported & SDL_HAPTIC_LEFTRIGHT) { SDL_Log(" effect %d: Left/Right\n", nefx); efx[nefx].type = SDL_HAPTIC_LEFTRIGHT; efx[nefx].leftright.length = 5000; efx[nefx].leftright.large_magnitude = 0x3000; efx[nefx].leftright.small_magnitude = 0xFFFF; id[nefx] = SDL_HapticNewEffect(haptic, &efx[nefx]); if (id[nefx] < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "UPLOADING EFFECT ERROR: %s\n", SDL_GetError()); abort_execution(); } nefx++; } SDL_Log ("\nNow playing effects for 5 seconds each with 1 second delay between\n"); for (i = 0; i < nefx; i++) { SDL_Log(" Playing effect %d\n", i); SDL_HapticRunEffect(haptic, id[i], 1); SDL_Delay(6000); /* Effects only have length 5000 */ } /* Quit */ if (haptic != NULL) SDL_HapticClose(haptic); SDL_Quit(); return 0; }