I’m currently working on Tac5Cool engine, the 5th iteration of Tac engine. Iteration might be too strong a word here, since it implies that each Tac engine is better than the last. Anyway, I’m at the stage where I need to select objects and move them about. Given my mouse pixel coordinates, object 3D world space coordinates, and camera info, i need to find the first out what’s being clicked.

I think the future method of deriving math is through Photoshop, so I’m ditching paper. Also, for now let’s assume that every object is a unit sphere.

### Finding the world space mouse direction

auto ab = gameInterface->mRenderer->GetPerspectiveProjectionAB( instance.f, instance.n );

auto a = ab.A;

auto b = ab.B;

auto fboTexture = instance.mFBOTexture;

const v2 fboDimensions(

( r32 )fboTexture->mWidth,

( r32 )fboTexture->mHeight );

const r32 fboAspectRatio

= ( r32 )fboTexture->mWidth

/ ( r32 )fboTexture->mHeight;

m4 proj;

ComputePerspectiveProjMatrix( ab, proj, instance.fovYRad, fboAspectRatio );

auto sx = proj( 0, 0 );

auto sy = proj( 1, 1 );

r32 mouseZViewSpace = -b / a; // <-- near plane. far plane is -b / ( a + 1 )

r32 mouseXViewSpace = -mouseZViewSpace * mouseNDC[ 0 ] / sx;

r32 mouseYViewSpace = -mouseZViewSpace * mouseNDC[ 1 ] / sy;

v4 mouseViewSpace( mouseXViewSpace, mouseYViewSpace, mouseZViewSpace, 1 );

m4 invview;

ComputeInverseViewMatrix( invview, instance.camPos, instance.camViewDir, instance.camWorldUp );

v3 mouseNearPlanePosWorldSpace = ( invview * mouseViewSpace ).xyz();

v3 mouseDirWorldSpace = Normalize( mouseNearPlanePosWorldSpace - instance.camPos );

auto a = ab.A;

auto b = ab.B;

auto fboTexture = instance.mFBOTexture;

const v2 fboDimensions(

( r32 )fboTexture->mWidth,

( r32 )fboTexture->mHeight );

const r32 fboAspectRatio

= ( r32 )fboTexture->mWidth

/ ( r32 )fboTexture->mHeight;

m4 proj;

ComputePerspectiveProjMatrix( ab, proj, instance.fovYRad, fboAspectRatio );

auto sx = proj( 0, 0 );

auto sy = proj( 1, 1 );

r32 mouseZViewSpace = -b / a; // <-- near plane. far plane is -b / ( a + 1 )

r32 mouseXViewSpace = -mouseZViewSpace * mouseNDC[ 0 ] / sx;

r32 mouseYViewSpace = -mouseZViewSpace * mouseNDC[ 1 ] / sy;

v4 mouseViewSpace( mouseXViewSpace, mouseYViewSpace, mouseZViewSpace, 1 );

m4 invview;

ComputeInverseViewMatrix( invview, instance.camPos, instance.camViewDir, instance.camWorldUp );

v3 mouseNearPlanePosWorldSpace = ( invview * mouseViewSpace ).xyz();

v3 mouseDirWorldSpace = Normalize( mouseNearPlanePosWorldSpace - instance.camPos );

### Ray-sphere intersection

TacRaycastResult TacRaySphere(

v3 rayPos,

v3 rayDir,

v3 spherePos,

r32 sphereRad )

{

TacRaycastResult result;

auto sphereRadSq = Square( sphereRad );

if( DistanceSq( rayPos, spherePos ) < sphereRadSq )

return result;

v3 v = spherePos - rayPos;

// ax^2 + bx + c = 0

// x = ( -b +/= sqrt( b^2 - 4ac ) ) / 2a

r32 a = Dot( rayDir, rayDir );

r32 b = -2 * Dot( rayDir, v );

r32 c = Dot( v, v ) - sphereRadSq;

r32 t = -b - std::sqrt( Square( b ) - 4 * a * c );

t /= 2 * a;

if( t > 0 )

{

result.mIntersected = true;

result.mDistance = t;

}

return result;

}

v3 rayPos,

v3 rayDir,

v3 spherePos,

r32 sphereRad )

{

TacRaycastResult result;

auto sphereRadSq = Square( sphereRad );

if( DistanceSq( rayPos, spherePos ) < sphereRadSq )

return result;

v3 v = spherePos - rayPos;

// ax^2 + bx + c = 0

// x = ( -b +/= sqrt( b^2 - 4ac ) ) / 2a

r32 a = Dot( rayDir, rayDir );

r32 b = -2 * Dot( rayDir, v );

r32 c = Dot( v, v ) - sphereRadSq;

r32 t = -b - std::sqrt( Square( b ) - 4 * a * c );

t /= 2 * a;

if( t > 0 )

{

result.mIntersected = true;

result.mDistance = t;

}

return result;

}