Picking in 3D

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 );

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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *