1067 lines
38 KiB
C++
1067 lines
38 KiB
C++
//================ Copyright (c) Valve Corporation. All Rights Reserved. ===========================
|
|
//
|
|
//
|
|
//
|
|
//==================================================================================================
|
|
|
|
#include "movecontroller_ps3.h"
|
|
#include <cell/camera.h> // PS3 eye camera
|
|
#include <pthread.h>
|
|
|
|
#include <vjobs/root.h>
|
|
#include <tier1/convar.h>
|
|
#include <tier0/dbg.h>
|
|
|
|
// NOTE: This has to be the last file included!
|
|
#include <tier0/memdbgon.h>
|
|
#include "input_device.h"
|
|
#include "inputsystem.h"
|
|
|
|
#include <vectormath/cpp/vectormath_aos.h>
|
|
|
|
using namespace Vectormath::Aos;
|
|
|
|
#define MC_MAX_NUM_SAMPLES 30
|
|
|
|
enum FilterModeType { NONE, LOW_PASS, MOVING_AVG, EXP_MOVING_AVG, GYRO_ATTEN_LOW_PASS, GYRO_PREDICTOR_CORRECTOR, DISTANCE_FALLOFF };
|
|
|
|
// time in microseconds between gyro samples (gyro samples are updated at approximately 180 Hz = ~5556 usec)
|
|
#define GYRO_SAMPLE_SPACING 5625
|
|
|
|
// [dkorus] lifted this define from the samples/sdk/gem/sharpshooter demo
|
|
#define SHARP_SHOOTER_DEVICE_ID 0x8081
|
|
|
|
extern IVJobs * g_pVJobs;
|
|
CMoveController g_moveController;
|
|
CMoveController* g_pMoveController = &g_moveController;
|
|
|
|
ConVar ps3_move_roll_trigger( "ps3_move_roll_trigger", "45.0", FCVAR_ARCHIVE, "amount of roll to trigger R/L shoulder button press in degrees" );
|
|
|
|
ConVar ps3_move_enabled( "ps3_move_enabled", "1", FCVAR_DEVELOPMENTONLY, "0 => Disabled, 1 => Enabled." );
|
|
ConVar ps3_move_filter_method( "ps3_move_filter_method", "6", FCVAR_DEVELOPMENTONLY, "Which filter method to use. 0 to 6 (none, low_pass, moving_average, gyro_atten_low_pass, gyro_corrector, distance_falloff)." );
|
|
ConVar ps3_move_cursor_sampling( "ps3_move_cursor_sampling", "0.5", FCVAR_DEVELOPMENTONLY, "0 to 1. Larger numbers = more samples, smoother, more lag.", true, 0.0f, true, 1.0f );
|
|
ConVar mc_cursor_sensitivity( "mc_cursor_sensitivity", "0.5", FCVAR_ARCHIVE, "0.0 to 1.0", true, 0.0f, true, 1.0f );
|
|
ConVar mc_min_cursor_sensitivity( "mc_min_cursor_sensitivity", "0.25", FCVAR_DEVELOPMENTONLY, "0.0 to 1.0", true, 0.0f, true, 1.0f );
|
|
ConVar mc_max_cursor_sensitivity( "mc_max_cursor_sensitivity", "1.25", FCVAR_DEVELOPMENTONLY, "0.0 to 4.0", true, 0.0f, true, 4.0f );
|
|
|
|
static sys_ppu_thread_t s_gemThread;
|
|
static bool s_bGemThreadExit = false;
|
|
static sys_memory_container_t s_camContainer;
|
|
|
|
struct { float left, right, bottom, top; } static s_tracker_plane_extents[MAX_PS3_MOVE_CONTROLLERS]; /**< @brief tracking region extents for each controller */
|
|
static int s_nCalibrationStep = 0;
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// GemThreadState contains motion controller state
|
|
// written to by Gem update thread
|
|
// read by main thread
|
|
//--------------------------------------------------------------------------------------------------
|
|
struct GemThreadState
|
|
{
|
|
CellGemInfo m_CellGemInfo;
|
|
CellGemState m_aCellGemState[MAX_PS3_MOVE_CONTROLLERS];
|
|
int32 m_aStatus[MAX_PS3_MOVE_CONTROLLERS]; // associated getState return values
|
|
uint64 m_aStatusFlags[MAX_PS3_MOVE_CONTROLLERS];
|
|
int32 m_camStatus;
|
|
Vector m_pos[MAX_PS3_MOVE_CONTROLLERS];
|
|
Quaternion m_quat[MAX_PS3_MOVE_CONTROLLERS];
|
|
float m_posX[MAX_PS3_MOVE_CONTROLLERS];
|
|
float m_posY[MAX_PS3_MOVE_CONTROLLERS];
|
|
};
|
|
static GemThreadState s_gemThreadState;
|
|
|
|
static void UpdateGemThread(uint64 args);
|
|
|
|
|
|
static float getPitch( vec_float4 q )
|
|
{
|
|
Vectormath::Aos::Quat quat( q );
|
|
Vectormath::Aos::Vector3 home_dir( 0, 0, -1 );
|
|
Vectormath::Aos::Vector3 new_dir = Vectormath::Aos::rotate( quat, home_dir );
|
|
float x = new_dir[0], y = new_dir[1], z = new_dir[2];
|
|
return atan2f( y, sqrtf( x * x + z * z) );
|
|
}
|
|
|
|
// Yaw - Rotation of the controller pointing axis, if it were projected into the x-z plane
|
|
// Same as telescope azimuth angle.
|
|
static float getYaw( vec_float4 q )
|
|
{
|
|
Vectormath::Aos::Quat quat( q );
|
|
Vectormath::Aos::Vector3 home_dir( 0, 0, -1 );
|
|
Vectormath::Aos::Vector3 new_dir = Vectormath::Aos::rotate( quat, home_dir );
|
|
new_dir[1] = 0; // portion on x-z plane
|
|
return atan2f( -new_dir[0], -new_dir[2] );
|
|
}
|
|
|
|
//Performs a raw ray cast into the tracker plane extents down the -Z axis of the controller
|
|
static void MoveKitPointerIntersectWithTrackerPlane(VmathVector3 position, VmathQuat orientation, float* pointerX, float* pointerY)
|
|
{
|
|
// given the gem position and orientation, form a ray from the ball down the gem -Z axis (direction of pointing)
|
|
VmathVector3 rayStart;
|
|
VmathVector3 rayDir;
|
|
|
|
vmathV3Copy(&rayStart, &position);
|
|
vmathV3MakeFromElems(&rayDir, 0.0f, 0.0f, -1.0f);
|
|
|
|
// rotate the direction into the orientation of the controller
|
|
vmathQRotate(&rayDir, &orientation, &rayDir);
|
|
|
|
// intersect the ray with the display plane (at z=0):
|
|
// isect.z = rayStart.z + rayDir.z * t, so t = (0 - rayStart.z) / rayDir.z
|
|
float t = -vmathV3GetZ(&rayStart) / vmathV3GetZ(&rayDir);
|
|
|
|
*pointerX = vmathV3GetX(&rayStart) + vmathV3GetX(&rayDir)*t;
|
|
*pointerY = vmathV3GetY(&rayStart) + vmathV3GetY(&rayDir)*t;
|
|
}
|
|
|
|
/** @brief Calculates position of cursor in screen space.
|
|
*
|
|
* Takes the most recent position and orientation of the given controller and converts the pointed at location in the display plane to screen space.
|
|
*
|
|
* @param[in] gem_num index of controller we're calculating for
|
|
* @param[in] position 3D position of controller from most recently queried state
|
|
* @param[in] orientation orientation of controller from most recently queried state
|
|
* @param[out] pointerX normalized but unclamped X position of pointer in screen space
|
|
* @param[out] pointerY normalized but unclamped Y position of pointer in screen space
|
|
*
|
|
* @post pointerX and pointerY contain normalized (but unclamped) screen positions based on the specified state
|
|
*
|
|
* @see MoveKitPointerCalcPointerNormalizedRawFromState
|
|
*/
|
|
void MoveKitPointerCalcPointerNormalizedRaw(int gem_num, VmathVector3 position, VmathQuat orientation, float* pointerX, float* pointerY)
|
|
{
|
|
MoveKitPointerIntersectWithTrackerPlane(position, orientation, pointerX, pointerY);
|
|
*pointerX = -1.0f + 2.0f*((*pointerX-s_tracker_plane_extents[gem_num].left)/(s_tracker_plane_extents[gem_num].right-s_tracker_plane_extents[gem_num].left));
|
|
*pointerY = -1.0f + 2.0f*((*pointerY-s_tracker_plane_extents[gem_num].bottom)/(s_tracker_plane_extents[gem_num].top-s_tracker_plane_extents[gem_num].bottom));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// ReadCamera
|
|
// Init camera if required and read image data for latest frame using cellCameraReadEx
|
|
// Returns the cellCameraReadEx return code
|
|
//--------------------------------------------------------------------------------------------------
|
|
static int32 ReadCamera(CellCameraReadEx *pCamReadEx)
|
|
{
|
|
pCamReadEx->version=CELL_CAMERA_READ_VER;
|
|
int32 camStatus = cellCameraReadEx(0,pCamReadEx);
|
|
|
|
if (camStatus==CELL_CAMERA_ERROR_NOT_OPEN)
|
|
{
|
|
CellCameraType type;
|
|
cellCameraGetType(0, &type);
|
|
if (type == CELL_CAMERA_EYETOY2)
|
|
{
|
|
sys_memory_container_create(&s_camContainer, 0x100000);
|
|
CellCameraInfoEx camera_info;
|
|
camera_info.format=CELL_CAMERA_RAW8;
|
|
camera_info.framerate=60;
|
|
camera_info.resolution=CELL_CAMERA_VGA;
|
|
camera_info.container=s_camContainer;
|
|
camera_info.info_ver=CELL_CAMERA_INFO_VER_101;
|
|
cellCameraOpenEx(0, &camera_info);
|
|
camStatus = cellCameraReadEx(0,pCamReadEx);
|
|
}
|
|
}
|
|
|
|
if (camStatus==CELL_CAMERA_ERROR_NOT_STARTED)
|
|
{
|
|
cellCameraReset(0);
|
|
cellCameraStart(0);
|
|
|
|
// Prepare camera using exposure and image quality settings
|
|
// for best tracking performance (see PS3 docs)
|
|
cellGemPrepareCamera(CELL_GEM_MIN_CAMERA_EXPOSURE,0);
|
|
|
|
camStatus = cellCameraReadEx(0,pCamReadEx);
|
|
}
|
|
|
|
return camStatus;
|
|
}
|
|
|
|
static void StartUpdateGemThread(GemThreadState *pState)
|
|
{
|
|
s_bGemThreadExit = false;
|
|
|
|
sys_ppu_thread_create( &s_gemThread, UpdateGemThread, (uint64)pState,
|
|
1001, // PRIORITY 0-3071, 0 highest
|
|
16*1024, // Stack size, multiple of 4 KB
|
|
SYS_PPU_THREAD_CREATE_JOINABLE, "UpdateMoveController" );
|
|
|
|
}
|
|
|
|
static inline void FlashSphere(const int controllerNum)
|
|
{
|
|
const int speed = (1<<18)-1;
|
|
float t = M_PI * (float)((int)clock()%speed) / (speed-1); // t in range [0,PI]
|
|
float ft = exp(-t*t); // f(t) = 1 / e^(t^2) in range [1,0)
|
|
cellGemForceRGB(controllerNum,ft,ft,ft);
|
|
}
|
|
|
|
static bool CheckForSharpshooterConnected( int controllerId )
|
|
{
|
|
unsigned int ext_id;
|
|
unsigned char ext_info[CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE];
|
|
cellGemReadExternalPortDeviceInfo( controllerId, &ext_id, ext_info);
|
|
bool sharpshooterConnected = g_pInputSystem->IsInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER );
|
|
|
|
if (ext_id == SHARP_SHOOTER_DEVICE_ID &&
|
|
!sharpshooterConnected )
|
|
{
|
|
Msg("Sharp Shooter is connected and ready!\n");
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER, true );
|
|
return true;
|
|
}
|
|
else if( !sharpshooterConnected )
|
|
{
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER, false );
|
|
Msg("Sharp Shooter is disconnected2!\n");
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CheckForMoveConnected( int controllerId )
|
|
{
|
|
CellGemState gemState;
|
|
int32 gemStatus = cellGemGetState(controllerId, CELL_GEM_STATE_FLAG_LATEST_IMAGE_TIME, 0, &gemState);
|
|
if(s_gemThreadState.m_CellGemInfo.status[controllerId]==CELL_GEM_STATUS_READY)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/** @brief Calculates position of cursor in screen space.
|
|
*
|
|
* Takes the state of the given controller and converts the pointed at location in the display plane to screen space.
|
|
*
|
|
* @param[in] gem_num index of controller corresponding to specified state
|
|
* @param[in] state the most recently queried gem state for the specified controller
|
|
* @param[out] pointerX normalized but unclamped X position of pointer in screen space
|
|
* @param[out] pointerY normalized but unclamped Y position of pointer in screen space
|
|
*
|
|
* @post pointerX and pointerY contain normalized (but unclamped) screen positions based on the specified state
|
|
*
|
|
* @see MoveKitPointerCalcPointerNormalizedRaw
|
|
*/
|
|
void MoveKitPointerCalcPointerNormalizedRawFromState(int gem_num, CellGemState* state, float* pointerX, float* pointerY)
|
|
{
|
|
VmathQuat orientation;
|
|
vmathQMakeFrom128(&orientation, state->quat);
|
|
VmathVector3 position;
|
|
vmathV3MakeFrom128(&position, state->pos);
|
|
return MoveKitPointerCalcPointerNormalizedRaw(gem_num, position, orientation, pointerX, pointerY);
|
|
}
|
|
|
|
/** @brief Implementation of simple low-pass filter
|
|
*
|
|
* Blend the current pointer position with previous samples.
|
|
* The blend weight controls the amount of lag and smoothness.
|
|
* Higher blend weight (alpha) favors the new position while
|
|
* lower favors the older (smoother).
|
|
*
|
|
* @note
|
|
* The canonical "sluggish feeling" filter.
|
|
*
|
|
* @param[out] filteredX X component of filtered pointer position
|
|
* @param[out] filteredY Y component of filtered pointer position
|
|
* @param screenWidth currently unused
|
|
* @param screenHeight currently unused
|
|
* @param[in] whichGem index of controller we're filtering [0-3]
|
|
* @param[in] numSamples number of consecutive samples to include in filtering calculation, including current sample
|
|
* @param[in] alpha blend weight [0.0-1.0]
|
|
*
|
|
* @returns return code from last \a cellGemGetState call
|
|
*/
|
|
int32_t calcFilteredPointerPos_LowPass(float &filteredX, float &filteredY,
|
|
const int whichGem, const int numSamples, const float alpha)
|
|
{
|
|
assert(numSamples<=MC_MAX_NUM_SAMPLES);
|
|
|
|
// grab the current gem state time stamp
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_CURRENT_TIME,CELL_GEM_LATENCY_OFFSET,&gemState);
|
|
system_time_t startTimeStamp = gemState.timestamp;
|
|
|
|
// blend numSamples consecutive samples
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*(numSamples-1),&gemState);
|
|
assert(retVal!=CELL_GEM_TIME_OUT_OF_RANGE);
|
|
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &filteredX, &filteredY);
|
|
//laserPointerCalcPos(filteredX,filteredY,gemState,screenWidth,screenHeight);
|
|
for (int i=numSamples-2; i>=0; i--)
|
|
{
|
|
float x, y;
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*i,&gemState);
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &x, &y);
|
|
//laserPointerCalcPos(x,y,gemState,screenWidth,screenHeight);
|
|
|
|
filteredX = (x * alpha) + (filteredX * (1-alpha));
|
|
filteredY = (y * alpha) + (filteredY * (1-alpha));
|
|
}
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
/** @brief Implementation of moving average filter
|
|
*
|
|
* Collect a history of N samples and average them.
|
|
*
|
|
* @note
|
|
* Increasing the number of samples increases the smoothness as well as the latency.
|
|
*
|
|
* @note
|
|
* More responsive than the simple low pass and nearly as stable.
|
|
*
|
|
* @note
|
|
* Optimal at removing "white noise" while preserving sharp transitions. http://www.dspguide.com/ch15/2.htm
|
|
*
|
|
* @param[out] filteredX X component of filtered pointer position
|
|
* @param[out] filteredY Y component of filtered pointer position
|
|
* @param screenWidth currently unused
|
|
* @param screenHeight currently unused
|
|
* @param[in] whichGem index of controller we're filtering [0-3]
|
|
* @param[in] numSamples number of consecutive samples to include in filtering calculation, including current sample
|
|
*
|
|
* @returns return code from last \a cellGemGetState call
|
|
*/
|
|
int32_t calcFilteredPointerPos_MovingAvg(float &filteredX, float &filteredY, const int whichGem, const int numSamples)
|
|
{
|
|
assert(numSamples<=MC_MAX_NUM_SAMPLES);
|
|
|
|
// grab the current gem state time stamp
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_CURRENT_TIME,CELL_GEM_LATENCY_OFFSET,&gemState);
|
|
system_time_t startTimeStamp = gemState.timestamp;
|
|
|
|
// average numSamples consecutive samples
|
|
filteredX=0.0f;
|
|
filteredY=0.0f;
|
|
int numActualSamples = 0;
|
|
for (int i=0; i<numSamples; i++)
|
|
{
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*i,&gemState);
|
|
if ( retVal == 0 )
|
|
{
|
|
float x=0.0f, y=0.0f;
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &x, &y);
|
|
filteredX +=x;
|
|
filteredY +=y;
|
|
numActualSamples ++;
|
|
}
|
|
else
|
|
{
|
|
assert(retVal!=CELL_GEM_TIME_OUT_OF_RANGE);
|
|
}
|
|
}
|
|
if ( numActualSamples > 1 )
|
|
{
|
|
filteredX /= (float)numActualSamples;
|
|
filteredY /= (float)numActualSamples;
|
|
}
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
/** @brief Implementation of exponential moving average filter
|
|
*
|
|
* Same as the moving average, but weights newer points in the average higher
|
|
* than the older ones. The weights drop off exponentially, so for 4 samples:
|
|
* 1, 0.5, 0.25, 0.125
|
|
*
|
|
* @note
|
|
* More responsive than the moving average, but also less smooth
|
|
*
|
|
* @param[out] filteredX X component of filtered pointer position
|
|
* @param[out] filteredY Y component of filtered pointer position
|
|
* @param screenWidth currently unused
|
|
* @param screenHeight currently unused
|
|
* @param[in] whichGem index of controller we're filtering [0-3]
|
|
* @param[in] numSamples number of consecutive samples to include in filtering calculation, including current sample
|
|
* @param[in] expMovAvgExponent exponential drop-off for samples
|
|
*
|
|
* @returns return code from last \a cellGemGetState call
|
|
*/
|
|
int32_t calcFilteredPointerPos_ExpMovingAvg(float &filteredX, float &filteredY,
|
|
const int whichGem, const int numSamples, const float expMovAvgExponent)
|
|
{
|
|
assert(numSamples<=MC_MAX_NUM_SAMPLES);
|
|
|
|
// grab the current gem state time stamp
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_CURRENT_TIME,CELL_GEM_LATENCY_OFFSET,&gemState);
|
|
system_time_t startTimeStamp = gemState.timestamp;
|
|
|
|
// average numSamples consecutive samples
|
|
filteredX=0;
|
|
filteredY=0;
|
|
float totalWeight=0;
|
|
for (int i=0; i<numSamples; i++)
|
|
{
|
|
float x, y;
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*i,&gemState);
|
|
assert(retVal!=CELL_GEM_TIME_OUT_OF_RANGE);
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &x, &y);
|
|
//laserPointerCalcPos(x,y,gemState,screenWidth,screenHeight);
|
|
float weight = 1.f / powf(expMovAvgExponent, i);
|
|
filteredX += (x * weight);
|
|
filteredY += (y * weight);
|
|
totalWeight += weight;
|
|
}
|
|
filteredX /= totalWeight;
|
|
filteredY /= totalWeight;
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
/** @brief Implementation of gyro attentuated low pass filter
|
|
*
|
|
* Uses the magnitude of the gyro signal to guide a simple low-pass filter.
|
|
* Faster gyro motion allows the filter to be more responsive, slower
|
|
* motion smooths the signal. The intended behavior is that when the
|
|
* user is moving slowly, the response is smoother. While this introduces
|
|
* latency, its not as noticeable at slow speeds, where smoothness and
|
|
* stability are more important. During fast motions, the gyro magnitude
|
|
* is high, which allows the filter to pass the signal through "raw" so
|
|
* there is no latency or smooth. Fast motions tend to be imprecise,
|
|
* so low latency is more important than smoothness. Slow motions are
|
|
* assumed to be "precision" adjustments, which can have some latency
|
|
* at the cost of smoothing.
|
|
*
|
|
* @note
|
|
* I think this filter feels best for twitchy precise motions. If the
|
|
* user does a lot of fine corrections, or a lot of "medium speed" motions
|
|
* it will feel sluggish. But for a precise user, it acts fast when
|
|
* twitching and then stabilizes on the local area.
|
|
*
|
|
* @param[out] filteredX X component of filtered pointer position
|
|
* @param[out] filteredY Y component of filtered pointer position
|
|
* @param screenWidth currently unused
|
|
* @param screenHeight currently unused
|
|
* @param[in] whichGem index of controller we're filtering [0-3]
|
|
* @param[in] numSamples number of consecutive samples to include in filtering calculation, including current sample
|
|
* @param[in] maxGyroAngVel speed at which the blend param = 1, in radians
|
|
*
|
|
* @returns return code from last \a cellGemGetState call
|
|
*/
|
|
int32_t calcFilteredPointerPos_GyroAttenLowPass(float &filteredX, float &filteredY,
|
|
const int whichGem, const int numSamples, const float maxGyroAngVel)
|
|
{
|
|
assert(numSamples<=MC_MAX_NUM_SAMPLES);
|
|
|
|
// grab the current gem state time stamp
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_CURRENT_TIME,CELL_GEM_LATENCY_OFFSET,&gemState);
|
|
system_time_t startTimeStamp = gemState.timestamp;
|
|
|
|
// blend numSamples consecutive samples
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*(numSamples-1),&gemState);
|
|
assert(retVal!=CELL_GEM_TIME_OUT_OF_RANGE);
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &filteredX, &filteredY);
|
|
//laserPointerCalcPos(filteredX,filteredY,gemState,screenWidth,screenHeight);
|
|
for (int i=numSamples-2; i>=0; i--)
|
|
{
|
|
float x, y;
|
|
retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_TIMESTAMP,startTimeStamp-GYRO_SAMPLE_SPACING*i,&gemState);
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &x, &y);
|
|
//laserPointerCalcPos(x,y,gemState,screenWidth,screenHeight);
|
|
|
|
float angVel = sqrtf( gemState.angvel[0]*gemState.angvel[0] + gemState.angvel[1]*gemState.angvel[1] + gemState.angvel[2]*gemState.angvel[2] );
|
|
angVel = (angVel>maxGyroAngVel) ? maxGyroAngVel : angVel;
|
|
float alpha = angVel / maxGyroAngVel;
|
|
filteredX = (x * alpha) + (filteredX * (1-alpha));
|
|
filteredY = (y * alpha) + (filteredY * (1-alpha));
|
|
}
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
static inline double rotationAngle(const Quat& u, const Quat& v)
|
|
{
|
|
double udotv = dot(u, v);
|
|
return (fabsf(udotv)>1.0f) ? 0.0f : 2.0f*acosf(udotv);
|
|
}
|
|
|
|
/** @brief Implementation of gyro-based predictor/corrector filter
|
|
*
|
|
* This filter prevents spurious angular corrections (like from the
|
|
* magnetometer or the internal gem filtering system) that are inconsistent
|
|
* with the current gyro measurements. Corrections should always be
|
|
* proportional to the magnitude of motions read from the gyros, and when
|
|
* it is motionless, stray corrections should not occur.
|
|
*
|
|
* This check is performed as follows:
|
|
* 1) Directly integrates the angular velocity from the gyros (the gemState angvel)
|
|
* 2) Compare the gyro motion to the "correction" motion (between the current
|
|
* state and the integrated result)
|
|
* 3) limit the amount of correction proportional to the amount of actual gyro motion.
|
|
*
|
|
* @note
|
|
* This is technically not a motion filter, but rather a spurious motion check
|
|
* that could be combined with other filters.
|
|
*
|
|
* @param[out] filteredX X component of filtered pointer position
|
|
* @param[out] filteredY Y component of filtered pointer position
|
|
* @param[in] whichGem index of controller we're filtering [0-3]
|
|
*
|
|
* @returns return code from last \a cellGemGetState call
|
|
*/
|
|
int32_t calcFilteredPointerPos_GyroPredictorCorrector(float &filteredX, float &filteredY,
|
|
const int whichGem)
|
|
{
|
|
static Quat laserQuat[4] = { Quat(0,0,0,1), Quat(0,0,0,1), Quat(0,0,0,1), Quat(0,0,0,1) }; // quat integration accumulators
|
|
static system_time_t lastStateTimestamp[4] = { 0,0,0,0 };
|
|
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState(whichGem,CELL_GEM_STATE_FLAG_CURRENT_TIME,CELL_GEM_LATENCY_OFFSET,&gemState);
|
|
float dt = (gemState.timestamp - lastStateTimestamp[whichGem]) * 1e-6f; // in seconds
|
|
lastStateTimestamp[whichGem] = gemState.timestamp;
|
|
|
|
if (dt < 3.0f/60.0f) // update normally if time between last sampling is less than a few frames
|
|
{
|
|
// this crazy math integrates the world-space angular velocity (which happens to come from the gyros only), and adds it to laserQuat.
|
|
Vector3 angVel(gemState.angvel);
|
|
Quat angVelQuat(angVel, 0.0f);
|
|
Quat quatDot = (0.5f * angVelQuat) * laserQuat[whichGem];
|
|
Quat integrationResult = normalize(laserQuat[whichGem] + quatDot*dt);
|
|
|
|
Quat stateQuat(gemState.quat);
|
|
|
|
float integrationAngle = fabsf(rotationAngle(laserQuat[whichGem], integrationResult)); // magnitude of the motion-due to gyros
|
|
float stateDiffAngle = fabsf(rotationAngle(integrationResult, stateQuat)); // magnitude of the correction that would be needed to match the gyro-based result to libgem's result
|
|
|
|
// limit the amount of "correction" that occurs to 0.1 * amount of the gyro motion, so as to hide it
|
|
// the big benefit of this is that no "correction" occurs if there is no gyro motion, so the pointer
|
|
// only moves when the gyros say there is motion occurring
|
|
float angleLimit = (stateDiffAngle > 0.1f*integrationAngle)? 0.1f*integrationAngle : stateDiffAngle;
|
|
|
|
// rotate the integrationResult by the correction amount. this is the new output quat
|
|
if (stateDiffAngle > 1e-10f)
|
|
laserQuat[whichGem] = slerp(angleLimit/stateDiffAngle, integrationResult, stateQuat);
|
|
else
|
|
laserQuat[whichGem] = integrationResult;
|
|
|
|
gemState.quat = laserQuat[whichGem].get128();
|
|
}
|
|
else // if too long since last sample, set the integration accumulator quat to the current gem state quat
|
|
{
|
|
laserQuat[whichGem] = Quat(gemState.quat);
|
|
}
|
|
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &filteredX, &filteredY);
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
// Weights samples based on their distance to the latest sample.
|
|
int32_t calcFilteredPointerPos_DistanceFalloff(float &filteredX, float &filteredY, const int whichGem)
|
|
{
|
|
// The higher the sampling (more lag), the smaller we want fRangeScale to be since a smaller range will include more samples and give them higher weights.
|
|
// So we use 1.0 - ps3_move_cursor_sampling.
|
|
const float fRangeScale = (float)(0x1 << (int)( (1.0f - ps3_move_cursor_sampling.GetFloat()) * 10.0f));
|
|
float latestSampleX=0.0f, latestSampleY=0.0f;
|
|
|
|
// grab the current gem state time stamp
|
|
CellGemState gemState;
|
|
int32_t retVal = cellGemGetState( whichGem, CELL_GEM_STATE_FLAG_CURRENT_TIME, CELL_GEM_LATENCY_OFFSET, &gemState );
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(whichGem, &gemState, &latestSampleX, &latestSampleY);
|
|
system_time_t startTimeStamp = gemState.timestamp;
|
|
|
|
// average numSamples consecutive samples
|
|
filteredX=0.0f;
|
|
filteredY=0.0f;
|
|
float fTotalWeight = 0.0f;
|
|
for (int i=0; i<MC_MAX_NUM_SAMPLES; i++)
|
|
{
|
|
retVal = cellGemGetState( whichGem, CELL_GEM_STATE_FLAG_TIMESTAMP, startTimeStamp-GYRO_SAMPLE_SPACING*i, &gemState );
|
|
if ( retVal == 0 )
|
|
{
|
|
float x=0.0f, y=0.0f;
|
|
MoveKitPointerCalcPointerNormalizedRawFromState( whichGem, &gemState, &x, &y );
|
|
float dx = x - latestSampleX;
|
|
float dy = y - latestSampleY;
|
|
float weight = 1.0f - ( sqrtf(dx*dx + dy*dy) * fRangeScale );
|
|
weight = clamp(weight, 0.0f, 1.0f);
|
|
|
|
filteredX += x*weight;
|
|
filteredY += y*weight;
|
|
fTotalWeight += weight;
|
|
}
|
|
else
|
|
{
|
|
assert(retVal!=CELL_GEM_TIME_OUT_OF_RANGE);
|
|
}
|
|
}
|
|
if ( fTotalWeight > 0.0f )
|
|
{
|
|
filteredX /= (float)fTotalWeight;
|
|
filteredY /= (float)fTotalWeight;
|
|
}
|
|
|
|
return(retVal);
|
|
}
|
|
|
|
|
|
|
|
static void GetFilteredPointerPosition( int gem_num, float *cursorX, float *cursorY )
|
|
{
|
|
CellGemState gemState;
|
|
float filter_lowpass_alpha = 0.1f; //parameter for low pass filter
|
|
float filter_exp_moving_avg_exponent = 0.1f; //parameter for exponential moving average filter
|
|
float filter_max_gyro_ang_vel = 0.1f; //parameter for gyro attenuated low pass filter
|
|
int retVal = -1;
|
|
|
|
FilterModeType filter_type = (FilterModeType)ps3_move_filter_method.GetInt();
|
|
// Higher sampling means use more samples (more smooth and laggy).
|
|
int filter_numsamples = (int)( ps3_move_cursor_sampling.GetFloat() * (float)MC_MAX_NUM_SAMPLES);
|
|
if (filter_numsamples < 1)
|
|
{
|
|
filter_numsamples = 1;
|
|
}
|
|
|
|
switch (filter_type)
|
|
{
|
|
case NONE:
|
|
retVal = cellGemGetState(gem_num, CELL_GEM_STATE_FLAG_CURRENT_TIME, CELL_GEM_LATENCY_OFFSET, &gemState);
|
|
MoveKitPointerCalcPointerNormalizedRawFromState(gem_num, &gemState, cursorX, cursorY);
|
|
break;
|
|
|
|
case LOW_PASS:
|
|
retVal = calcFilteredPointerPos_LowPass(*cursorX, *cursorY, gem_num, filter_numsamples, filter_lowpass_alpha);
|
|
break;
|
|
|
|
case MOVING_AVG:
|
|
retVal = calcFilteredPointerPos_MovingAvg(*cursorX, *cursorY, gem_num, filter_numsamples);
|
|
break;
|
|
|
|
case EXP_MOVING_AVG:
|
|
retVal = calcFilteredPointerPos_ExpMovingAvg(*cursorX, *cursorY, gem_num, filter_numsamples, filter_exp_moving_avg_exponent);
|
|
break;
|
|
|
|
case GYRO_ATTEN_LOW_PASS:
|
|
retVal = calcFilteredPointerPos_GyroAttenLowPass(*cursorX, *cursorY, gem_num, filter_numsamples, filter_max_gyro_ang_vel);
|
|
break;
|
|
|
|
case GYRO_PREDICTOR_CORRECTOR:
|
|
retVal = calcFilteredPointerPos_GyroPredictorCorrector(*cursorX, *cursorY, gem_num);
|
|
break;
|
|
|
|
case DISTANCE_FALLOFF:
|
|
retVal = calcFilteredPointerPos_DistanceFalloff(*cursorX, *cursorY, gem_num);
|
|
break;
|
|
|
|
default:
|
|
*cursorX = 0.0f;
|
|
*cursorY = 0.0f;
|
|
|
|
break;
|
|
}
|
|
|
|
const float fMinCursorSensitivity = mc_min_cursor_sensitivity.GetFloat();
|
|
const float fCursorSensitivity = fMinCursorSensitivity + (mc_max_cursor_sensitivity.GetFloat() - fMinCursorSensitivity) * mc_cursor_sensitivity.GetFloat();
|
|
|
|
(*cursorX) *= fCursorSensitivity;
|
|
(*cursorY) *= fCursorSensitivity;
|
|
}
|
|
|
|
// the main gem update function (this can be called in a thread or directly)
|
|
static void GemFrameUpdate()
|
|
{
|
|
// Read move controller state if camera is ok
|
|
if (s_gemThreadState.m_camStatus == CELL_OK)
|
|
{
|
|
bool moveConnected = g_pInputSystem->IsInputDeviceConnected( INPUT_DEVICE_PLAYSTATION_MOVE );
|
|
bool sharpshooterConnected = g_pInputSystem->IsInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER );
|
|
|
|
if ( !moveConnected && !sharpshooterConnected )
|
|
{
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CONTROLLER_NOT_CONNECTED );
|
|
}
|
|
|
|
cellGemGetInfo(&s_gemThreadState.m_CellGemInfo);
|
|
for (int ii=0; ii<MAX_PS3_MOVE_CONTROLLERS; ++ii)
|
|
{
|
|
CellGemState gemState;
|
|
int32 gemStatus = cellGemGetState( ii, CELL_GEM_STATE_FLAG_CURRENT_TIME, 0, &gemState );
|
|
uint64 gemStatusFlags = 0;
|
|
cellGemGetStatusFlags( ii, &gemStatusFlags );
|
|
if(s_gemThreadState.m_CellGemInfo.status[ii]==CELL_GEM_STATUS_READY)
|
|
{
|
|
// check for sharpshooter connection:
|
|
if( !sharpshooterConnected && gemState.ext.status != 0 )
|
|
{
|
|
CheckForSharpshooterConnected( ii );
|
|
}
|
|
else if ( sharpshooterConnected && gemState.ext.status == 0 )
|
|
{
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER, false );
|
|
Msg("Sharp Shooter is disconnected!\n");
|
|
}
|
|
|
|
if( !moveConnected )
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_PLAYSTATION_MOVE, true );
|
|
|
|
if (gemStatus == CELL_GEM_HUE_NOT_SET)
|
|
{
|
|
// if the gem needs a color and the sphere calibration is complete
|
|
|
|
// pick a color (see http://en.wikipedia.org/wiki/Hue for 0-360 hue value description)
|
|
#define HUE_BLUE 200
|
|
unsigned int hues[] = {HUE_BLUE,HUE_BLUE,HUE_BLUE,HUE_BLUE};
|
|
cellGemTrackHues(hues, NULL);
|
|
}
|
|
|
|
if (gemStatus == CELL_GEM_SPHERE_NOT_CALIBRATED )
|
|
{
|
|
if ( g_pInputSystem->GetMotionControllerDeviceStatus() != INPUT_DEVICE_MC_STATE_CONTROLLER_ERROR )
|
|
{
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CONTROLLER_NOT_CALIBRATED );
|
|
}
|
|
}
|
|
|
|
if (gemStatus == CELL_GEM_SPHERE_CALIBRATING) { // several frames are needed to finish calibration
|
|
// several frames are needed to finish calibration
|
|
FlashSphere( ii );
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CONTROLLER_CALIBRATING );
|
|
}
|
|
|
|
if ( gemStatus == CELL_OK )
|
|
{
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_OK );
|
|
}
|
|
|
|
|
|
|
|
//// handle buttons ////
|
|
uint16 lastpadbuttons = s_gemThreadState.m_aCellGemState[ii].pad.digitalbuttons;
|
|
uint16 pressedbuttons = gemState.pad.digitalbuttons & ~lastpadbuttons;
|
|
|
|
if ( pressedbuttons & CELL_GEM_CTRL_MOVE &&
|
|
gemStatus != CELL_OK )
|
|
{ // Calibrates when pointing at the camera if it's not already calibrated
|
|
//DevMsg("gem calibration started...\n");
|
|
// dkorus note: we should handle failure conditions on the PS move for PSMove calibration
|
|
cellGemCalibrate(0);
|
|
}
|
|
}
|
|
else if( g_pInputSystem->IsInputDeviceConnected( INPUT_DEVICE_PLAYSTATION_MOVE ) )
|
|
{
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_PLAYSTATION_MOVE, false );
|
|
|
|
// [dkorus] without the MOVE, we certainly can't have the sharpshooter connected
|
|
g_pInputSystem->SetInputDeviceConnected( INPUT_DEVICE_SHARPSHOOTER, false );
|
|
}
|
|
|
|
if ( g_pInputSystem->GetMotionControllerDeviceStatus() == INPUT_DEVICE_MC_STATE_OK )
|
|
{
|
|
// Do not display these notices while calibrating
|
|
if ( (s_gemThreadState.m_aCellGemState[ii].tracking_flags & CELL_GEM_TRACKING_FLAG_VISIBLE) !=
|
|
( gemState.tracking_flags & CELL_GEM_TRACKING_FLAG_VISIBLE ) )
|
|
{
|
|
// if status changes, send over a message
|
|
InputEvent_t event;
|
|
memset( &event, 0, sizeof(event) );
|
|
event.m_nTick = g_pInputSystem->GetPollTick();
|
|
event.m_nType = IE_PS_Move_OutOfView;
|
|
event.m_nData = gemState.tracking_flags & CELL_GEM_TRACKING_FLAG_VISIBLE;
|
|
g_pInputSystem->PostUserEvent( event );
|
|
|
|
// handle the red light if we're using the sharpshooter or move
|
|
if ( g_pInputSystem->GetCurrentInputDevice() == INPUT_DEVICE_PLAYSTATION_MOVE ||
|
|
g_pInputSystem->GetCurrentInputDevice() == INPUT_DEVICE_SHARPSHOOTER )
|
|
{
|
|
bool redLightOn = false;
|
|
if ( !event.m_nData )
|
|
redLightOn = true;
|
|
|
|
// set m_nData to 1 (TRUE) if in view
|
|
cellCameraSetAttribute( 0, CELL_CAMERA_LED, ( int ) redLightOn, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( gemStatusFlags & CELL_GEM_FLAG_CALIBRATION_OCCURRED )
|
|
{
|
|
if ( gemStatusFlags & CELL_GEM_FLAG_CALIBRATION_FAILED_CANT_FIND_SPHERE ||
|
|
gemStatusFlags & CELL_GEM_FLAG_CALIBRATION_FAILED_MOTION_DETECTED ||
|
|
gemStatusFlags & CELL_GEM_FLAG_CALIBRATION_FAILED_BRIGHT_LIGHTING )
|
|
{
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CONTROLLER_ERROR );
|
|
}
|
|
}
|
|
|
|
s_gemThreadState.m_aStatus[ii] = gemStatus;
|
|
s_gemThreadState.m_aStatusFlags[ii] = gemStatusFlags;
|
|
s_gemThreadState.m_aCellGemState[ii] = gemState;
|
|
|
|
if ( s_nCalibrationStep == 4 )
|
|
{
|
|
// We've calibrated the cursor, so now we can filter it.
|
|
GetFilteredPointerPosition( ii, &s_gemThreadState.m_posX[ii], &s_gemThreadState.m_posY[ii] );
|
|
}
|
|
else
|
|
{
|
|
VmathVector3 position;
|
|
VmathQuat orientation;
|
|
vmathV3MakeFrom128(&position, s_gemThreadState.m_aCellGemState[ii].pos);
|
|
vmathQMakeFrom128(&orientation, s_gemThreadState.m_aCellGemState[ii].quat);
|
|
MoveKitPointerIntersectWithTrackerPlane( position, orientation, &s_gemThreadState.m_posX[ii], &s_gemThreadState.m_posY[ii] );
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CAMERA_NOT_CONNECTED );
|
|
}
|
|
|
|
}
|
|
|
|
static void UpdateGemThread(uint64 args)
|
|
{
|
|
sys_ipc_key_t ipckey = 0xabcdefab;
|
|
sys_event_queue_t eventQueue;
|
|
sys_event_queue_attribute_t evattr;
|
|
sys_event_queue_attribute_initialize(evattr);
|
|
sys_event_queue_create(&eventQueue, &evattr, ipckey, 10);
|
|
cellCameraSetNotifyEventQueue2(ipckey, SYS_EVENT_PORT_NO_NAME, CELL_CAMERA_EFLAG_FRAME_UPDATE);
|
|
|
|
while(!s_bGemThreadExit)
|
|
{
|
|
sys_event_t event;
|
|
|
|
// timeout after 4 frames
|
|
int receive_ret = sys_event_queue_receive(eventQueue, &event, 4*1000000/60);
|
|
//Assert(receive_ret == CELL_OK);
|
|
|
|
// if(receive_ret == ETIMEDOUT) {
|
|
// Msg("ERROR: UpdateGemThread timeout!\n");
|
|
// } else if(receive_ret != CELL_OK) {
|
|
// Msg("ERROR: UpdateGemThread failed\n");
|
|
// }
|
|
|
|
if (receive_ret == CELL_OK)
|
|
{
|
|
// read the camera
|
|
CellCameraReadEx camReadEx;
|
|
int32 camStatus = ReadCamera(&camReadEx);
|
|
|
|
if ( s_gemThreadState.m_camStatus != camStatus )
|
|
{
|
|
// if status changes, send over a message
|
|
InputEvent_t event;
|
|
memset( &event, 0, sizeof(event) );
|
|
event.m_nTick = g_pInputSystem->GetPollTick();
|
|
event.m_nType = IE_PS_CameraUnplugged;
|
|
event.m_nData = camStatus;
|
|
g_pInputSystem->PostUserEvent( event );
|
|
}
|
|
|
|
// analyze the image / update gem (do this regardless of the pseye status)
|
|
CellCameraInfoEx camera_info;
|
|
camera_info.buffer=0; // set to NULL just in case the camera isn't ready
|
|
cellCameraGetBufferInfoEx(0,&camera_info);
|
|
cellGemUpdateStart(camera_info.buffer, camReadEx.timestamp);
|
|
cellGemUpdateFinish();
|
|
|
|
s_gemThreadState.m_camStatus = camStatus;
|
|
}
|
|
|
|
}
|
|
|
|
cellCameraRemoveNotifyEventQueue2(ipckey);
|
|
sys_event_queue_destroy(eventQueue, 0);
|
|
|
|
sys_ppu_thread_exit(0);
|
|
}
|
|
|
|
static void EndUpdateGemThread()
|
|
{
|
|
s_bGemThreadExit = true;
|
|
uint64 threadRet;
|
|
sys_ppu_thread_join(s_gemThread, &threadRet);
|
|
}
|
|
|
|
void CMoveController::Init()
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
// Init PS Eye lib
|
|
cellSysmoduleLoadModule( CELL_SYSMODULE_CAMERA );
|
|
cellCameraInit();
|
|
|
|
// Load Move controller lib and alloc memory for it
|
|
// (but can't complete init until SPURS instance is available)
|
|
cellSysmoduleLoadModule( CELL_SYSMODULE_GEM );
|
|
m_iSizeGemMem = cellGemGetMemorySize(MAX_PS3_MOVE_CONTROLLERS);
|
|
m_pGemMem = malloc(m_iSizeGemMem);
|
|
|
|
// Move controller lib requires a SPURS instance, so register with VJobs
|
|
g_pVJobs->Register( this );
|
|
}
|
|
|
|
void CMoveController::Shutdown()
|
|
{
|
|
g_pVJobs->Unregister( this );
|
|
|
|
free(m_pGemMem);
|
|
m_pGemMem = NULL;
|
|
|
|
cellCameraEnd();
|
|
cellSysmoduleUnloadModule(CELL_SYSMODULE_CAMERA);
|
|
}
|
|
|
|
// note: rumbleVal should represent between 0 and 255 to match cellGemSetRumble
|
|
void CMoveController::Rumble( unsigned char rumbleVal )
|
|
{
|
|
for ( int ii = 0; ii < MAX_PS3_MOVE_CONTROLLERS; ++ii )
|
|
{
|
|
int32_t result = cellGemSetRumble( ii, rumbleVal );
|
|
if ( result == CELL_GEM_ERROR_INVALID_PARAMETER )
|
|
{
|
|
Warning( "CMoveController::Rumble invalid paramater for rumble \n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CMoveController::OnVjobsInit()
|
|
{
|
|
|
|
// Init move controller lib using VJobs SPURS instance
|
|
CellGemAttribute gem_attr;
|
|
|
|
cellGemAttributeInit(&gem_attr, 1, m_pGemMem, &m_pRoot->m_spurs, ( uint8_t* )&m_pRoot->m_nGemWorkloadPriority);
|
|
int res = cellGemInit(&gem_attr);
|
|
Assert(res == CELL_OK);
|
|
if (res!= CELL_OK) Msg("Error on cellGemInit %d", res);
|
|
|
|
// Start Gem update thread (must run at 60Hz for accurate tracking performance)
|
|
memset(&s_gemThreadState,0,sizeof(s_gemThreadState));
|
|
s_gemThreadState.m_camStatus=CELL_CAMERA_ERROR_NOT_OPEN;
|
|
s_gemThread = 0;
|
|
|
|
m_bEnabled = false;
|
|
if(ps3_move_enabled.GetBool())
|
|
{
|
|
StartUpdateGemThread(&s_gemThreadState);
|
|
m_bEnabled = true;
|
|
}
|
|
}
|
|
|
|
void CMoveController::OnVjobsShutdown()
|
|
{
|
|
EndUpdateGemThread();
|
|
m_bEnabled = false;
|
|
|
|
// End move controller lib
|
|
cellGemEnd();
|
|
cellSysmoduleUnloadModule(CELL_SYSMODULE_GEM);
|
|
}
|
|
|
|
void CMoveController::ReadState( MoveControllerState* pState )
|
|
{
|
|
GemFrameUpdate();
|
|
|
|
pState->m_CellGemInfo = s_gemThreadState.m_CellGemInfo;
|
|
pState->m_camStatus = s_gemThreadState.m_camStatus;
|
|
for(int ii=0; ii< MAX_PS3_MOVE_CONTROLLERS; ++ii)
|
|
{
|
|
pState->m_aCellGemState[ii] = s_gemThreadState.m_aCellGemState[ii];
|
|
pState->m_aStatus[ii] = s_gemThreadState.m_aStatus[ii];
|
|
pState->m_aStatusFlags[ii] = s_gemThreadState.m_aStatusFlags[ii];
|
|
pState->m_pos[ii] = s_gemThreadState.m_pos[ii];
|
|
pState->m_quat[ii] = s_gemThreadState.m_quat[ii];
|
|
pState->m_posX[ii] = s_gemThreadState.m_posX[ii];
|
|
pState->m_posY[ii] = s_gemThreadState.m_posY[ii];
|
|
}
|
|
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Disable (for debugging)
|
|
//--------------------------------------------------------------------------------------------------
|
|
void CMoveController::Disable()
|
|
{
|
|
if(m_bEnabled)
|
|
{
|
|
EndUpdateGemThread();
|
|
m_bEnabled = false;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Enable (for debugging)
|
|
//--------------------------------------------------------------------------------------------------
|
|
void CMoveController::Enable()
|
|
{
|
|
if(!m_bEnabled)
|
|
{
|
|
StartUpdateGemThread(&s_gemThreadState);
|
|
m_bEnabled = true;
|
|
}
|
|
}
|
|
|
|
void CMoveController::InvalidateCalibration( void )
|
|
{
|
|
for ( int ii = 0; ii < MAX_PS3_MOVE_CONTROLLERS; ++ii )
|
|
{
|
|
cellGemClearStatusFlags( ii, CELL_GEM_ALL_FLAGS );
|
|
cellGemInvalidateCalibration( ii );
|
|
cellGemReset( ii );
|
|
g_pInputSystem->SetMotionControllerDeviceStatus( INPUT_DEVICE_MC_STATE_CAMERA_NOT_CONNECTED );
|
|
}
|
|
|
|
ResetMotionControllerScreenCalibration();
|
|
}
|
|
|
|
void CMoveController::ResetMotionControllerScreenCalibration( void )
|
|
{
|
|
s_nCalibrationStep = 0;
|
|
}
|
|
|
|
void CMoveController::StepMotionControllerCalibration( void )
|
|
{
|
|
float pointerX = 0.0;
|
|
float pointerY = 0.0;
|
|
|
|
VmathVector3 position;
|
|
VmathQuat orientation;
|
|
|
|
MoveControllerState currentState;
|
|
|
|
ReadState( ¤tState );
|
|
|
|
switch ( s_nCalibrationStep )
|
|
{
|
|
case 0: // left
|
|
s_tracker_plane_extents[0].left = currentState.m_posX[0];
|
|
break;
|
|
|
|
case 1: // right
|
|
s_tracker_plane_extents[0].right = currentState.m_posX[0];
|
|
break;
|
|
|
|
case 2: // bottom
|
|
s_tracker_plane_extents[0].bottom = currentState.m_posY[0];
|
|
break;
|
|
|
|
case 3: // top
|
|
s_tracker_plane_extents[0].top = currentState.m_posY[0];
|
|
break;
|
|
|
|
default:
|
|
AssertMsg( false, "Invalid step.\n" );
|
|
break;
|
|
}
|
|
|
|
if ( s_nCalibrationStep <= 3 )
|
|
{
|
|
s_nCalibrationStep++;
|
|
}
|
|
}
|